diff --git a/build.gradle b/build.gradle index 25c9fa40..db9cbf67 100644 --- a/build.gradle +++ b/build.gradle @@ -56,6 +56,7 @@ dependencies { compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.3' compile 'org.kordamp.ikonli:ikonli-javafx:2.1.1' compile 'org.kordamp.ikonli:ikonli-fontawesome-pack:2.1.1' + compile 'com.google.guava:guava:27.0-jre' } task wrapper(type: Wrapper) { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 34c937c9..ec178c54 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-bin.zip +#Sun Nov 25 05:28:28 EST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip diff --git a/sdr-trunk.ipr b/sdr-trunk.ipr index 912b20d7..28efe348 100644 --- a/sdr-trunk.ipr +++ b/sdr-trunk.ipr @@ -1,79 +1,728 @@ - - - + + + + + - + + - - + - + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + baseband + costas + + - + + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + @@ -83,7 +732,7 @@ @@ -94,11 +743,11 @@ - + - + - + \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/alias/Alias.java b/src/main/java/io/github/dsheirer/alias/Alias.java index 588995cd..cd50e0db 100644 --- a/src/main/java/io/github/dsheirer/alias/Alias.java +++ b/src/main/java/io/github/dsheirer/alias/Alias.java @@ -1,20 +1,22 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 Dennis Sheirer +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 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. + * 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 - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.alias; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -24,10 +26,10 @@ import io.github.dsheirer.alias.action.AliasAction; import io.github.dsheirer.alias.id.AliasID; import io.github.dsheirer.alias.id.AliasIDType; import io.github.dsheirer.alias.id.broadcast.BroadcastChannel; -import io.github.dsheirer.alias.id.nonrecordable.NonRecordable; import io.github.dsheirer.alias.id.priority.Priority; +import io.github.dsheirer.alias.id.record.Record; -import java.awt.*; +import java.awt.Color; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -187,7 +189,7 @@ public class Alias * Returns the priority level of this alias, if defined, or the default priority */ @JsonIgnore - public int getCallPriority() + public int getPlaybackPriority() { for(AliasID id : mAliasIDs) { @@ -245,27 +247,39 @@ public class Alias { for(AliasID id : getId()) { - if(id.getType() == AliasIDType.NON_RECORDABLE) + if(id.getType() == AliasIDType.RECORD) { - return false; + return true; } } - return true; + return false; } /** - * Sets or removes the non-recordable alias ID for this alias. + * Sets or removes the record alias ID for this alias. */ public void setRecordable(boolean recordable) { if(recordable) + { + for(AliasID id : getId()) + { + if(id.getType() == AliasIDType.RECORD) + { + return; + } + } + + addAliasID(new Record()); + } + else { AliasID toRemove = null; for(AliasID id : getId()) { - if(id.getType() == AliasIDType.NON_RECORDABLE) + if(id.getType() == AliasIDType.RECORD) { toRemove = id; break; @@ -277,18 +291,6 @@ public class Alias removeAliasID(toRemove); } } - else - { - for(AliasID id : getId()) - { - if(id.getType() == AliasIDType.NON_RECORDABLE) - { - return; - } - } - - addAliasID(new NonRecordable()); - } } /** diff --git a/src/main/java/io/github/dsheirer/alias/AliasFactory.java b/src/main/java/io/github/dsheirer/alias/AliasFactory.java index ad28d053..102007cb 100644 --- a/src/main/java/io/github/dsheirer/alias/AliasFactory.java +++ b/src/main/java/io/github/dsheirer/alias/AliasFactory.java @@ -1,20 +1,22 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias; import io.github.dsheirer.alias.action.AliasAction; @@ -31,26 +33,32 @@ import io.github.dsheirer.alias.id.broadcast.BroadcastChannel; import io.github.dsheirer.alias.id.broadcast.BroadcastChannelEditor; import io.github.dsheirer.alias.id.esn.ESNEditor; import io.github.dsheirer.alias.id.esn.Esn; -import io.github.dsheirer.alias.id.fleetsync.FleetsyncID; -import io.github.dsheirer.alias.id.fleetsync.FleetsyncIDEditor; +import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID; +import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncIDEditor; +import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID; +import io.github.dsheirer.alias.id.legacy.mdc.MDC1200IDEditor; +import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327ID; +import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327IDEditor; +import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordable; +import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordableEditor; +import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID; +import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupIDEditor; import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID; import io.github.dsheirer.alias.id.lojack.LoJackIDEditor; -import io.github.dsheirer.alias.id.mdc.MDC1200ID; -import io.github.dsheirer.alias.id.mdc.MDC1200IDEditor; import io.github.dsheirer.alias.id.mobileID.MINEditor; import io.github.dsheirer.alias.id.mobileID.Min; -import io.github.dsheirer.alias.id.mpt1327.MPT1327ID; -import io.github.dsheirer.alias.id.mpt1327.MPT1327IDEditor; -import io.github.dsheirer.alias.id.nonrecordable.NonRecordable; -import io.github.dsheirer.alias.id.nonrecordable.NonRecordableEditor; import io.github.dsheirer.alias.id.priority.Priority; import io.github.dsheirer.alias.id.priority.PriorityEditor; +import io.github.dsheirer.alias.id.record.Record; +import io.github.dsheirer.alias.id.record.RecordEditor; import io.github.dsheirer.alias.id.siteID.SiteID; import io.github.dsheirer.alias.id.siteID.SiteIDEditor; import io.github.dsheirer.alias.id.status.StatusID; import io.github.dsheirer.alias.id.status.StatusIDEditor; -import io.github.dsheirer.alias.id.talkgroup.TalkgroupID; -import io.github.dsheirer.alias.id.talkgroup.TalkgroupIDEditor; +import io.github.dsheirer.alias.id.talkgroup.Talkgroup; +import io.github.dsheirer.alias.id.talkgroup.TalkgroupEditor; +import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange; +import io.github.dsheirer.alias.id.talkgroup.TalkgroupRangeEditor; import io.github.dsheirer.alias.id.uniqueID.UniqueID; import io.github.dsheirer.alias.id.uniqueID.UniqueIDEditor; import io.github.dsheirer.audio.broadcast.BroadcastModel; @@ -59,246 +67,255 @@ import io.github.dsheirer.gui.editor.EmptyEditor; public class AliasFactory { - public static AliasID copyOf( AliasID id ) - { - switch( id.getType() ) - { - case BROADCAST_CHANNEL: - BroadcastChannel originalBroadcast = (BroadcastChannel)id; - BroadcastChannel copyBroadcast = new BroadcastChannel(); - copyBroadcast.setChannelName(originalBroadcast.getChannelName()); - return copyBroadcast; - case ESN: - Esn originalESN = (Esn)id; - Esn copyESN = new Esn(); - copyESN.setEsn( originalESN.getEsn() ); - return copyESN; - case FLEETSYNC: - FleetsyncID originalFleetsyncID = (FleetsyncID)id; - FleetsyncID copyFleetsyncID = new FleetsyncID(); - copyFleetsyncID.setIdent( originalFleetsyncID.getIdent() ); - return copyFleetsyncID; - case LTR_NET_UID: - UniqueID originalUniqueID = (UniqueID)id; - UniqueID copyUniqueID = new UniqueID(); - copyUniqueID.setUid( originalUniqueID.getUid() ); - return copyUniqueID; - case LOJACK: - LoJackFunctionAndID originalLoJackFunctionAndID = (LoJackFunctionAndID)id; - LoJackFunctionAndID copyLoJackFunctionAndID = new LoJackFunctionAndID(); - copyLoJackFunctionAndID.setFunction( originalLoJackFunctionAndID.getFunction() ); - copyLoJackFunctionAndID.setID( originalLoJackFunctionAndID.getID() ); - return copyLoJackFunctionAndID; - case MDC1200: - MDC1200ID originalMDC1200ID = (MDC1200ID)id; - MDC1200ID copyMDC1200ID = new MDC1200ID(); - copyMDC1200ID.setIdent( originalMDC1200ID.getIdent() ); - return copyMDC1200ID; - case MIN: - Min originalMin = (Min)id; - Min copyMin = new Min(); - copyMin.setMin( originalMin.getMin() ); - return copyMin; - case MPT1327: - MPT1327ID originalMPT1327ID = (MPT1327ID)id; - MPT1327ID copyMPT1327ID = new MPT1327ID(); - copyMPT1327ID.setIdent( originalMPT1327ID.getIdent() ); - return copyMPT1327ID; - case NON_RECORDABLE: - return new NonRecordable(); - case PRIORITY: - Priority originalPriority = (Priority)id; - Priority copyPriority = new Priority(); - copyPriority.setPriority( originalPriority.getPriority() ); - return copyPriority; - case SITE: - SiteID originalSiteID = (SiteID)id; - SiteID copySiteID = new SiteID(); - copySiteID.setSite( originalSiteID.getSite() ); - return copySiteID; - case TALKGROUP: - TalkgroupID originalTalkgroupID = (TalkgroupID)id; - TalkgroupID copyTalkgroupID = new TalkgroupID(); - copyTalkgroupID.setTalkgroup( originalTalkgroupID.getTalkgroup() ); - return copyTalkgroupID; - case STATUS: - StatusID originalStatusID = (StatusID)id; - StatusID copyStatusID = new StatusID(); - copyStatusID.setStatus( originalStatusID.getStatus() ); - return copyStatusID; - default: - } + public static AliasID copyOf(AliasID id) + { + switch(id.getType()) + { + case BROADCAST_CHANNEL: + BroadcastChannel originalBroadcast = (BroadcastChannel)id; + BroadcastChannel copyBroadcast = new BroadcastChannel(); + copyBroadcast.setChannelName(originalBroadcast.getChannelName()); + return copyBroadcast; + case ESN: + Esn originalESN = (Esn)id; + Esn copyESN = new Esn(); + copyESN.setEsn(originalESN.getEsn()); + return copyESN; + case LTR_NET_UID: + UniqueID originalUniqueID = (UniqueID)id; + UniqueID copyUniqueID = new UniqueID(); + copyUniqueID.setUid(originalUniqueID.getUid()); + return copyUniqueID; + case LOJACK: + LoJackFunctionAndID originalLoJackFunctionAndID = (LoJackFunctionAndID)id; + LoJackFunctionAndID copyLoJackFunctionAndID = new LoJackFunctionAndID(); + copyLoJackFunctionAndID.setFunction(originalLoJackFunctionAndID.getFunction()); + copyLoJackFunctionAndID.setID(originalLoJackFunctionAndID.getID()); + return copyLoJackFunctionAndID; + case MIN: + Min originalMin = (Min)id; + Min copyMin = new Min(); + copyMin.setMin(originalMin.getMin()); + return copyMin; + case PRIORITY: + Priority originalPriority = (Priority)id; + Priority copyPriority = new Priority(); + copyPriority.setPriority(originalPriority.getPriority()); + return copyPriority; + case RECORD: + return new Record(); + case SITE: + SiteID originalSiteID = (SiteID)id; + SiteID copySiteID = new SiteID(); + copySiteID.setSite(originalSiteID.getSite()); + return copySiteID; + case STATUS: + StatusID originalStatusID = (StatusID)id; + StatusID copyStatusID = new StatusID(); + copyStatusID.setStatus(originalStatusID.getStatus()); + return copyStatusID; + case TALKGROUP: + Talkgroup originalTalkgroup = (Talkgroup)id; + Talkgroup copyTalkgroup = new Talkgroup(originalTalkgroup.getProtocol(), originalTalkgroup.getValue()); + return copyTalkgroup; + case TALKGROUP_RANGE: + TalkgroupRange originalRange = (TalkgroupRange)id; + TalkgroupRange copyRange = new TalkgroupRange(originalRange.getProtocol(), originalRange.getMinTalkgroup(), + originalRange.getMaxTalkgroup()); + return copyRange; - return null; - } - - public static AliasAction copyOf( AliasAction action ) - { - if( action instanceof BeepAction ) - { - return new BeepAction(); - } - else if( action instanceof ClipAction) - { - ClipAction originalClip = (ClipAction)action; - ClipAction copyClip = new ClipAction(); - copyClip.setInterval( originalClip.getInterval() ); - copyClip.setPath( originalClip.getPath() ); - copyClip.setPeriod( originalClip.getPeriod() ); - return copyClip; - } - else if( action instanceof ScriptAction ) - { - ScriptAction originalScript = (ScriptAction)action; - ScriptAction copyScript = new ScriptAction(); - copyScript.setInterval( originalScript.getInterval() ); - copyScript.setPeriod( originalScript.getPeriod() ); - copyScript.setScript( originalScript.getScript() ); - return copyScript; - } - - return null; - } - - public static Alias copyOf( Alias original ) - { - Alias copy = new Alias( original.getName() ); - copy.setList( original.getList() ); - copy.setGroup( original.getGroup() ); - copy.setColor( original.getColor() ); - copy.setIconName( original.getIconName() ); - - for( AliasID id: original.getId() ) - { - AliasID copyID = copyOf( id ); - - if( copyID != null ) - { - copy.addAliasID( copyID ); - } - } + //Legacy identifiers ... not supported + case FLEETSYNC: + case LEGACY_TALKGROUP: + case MDC1200: + case MPT1327: + case NON_RECORDABLE: + default: + } - for( AliasAction action: original.getAction() ) - { - AliasAction copyAction = copyOf( action ); - - if( copyAction != null ) - { - copy.addAliasAction( copyAction ); - } - } - - return copy; - } + return null; + } + + public static AliasAction copyOf(AliasAction action) + { + if(action instanceof BeepAction) + { + return new BeepAction(); + } + else if(action instanceof ClipAction) + { + ClipAction originalClip = (ClipAction)action; + ClipAction copyClip = new ClipAction(); + copyClip.setInterval(originalClip.getInterval()); + copyClip.setPath(originalClip.getPath()); + copyClip.setPeriod(originalClip.getPeriod()); + return copyClip; + } + else if(action instanceof ScriptAction) + { + ScriptAction originalScript = (ScriptAction)action; + ScriptAction copyScript = new ScriptAction(); + copyScript.setInterval(originalScript.getInterval()); + copyScript.setPeriod(originalScript.getPeriod()); + copyScript.setScript(originalScript.getScript()); + return copyScript; + } + + return null; + } + + public static Alias copyOf(Alias original) + { + Alias copy = new Alias(original.getName()); + copy.setList(original.getList()); + copy.setGroup(original.getGroup()); + copy.setColor(original.getColor()); + copy.setIconName(original.getIconName()); + + for(AliasID id : original.getId()) + { + AliasID copyID = copyOf(id); + + if(copyID != null) + { + copy.addAliasID(copyID); + } + } + + for(AliasAction action : original.getAction()) + { + AliasAction copyAction = copyOf(action); + + if(copyAction != null) + { + copy.addAliasAction(copyAction); + } + } + + return copy; + } + + public static Editor getEditor(AliasID aliasID, BroadcastModel broadcastModel) + { + if(aliasID != null) + { + switch(aliasID.getType()) + { + case BROADCAST_CHANNEL: + return new BroadcastChannelEditor(aliasID, broadcastModel); + case ESN: + return new ESNEditor(aliasID); + case LTR_NET_UID: + return new UniqueIDEditor(aliasID); + case LOJACK: + return new LoJackIDEditor(aliasID); + case MIN: + return new MINEditor(aliasID); + case PRIORITY: + return new PriorityEditor(aliasID); + case RECORD: + return new RecordEditor(aliasID); + case SITE: + return new SiteIDEditor(aliasID); + case STATUS: + return new StatusIDEditor(aliasID); + case TALKGROUP: + return new TalkgroupEditor(aliasID); + case TALKGROUP_RANGE: + return new TalkgroupRangeEditor(aliasID); + + case FLEETSYNC: + return new FleetsyncIDEditor(aliasID); + case LEGACY_TALKGROUP: + return new LegacyTalkgroupIDEditor(aliasID); + case MDC1200: + return new MDC1200IDEditor(aliasID); + case MPT1327: + return new MPT1327IDEditor(aliasID); + case NON_RECORDABLE: + return new NonRecordableEditor(aliasID); + default: + break; + } + } + + return new EmptyEditor(); + } + + public static Editor getEditor(AliasAction aliasAction) + { + if(aliasAction != null) + { + switch(aliasAction.getType()) + { + case BEEP: + return new BeepActionEditor(aliasAction); + case CLIP: + return new ClipActionEditor(aliasAction); + case SCRIPT: + return new ScriptActionEditor(aliasAction); + default: + break; + } + } + + return new EmptyEditor(); + } + + public static AliasID getAliasID(AliasIDType type) + { + switch(type) + { + case BROADCAST_CHANNEL: + return new BroadcastChannel(); + case ESN: + return new Esn(); + case FLEETSYNC: + return new FleetsyncID(); + case LTR_NET_UID: + return new UniqueID(); + case LOJACK: + return new LoJackFunctionAndID(); + case MDC1200: + return new MDC1200ID(); + case MIN: + return new Min(); + case MPT1327: + return new MPT1327ID(); + case NON_RECORDABLE: + return new NonRecordable(); + case PRIORITY: + return new Priority(); + case RECORD: + return new Record(); + case TALKGROUP: + return new Talkgroup(); + case TALKGROUP_RANGE: + return new TalkgroupRange(); + case SITE: + return new SiteID(); + case STATUS: + return new StatusID(); + case LEGACY_TALKGROUP: + return new LegacyTalkgroupID(); + default: + throw new IllegalArgumentException("Unrecognized Alias ID type: " + type); + } + } + + public static AliasAction getAliasAction(AliasActionType type) + { + switch(type) + { + case BEEP: + return new BeepAction(); + case CLIP: + return new ClipAction(); + case SCRIPT: + return new ScriptAction(); + default: + throw new IllegalArgumentException("Unrecognized Alias Action type: " + type); + } + } - public static Editor getEditor( AliasID aliasID, BroadcastModel broadcastModel ) - { - if( aliasID != null ) - { - switch( aliasID.getType() ) - { - case BROADCAST_CHANNEL: - return new BroadcastChannelEditor(aliasID, broadcastModel); - case ESN: - return new ESNEditor( aliasID ); - case FLEETSYNC: - return new FleetsyncIDEditor( aliasID ); - case LTR_NET_UID: - return new UniqueIDEditor( aliasID ); - case LOJACK: - return new LoJackIDEditor( aliasID ); - case MDC1200: - return new MDC1200IDEditor( aliasID ); - case MIN: - return new MINEditor( aliasID ); - case MPT1327: - return new MPT1327IDEditor( aliasID ); - case NON_RECORDABLE: - return new NonRecordableEditor( aliasID ); - case PRIORITY: - return new PriorityEditor( aliasID ); - case SITE: - return new SiteIDEditor( aliasID ); - case STATUS: - return new StatusIDEditor( aliasID ); - case TALKGROUP: - return new TalkgroupIDEditor( aliasID ); - default: - break; - } - } - - return new EmptyEditor(); - } - - public static Editor getEditor( AliasAction aliasAction ) - { - if( aliasAction != null ) - { - switch( aliasAction.getType() ) - { - case BEEP: - return new BeepActionEditor( aliasAction ); - case CLIP: - return new ClipActionEditor( aliasAction ); - case SCRIPT: - return new ScriptActionEditor( aliasAction ); - default: - break; - } - } - - return new EmptyEditor(); - } - - public static AliasID getAliasID( AliasIDType type ) - { - switch( type ) - { - case BROADCAST_CHANNEL: - return new BroadcastChannel(); - case ESN: - return new Esn(); - case FLEETSYNC: - return new FleetsyncID(); - case LTR_NET_UID: - return new UniqueID(); - case LOJACK: - return new LoJackFunctionAndID(); - case MDC1200: - return new MDC1200ID(); - case MIN: - return new Min(); - case MPT1327: - return new MPT1327ID(); - case NON_RECORDABLE: - return new NonRecordable(); - case PRIORITY: - return new Priority(); - case SITE: - return new SiteID(); - case STATUS: - return new StatusID(); - case TALKGROUP: - return new TalkgroupID(); - default: - throw new IllegalArgumentException( "Unrecognized Alias ID type: " + type ); - } - } - - public static AliasAction getAliasAction( AliasActionType type ) - { - switch( type ) - { - case BEEP: - return new BeepAction(); - case CLIP: - return new ClipAction(); - case SCRIPT: - return new ScriptAction(); - default: - throw new IllegalArgumentException( "Unrecognized Alias Action type: " + type ); - } - } - } diff --git a/src/main/java/io/github/dsheirer/alias/AliasList.java b/src/main/java/io/github/dsheirer/alias/AliasList.java index b51503bf..a6051822 100644 --- a/src/main/java/io/github/dsheirer/alias/AliasList.java +++ b/src/main/java/io/github/dsheirer/alias/AliasList.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,24 +15,25 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.alias; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import io.github.dsheirer.alias.id.AliasID; import io.github.dsheirer.alias.id.WildcardID; +import io.github.dsheirer.alias.id.broadcast.BroadcastChannel; import io.github.dsheirer.alias.id.esn.Esn; -import io.github.dsheirer.alias.id.fleetsync.FleetsyncID; -import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID; -import io.github.dsheirer.alias.id.mdc.MDC1200ID; -import io.github.dsheirer.alias.id.mobileID.Min; -import io.github.dsheirer.alias.id.mpt1327.MPT1327ID; +import io.github.dsheirer.alias.id.priority.Priority; import io.github.dsheirer.alias.id.siteID.SiteID; import io.github.dsheirer.alias.id.status.StatusID; -import io.github.dsheirer.alias.id.talkgroup.TalkgroupID; +import io.github.dsheirer.alias.id.talkgroup.Talkgroup; import io.github.dsheirer.alias.id.uniqueID.UniqueID; -import io.github.dsheirer.module.decode.lj1200.LJ1200Message; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.identifier.esn.ESNIdentifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.protocol.Protocol; import io.github.dsheirer.sample.Listener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,31 +43,23 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.TreeMap; public class AliasList implements Listener { private final static Logger mLog = LoggerFactory.getLogger(AliasList.class); public static final String WILDCARD = "*"; - public static final String REGEX_WILDCARD = "."; private Map mESN = new HashMap<>(); - private Map mFleetsync = new HashMap<>(); - private Map mLoJack = new HashMap<>(); - private Map mMDC1200 = new HashMap<>(); - private Map mMobileID = new HashMap<>(); - private Map mMPT1327 = new HashMap<>(); private Map mSiteID = new HashMap<>(); private Map mStatus = new HashMap<>(); - private Map mTalkgroup = new HashMap<>(); private Map mUniqueID = new HashMap<>(); private List mESNWildcards = new ArrayList<>(); - private List mMobileIDWildcards = new ArrayList<>(); - private List mFleetsyncWildcards = new ArrayList<>(); - private List mMDC1200Wildcards = new ArrayList<>(); - private List mMPT1327Wildcards = new ArrayList<>(); private List mSiteWildcards = new ArrayList<>(); - private List mTalkgroupWildcards = new ArrayList<>(); + private boolean mHasAliasActions = false; + + private Map> mTalkgroupProtocolMap = new HashMap<>(); private String mName; @@ -92,6 +86,11 @@ public class AliasList implements Listener addAliasID(aliasID, alias); } } + + if(alias.hasActions()) + { + mHasAliasActions = true; + } } /** @@ -105,6 +104,19 @@ public class AliasList implements Listener { switch(id.getType()) { + case TALKGROUP: + Talkgroup talkgroup = (Talkgroup)id; + + Map protocolMap = mTalkgroupProtocolMap.get(talkgroup.getProtocol()); + + if(protocolMap == null) + { + protocolMap = new TreeMap(); + mTalkgroupProtocolMap.put(talkgroup.getProtocol(), protocolMap); + } + + protocolMap.put(talkgroup.getValue(), alias); + break; case ESN: String esn = ((Esn) id).getEsn(); @@ -119,65 +131,6 @@ public class AliasList implements Listener mESN.put(esn, alias); } break; - case FLEETSYNC: - String fleetsync = ((FleetsyncID) id).getIdent(); - - if(fleetsync != null) - { - if(fleetsync.contains(WILDCARD)) - { - mFleetsyncWildcards.add(new WildcardID(fleetsync)); - Collections.sort(mFleetsyncWildcards); - } - - mFleetsync.put(fleetsync, alias); - } - break; - case LOJACK: - mLoJack.put((LoJackFunctionAndID) id, alias); - break; - case MDC1200: - String mdc = ((MDC1200ID) id).getIdent(); - - if(mdc != null) - { - if(mdc.contains(WILDCARD)) - { - mMDC1200Wildcards.add(new WildcardID(mdc)); - Collections.sort(mMDC1200Wildcards); - } - - mMDC1200.put(mdc, alias); - } - break; - case MPT1327: - String mpt = ((MPT1327ID) id).getIdent(); - - if(mpt != null) - { - if(mpt.contains(WILDCARD)) - { - mMPT1327Wildcards.add(new WildcardID(mpt)); - Collections.sort(mMPT1327Wildcards); - } - - mMPT1327.put(mpt, alias); - } - break; - case MIN: - String min = ((Min) id).getMin(); - - if(min != null) - { - if(min.contains(WILDCARD)) - { - mMobileIDWildcards.add(new WildcardID(min)); - Collections.sort(mMobileIDWildcards); - } - - mMobileID.put(min, alias); - } - break; case LTR_NET_UID: mUniqueID.put(((UniqueID) id).getUid(), alias); break; @@ -198,20 +151,6 @@ public class AliasList implements Listener case STATUS: mStatus.put(((StatusID) id).getStatus(), alias); break; - case TALKGROUP: - String tgid = ((TalkgroupID) id).getTalkgroup(); - - if(tgid != null) - { - if(tgid.contains(WILDCARD)) - { - mTalkgroupWildcards.add(new WildcardID(tgid)); - Collections.sort(mTalkgroupWildcards); - } - - mTalkgroup.put(tgid, alias); - } - break; case BROADCAST_CHANNEL: case NON_RECORDABLE: case PRIORITY: @@ -279,61 +218,6 @@ public class AliasList implements Listener mESN.remove(esn); break; - case FLEETSYNC: - String fleetsync = ((FleetsyncID) id).getIdent(); - - if(fleetsync != null) - { - if(fleetsync.contains(WILDCARD)) - { - removeWildcard(fleetsync, mFleetsyncWildcards); - } - - mFleetsync.remove(fleetsync); - } - break; - case LOJACK: - mLoJack.remove((LoJackFunctionAndID) id); - break; - case MDC1200: - String mdc = ((MDC1200ID) id).getIdent(); - - if(mdc != null) - { - if(mdc.contains(WILDCARD)) - { - removeWildcard(mdc, mMDC1200Wildcards); - } - - mMDC1200.remove(mdc); - } - break; - case MPT1327: - String mpt = ((MPT1327ID) id).getIdent(); - - if(mpt != null) - { - if(mpt.contains(WILDCARD)) - { - removeWildcard(mpt, mMPT1327Wildcards); - } - - mMPT1327.remove(mpt); - } - break; - case MIN: - String min = ((Min) id).getMin(); - - if(min != null) - { - if(min.contains(WILDCARD)) - { - removeWildcard(min, mMobileIDWildcards); - } - - mMobileID.remove(min); - } - break; case LTR_NET_UID: mUniqueID.remove(((UniqueID) id).getUid()); break; @@ -343,19 +227,6 @@ public class AliasList implements Listener case STATUS: mStatus.remove(((StatusID) id).getStatus()); break; - case TALKGROUP: - String tgid = ((TalkgroupID) id).getTalkgroup(); - - if(tgid != null) - { - if(tgid.contains(WILDCARD)) - { - removeWildcard(tgid, mTalkgroupWildcards); - } - - mTalkgroup.remove(tgid); - } - break; case NON_RECORDABLE: case PRIORITY: case BROADCAST_CHANNEL: @@ -458,162 +329,6 @@ public class AliasList implements Listener return alias; } - /** - * Lookup alias by fleetsync ID - */ - public Alias getFleetsyncAlias(String ident) - { - Alias alias = null; - - if(ident != null) - { - alias = mFleetsync.get(ident); - - if(alias == null) - { - String wildcard = getWildcardMatch(ident, mFleetsyncWildcards); - - if(wildcard != null) - { - alias = mFleetsync.get(wildcard); - } - } - } - - return alias; - } - - /** - * Lookup alias by lojack function and ID - */ - public Alias getLoJackAlias(LJ1200Message.Function function, String id) - { - if(id != null) - { - for(LoJackFunctionAndID lojack : mLoJack.keySet()) - { - if(lojack.matches(function, id)) - { - return mLoJack.get(lojack); - } - } - } - - return null; - } - - /** - * Lookup alias by MDC1200 ID - */ - public Alias getMDC1200Alias(String ident) - { - Alias alias = null; - - if(ident != null) - { - alias = mMDC1200.get(ident); - - if(alias == null) - { - String wildcard = getWildcardMatch(ident, mMDC1200Wildcards); - - if(wildcard != null) - { - alias = mMDC1200.get(wildcard); - } - } - } - - return alias; - } - - /** - * Lookup alias by MPT1327 Ident - */ - public Alias getMPT1327Alias(String ident) - { - Alias alias = null; - - if(ident != null) - { - alias = mMPT1327.get(ident); - - if(alias == null) - { - String wildcard = getWildcardMatch(ident, mMPT1327Wildcards); - - if(wildcard != null) - { - alias = mMPT1327.get(wildcard); - } - } - } - - return alias; - } - - /** - * Lookup alias by Mobile ID Number (MIN) - */ - public Alias getMobileIDNumberAlias(String ident) - { - Alias alias = null; - - if(ident != null) - { - alias = mMobileID.get(ident); - - if(alias == null) - { - String wildcard = getWildcardMatch(ident, mMobileIDWildcards); - - if(wildcard != null) - { - alias = mMobileID.get(wildcard); - } - } - } - - return alias; - } - - /** - * Lookup alias by talkgroup - * - * @param tgid to use when searching for an alias - * @param includeWildcards true if you want to additionally search for a matching alias that contains wildcard(s) - * when there is not a direct match to the TGID. - */ - public Alias getTalkgroupAlias(String tgid, boolean includeWildcards) - { - Alias alias = null; - - if(tgid != null) - { - alias = mTalkgroup.get(tgid); - - if(alias == null && includeWildcards) - { - String wildcard = getWildcardMatch(tgid, mTalkgroupWildcards); - - if(wildcard != null) - { - alias = mTalkgroup.get(wildcard); - } - } - } - - return alias; - } - - /** - * Lookup alias by talkgroup. Defaults to matching to wildcard talkgroups. - */ - public Alias getTalkgroupAlias(String tgid) - { - return getTalkgroupAlias(tgid, true); - } - /** * Alias list name */ @@ -674,4 +389,136 @@ public class AliasList implements Listener } } } + + /** + * Returns an optional alias that is associated with the identifier + * @param identifier to alias + * @return alias or null + */ + public Alias getAlias(Identifier identifier) + { + if(identifier != null) + { + switch(identifier.getForm()) + { + case TALKGROUP: + TalkgroupIdentifier talkgroup = (TalkgroupIdentifier)identifier; + Map protocolMap = mTalkgroupProtocolMap.get(identifier.getProtocol()); + + if(protocolMap != null) + { + return protocolMap.get(talkgroup.getValue()); + } + break; + case ESN: + if(identifier instanceof ESNIdentifier) + { + return getESNAlias(((ESNIdentifier)identifier).getValue()); + } + break; + } + } + + return null; + } + + /** + * Indicates if any of the identifiers contain a broadcast channel for streaming of audio. + * @param identifierCollection to inspect + * @return true if the identifier collection is designated for streaming to one or more channels. + */ + public boolean isStreamable(IdentifierCollection identifierCollection) + { + for(Identifier identifier: identifierCollection.getIdentifiers()) + { + Alias alias = getAlias(identifier); + + if(alias != null && alias.isStreamable()) + { + return true; + } + } + + return false; + } + + /** + * Indicates if any of the identifiers have been identified for recording. + * @param identifierCollection to inspect + * @return true if recordable. + */ + public boolean isRecordable(IdentifierCollection identifierCollection) + { + for(Identifier identifier: identifierCollection.getIdentifiers()) + { + Alias alias = getAlias(identifier); + + if(alias != null && alias.isRecordable()) + { + return true; + } + } + + return false; + } + + /** + * Indicates if any of the aliases in this list have an associated alias action + */ + public boolean hasAliasActions() + { + return mHasAliasActions; + } + + /** + * Returns the lowest audio playback priority specified by aliases for identifiers in the + * identifier collection. + * + * @param identifierCollection to inspect for audio priority + * @return audio playback priority + */ + public int getAudioPlaybackPriority(IdentifierCollection identifierCollection) + { + int priority = Priority.DEFAULT_PRIORITY; + + for(Identifier identifier: identifierCollection.getIdentifiers()) + { + Alias alias = getAlias(identifier); + + if(alias != null && alias.getPlaybackPriority() < priority) + { + priority = alias.getPlaybackPriority(); + } + } + + return priority; + } + + /** + * Returns a list of streaming broadcast channels specified for any of the identifiers in the collection. + * + * @return list of broadcast channels or an empty list + */ + public List getBroadcastChannels(IdentifierCollection identifierCollection) + { + List channels = new ArrayList<>(); + + for(Identifier identifier: identifierCollection.getIdentifiers()) + { + Alias alias = getAlias(identifier); + + if(alias != null && alias.isStreamable()) + { + for(BroadcastChannel broadcastChannel: alias.getBroadcastChannels()) + { + if(!channels.contains(broadcastChannel)) + { + channels.add(broadcastChannel); + } + } + } + } + + return channels; + } } diff --git a/src/main/java/io/github/dsheirer/alias/AliasModel.java b/src/main/java/io/github/dsheirer/alias/AliasModel.java index 2a62f05f..daba8830 100644 --- a/src/main/java/io/github/dsheirer/alias/AliasModel.java +++ b/src/main/java/io/github/dsheirer/alias/AliasModel.java @@ -1,23 +1,27 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 Dennis Sheirer +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 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. + * 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 - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.alias; import io.github.dsheirer.alias.id.broadcast.BroadcastChannel; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; @@ -30,7 +34,9 @@ import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; /** - * Alias Model + * Alias Model contains all aliases and is responsible for creation and management of alias lists. Alias lists are a + * set of aliases that all share a common alias list name and can be attached to a decoding channel for aliasing + * identifiers produced by channel decoder(s). */ public class AliasModel extends AbstractTableModel { @@ -68,31 +74,69 @@ public class AliasModel extends AbstractTableModel return null; } + /** + * Returns an optional alias list associated with the identifier collection + * + * @return alias list or null + */ + public AliasList getAliasList(IdentifierCollection identifierCollection) + { + if(identifierCollection != null) + { + return getAliasList(identifierCollection.getAliasListConfiguration()); + } + + return null; + } + + /** + * Retrieves an alias list specified by the alias list configuration identifier + * + * @param configurationIdentifier containing the name of an alias list + * @return alias list or null. + */ + public AliasList getAliasList(AliasListConfigurationIdentifier configurationIdentifier) + { + if(configurationIdentifier != null && configurationIdentifier.isValid()) + { + return getAliasList(configurationIdentifier.getValue()); + } + + return null; + } + + /** + * Creates a new alias list containing all aliases that match the alias name, or returns a previously created and + * cached alias list. Returned alias list is automatically registered as a listener to this model so that any + * updates to the list by the user will automatically be reflected in constructed alias lists. + */ public AliasList getAliasList(String name) { - if(name != null && mAliasListMap.containsKey(name)) + if(name == null || name.isEmpty()) + { + return new AliasList(name); + } + + if(mAliasListMap.containsKey(name)) { return mAliasListMap.get(name); } AliasList aliasList = new AliasList(name); - if(name != null) + for(Alias alias : mAliases) { - for(Alias alias : mAliases) + if(alias.hasList() && alias.getList().equalsIgnoreCase(name)) { - if(alias.hasList() && alias.getList().equalsIgnoreCase(name)) - { - aliasList.addAlias(alias); - } + aliasList.addAlias(alias); } - - mAliasListMap.put(name, aliasList); - - //Register the new alias list to receive updates from this model - addListener(aliasList); } + mAliasListMap.put(name, aliasList); + + //Register the new alias list to receive updates from this model + addListener(aliasList); + return aliasList; } diff --git a/src/main/java/io/github/dsheirer/alias/PatchGroupAlias.java b/src/main/java/io/github/dsheirer/alias/PatchGroupAlias.java deleted file mode 100644 index 9350fec8..00000000 --- a/src/main/java/io/github/dsheirer/alias/PatchGroupAlias.java +++ /dev/null @@ -1,379 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.alias; - -import io.github.dsheirer.alias.action.AliasAction; -import io.github.dsheirer.alias.id.AliasID; -import io.github.dsheirer.alias.id.AliasIDType; -import io.github.dsheirer.alias.id.broadcast.BroadcastChannel; -import io.github.dsheirer.alias.id.priority.Priority; -import io.github.dsheirer.alias.id.talkgroup.TalkgroupID; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; - -public class PatchGroupAlias extends Alias -{ - private final static Logger mLog = LoggerFactory.getLogger(PatchGroupAlias.class); - - private Alias mPatchGroupAlias; - private List mPatchedAliases = new ArrayList<>(); - private List mPatchedTalkgroupIDs = 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 the patch group ID alias and where applicable, return the highest - * value/priority from the group of patched aliases. - */ - public PatchGroupAlias() - { - } - - @Override - public int getColor() - { - if(mPatchGroupAlias != null) - { - return mPatchGroupAlias.getColor(); - } - - return super.getColor(); - } - - /** - * Sets an alias for this patch group ID - */ - public void setPatchGroupAlias(Alias alias) - { - mPatchGroupAlias = alias; - } - - /** - * Alias for this patch group - */ - public Alias getPatchGroupAlias() - { - return mPatchGroupAlias; - } - - /** - * Indicates if this patch group has an alias - */ - public boolean hasPatchGroupAlias() - { - return mPatchGroupAlias != null; - } - - /** - * 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) - { - if(alias == PatchGroupAlias.this) - { - throw new IllegalArgumentException("Can't add patch group alias to itself - would create an infinite loop"); - } - - mPatchedAliases.add(alias); - } - - /** - * Removes the patched alias from this patch alias group - */ - public void removePatchedAlias(Alias alias) - { - mPatchedAliases.remove(alias); - } - - /** - * Sets the list of patched talkgroup IDs for this patch group - */ - public void setPatchedTalkgroupIDs(List talkgroupIDs) - { - mPatchedTalkgroupIDs = talkgroupIDs; - } - - /** - * List of patched talkgroup IDs for this patch group - */ - public List getPatchedTalkgroupIDs() - { - return mPatchedTalkgroupIDs; - } - - @Override - public String getName() - { - if(hasPatchGroupAlias()) - { - return getPatchGroupAlias().getName(); - } - else - { - for(AliasID id: getId()) - { - if(id.getType() == AliasIDType.TALKGROUP) - { - return "PATCH:" + ((TalkgroupID)id).getTalkgroup(); - } - } - } - - return "PATCH:****"; - } - - @Override - public List getAction() - { - List aliasActions = new ArrayList<>(); - - if(hasPatchGroupAlias()) - { - aliasActions.addAll(getPatchGroupAlias().getAction()); - } - - for(Alias alias: mPatchedAliases) - { - if(!(alias instanceof PatchGroupAlias)) - { - for(AliasAction action: alias.getAction()) - { - if(!aliasActions.contains(action)) - { - aliasActions.add(action); - } - } - } - } - - return aliasActions; - } - - @Override - public boolean hasActions() - { - for(Alias alias: mPatchedAliases) - { - if(!(alias instanceof PatchGroupAlias) && alias.hasActions()) - { - return true; - } - } - - return hasPatchGroupAlias() && getPatchGroupAlias().hasActions(); - } - - /** - * Returns the highest listenable audio priority defined among the aliases in the group, or returns 'Do Not Monitor' - * if that is the only priority defined. Otherwise, returns default (100) priority. - */ - @Override - public int getCallPriority() - { - boolean hasDoNotMonitor = false; - - int highestPriority = (hasPatchGroupAlias() ? getPatchGroupAlias().getCallPriority() : Priority.DEFAULT_PRIORITY + 1); - - for(Alias alias: mPatchedAliases) - { - if(!(alias instanceof PatchGroupAlias) && alias.hasCallPriority() && alias.getCallPriority() < highestPriority) - { - if(alias.getCallPriority() == Priority.DO_NOT_MONITOR) - { - hasDoNotMonitor = true; - } - else - { - highestPriority = alias.getCallPriority(); - } - } - } - - if(highestPriority <= Priority.DEFAULT_PRIORITY) - { - return highestPriority; - } - else if(hasDoNotMonitor) - { - return Priority.DO_NOT_MONITOR; - } - else - { - return Priority.DEFAULT_PRIORITY; - } - } - - @Override - public boolean hasCallPriority() - { - if(hasPatchGroupAlias() && getPatchGroupAlias().hasCallPriority()) - { - return true; - } - - for(Alias alias: mPatchedAliases) - { - if(!(alias instanceof PatchGroupAlias) && alias.hasCallPriority()) - { - return true; - } - } - - return false; - } - - @Override - public boolean isRecordable() - { - if(hasPatchGroupAlias() && getPatchGroupAlias().isRecordable()) - { - return true; - } - - for(Alias alias: mPatchedAliases) - { - if(!(alias instanceof PatchGroupAlias) && alias.isRecordable()) - { - return true; - } - } - - return false; - } - - @Override - public boolean isStreamable() - { - if(hasPatchGroupAlias() && getPatchGroupAlias().isStreamable()) - { - return true; - } - - for(Alias alias: mPatchedAliases) - { - if(!(alias instanceof PatchGroupAlias) && alias.isStreamable()) - { - return true; - } - } - - return false; - } - - @Override - public Set getBroadcastChannels() - { - Set broadcastChannels = new TreeSet<>(); - - if(hasPatchGroupAlias()) - { - broadcastChannels.addAll(getPatchGroupAlias().getBroadcastChannels()); - } - - for(Alias alias: mPatchedAliases) - { - if(!(alias instanceof PatchGroupAlias)) - { - broadcastChannels.addAll(alias.getBroadcastChannels()); - } - } - - return broadcastChannels; - } - - @Override - public boolean hasBroadcastChannel(String channel) - { - if(hasPatchGroupAlias() && getPatchGroupAlias().hasBroadcastChannel(channel)) - { - return true; - } - - for(Alias alias: mPatchedAliases) - { - if(!(alias instanceof PatchGroupAlias) && alias.hasBroadcastChannel(channel)) - { - return true; - } - } - - return false; - } - - public static void main(String[] args) - { - Alias a1 = new Alias("Alias 1"); - a1.addAliasID(new BroadcastChannel("Channel 1")); - a1.addAliasID(new BroadcastChannel("Channel 2")); - a1.addAliasID(new BroadcastChannel("Channel 3")); - a1.addAliasID(new BroadcastChannel("Channel 4")); - - Alias a2 = new Alias("Alias 1"); - a1.addAliasID(new BroadcastChannel("Channel 1")); - a1.addAliasID(new BroadcastChannel("Channel 2")); - a1.addAliasID(new BroadcastChannel("Channel 3")); - a1.addAliasID(new BroadcastChannel("Channel 4")); - - Alias a3 = new Alias("Alias 1"); - a1.addAliasID(new BroadcastChannel("Channel 1")); - a1.addAliasID(new BroadcastChannel("Channel 2")); - a1.addAliasID(new BroadcastChannel("Channel 3")); - a1.addAliasID(new BroadcastChannel("Channel 4")); - - Alias a4 = new Alias("Alias 1"); - a1.addAliasID(new BroadcastChannel("Channel 1")); - a1.addAliasID(new BroadcastChannel("Channel 2")); - a1.addAliasID(new BroadcastChannel("Channel 3")); - a1.addAliasID(new BroadcastChannel("Channel 4")); - - PatchGroupAlias pga = new PatchGroupAlias(); - pga.setPatchGroupAlias(a1); - pga.addPatchedAlias(a2); - pga.addPatchedAlias(a3); - pga.addPatchedAlias(a4); - - Set bc = pga.getBroadcastChannels(); - - mLog.debug("Channels:" + bc); - } -} diff --git a/src/main/java/io/github/dsheirer/alias/action/AliasAction.java b/src/main/java/io/github/dsheirer/alias/action/AliasAction.java index ae514992..6018019d 100644 --- a/src/main/java/io/github/dsheirer/alias/action/AliasAction.java +++ b/src/main/java/io/github/dsheirer/alias/action/AliasAction.java @@ -26,9 +26,7 @@ import io.github.dsheirer.alias.Alias; import io.github.dsheirer.alias.action.beep.BeepAction; import io.github.dsheirer.alias.action.clip.ClipAction; import io.github.dsheirer.alias.action.script.ScriptAction; -import io.github.dsheirer.message.Message; - -import java.util.concurrent.ScheduledExecutorService; +import io.github.dsheirer.message.IMessage; /** * Alias action defines an action to execute when an alias is detected active. @@ -56,7 +54,7 @@ public abstract class AliasAction * action attached. The alias argument is the parent alias containing the * alias action. */ - public abstract void execute(ScheduledExecutorService scheduledExecutorService, Alias alias, Message message); + public abstract void execute(Alias alias, IMessage message); /** * Dismiss a persistent alias action diff --git a/src/main/java/io/github/dsheirer/alias/action/AliasActionManager.java b/src/main/java/io/github/dsheirer/alias/action/AliasActionManager.java index 760122cb..8d73f0eb 100644 --- a/src/main/java/io/github/dsheirer/alias/action/AliasActionManager.java +++ b/src/main/java/io/github/dsheirer/alias/action/AliasActionManager.java @@ -19,51 +19,86 @@ package io.github.dsheirer.alias.action; import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.message.IMessageListener; +import io.github.dsheirer.module.Module; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.util.ThreadPool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.List; /** - * Manages all alias action events. Each received message is interrogated for - * any alias entries and then each alias is interrogated for any alias actions. + * Manages all alias action events. Each received message is interrogated for any identifiers that may be aliasable + * and have associated alias actions. * - * Each alias action is executed and provided a copy of the source message so - * that the contents of the message can be used as part of the action. + * Each alias action is executed and provided a copy of the source message so that the contents of the message can be + * used as part of the action. */ -public class AliasActionManager implements Listener +public class AliasActionManager extends Module implements IMessageListener, Listener { - public AliasActionManager() + private final static Logger mLog = LoggerFactory.getLogger(AliasActionManager.class); + private AliasList mAliasList; + + public AliasActionManager(AliasList aliasList) { + mAliasList = aliasList; } @Override - public void receive(Message message) + public void receive(IMessage message) { - if(message.isValid()) + if(mAliasList != null && message.isValid()) { - List aliases = message.getAliases(); + List identifiers = message.getIdentifiers(); - if(aliases != null) + for(Identifier identifier: identifiers) { - for(Alias alias : aliases) - { - if(alias.hasActions()) - { - List actions = alias.getAction(); + Alias alias = mAliasList.getAlias(identifier); - for(AliasAction action : actions) - { - /* Provide access to the thread pool manager in case the - * action needs to setup a timer, and provide the original - * message to be used as part of the action (e.g. sending - * the message as a text message to a cell phone */ - action.execute(ThreadPool.SCHEDULED, alias, message); - } + if(alias != null && alias.hasActions()) + { + for(AliasAction action: alias.getAction()) + { + action.execute(alias, message); } } } } } + + @Override + public void reset() + { + //No actions neeeded + } + + @Override + public void start() + { + //No actions neeeded + } + + @Override + public void stop() + { + //No actions neeeded + } + + @Override + public void dispose() + { + //No actions neeeded + } + + /** + * IMessageListener interface ... delegates to this class implementation of Listener + */ + @Override + public Listener getMessageListener() + { + return this; + } } diff --git a/src/main/java/io/github/dsheirer/alias/action/RecurringAction.java b/src/main/java/io/github/dsheirer/alias/action/RecurringAction.java index 8847eddf..5df29108 100644 --- a/src/main/java/io/github/dsheirer/alias/action/RecurringAction.java +++ b/src/main/java/io/github/dsheirer/alias/action/RecurringAction.java @@ -1,13 +1,33 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias.action; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.util.ThreadPool; -import javax.swing.*; -import java.awt.*; -import java.util.concurrent.ScheduledExecutorService; +import javax.swing.JOptionPane; +import java.awt.EventQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -18,19 +38,15 @@ public abstract class RecurringAction extends AliasAction protected AtomicBoolean mRunning = new AtomicBoolean(false); @JsonIgnore private ScheduledFuture mPerpetualAction; - @JsonIgnore - protected ScheduledExecutorService mScheduledExecutorService; protected Interval mInterval = Interval.ONCE; protected int mPeriod = 5; - public abstract void performAction(Alias alias, Message message); + public abstract void performAction(Alias alias, IMessage message); @Override - public void execute(ScheduledExecutorService scheduledExecutorService, Alias alias, Message message) + public void execute(Alias alias, IMessage message) { - mScheduledExecutorService = scheduledExecutorService; - if(mRunning.compareAndSet(false, true)) { switch(mInterval) @@ -41,10 +57,10 @@ public abstract class RecurringAction extends AliasAction break; case DELAYED_RESET: performThreadedAction(alias, message); - mScheduledExecutorService.schedule(new ResetTask(), mPeriod, TimeUnit.SECONDS); + ThreadPool.SCHEDULED.schedule(new ResetTask(), mPeriod, TimeUnit.SECONDS); break; case UNTIL_DISMISSED: - mPerpetualAction = mScheduledExecutorService.scheduleAtFixedRate( + mPerpetualAction = ThreadPool.SCHEDULED.scheduleAtFixedRate( new PerformActionTask(alias, message), mPeriod, mPeriod, TimeUnit.SECONDS); StringBuilder sb = new StringBuilder(); @@ -56,18 +72,13 @@ public abstract class RecurringAction extends AliasAction final String text = sb.toString(); - EventQueue.invokeLater(new Runnable() - { - @Override - public void run() - { - JOptionPane.showMessageDialog(null, text, - "Alias Alert", JOptionPane.INFORMATION_MESSAGE); + EventQueue.invokeLater(() -> { + JOptionPane.showMessageDialog(null, text, + "Alias Alert", JOptionPane.INFORMATION_MESSAGE); - dismiss(false); + dismiss(false); - mScheduledExecutorService.schedule(new ResetTask(), 15, TimeUnit.SECONDS); - } + ThreadPool.SCHEDULED.schedule(new ResetTask(), 15, TimeUnit.SECONDS); }); break; default: @@ -79,16 +90,9 @@ public abstract class RecurringAction extends AliasAction * Spawns the performAction() event into a new thread so that it doesn't * delay any decoder actions. */ - private void performThreadedAction(final Alias alias, final Message message) + private void performThreadedAction(final Alias alias, final IMessage message) { - mScheduledExecutorService.schedule(new Runnable() - { - @Override - public void run() - { - performAction(alias, message); - } - }, 0, TimeUnit.SECONDS); + ThreadPool.SCHEDULED.schedule(() -> performAction(alias, message), 0, TimeUnit.SECONDS); } @Override @@ -139,7 +143,7 @@ public abstract class RecurringAction extends AliasAction private String mLabel; - private Interval(String label) + Interval(String label) { mLabel = label; } @@ -153,9 +157,9 @@ public abstract class RecurringAction extends AliasAction public class PerformActionTask implements Runnable { private Alias mAlias; - private Message mMessage; + private IMessage mMessage; - public PerformActionTask(Alias alias, Message message) + public PerformActionTask(Alias alias, IMessage message) { mAlias = alias; mMessage = message; diff --git a/src/main/java/io/github/dsheirer/alias/action/beep/BeepAction.java b/src/main/java/io/github/dsheirer/alias/action/beep/BeepAction.java index 6a0717ac..644b655d 100644 --- a/src/main/java/io/github/dsheirer/alias/action/beep/BeepAction.java +++ b/src/main/java/io/github/dsheirer/alias/action/beep/BeepAction.java @@ -1,10 +1,30 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias.action.beep; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import io.github.dsheirer.alias.Alias; import io.github.dsheirer.alias.action.AliasActionType; import io.github.dsheirer.alias.action.RecurringAction; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; public class BeepAction extends RecurringAction { @@ -17,7 +37,7 @@ public class BeepAction extends RecurringAction } @Override - public void performAction(Alias alias, Message message ) + public void performAction(Alias alias, IMessage message ) { System.out.println( "\007" ); } diff --git a/src/main/java/io/github/dsheirer/alias/action/clip/ClipAction.java b/src/main/java/io/github/dsheirer/alias/action/clip/ClipAction.java index 15807304..9bea3933 100644 --- a/src/main/java/io/github/dsheirer/alias/action/clip/ClipAction.java +++ b/src/main/java/io/github/dsheirer/alias/action/clip/ClipAction.java @@ -1,3 +1,23 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias.action.clip; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -5,7 +25,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import io.github.dsheirer.alias.Alias; import io.github.dsheirer.alias.action.AliasActionType; import io.github.dsheirer.alias.action.RecurringAction; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,7 +65,7 @@ public class ClipAction extends RecurringAction } @Override - public void performAction(Alias alias, Message message) + public void performAction(Alias alias, IMessage message) { try { diff --git a/src/main/java/io/github/dsheirer/alias/action/script/ScriptAction.java b/src/main/java/io/github/dsheirer/alias/action/script/ScriptAction.java index 9aa920c2..e837010d 100644 --- a/src/main/java/io/github/dsheirer/alias/action/script/ScriptAction.java +++ b/src/main/java/io/github/dsheirer/alias/action/script/ScriptAction.java @@ -1,10 +1,30 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias.action.script; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import io.github.dsheirer.alias.Alias; import io.github.dsheirer.alias.action.AliasActionType; import io.github.dsheirer.alias.action.RecurringAction; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,7 +59,7 @@ public class ScriptAction extends RecurringAction } @Override - public void performAction(Alias alias, Message message) + public void performAction(Alias alias, IMessage message) { try { @@ -63,8 +83,7 @@ public class ScriptAction extends RecurringAction int exitCode = p.waitFor(); - BufferedReader reader = new BufferedReader( - new InputStreamReader(p.getInputStream())); + BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); String line = ""; @@ -77,8 +96,7 @@ public class ScriptAction extends RecurringAction if(exitCode != 0) { - throw new RuntimeException("Exit Code: " + exitCode + - " Console:" + sb.toString()); + throw new RuntimeException("Exit Code: " + exitCode + " Console:" + sb.toString()); } } } diff --git a/src/main/java/io/github/dsheirer/alias/id/AliasID.java b/src/main/java/io/github/dsheirer/alias/id/AliasID.java index 0534f4a2..935c764e 100644 --- a/src/main/java/io/github/dsheirer/alias/id/AliasID.java +++ b/src/main/java/io/github/dsheirer/alias/id/AliasID.java @@ -1,17 +1,17 @@ /******************************************************************************* * SDR Trunk * Copyright (C) 2014-2016 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 ******************************************************************************/ @@ -23,46 +23,52 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import io.github.dsheirer.alias.id.broadcast.BroadcastChannel; import io.github.dsheirer.alias.id.esn.Esn; -import io.github.dsheirer.alias.id.fleetsync.FleetsyncID; +import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID; +import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID; +import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327ID; +import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordable; +import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID; import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID; -import io.github.dsheirer.alias.id.mdc.MDC1200ID; import io.github.dsheirer.alias.id.mobileID.Min; -import io.github.dsheirer.alias.id.mpt1327.MPT1327ID; -import io.github.dsheirer.alias.id.nonrecordable.NonRecordable; import io.github.dsheirer.alias.id.priority.Priority; +import io.github.dsheirer.alias.id.record.Record; import io.github.dsheirer.alias.id.siteID.SiteID; import io.github.dsheirer.alias.id.status.StatusID; -import io.github.dsheirer.alias.id.talkgroup.TalkgroupID; +import io.github.dsheirer.alias.id.talkgroup.Talkgroup; +import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange; import io.github.dsheirer.alias.id.uniqueID.UniqueID; -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include= JsonTypeInfo.As.PROPERTY, property="type") +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes({ - @JsonSubTypes.Type(value=BroadcastChannel.class, name="broadcastChannel"), - @JsonSubTypes.Type(value=Esn.class, name="esn"), - @JsonSubTypes.Type(value=FleetsyncID.class, name="fleetsyncID"), + @JsonSubTypes.Type(value = BroadcastChannel.class, name = "broadcastChannel"), + @JsonSubTypes.Type(value = Esn.class, name = "esn"), + @JsonSubTypes.Type(value = FleetsyncID.class, name = "fleetsyncID"), @JsonSubTypes.Type(value = LoJackFunctionAndID.class, name = "loJackFunctionAndID"), @JsonSubTypes.Type(value = MDC1200ID.class, name = "mdc1200ID"), @JsonSubTypes.Type(value = Min.class, name = "min"), @JsonSubTypes.Type(value = MPT1327ID.class, name = "mpt1327ID"), @JsonSubTypes.Type(value = NonRecordable.class, name = "nonRecordable"), + @JsonSubTypes.Type(value = Talkgroup.class, name = "talkgroup"), + @JsonSubTypes.Type(value = TalkgroupRange.class, name = "talkgroupRange"), @JsonSubTypes.Type(value = Priority.class, name = "priority"), + @JsonSubTypes.Type(value = Record.class, name = "record"), @JsonSubTypes.Type(value = SiteID.class, name = "siteID"), @JsonSubTypes.Type(value = StatusID.class, name = "statusID"), - @JsonSubTypes.Type(value = TalkgroupID.class, name = "talkgroupID"), + @JsonSubTypes.Type(value = LegacyTalkgroupID.class, name = "talkgroupID"), @JsonSubTypes.Type(value = UniqueID.class, name = "uniqueID") }) -@JacksonXmlRootElement( localName = "id" ) +@JacksonXmlRootElement(localName = "id") public abstract class AliasID { - public AliasID() - { - } + public AliasID() + { + } - @JsonIgnore - public abstract AliasIDType getType(); - - public abstract boolean matches( AliasID id ); + @JsonIgnore + public abstract AliasIDType getType(); - @JsonIgnore - public abstract boolean isValid(); + public abstract boolean matches(AliasID id); + + @JsonIgnore + public abstract boolean isValid(); } diff --git a/src/main/java/io/github/dsheirer/alias/id/AliasIDType.java b/src/main/java/io/github/dsheirer/alias/id/AliasIDType.java index bd95471e..206a9e09 100644 --- a/src/main/java/io/github/dsheirer/alias/id/AliasIDType.java +++ b/src/main/java/io/github/dsheirer/alias/id/AliasIDType.java @@ -1,49 +1,55 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias.id; public enum AliasIDType { - ESN( "ESN" ), - FLEETSYNC( "Fleetsync" ), - LOJACK( "LoJack" ), - LTR_NET_UID( "LTR-Net UID" ), - MDC1200( "MDC-1200" ), - MIN( "Passport MIN" ), - MPT1327( "MPT-1327" ), - SITE( "Site" ), - STATUS( "Status" ), - TALKGROUP( "Talkgroup" ), + BROADCAST_CHANNEL("Audio Broadcast Channel"), + ESN("ESN"), + INVERT("Audio Inversion"), + LOJACK("LoJack"), + LTR_NET_UID("LTR-Net UID"), + MIN("Passport MIN"), + PRIORITY("Audio Priority"), + RECORD("Record"), + SITE("Site"), + STATUS("Status"), + TALKGROUP("Talkgroup"), + TALKGROUP_RANGE("Talkgroup Range"), - INVERT( "Audio Inversion" ), - PRIORITY( "Audio Priority" ), - NON_RECORDABLE( "Audio Non-Recordable" ), - BROADCAST_CHANNEL( "Audio Broadcast Channel" ); - - private String mLabel; - - private AliasIDType( String label ) - { - mLabel = label; - } - - public String toString() - { - return mLabel; - } + //Legacy identifier types - no longer used + FLEETSYNC("Fleetsync"), + LEGACY_TALKGROUP("Legacy Talkgroup"), + MDC1200("MDC-1200"), + MPT1327("MPT-1327"), + NON_RECORDABLE("Audio Non-Recordable"); + + private String mLabel; + + AliasIDType(String label) + { + mLabel = label; + } + + public String toString() + { + return mLabel; + } } diff --git a/src/main/java/io/github/dsheirer/alias/id/AliasIdentifierEditor.java b/src/main/java/io/github/dsheirer/alias/id/AliasIdentifierEditor.java index d0d68901..e6bb7b9d 100644 --- a/src/main/java/io/github/dsheirer/alias/id/AliasIdentifierEditor.java +++ b/src/main/java/io/github/dsheirer/alias/id/AliasIdentifierEditor.java @@ -1,20 +1,22 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias.id; import io.github.dsheirer.alias.Alias; @@ -26,10 +28,19 @@ import io.github.dsheirer.gui.editor.Editor; import io.github.dsheirer.gui.editor.EmptyEditor; import net.miginfocom.swing.MigLayout; -import javax.swing.*; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JList; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.ListModel; +import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; -import java.awt.*; +import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; @@ -40,328 +51,327 @@ import java.util.List; public class AliasIdentifierEditor extends Editor { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - private static ListModel EMPTY_MODEL = new DefaultListModel<>(); - private JList mAliasIDList = new JList<>( EMPTY_MODEL ); - private JButton mNewIDButton; - private JButton mCloneIDButton; - private JButton mDeleteIDButton; - private EditorContainer mEditorContainer = new EditorContainer(); + private static ListModel EMPTY_MODEL = new DefaultListModel<>(); + private JList mAliasIDList = new JList<>(EMPTY_MODEL); + private JButton mNewIDButton; + private JButton mCloneIDButton; + private JButton mDeleteIDButton; + private EditorContainer mEditorContainer = new EditorContainer(); - private AliasModel mAliasModel; + private AliasModel mAliasModel; private BroadcastModel mBroadcastModel; - - public AliasIdentifierEditor( AliasModel aliasModel, BroadcastModel broadcastModel ) - { - mAliasModel = aliasModel; + + public AliasIdentifierEditor(AliasModel aliasModel, BroadcastModel broadcastModel) + { + mAliasModel = aliasModel; mBroadcastModel = broadcastModel; - init(); - } + init(); + } - private void init() - { - setLayout( new MigLayout( "fill,wrap 3", - "[grow,fill][grow,fill][grow,fill]", "[][grow,fill][]" ) ); + private void init() + { + setLayout(new MigLayout("fill,wrap 3", + "[grow,fill][grow,fill][grow,fill]", "[][grow,fill][]")); - mAliasIDList.setVisibleRowCount( 6 ); - mAliasIDList.setSelectionMode( ListSelectionModel.SINGLE_SELECTION ); - mAliasIDList.setLayoutOrientation( JList.VERTICAL ); - mAliasIDList.addListSelectionListener( new ListSelectionListener() - { - @Override - public void valueChanged( ListSelectionEvent event ) - { - if( !event.getValueIsAdjusting() ) - { - JList list = (JList)event.getSource(); - - Object selectedItem = list.getSelectedValue(); - - if( selectedItem != null && selectedItem instanceof AliasID ) - { - AliasID selected = (AliasID)selectedItem; + mAliasIDList.setVisibleRowCount(6); + mAliasIDList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + mAliasIDList.setLayoutOrientation(JList.VERTICAL); + mAliasIDList.addListSelectionListener(new ListSelectionListener() + { + @Override + public void valueChanged(ListSelectionEvent event) + { + if(!event.getValueIsAdjusting()) + { + JList list = (JList)event.getSource(); - mEditorContainer.setAliasID( selected ); - - mCloneIDButton.setEnabled( true ); - mDeleteIDButton.setEnabled( true ); - } - else - { - mCloneIDButton.setEnabled( false ); - mDeleteIDButton.setEnabled( false ); - } - } - } - } ); - - JScrollPane scroller = new JScrollPane( mAliasIDList ); - add( scroller, "span,grow" ); - - add( mEditorContainer, "span,grow" ); + Object selectedItem = list.getSelectedValue(); - mNewIDButton = new JButton( "New ..." ); - mNewIDButton.setToolTipText( "Create a new Alias Identifier" ); - mNewIDButton.addMouseListener( new MouseAdapter() - { - @Override - public void mouseClicked( MouseEvent e ) - { - JPopupMenu menu = new JPopupMenu(); + if(selectedItem != null && selectedItem instanceof AliasID) + { + AliasID selected = (AliasID)selectedItem; - menu.add( new AddAliasIdentifierItem( AliasIDType.ESN ) ); - menu.add( new AddAliasIdentifierItem( AliasIDType.FLEETSYNC) ); - menu.add( new AddAliasIdentifierItem( AliasIDType.LOJACK) ); - menu.add( new AddAliasIdentifierItem( AliasIDType.LTR_NET_UID) ); - menu.add( new AddAliasIdentifierItem( AliasIDType.MDC1200 ) ); - menu.add( new AddAliasIdentifierItem( AliasIDType.MPT1327 ) ); - menu.add( new AddAliasIdentifierItem( AliasIDType.MIN ) ); - menu.add( new AddAliasIdentifierItem( AliasIDType.SITE) ); - menu.add( new AddAliasIdentifierItem( AliasIDType.STATUS) ); - menu.add( new AddAliasIdentifierItem( AliasIDType.TALKGROUP) ); + mEditorContainer.setAliasID(selected); - menu.addSeparator(); + mCloneIDButton.setEnabled(true); + mDeleteIDButton.setEnabled(true); + } + else + { + mCloneIDButton.setEnabled(false); + mDeleteIDButton.setEnabled(false); + } + } + } + }); - menu.add( new AddAliasIdentifierItem( AliasIDType.BROADCAST_CHANNEL) ); + JScrollPane scroller = new JScrollPane(mAliasIDList); + add(scroller, "span,grow"); - if( hasItem() ) - { - Alias alias = getItem(); + add(mEditorContainer, "span,grow"); - if( alias.isRecordable() ) - { - menu.add( new AddAliasIdentifierItem( AliasIDType.NON_RECORDABLE) ); - } + mNewIDButton = new JButton("New ..."); + mNewIDButton.setToolTipText("Create a new Alias Identifier"); + mNewIDButton.addMouseListener(new MouseAdapter() + { + @Override + public void mouseClicked(MouseEvent e) + { + JPopupMenu menu = new JPopupMenu(); - if( !alias.hasCallPriority() ) - { - menu.add( new AddAliasIdentifierItem( AliasIDType.PRIORITY) ); - } - } + menu.add(new AddAliasIdentifierItem(AliasIDType.TALKGROUP)); + menu.add(new AddAliasIdentifierItem(AliasIDType.TALKGROUP_RANGE)); + menu.add(new AddAliasIdentifierItem(AliasIDType.ESN)); + menu.add(new AddAliasIdentifierItem(AliasIDType.LOJACK)); + menu.add(new AddAliasIdentifierItem(AliasIDType.LTR_NET_UID)); + menu.add(new AddAliasIdentifierItem(AliasIDType.MDC1200)); + menu.add(new AddAliasIdentifierItem(AliasIDType.MIN)); + menu.add(new AddAliasIdentifierItem(AliasIDType.SITE)); + menu.add(new AddAliasIdentifierItem(AliasIDType.STATUS)); - menu.show( e.getComponent(), e.getX(), e.getY() ); - } - } ); - - add( mNewIDButton ); - - mCloneIDButton = new JButton( "Clone" ); - mCloneIDButton.setToolTipText( "Create a copy of the currently selected identifier" ); - mCloneIDButton.setEnabled( false ); - mCloneIDButton.addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent e ) - { - AliasID selected = mAliasIDList.getSelectedValue(); - - if( selected != null ) - { - AliasID clone = AliasFactory.copyOf( selected ); + menu.addSeparator(); - if( clone != null ) - { - addAliasIDToList( clone ); - } - } - } - } ); - add( mCloneIDButton ); - - mDeleteIDButton = new JButton( "Delete" ); - mDeleteIDButton.setToolTipText( "Delete the currently selected identifier" ); - mDeleteIDButton.setEnabled( false ); - mDeleteIDButton.addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent e ) - { - final AliasID selected = mAliasIDList.getSelectedValue(); - - if( selected != null ) - { - int choice = JOptionPane.showConfirmDialog( mDeleteIDButton, - "Do you want to delete [" + selected.toString() + "]", - "Are you sure?", JOptionPane.YES_NO_OPTION ); - - if( choice == JOptionPane.YES_OPTION && hasItem() ) - { - Alias alias = getItem(); - - alias.removeAliasID( selected ); - - mAliasModel.broadcast( new AliasEvent( alias, AliasEvent.Event.CHANGE ) ); + menu.add(new AddAliasIdentifierItem(AliasIDType.BROADCAST_CHANNEL)); - setItem( alias ); - } - } - } - } ); - add( mDeleteIDButton, "wrap" ); - } - - @Override - public void setItem( Alias alias ) - { - super.setItem( alias ); - - if( alias == null || alias.getId().isEmpty() ) - { - mAliasIDList.setModel( EMPTY_MODEL ); - } - else - { - DefaultListModel model = new DefaultListModel(); + if(hasItem()) + { + Alias alias = getItem(); - List ids = alias.getId(); - - Collections.sort( ids, new Comparator() - { - @Override - public int compare( AliasID o1, AliasID o2 ) - { - return o1.toString().compareTo( o2.toString() ); - } - } ); - - for( AliasID id: ids ) - { - model.addElement( id ); - } - - mAliasIDList.setModel( model ); - } - - mEditorContainer.setAliasID( null ); - } - - @Override - public void save() - { - if( isModified() || mEditorContainer.isModified() ) - { - if( mEditorContainer.isModified() ) - { - mEditorContainer.save(); - } - - setModified( false ); - - if( hasItem() ) - { - mAliasModel.broadcast( new AliasEvent( getItem(), AliasEvent.Event.CHANGE ) ); - } - } - } - - public class EditorContainer extends JPanel - { - private static final long serialVersionUID = 1L; + if(!alias.isRecordable()) + { + menu.add(new AddAliasIdentifierItem(AliasIDType.RECORD)); + } - private Editor mEditor = new EmptyEditor<>(); + if(!alias.hasCallPriority()) + { + menu.add(new AddAliasIdentifierItem(AliasIDType.PRIORITY)); + } + } - public EditorContainer() - { - setLayout( new MigLayout( "","[grow,fill]", "[grow,fill]" ) ); + menu.show(e.getComponent(), e.getX(), e.getY()); + } + }); - add( mEditor ); - } - - public boolean isModified() - { - if( mEditor != null ) - { - return mEditor.isModified(); - } - - return false; - } - - public void save() - { - if( mEditor != null ) - { - mEditor.save(); - } - } - - public void setAliasID( AliasID aliasID ) - { - if( mEditor != null ) - { - if( mEditor.isModified() ) - { - int option = JOptionPane.showConfirmDialog( - EditorContainer.this, - "Identifier settings have changed. Do you want to save these changes?", - "Save Changes?", - JOptionPane.YES_NO_OPTION, - JOptionPane.QUESTION_MESSAGE ); - - if( option == JOptionPane.YES_OPTION ) - { - mEditor.save(); - } - } - } - removeAll(); - - //This will always give us an editor - mEditor = AliasFactory.getEditor( aliasID, mBroadcastModel ); - - add( mEditor ); + add(mNewIDButton); - revalidate(); - } - } - - private void addAliasIDToList( final AliasID id ) - { - if( id != null && hasItem() ) - { - final Alias alias = getItem(); - - alias.addAliasID( id ); - - EventQueue.invokeLater( new Runnable() - { - @Override - public void run() - { - setItem( alias ); - - mAliasIDList.setSelectedValue( id, true ); - - AliasIdentifierEditor.this.setModified( true ); - } - } ); - } - } - - public class AddAliasIdentifierItem extends JMenuItem - { - private static final long serialVersionUID = 1L; - - private AliasIDType mAliasIDType; + mCloneIDButton = new JButton("Clone"); + mCloneIDButton.setToolTipText("Create a copy of the currently selected identifier"); + mCloneIDButton.setEnabled(false); + mCloneIDButton.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + AliasID selected = mAliasIDList.getSelectedValue(); - public AddAliasIdentifierItem( AliasIDType type ) - { - super( type.toString() ); - - mAliasIDType = type; - - addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent e ) - { - final AliasID id = AliasFactory.getAliasID( mAliasIDType ); - - addAliasIDToList( id ); - } - } ); - } - } + if(selected != null) + { + AliasID clone = AliasFactory.copyOf(selected); + + if(clone != null) + { + addAliasIDToList(clone); + } + } + } + }); + add(mCloneIDButton); + + mDeleteIDButton = new JButton("Delete"); + mDeleteIDButton.setToolTipText("Delete the currently selected identifier"); + mDeleteIDButton.setEnabled(false); + mDeleteIDButton.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + final AliasID selected = mAliasIDList.getSelectedValue(); + + if(selected != null) + { + int choice = JOptionPane.showConfirmDialog(mDeleteIDButton, + "Do you want to delete [" + selected.toString() + "]", + "Are you sure?", JOptionPane.YES_NO_OPTION); + + if(choice == JOptionPane.YES_OPTION && hasItem()) + { + Alias alias = getItem(); + + alias.removeAliasID(selected); + + mAliasModel.broadcast(new AliasEvent(alias, AliasEvent.Event.CHANGE)); + + setItem(alias); + } + } + } + }); + add(mDeleteIDButton, "wrap"); + } + + @Override + public void setItem(Alias alias) + { + super.setItem(alias); + + if(alias == null || alias.getId().isEmpty()) + { + mAliasIDList.setModel(EMPTY_MODEL); + } + else + { + DefaultListModel model = new DefaultListModel(); + + List ids = alias.getId(); + + Collections.sort(ids, new Comparator() + { + @Override + public int compare(AliasID o1, AliasID o2) + { + return o1.toString().compareTo(o2.toString()); + } + }); + + for(AliasID id : ids) + { + model.addElement(id); + } + + mAliasIDList.setModel(model); + } + + mEditorContainer.setAliasID(null); + } + + @Override + public void save() + { + if(isModified() || mEditorContainer.isModified()) + { + if(mEditorContainer.isModified()) + { + mEditorContainer.save(); + } + + setModified(false); + + if(hasItem()) + { + mAliasModel.broadcast(new AliasEvent(getItem(), AliasEvent.Event.CHANGE)); + } + } + } + + public class EditorContainer extends JPanel + { + private static final long serialVersionUID = 1L; + + private Editor mEditor = new EmptyEditor<>(); + + public EditorContainer() + { + setLayout(new MigLayout("", "[grow,fill]", "[grow,fill]")); + + add(mEditor); + } + + public boolean isModified() + { + if(mEditor != null) + { + return mEditor.isModified(); + } + + return false; + } + + public void save() + { + if(mEditor != null) + { + mEditor.save(); + } + } + + public void setAliasID(AliasID aliasID) + { + if(mEditor != null) + { + if(mEditor.isModified()) + { + int option = JOptionPane.showConfirmDialog( + EditorContainer.this, + "Identifier settings have changed. Do you want to save these changes?", + "Save Changes?", + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE); + + if(option == JOptionPane.YES_OPTION) + { + mEditor.save(); + } + } + } + removeAll(); + + //This will always give us an editor + mEditor = AliasFactory.getEditor(aliasID, mBroadcastModel); + + add(mEditor); + + revalidate(); + } + } + + private void addAliasIDToList(final AliasID id) + { + if(id != null && hasItem()) + { + final Alias alias = getItem(); + + alias.addAliasID(id); + + EventQueue.invokeLater(new Runnable() + { + @Override + public void run() + { + setItem(alias); + + mAliasIDList.setSelectedValue(id, true); + + AliasIdentifierEditor.this.setModified(true); + } + }); + } + } + + public class AddAliasIdentifierItem extends JMenuItem + { + private static final long serialVersionUID = 1L; + + private AliasIDType mAliasIDType; + + public AddAliasIdentifierItem(AliasIDType type) + { + super(type.toString()); + + mAliasIDType = type; + + addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + final AliasID id = AliasFactory.getAliasID(mAliasIDType); + + addAliasIDToList(id); + } + }); + } + } } diff --git a/src/main/java/io/github/dsheirer/alias/id/esn/Esn.java b/src/main/java/io/github/dsheirer/alias/id/esn/Esn.java index 656c7793..a2b66022 100644 --- a/src/main/java/io/github/dsheirer/alias/id/esn/Esn.java +++ b/src/main/java/io/github/dsheirer/alias/id/esn/Esn.java @@ -48,7 +48,13 @@ public class Esn extends AliasID public String toString() { - return "ESN: " + mEsn; + StringBuilder sb = new StringBuilder(); + sb.append("ESN: ").append(mEsn != null ? mEsn : "(empty)"); + if(!isValid()) + { + sb.append(" **NOT VALID**"); + } + return sb.toString(); } @Override diff --git a/src/main/java/io/github/dsheirer/alias/id/fleetsync/FleetsyncID.java b/src/main/java/io/github/dsheirer/alias/id/legacy/fleetsync/FleetsyncID.java similarity index 53% rename from src/main/java/io/github/dsheirer/alias/id/fleetsync/FleetsyncID.java rename to src/main/java/io/github/dsheirer/alias/id/legacy/fleetsync/FleetsyncID.java index 19473bdf..3076d3b7 100644 --- a/src/main/java/io/github/dsheirer/alias/id/fleetsync/FleetsyncID.java +++ b/src/main/java/io/github/dsheirer/alias/id/legacy/fleetsync/FleetsyncID.java @@ -1,21 +1,23 @@ -/******************************************************************************* - * 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 io.github.dsheirer.alias.id.fleetsync; +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias.id.legacy.fleetsync; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import io.github.dsheirer.alias.id.AliasID; @@ -48,7 +50,7 @@ public class FleetsyncID extends AliasID public String toString() { - return "Fleetsync: " + mID; + return "Fleetsync: " + (mID != null ? mID : "(empty)") + " **INVALID - USE TALKGROUP INSTEAD"; } @Override diff --git a/src/main/java/io/github/dsheirer/alias/id/fleetsync/FleetsyncIDEditor.java b/src/main/java/io/github/dsheirer/alias/id/legacy/fleetsync/FleetsyncIDEditor.java similarity index 69% rename from src/main/java/io/github/dsheirer/alias/id/fleetsync/FleetsyncIDEditor.java rename to src/main/java/io/github/dsheirer/alias/id/legacy/fleetsync/FleetsyncIDEditor.java index 89fe739c..85f7a2ba 100644 --- a/src/main/java/io/github/dsheirer/alias/id/fleetsync/FleetsyncIDEditor.java +++ b/src/main/java/io/github/dsheirer/alias/id/legacy/fleetsync/FleetsyncIDEditor.java @@ -1,21 +1,23 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 io.github.dsheirer.alias.id.fleetsync; +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias.id.legacy.fleetsync; import io.github.dsheirer.alias.id.AliasID; import io.github.dsheirer.gui.editor.DocumentListenerEditor; @@ -23,9 +25,13 @@ import net.miginfocom.swing.MigLayout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.swing.*; +import javax.swing.JFormattedTextField; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JTextField; import javax.swing.text.MaskFormatter; -import java.awt.*; +import java.awt.Color; +import java.awt.Cursor; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; diff --git a/src/main/java/io/github/dsheirer/alias/id/mdc/MDC1200ID.java b/src/main/java/io/github/dsheirer/alias/id/legacy/mdc/MDC1200ID.java similarity index 56% rename from src/main/java/io/github/dsheirer/alias/id/mdc/MDC1200ID.java rename to src/main/java/io/github/dsheirer/alias/id/legacy/mdc/MDC1200ID.java index e8f50df7..85dc60d9 100644 --- a/src/main/java/io/github/dsheirer/alias/id/mdc/MDC1200ID.java +++ b/src/main/java/io/github/dsheirer/alias/id/legacy/mdc/MDC1200ID.java @@ -1,21 +1,23 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014 Dennis Sheirer +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 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. + * 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 io.github.dsheirer.alias.id.mdc; + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ +package io.github.dsheirer.alias.id.legacy.mdc; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; @@ -49,7 +51,7 @@ public class MDC1200ID extends AliasID public String toString() { - return "MDC-1200: " + mID; + return "MDC-1200: " + (mID != null ? mID : "(empty)") + " **INVALID - USE TALKGROUP INSTEAD**"; } @Override diff --git a/src/main/java/io/github/dsheirer/alias/id/legacy/mdc/MDC1200IDEditor.java b/src/main/java/io/github/dsheirer/alias/id/legacy/mdc/MDC1200IDEditor.java new file mode 100644 index 00000000..f472e6d5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/alias/id/legacy/mdc/MDC1200IDEditor.java @@ -0,0 +1,133 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias.id.legacy.mdc; + +import io.github.dsheirer.alias.id.AliasID; +import io.github.dsheirer.gui.editor.DocumentListenerEditor; +import net.miginfocom.swing.MigLayout; + +import javax.swing.JFormattedTextField; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JTextField; +import javax.swing.text.MaskFormatter; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class MDC1200IDEditor extends DocumentListenerEditor +{ + private static final long serialVersionUID = 1L; + + private static final String HELP_TEXT = "" + + "

MDC-1200 Identifier

" + + "MDC-1200: four digit [0-9] value (e.g. 1234)
" + + "Wildcard: use an asterisk (*) to wildcard individual
" + + "digits (e.g. 123* or **34)" + + ""; + + private JTextField mTextField; + + public MDC1200IDEditor(AliasID aliasID) + { + initGUI(); + + setItem(aliasID); + } + + private void initGUI() + { + setLayout(new MigLayout("fill,wrap 2", "[right][left]", "[][]")); + + add(new JLabel("MDC-1200 ID:")); + + MaskFormatter formatter = null; + + try + { + //Mask: 4 digits + formatter = new MaskFormatter("####"); + } + catch(Exception e) + { + //Do nothing, the mask was invalid + } + + mTextField = new JFormattedTextField(formatter); + mTextField.getDocument().addDocumentListener(this); + mTextField.setToolTipText(HELP_TEXT); + add(mTextField, "growx,push"); + + JLabel help = new JLabel("Help ..."); + help.setForeground(Color.BLUE.brighter()); + help.setCursor(new Cursor(Cursor.HAND_CURSOR)); + help.addMouseListener(new MouseAdapter() + { + @Override + public void mouseClicked(MouseEvent e) + { + JOptionPane.showMessageDialog(MDC1200IDEditor.this, + HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE); + } + }); + add(help, "align left"); + } + + public MDC1200ID getMDC1200ID() + { + if(getItem() instanceof MDC1200ID) + { + return (MDC1200ID)getItem(); + } + + return null; + } + + @Override + public void setItem(AliasID aliasID) + { + super.setItem(aliasID); + + MDC1200ID mdc = getMDC1200ID(); + + if(mdc != null) + { + mTextField.setText(mdc.getIdent()); + } + + setModified(false); + + repaint(); + } + + @Override + public void save() + { + MDC1200ID mdc = getMDC1200ID(); + + if(mdc != null) + { + mdc.setIdent(mTextField.getText()); + } + + setModified(false); + } +} diff --git a/src/main/java/io/github/dsheirer/alias/id/mpt1327/MPT1327ID.java b/src/main/java/io/github/dsheirer/alias/id/legacy/mpt1327/MPT1327ID.java similarity index 55% rename from src/main/java/io/github/dsheirer/alias/id/mpt1327/MPT1327ID.java rename to src/main/java/io/github/dsheirer/alias/id/legacy/mpt1327/MPT1327ID.java index 53208bbb..b27ad6ee 100644 --- a/src/main/java/io/github/dsheirer/alias/id/mpt1327/MPT1327ID.java +++ b/src/main/java/io/github/dsheirer/alias/id/legacy/mpt1327/MPT1327ID.java @@ -1,21 +1,23 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014 Dennis Sheirer +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 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. + * 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 io.github.dsheirer.alias.id.mpt1327; + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ +package io.github.dsheirer.alias.id.legacy.mpt1327; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; @@ -49,7 +51,7 @@ public class MPT1327ID extends AliasID public String toString() { - return "MPT-1327: " + mID; + return "MPT-1327: " + (mID != null ? mID : "(empty)") + " **INVALID - USE TALKGROUP INSTEAD**"; } @Override diff --git a/src/main/java/io/github/dsheirer/alias/id/mpt1327/MPT1327IDEditor.java b/src/main/java/io/github/dsheirer/alias/id/legacy/mpt1327/MPT1327IDEditor.java similarity index 68% rename from src/main/java/io/github/dsheirer/alias/id/mpt1327/MPT1327IDEditor.java rename to src/main/java/io/github/dsheirer/alias/id/legacy/mpt1327/MPT1327IDEditor.java index 6ce3ccad..36c38ecc 100644 --- a/src/main/java/io/github/dsheirer/alias/id/mpt1327/MPT1327IDEditor.java +++ b/src/main/java/io/github/dsheirer/alias/id/legacy/mpt1327/MPT1327IDEditor.java @@ -1,29 +1,35 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 io.github.dsheirer.alias.id.mpt1327; +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias.id.legacy.mpt1327; import io.github.dsheirer.alias.id.AliasID; import io.github.dsheirer.gui.editor.DocumentListenerEditor; import net.miginfocom.swing.MigLayout; -import javax.swing.*; +import javax.swing.JFormattedTextField; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JTextField; import javax.swing.text.MaskFormatter; -import java.awt.*; +import java.awt.Color; +import java.awt.Cursor; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; diff --git a/src/main/java/io/github/dsheirer/alias/id/legacy/nonrecordable/NonRecordable.java b/src/main/java/io/github/dsheirer/alias/id/legacy/nonrecordable/NonRecordable.java new file mode 100644 index 00000000..b9d10706 --- /dev/null +++ b/src/main/java/io/github/dsheirer/alias/id/legacy/nonrecordable/NonRecordable.java @@ -0,0 +1,53 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias.id.legacy.nonrecordable; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import io.github.dsheirer.alias.id.AliasID; +import io.github.dsheirer.alias.id.AliasIDType; + +public class NonRecordable extends AliasID +{ + @JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance") + @Override + public AliasIDType getType() + { + return AliasIDType.NON_RECORDABLE; + } + + @Override + public boolean isValid() + { + return true; + } + + @Override + public boolean matches(AliasID id) + { + return false; + } + + @Override + public String toString() + { + return "Audio Non-Recordable - **INVALID - USE RECORD INSTEAD**"; + } +} diff --git a/src/main/java/io/github/dsheirer/alias/id/legacy/nonrecordable/NonRecordableEditor.java b/src/main/java/io/github/dsheirer/alias/id/legacy/nonrecordable/NonRecordableEditor.java new file mode 100644 index 00000000..51a3438a --- /dev/null +++ b/src/main/java/io/github/dsheirer/alias/id/legacy/nonrecordable/NonRecordableEditor.java @@ -0,0 +1,78 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias.id.legacy.nonrecordable; + +import io.github.dsheirer.alias.id.AliasID; +import io.github.dsheirer.gui.editor.DocumentListenerEditor; +import net.miginfocom.swing.MigLayout; + +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class NonRecordableEditor extends DocumentListenerEditor +{ + private static final long serialVersionUID = 1L; + + private static final String HELP_TEXT = "" + + "

Audio Non-Recordable

" + + "This identifies an alias as non-recordable.

" + + "The alias can still be monitored but associated audio will
" + + "not be recorded.

" + + "The default behavior when audio recording is enabled for a
" + + "channel is to record all aliases except any alias with a
" + + "non-recordable identifier"; + + public NonRecordableEditor(AliasID aliasID) + { + initGUI(); + + setItem(aliasID); + } + + private void initGUI() + { + setLayout(new MigLayout("fill,wrap 1", "[]", "[][]")); + + add(new JLabel("Non-Recordable"), "wrap"); + + JLabel help = new JLabel("Help ..."); + help.setForeground(Color.BLUE.brighter()); + help.setCursor(new Cursor(Cursor.HAND_CURSOR)); + help.addMouseListener(new MouseAdapter() + { + @Override + public void mouseClicked(MouseEvent e) + { + JOptionPane.showMessageDialog(NonRecordableEditor.this, + HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE); + } + }); + add(help, "align left"); + } + + @Override + public void save() + { + } +} diff --git a/src/main/java/io/github/dsheirer/alias/id/talkgroup/TalkgroupID.java b/src/main/java/io/github/dsheirer/alias/id/legacy/talkgroup/LegacyTalkgroupID.java similarity index 50% rename from src/main/java/io/github/dsheirer/alias/id/talkgroup/TalkgroupID.java rename to src/main/java/io/github/dsheirer/alias/id/legacy/talkgroup/LegacyTalkgroupID.java index c70accbd..bc925e83 100644 --- a/src/main/java/io/github/dsheirer/alias/id/talkgroup/TalkgroupID.java +++ b/src/main/java/io/github/dsheirer/alias/id/legacy/talkgroup/LegacyTalkgroupID.java @@ -1,37 +1,39 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014 Dennis Sheirer +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 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. + * 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 io.github.dsheirer.alias.id.talkgroup; + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ +package io.github.dsheirer.alias.id.legacy.talkgroup; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import io.github.dsheirer.alias.id.AliasID; import io.github.dsheirer.alias.id.AliasIDType; -public class TalkgroupID extends AliasID +public class LegacyTalkgroupID extends AliasID { private String mTalkgroup; - public TalkgroupID() + public LegacyTalkgroupID() { //No arg JAXB constructor } - public TalkgroupID(String talkgroup) + public LegacyTalkgroupID(String talkgroup) { mTalkgroup = talkgroup; } @@ -57,7 +59,7 @@ public class TalkgroupID extends AliasID { StringBuilder sb = new StringBuilder(); - sb.append("Talkgroup: " + mTalkgroup); + sb.append("Legacy Talkgroup:").append(mTalkgroup).append(" *Disabled - Please Convert"); return sb.toString(); } @@ -67,9 +69,9 @@ public class TalkgroupID extends AliasID { boolean retVal = false; - if(id instanceof TalkgroupID) + if(id instanceof LegacyTalkgroupID) { - TalkgroupID tgid = (TalkgroupID)id; + LegacyTalkgroupID tgid = (LegacyTalkgroupID)id; //Create a pattern - replace * wildcards with regex single-char wildcard String pattern = mTalkgroup.replace("*", ".?"); @@ -84,6 +86,6 @@ public class TalkgroupID extends AliasID @Override public AliasIDType getType() { - return AliasIDType.TALKGROUP; + return AliasIDType.LEGACY_TALKGROUP; } } diff --git a/src/main/java/io/github/dsheirer/alias/id/legacy/talkgroup/LegacyTalkgroupIDEditor.java b/src/main/java/io/github/dsheirer/alias/id/legacy/talkgroup/LegacyTalkgroupIDEditor.java new file mode 100644 index 00000000..7d88b587 --- /dev/null +++ b/src/main/java/io/github/dsheirer/alias/id/legacy/talkgroup/LegacyTalkgroupIDEditor.java @@ -0,0 +1,121 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias.id.legacy.talkgroup; + +import io.github.dsheirer.alias.id.AliasID; +import io.github.dsheirer.gui.editor.DocumentListenerEditor; +import net.miginfocom.swing.MigLayout; + +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JTextField; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class LegacyTalkgroupIDEditor extends DocumentListenerEditor +{ + private static final long serialVersionUID = 1L; + + private static final String HELP_TEXT = + "

Talkgroup Identifier

" + + "P25: 4 or 6 hex characters (e.g. AB12 or ABC123)
" + + "LTR: A-HH-TTT where A=Area H=Home T=Talkgroup (0-01-128)
" + + "Passport: 5-digit number (12345 or 00023)
" + + "
" + + "Wildcard: use an asterisk (*) in place of each talkgroup digit (e.g. 0*1*5)" + + ""; + + private JTextField mTextField; + + public LegacyTalkgroupIDEditor(AliasID aliasID) + { + initGUI(); + + setItem(aliasID); + } + + private void initGUI() + { + setLayout(new MigLayout("fill,wrap 2", "[right][left]", "[][]")); + + add(new JLabel("Talkgroup:")); + + mTextField = new JTextField(); + mTextField.getDocument().addDocumentListener(this); + mTextField.setToolTipText(HELP_TEXT); + add(mTextField, "growx,push"); + + JLabel help = new JLabel("Help ..."); + help.setForeground(Color.BLUE.brighter()); + help.setCursor(new Cursor(Cursor.HAND_CURSOR)); + help.addMouseListener(new MouseAdapter() + { + @Override + public void mouseClicked(MouseEvent e) + { + JOptionPane.showMessageDialog(LegacyTalkgroupIDEditor.this, + HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE); + } + }); + add(help, "align left"); + } + + public LegacyTalkgroupID getTalkgroupID() + { + if(getItem() instanceof LegacyTalkgroupID) + { + return (LegacyTalkgroupID)getItem(); + } + + return null; + } + + @Override + public void setItem(AliasID aliasID) + { + super.setItem(aliasID); + + LegacyTalkgroupID talkgroup = getTalkgroupID(); + + if(talkgroup != null) + { + mTextField.setText(talkgroup.getTalkgroup()); + } + + setModified(false); + + repaint(); + } + + @Override + public void save() + { + LegacyTalkgroupID talkgroup = getTalkgroupID(); + + if(talkgroup != null) + { + talkgroup.setTalkgroup(mTextField.getText()); + } + + setModified(false); + } +} diff --git a/src/main/java/io/github/dsheirer/alias/id/lojack/LoJackFunctionAndID.java b/src/main/java/io/github/dsheirer/alias/id/lojack/LoJackFunctionAndID.java index 95f15688..8ec2802e 100644 --- a/src/main/java/io/github/dsheirer/alias/id/lojack/LoJackFunctionAndID.java +++ b/src/main/java/io/github/dsheirer/alias/id/lojack/LoJackFunctionAndID.java @@ -26,7 +26,7 @@ import io.github.dsheirer.module.decode.lj1200.LJ1200Message; public class LoJackFunctionAndID extends AliasID { private LJ1200Message.Function mFunction = LJ1200Message.Function.F0_UNKNOWN; - private String mID = "*****"; + private String mID = null; public LoJackFunctionAndID() { @@ -62,7 +62,8 @@ public class LoJackFunctionAndID extends AliasID public String toString() { - return "LoJack FUNC: " + mFunction.getLabel() + " ID:" + (mID == null ? "" : mID); + return "LoJack FUNCTION: " + mFunction.getLabel() + + " ID:" + (mID == null ? "" : mID) + (isValid() ? "" : " **NOT VALID**"); } /** diff --git a/src/main/java/io/github/dsheirer/alias/id/mdc/MDC1200IDEditor.java b/src/main/java/io/github/dsheirer/alias/id/mdc/MDC1200IDEditor.java deleted file mode 100644 index e688421e..00000000 --- a/src/main/java/io/github/dsheirer/alias/id/mdc/MDC1200IDEditor.java +++ /dev/null @@ -1,127 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 io.github.dsheirer.alias.id.mdc; - -import io.github.dsheirer.alias.id.AliasID; -import io.github.dsheirer.gui.editor.DocumentListenerEditor; -import net.miginfocom.swing.MigLayout; - -import javax.swing.*; -import javax.swing.text.MaskFormatter; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; - -public class MDC1200IDEditor extends DocumentListenerEditor -{ - private static final long serialVersionUID = 1L; - - private static final String HELP_TEXT = "" - + "

MDC-1200 Identifier

" - + "MDC-1200: four digit [0-9] value (e.g. 1234)
" - + "Wildcard: use an asterisk (*) to wildcard individual
" - + "digits (e.g. 123* or **34)" - + ""; - - private JTextField mTextField; - - public MDC1200IDEditor( AliasID aliasID ) - { - initGUI(); - - setItem( aliasID ); - } - - private void initGUI() - { - setLayout( new MigLayout( "fill,wrap 2", "[right][left]", "[][]" ) ); - - add( new JLabel( "MDC-1200 ID:" ) ); - - MaskFormatter formatter = null; - - try - { - //Mask: 4 digits - formatter = new MaskFormatter( "####" ); - } - catch( Exception e ) - { - //Do nothing, the mask was invalid - } - - mTextField = new JFormattedTextField( formatter ); - mTextField.getDocument().addDocumentListener( this ); - mTextField.setToolTipText( HELP_TEXT ); - add( mTextField, "growx,push" ); - - JLabel help = new JLabel( "Help ..." ); - help.setForeground( Color.BLUE.brighter() ); - help.setCursor( new Cursor( Cursor.HAND_CURSOR ) ); - help.addMouseListener( new MouseAdapter() - { - @Override - public void mouseClicked( MouseEvent e ) - { - JOptionPane.showMessageDialog( MDC1200IDEditor.this, - HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE ); - } - } ); - add( help, "align left" ); - } - - public MDC1200ID getMDC1200ID() - { - if( getItem() instanceof MDC1200ID ) - { - return (MDC1200ID)getItem(); - } - - return null; - } - - @Override - public void setItem( AliasID aliasID ) - { - super.setItem( aliasID ); - - MDC1200ID mdc = getMDC1200ID(); - - if( mdc != null ) - { - mTextField.setText( mdc.getIdent() ); - } - - setModified( false ); - - repaint(); - } - - @Override - public void save() - { - MDC1200ID mdc = getMDC1200ID(); - - if( mdc != null ) - { - mdc.setIdent( mTextField.getText() ); - } - - setModified( false ); - } -} diff --git a/src/main/java/io/github/dsheirer/alias/id/mobileID/Min.java b/src/main/java/io/github/dsheirer/alias/id/mobileID/Min.java index aec0eb72..89282fc6 100644 --- a/src/main/java/io/github/dsheirer/alias/id/mobileID/Min.java +++ b/src/main/java/io/github/dsheirer/alias/id/mobileID/Min.java @@ -52,7 +52,7 @@ public class Min extends AliasID public String toString() { - return "MIN: " + mMin; + return "MIN: " + (mMin != null ? mMin : "(empty)") + (isValid() ? "" : " **NOT VALID**"); } @Override diff --git a/src/main/java/io/github/dsheirer/alias/id/nonrecordable/NonRecordable.java b/src/main/java/io/github/dsheirer/alias/id/nonrecordable/NonRecordable.java deleted file mode 100644 index b6a2fcb9..00000000 --- a/src/main/java/io/github/dsheirer/alias/id/nonrecordable/NonRecordable.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.github.dsheirer.alias.id.nonrecordable; - -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import io.github.dsheirer.alias.id.AliasID; -import io.github.dsheirer.alias.id.AliasIDType; - -public class NonRecordable extends AliasID -{ - @JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance") - @Override - public AliasIDType getType() - { - return AliasIDType.NON_RECORDABLE; - } - - @Override - public boolean isValid() - { - return true; - } - - @Override - public boolean matches(AliasID id) - { - return false; - } - - @Override - public String toString() - { - return "Audio Non-Recordable"; - } -} diff --git a/src/main/java/io/github/dsheirer/alias/id/nonrecordable/NonRecordableEditor.java b/src/main/java/io/github/dsheirer/alias/id/nonrecordable/NonRecordableEditor.java deleted file mode 100644 index 02726cc9..00000000 --- a/src/main/java/io/github/dsheirer/alias/id/nonrecordable/NonRecordableEditor.java +++ /dev/null @@ -1,74 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 io.github.dsheirer.alias.id.nonrecordable; - -import io.github.dsheirer.alias.id.AliasID; -import io.github.dsheirer.gui.editor.DocumentListenerEditor; -import net.miginfocom.swing.MigLayout; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; - -public class NonRecordableEditor extends DocumentListenerEditor -{ - private static final long serialVersionUID = 1L; - - private static final String HELP_TEXT = "" - + "

Audio Non-Recordable

" - + "This identifies an alias as non-recordable.

" - + "The alias can still be monitored but associated audio will
" - + "not be recorded.

" - + "The default behavior when audio recording is enabled for a
" - + "channel is to record all aliases except any alias with a
" - + "non-recordable identifier"; - - public NonRecordableEditor( AliasID aliasID ) - { - initGUI(); - - setItem( aliasID ); - } - - private void initGUI() - { - setLayout( new MigLayout( "fill,wrap 1", "[]", "[][]" ) ); - - add( new JLabel( "Non-Recordable" ), "wrap" ); - - JLabel help = new JLabel( "Help ..." ); - help.setForeground( Color.BLUE.brighter() ); - help.setCursor( new Cursor( Cursor.HAND_CURSOR ) ); - help.addMouseListener( new MouseAdapter() - { - @Override - public void mouseClicked( MouseEvent e ) - { - JOptionPane.showMessageDialog( NonRecordableEditor.this, - HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE ); - } - } ); - add( help, "align left" ); - } - - @Override - public void save() - { - } -} diff --git a/src/main/java/io/github/dsheirer/alias/id/record/Record.java b/src/main/java/io/github/dsheirer/alias/id/record/Record.java new file mode 100644 index 00000000..85ab55ca --- /dev/null +++ b/src/main/java/io/github/dsheirer/alias/id/record/Record.java @@ -0,0 +1,56 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias.id.record; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import io.github.dsheirer.alias.id.AliasID; +import io.github.dsheirer.alias.id.AliasIDType; + +/** + * Alias identifier to flag an alias for audio recording + */ +public class Record extends AliasID +{ + @JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance") + @Override + public AliasIDType getType() + { + return AliasIDType.RECORD; + } + + @Override + public boolean isValid() + { + return true; + } + + @Override + public boolean matches(AliasID id) + { + return false; + } + + @Override + public String toString() + { + return "Record Audio"; + } +} diff --git a/src/main/java/io/github/dsheirer/alias/id/record/RecordEditor.java b/src/main/java/io/github/dsheirer/alias/id/record/RecordEditor.java new file mode 100644 index 00000000..91b8c809 --- /dev/null +++ b/src/main/java/io/github/dsheirer/alias/id/record/RecordEditor.java @@ -0,0 +1,73 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias.id.record; + +import io.github.dsheirer.alias.id.AliasID; +import io.github.dsheirer.gui.editor.DocumentListenerEditor; +import net.miginfocom.swing.MigLayout; + +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class RecordEditor extends DocumentListenerEditor +{ + private static final long serialVersionUID = 1L; + + private static final String HELP_TEXT = "" + + "

Record Audio

" + + "Any audio associated with this alias will be recorded.

"; + + public RecordEditor(AliasID aliasID ) + { + initGUI(); + + setItem( aliasID ); + } + + private void initGUI() + { + setLayout( new MigLayout( "fill,wrap 1", "[]", "[][]" ) ); + + add( new JLabel( "Record Audio" ), "wrap" ); + + JLabel help = new JLabel( "Help ..." ); + help.setForeground( Color.BLUE.brighter() ); + help.setCursor( new Cursor( Cursor.HAND_CURSOR ) ); + help.addMouseListener( new MouseAdapter() + { + @Override + public void mouseClicked( MouseEvent e ) + { + JOptionPane.showMessageDialog( RecordEditor.this, + HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE ); + } + } ); + add( help, "align left" ); + } + + @Override + public void save() + { + } +} diff --git a/src/main/java/io/github/dsheirer/alias/id/siteID/SiteID.java b/src/main/java/io/github/dsheirer/alias/id/siteID/SiteID.java index acf3016e..f4bdbca8 100644 --- a/src/main/java/io/github/dsheirer/alias/id/siteID/SiteID.java +++ b/src/main/java/io/github/dsheirer/alias/id/siteID/SiteID.java @@ -48,7 +48,7 @@ public class SiteID extends AliasID public String toString() { - return "Site: " + mSite; + return "Site: " + (mSite != null ? mSite : "(empty)") + (isValid() ? "" : " **NOT VALID**"); } @Override diff --git a/src/main/java/io/github/dsheirer/alias/id/talkgroup/Talkgroup.java b/src/main/java/io/github/dsheirer/alias/id/talkgroup/Talkgroup.java new file mode 100644 index 00000000..7562ef84 --- /dev/null +++ b/src/main/java/io/github/dsheirer/alias/id/talkgroup/Talkgroup.java @@ -0,0 +1,107 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias.id.talkgroup; + + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import io.github.dsheirer.alias.id.AliasID; +import io.github.dsheirer.alias.id.AliasIDType; +import io.github.dsheirer.protocol.Protocol; + +/** + * Integer talkgroup identifier with protocol. + */ +public class Talkgroup extends AliasID +{ + private Protocol mProtocol; + private int mValue; + + public Talkgroup() + { + //No arg JAXB constructor + } + + public Talkgroup(Protocol protocol, int value) + { + mProtocol = protocol; + mValue = value; + } + + @JacksonXmlProperty(isAttribute = true, localName = "value") + public int getValue() + { + return mValue; + } + + public void setValue(int value) + { + mValue = value; + } + + @JacksonXmlProperty(isAttribute = true, localName = "protocol") + public Protocol getProtocol() + { + return mProtocol; + } + + public void setProtocol(Protocol protocol) + { + mProtocol = protocol; + } + + @Override + public boolean isValid() + { + return mValue != 0; + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append("Talkgroup:").append(mValue).append(" Protocol:").append((mProtocol != null ? mProtocol : "(unspecified)")); + + if(!isValid()) + { + sb.append(" **NOT VALID**"); + } + + return sb.toString(); + } + + @Override + public boolean matches(AliasID id) + { + if(id instanceof Talkgroup) + { + Talkgroup tgid = (Talkgroup)id; + return (getProtocol() == tgid.getProtocol()) && (getValue() == tgid.getValue()); + } + + return false; + } + + @JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance") + @Override + public AliasIDType getType() + { + return AliasIDType.TALKGROUP; + } +} diff --git a/src/main/java/io/github/dsheirer/alias/id/talkgroup/TalkgroupEditor.java b/src/main/java/io/github/dsheirer/alias/id/talkgroup/TalkgroupEditor.java new file mode 100644 index 00000000..68132da5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/alias/id/talkgroup/TalkgroupEditor.java @@ -0,0 +1,164 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias.id.talkgroup; + +import io.github.dsheirer.alias.id.AliasID; +import io.github.dsheirer.gui.editor.DocumentListenerEditor; +import io.github.dsheirer.protocol.Protocol; +import net.miginfocom.swing.MigLayout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.JFormattedTextField; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class TalkgroupEditor extends DocumentListenerEditor +{ + private final static Logger mLog = LoggerFactory.getLogger(TalkgroupEditor.class); + + private static final long serialVersionUID = 1L; + + private static final String HELP_TEXT = + "

Talkgroup Identifier

"; + + private JComboBox mComboProtocol; + private JFormattedTextField mTalkgroupField; + + public TalkgroupEditor(AliasID aliasID) + { + initGUI(); + setItem(aliasID); + } + + private void initGUI() + { + setLayout(new MigLayout("fill,wrap 2", "[right][left]", "[][]")); + + add(new JLabel("Protocol:")); + + mComboProtocol = new JComboBox(); + + DefaultComboBoxModel model = new DefaultComboBoxModel<>(); + + for(Protocol protocol : Protocol.TALKGROUP_PROTOCOLS) + { + model.addElement(protocol); + } + + mComboProtocol.setModel(model); + mComboProtocol.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + Protocol selected = mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex()); + setModified(true); +// ValidatingEditor editor = DecoderFactory.getEditor(selected, mChannelMapModel); +// setEditor(editor); + } + }); + + add(mComboProtocol); + + add(new JLabel("Value:")); + + mTalkgroupField = new JFormattedTextField(); + mTalkgroupField.setColumns(10); + mTalkgroupField.setValue(new Integer(0)); + mTalkgroupField.getDocument().addDocumentListener(this); + mTalkgroupField.setToolTipText(HELP_TEXT); + add(mTalkgroupField, "growx,push"); + + JLabel help = new JLabel("Help ..."); + help.setForeground(Color.BLUE.brighter()); + help.setCursor(new Cursor(Cursor.HAND_CURSOR)); + help.addMouseListener(new MouseAdapter() + { + @Override + public void mouseClicked(MouseEvent e) + { + JOptionPane.showMessageDialog(TalkgroupEditor.this, + HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE); + } + }); + add(help, "align left"); + } + + public Talkgroup getTalkgroup() + { + if(getItem() instanceof Talkgroup) + { + return (Talkgroup)getItem(); + } + + return null; + } + + @Override + public void setItem(AliasID aliasID) + { + super.setItem(aliasID); + + Talkgroup talkgroup = getTalkgroup(); + + if(talkgroup != null) + { + mComboProtocol.setSelectedItem(talkgroup.getProtocol()); + mTalkgroupField.setValue(new Integer(talkgroup.getValue())); + } + + setModified(false); + + repaint(); + } + + @Override + public void save() + { + Talkgroup talkgroup = getTalkgroup(); + + if(talkgroup != null) + { + Protocol selected = mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex()); + talkgroup.setProtocol(selected); + + try + { + int value = ((Number)mTalkgroupField.getValue()).intValue(); + talkgroup.setValue(value); + } + catch(Exception e) + { + mLog.error("Error parsing minimum talkgroup value from [" + mTalkgroupField.getValue() + "]"); + } + } + + setModified(false); + } +} diff --git a/src/main/java/io/github/dsheirer/alias/id/talkgroup/TalkgroupIDEditor.java b/src/main/java/io/github/dsheirer/alias/id/talkgroup/TalkgroupIDEditor.java deleted file mode 100644 index 8cf061c5..00000000 --- a/src/main/java/io/github/dsheirer/alias/id/talkgroup/TalkgroupIDEditor.java +++ /dev/null @@ -1,116 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 io.github.dsheirer.alias.id.talkgroup; - -import io.github.dsheirer.alias.id.AliasID; -import io.github.dsheirer.gui.editor.DocumentListenerEditor; -import net.miginfocom.swing.MigLayout; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; - -public class TalkgroupIDEditor extends DocumentListenerEditor -{ - private static final long serialVersionUID = 1L; - - private static final String HELP_TEXT = - "

Talkgroup Identifier

" - + "P25: 4 or 6 hex characters (e.g. AB12 or ABC123)
" - + "LTR: A-HH-TTT where A=Area H=Home T=Talkgroup (0-01-128)
" - + "Passport: 5-digit number (12345 or 00023)
" - + "
" - + "Wildcard: use an asterisk (*) in place of each talkgroup digit (e.g. 0*1*5)" - + ""; - - private JTextField mTextField; - - public TalkgroupIDEditor( AliasID aliasID ) - { - initGUI(); - - setItem( aliasID ); - } - - private void initGUI() - { - setLayout( new MigLayout( "fill,wrap 2", "[right][left]", "[][]" ) ); - - add( new JLabel( "Talkgroup:" ) ); - - mTextField = new JTextField(); - mTextField.getDocument().addDocumentListener( this ); - mTextField.setToolTipText( HELP_TEXT ); - add( mTextField, "growx,push" ); - - JLabel help = new JLabel( "Help ..." ); - help.setForeground( Color.BLUE.brighter() ); - help.setCursor( new Cursor( Cursor.HAND_CURSOR ) ); - help.addMouseListener( new MouseAdapter() - { - @Override - public void mouseClicked( MouseEvent e ) - { - JOptionPane.showMessageDialog( TalkgroupIDEditor.this, - HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE ); - } - } ); - add( help, "align left" ); - } - - public TalkgroupID getTalkgroupID() - { - if( getItem() instanceof TalkgroupID ) - { - return (TalkgroupID)getItem(); - } - - return null; - } - - @Override - public void setItem( AliasID aliasID ) - { - super.setItem( aliasID ); - - TalkgroupID talkgroup = getTalkgroupID(); - - if( talkgroup != null ) - { - mTextField.setText( talkgroup.getTalkgroup() ); - } - - setModified( false ); - - repaint(); - } - - @Override - public void save() - { - TalkgroupID talkgroup = getTalkgroupID(); - - if( talkgroup != null ) - { - talkgroup.setTalkgroup( mTextField.getText() ); - } - - setModified( false ); - } -} diff --git a/src/main/java/io/github/dsheirer/alias/id/talkgroup/TalkgroupRange.java b/src/main/java/io/github/dsheirer/alias/id/talkgroup/TalkgroupRange.java new file mode 100644 index 00000000..ba0f92cd --- /dev/null +++ b/src/main/java/io/github/dsheirer/alias/id/talkgroup/TalkgroupRange.java @@ -0,0 +1,129 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias.id.talkgroup; + + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import io.github.dsheirer.alias.id.AliasID; +import io.github.dsheirer.alias.id.AliasIDType; +import io.github.dsheirer.protocol.Protocol; + +/** + * Integer talkgroup identifier range of values with protocol. + */ +public class TalkgroupRange extends AliasID +{ + private Protocol mProtocol; + private int mMinTalkgroup; + private int mMaxTalkgroup; + + public TalkgroupRange() + { + //No arg JAXB constructor + } + + /** + * Creates a talkgroup range of from - to talkgroup values (inclusive) for the specified protocol + * @param protocol for the talkgroup range + * @param minTalkgroup starting or minimum talkgroup value + * @param maxTalkgroup ending or maximum talkgroup value + */ + public TalkgroupRange(Protocol protocol, int minTalkgroup, int maxTalkgroup) + { + mProtocol = protocol; + mMinTalkgroup = minTalkgroup; + mMaxTalkgroup = maxTalkgroup; + } + + @JacksonXmlProperty(isAttribute = true, localName = "min") + public int getMinTalkgroup() + { + return mMinTalkgroup; + } + + public void setMinTalkgroup(int minTalkgroup) + { + mMinTalkgroup = minTalkgroup; + } + + @JacksonXmlProperty(isAttribute = true, localName = "max") + public int getMaxTalkgroup() + { + return mMaxTalkgroup; + } + + public void setMaxTalkgroup(int maxTalkgroup) + { + mMaxTalkgroup = maxTalkgroup; + } + + @JacksonXmlProperty(isAttribute = true, localName = "protocol") + public Protocol getProtocol() + { + return mProtocol; + } + + public void setProtocol(Protocol protocol) + { + mProtocol = protocol; + } + + @Override + public boolean isValid() + { + return mProtocol != null && mMinTalkgroup != 0 && mMaxTalkgroup != 0 && mMinTalkgroup <= mMaxTalkgroup; + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append("Talkgroup Range:").append(mMinTalkgroup).append(" to ").append(mMaxTalkgroup) + .append(" Protocol:").append(mProtocol == null ? "unspecified" : mProtocol); + + if(!isValid()) + { + sb.append(" **NOT VALID**"); + } + + return sb.toString(); + } + + @Override + public boolean matches(AliasID id) + { + if(id instanceof TalkgroupRange) + { + TalkgroupRange tgid = (TalkgroupRange)id; + return (getProtocol() == tgid.getProtocol()) && + (getMinTalkgroup() == tgid.getMinTalkgroup()) && + (getMaxTalkgroup() == tgid.getMaxTalkgroup()); + } + + return false; + } + + @JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance") + @Override + public AliasIDType getType() + { + return AliasIDType.TALKGROUP_RANGE; + } +} diff --git a/src/main/java/io/github/dsheirer/alias/id/talkgroup/TalkgroupRangeEditor.java b/src/main/java/io/github/dsheirer/alias/id/talkgroup/TalkgroupRangeEditor.java new file mode 100644 index 00000000..0f1c613b --- /dev/null +++ b/src/main/java/io/github/dsheirer/alias/id/talkgroup/TalkgroupRangeEditor.java @@ -0,0 +1,185 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.alias.id.talkgroup; + +import io.github.dsheirer.alias.id.AliasID; +import io.github.dsheirer.gui.editor.DocumentListenerEditor; +import io.github.dsheirer.protocol.Protocol; +import net.miginfocom.swing.MigLayout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.JFormattedTextField; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class TalkgroupRangeEditor extends DocumentListenerEditor +{ + private final static Logger mLog = LoggerFactory.getLogger(TalkgroupRangeEditor.class); + private static final long serialVersionUID = 1L; + + private static final String HELP_TEXT = + "

Talkgroup Range Identifier

Identifies a range of talkgroup values"; + + private JComboBox mComboProtocol; + private JFormattedTextField mMinTalkgroupField; + private JFormattedTextField mMaxTalkgroupField; + + public TalkgroupRangeEditor(AliasID aliasID) + { + initGUI(); + + setItem(aliasID); + } + + private void initGUI() + { + setLayout(new MigLayout("fill,wrap 2", "[right][left]", "[][]")); + + add(new JLabel("Protocol:")); + + mComboProtocol = new JComboBox(); + + DefaultComboBoxModel model = new DefaultComboBoxModel<>(); + + for(Protocol protocol : Protocol.TALKGROUP_PROTOCOLS) + { + model.addElement(protocol); + } + + mComboProtocol.setModel(model); + mComboProtocol.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + Protocol selected = mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex()); + setModified(true); +// ValidatingEditor editor = DecoderFactory.getEditor(selected, mChannelMapModel); +// setEditor(editor); + } + }); + + add(mComboProtocol); + + add(new JLabel("Min:")); + + mMinTalkgroupField = new JFormattedTextField(); + mMinTalkgroupField.setColumns(10); + mMinTalkgroupField.setValue(new Integer(0)); + mMinTalkgroupField.getDocument().addDocumentListener(this); + mMinTalkgroupField.setToolTipText(HELP_TEXT); + add(mMinTalkgroupField, "growx,push"); + + add(new JLabel("Max:")); + + mMaxTalkgroupField = new JFormattedTextField(); + mMaxTalkgroupField.setColumns(10); + mMaxTalkgroupField.setValue(new Integer(0)); + mMaxTalkgroupField.getDocument().addDocumentListener(this); + mMaxTalkgroupField.setToolTipText(HELP_TEXT); + add(mMaxTalkgroupField, "growx,push"); + + JLabel help = new JLabel("Help ..."); + help.setForeground(Color.BLUE.brighter()); + help.setCursor(new Cursor(Cursor.HAND_CURSOR)); + help.addMouseListener(new MouseAdapter() + { + @Override + public void mouseClicked(MouseEvent e) + { + JOptionPane.showMessageDialog(TalkgroupRangeEditor.this, + HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE); + } + }); + add(help, "align left"); + } + + public TalkgroupRange getTalkgroupRange() + { + if(getItem() instanceof TalkgroupRange) + { + return (TalkgroupRange)getItem(); + } + + return null; + } + + @Override + public void setItem(AliasID aliasID) + { + super.setItem(aliasID); + + TalkgroupRange talkgroupRange = getTalkgroupRange(); + + if(talkgroupRange != null) + { + mComboProtocol.setSelectedItem(talkgroupRange.getProtocol()); + mMinTalkgroupField.setValue(new Integer(talkgroupRange.getMinTalkgroup())); + mMaxTalkgroupField.setValue(new Integer(talkgroupRange.getMaxTalkgroup())); + } + + setModified(false); + + repaint(); + } + + @Override + public void save() + { + TalkgroupRange talkgroupRange = getTalkgroupRange(); + + if(talkgroupRange != null) + { + Protocol selected = mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex()); + talkgroupRange.setProtocol(selected); + + try + { + int minTalkgroup = ((Number)mMinTalkgroupField.getValue()).intValue(); + talkgroupRange.setMinTalkgroup(minTalkgroup); + } + catch(Exception e) + { + mLog.error("Error parsing minimum talkgroup value from [" + mMinTalkgroupField.getText() + "]"); + } + + try + { + int maxTalkgroup = ((Number)mMaxTalkgroupField.getValue()).intValue(); + talkgroupRange.setMaxTalkgroup(maxTalkgroup); + } + catch(Exception e) + { + mLog.error("Error parsing maximum talkgroup value from [" + mMaxTalkgroupField.getText() + "]"); + } + } + + setModified(false); + } +} diff --git a/src/main/java/io/github/dsheirer/audio/AbstractAudioModule.java b/src/main/java/io/github/dsheirer/audio/AbstractAudioModule.java new file mode 100644 index 00000000..b243dc70 --- /dev/null +++ b/src/main/java/io/github/dsheirer/audio/AbstractAudioModule.java @@ -0,0 +1,111 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.audio; + +import io.github.dsheirer.audio.squelch.ISquelchStateListener; +import io.github.dsheirer.identifier.IdentifierUpdateListener; +import io.github.dsheirer.identifier.IdentifierUpdateNotification; +import io.github.dsheirer.identifier.MutableIdentifierCollection; +import io.github.dsheirer.module.Module; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.sample.buffer.ReusableAudioPacket; + +/** + * Base audio module implementation. + */ +public abstract class AbstractAudioModule extends Module implements IAudioPacketProvider, IdentifierUpdateListener, + ISquelchStateListener +{ + //Static unique audio channel identifier. + private static int AUDIO_CHANNEL_ID_GENERATOR = 1; + + private int mAudioChannelId = AUDIO_CHANNEL_ID_GENERATOR++; + private Listener mAudioPacketListener; + private MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection(); + + /** + * Constructs an abstract audio module + */ + public AbstractAudioModule() + { + } + + /** + * Unique channel identifier for use in tagging audio packets with an audio channel ID so that they can + * be identified within a combined audio packet stream + */ + public int getAudioChannelId() + { + return mAudioChannelId; + } + + /** + * Receive updated identifiers from decoder state(s). + */ + @Override + public Listener getIdentifierUpdateListener() + { + return mIdentifierCollection; + } + + /** + * Identifier collection containing the current set of identifiers received from the decoder state(s). + */ + public MutableIdentifierCollection getIdentifierCollection() + { + return mIdentifierCollection; + } + + /** + * Registers an audio packet listener to receive the output from this audio module. + */ + @Override + public void setAudioPacketListener(Listener listener) + { + mAudioPacketListener = listener; + } + + /** + * Unregisters the audio packet listener from receiving audio packets from this module. + */ + @Override + public void removeAudioPacketListener() + { + mAudioPacketListener = null; + } + + /** + * Registered listener for receiving audio packets produced by this module + */ + public Listener getAudioPacketListener() + { + return mAudioPacketListener; + } + + /** + * Indicates if there is a listener registered to receive audio packets from this audio module. + */ + public boolean hasAudioPacketListener() + { + return mAudioPacketListener != null; + } + +} diff --git a/src/main/java/io/github/dsheirer/audio/AudioMetadataProcessor.java b/src/main/java/io/github/dsheirer/audio/AudioMetadataProcessor.java new file mode 100644 index 00000000..4541195e --- /dev/null +++ b/src/main/java/io/github/dsheirer/audio/AudioMetadataProcessor.java @@ -0,0 +1,79 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.audio; + +import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.alias.AliasModel; +import io.github.dsheirer.sample.buffer.ReusableAudioPacket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +/** + * Processes audio packets to assign attributes to each packet based on alias list settings. Updates the + * monitoring, recording and streaming attributes. + */ +public class AudioMetadataProcessor +{ + private static final Logger mLog = LoggerFactory.getLogger(AudioMetadataProcessor.class); + private AliasModel mAliasModel; + private Map mAliasListMap = new HashMap<>(); + private Set mAliasListRetrievedSet = new TreeSet<>(); + + public AudioMetadataProcessor(AliasModel aliasModel) + { + mAliasModel = aliasModel; + } + + public void process(ReusableAudioPacket audioPacket) + { + if(audioPacket.hasIdentifierCollection() && audioPacket.getIdentifierCollection().hasAliasListConfiguration()) + { + AliasList aliasList = null; + + if(mAliasListRetrievedSet.contains(audioPacket.getAudioChannelId())) + { + aliasList = mAliasListMap.get(audioPacket.getAudioChannelId()); + } + else + { + aliasList = mAliasModel.getAliasList(audioPacket.getIdentifierCollection().getAliasListConfiguration()); + + if(aliasList != null) + { + mAliasListMap.put(audioPacket.getAudioChannelId(), aliasList); + mAliasListRetrievedSet.add(audioPacket.getAudioChannelId()); + } + } + + if(aliasList != null) + { + audioPacket.addBroadcastChannels(aliasList.getBroadcastChannels(audioPacket.getIdentifierCollection())); + audioPacket.setRecordable(aliasList.isRecordable(audioPacket.getIdentifierCollection())); + audioPacket.setMonitoringPriority(aliasList.getAudioPlaybackPriority(audioPacket.getIdentifierCollection())); + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/audio/AudioModule.java b/src/main/java/io/github/dsheirer/audio/AudioModule.java index 5e60c0ca..ceb0efe1 100644 --- a/src/main/java/io/github/dsheirer/audio/AudioModule.java +++ b/src/main/java/io/github/dsheirer/audio/AudioModule.java @@ -15,14 +15,11 @@ ******************************************************************************/ package io.github.dsheirer.audio; -import io.github.dsheirer.audio.squelch.ISquelchStateListener; import io.github.dsheirer.audio.squelch.SquelchState; -import io.github.dsheirer.channel.metadata.Metadata; import io.github.dsheirer.dsp.filter.design.FilterDesignException; import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification; import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2; import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner; -import io.github.dsheirer.module.Module; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.buffer.IReusableBufferListener; import io.github.dsheirer.sample.buffer.ReusableAudioPacket; @@ -39,12 +36,10 @@ import org.slf4j.LoggerFactory; * * This class is designed to support 8 kHz sample rate demodulated audio. */ -public class AudioModule extends Module implements IAudioPacketProvider, IReusableBufferListener, - ISquelchStateListener, Listener +public class AudioModule extends AbstractAudioModule implements IReusableBufferListener, Listener { - protected static final Logger mLog = LoggerFactory.getLogger(AudioModule.class); - private ReusableAudioPacketQueue mAudioPacketQueue = new ReusableAudioPacketQueue("AudioModule"); + private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class); private static float[] sHighPassFilterCoefficients; static @@ -73,32 +68,29 @@ public class AudioModule extends Module implements IAudioPacketProvider, IReusab } } + private ReusableAudioPacketQueue mAudioPacketQueue = new ReusableAudioPacketQueue("AudioModule"); private RealFIRFilter2 mHighPassFilter = new RealFIRFilter2(sHighPassFilterCoefficients); private SquelchStateListener mSquelchStateListener = new SquelchStateListener(); private SquelchState mSquelchState = SquelchState.SQUELCH; - private Listener mAudioPacketListener; - private Metadata mMetadata; /** * Creates an Audio Module. - * - * @param metadata to use for audio packets produced by this audio module. */ - public AudioModule(Metadata metadata) + public AudioModule() { - mMetadata = metadata; } @Override public void dispose() { - mAudioPacketListener = null; + removeAudioPacketListener(); mSquelchStateListener = null; } @Override public void reset() { + getIdentifierCollection().clear(); } @Override @@ -111,26 +103,17 @@ public class AudioModule extends Module implements IAudioPacketProvider, IReusab public void stop() { /* Issue an end-audio packet in case a recorder is still rolling */ - if(mAudioPacketListener != null) + if(hasAudioPacketListener()) { ReusableAudioPacket endAudioPacket = mAudioPacketQueue.getEndAudioBuffer(); - endAudioPacket.setMetadata(mMetadata.copyOf()); + endAudioPacket.resetAttributes(); + endAudioPacket.setAudioChannelId(getAudioChannelId()); + endAudioPacket.setIdentifierCollection(getIdentifierCollection().copyOf()); endAudioPacket.incrementUserCount(); - mAudioPacketListener.receive(endAudioPacket); + getAudioPacketListener().receive(endAudioPacket); } } - @Override - public void setAudioPacketListener(Listener listener) - { - mAudioPacketListener = listener; - } - - @Override - public void removeAudioPacketListener() - { - mAudioPacketListener = null; - } @Override public Listener getSquelchStateListener() @@ -141,15 +124,17 @@ public class AudioModule extends Module implements IAudioPacketProvider, IReusab @Override public void receive(ReusableFloatBuffer reusableFloatBuffer) { - if(mAudioPacketListener != null && mSquelchState == SquelchState.UNSQUELCH) + if(hasAudioPacketListener() && mSquelchState == SquelchState.UNSQUELCH) { ReusableFloatBuffer highPassFiltered = mHighPassFilter.filter(reusableFloatBuffer); ReusableAudioPacket audioPacket = mAudioPacketQueue.getBuffer(highPassFiltered.getSampleCount()); + audioPacket.resetAttributes(); + audioPacket.setAudioChannelId(getAudioChannelId()); audioPacket.loadAudioFrom(highPassFiltered); - audioPacket.setMetadata(mMetadata.copyOf()); + audioPacket.setIdentifierCollection(getIdentifierCollection().copyOf()); - mAudioPacketListener.receive(audioPacket); + getAudioPacketListener().receive(audioPacket); highPassFiltered.decrementUserCount(); } @@ -174,11 +159,13 @@ public class AudioModule extends Module implements IAudioPacketProvider, IReusab @Override public void receive(SquelchState state) { - if(state == SquelchState.SQUELCH && mAudioPacketListener != null) + if(state == SquelchState.SQUELCH && hasAudioPacketListener()) { ReusableAudioPacket endAudioPacket = mAudioPacketQueue.getEndAudioBuffer(); - endAudioPacket.setMetadata(mMetadata.copyOf()); - mAudioPacketListener.receive(endAudioPacket); + endAudioPacket.resetAttributes(); + endAudioPacket.setAudioChannelId(getAudioChannelId()); + endAudioPacket.setIdentifierCollection(getIdentifierCollection().copyOf()); + getAudioPacketListener().receive(endAudioPacket); } mSquelchState = state; diff --git a/src/main/java/io/github/dsheirer/audio/AudioPacketManager.java b/src/main/java/io/github/dsheirer/audio/AudioPacketManager.java new file mode 100644 index 00000000..e6c0ae36 --- /dev/null +++ b/src/main/java/io/github/dsheirer/audio/AudioPacketManager.java @@ -0,0 +1,98 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.audio; + +import io.github.dsheirer.alias.AliasModel; +import io.github.dsheirer.dsp.filter.channelizer.ContinuousReusableBufferProcessor; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.sample.buffer.ReusableAudioPacket; +import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * Processes audio packets from all decoder channels, assigns alias attributes and distributes packets + * for playback, streaming and recording. + */ +public class AudioPacketManager implements Listener +{ + private final static Logger mLog = LoggerFactory.getLogger(AudioPacketManager.class); + private AudioMetadataProcessor mAudioMetadataProcessor; + private ReusableBufferBroadcaster mBroadcaster = new ReusableBufferBroadcaster<>(); + private ContinuousReusableBufferProcessor mBufferProcessor; + + public AudioPacketManager(AliasModel aliasModel) + { + mAudioMetadataProcessor = new AudioMetadataProcessor(aliasModel); + mBufferProcessor = new ContinuousReusableBufferProcessor<>(10000, 500); + mBufferProcessor.setListener(new Listener>() + { + @Override + public void receive(List reusableAudioPackets) + { + for(ReusableAudioPacket audioPacket: reusableAudioPackets) + { + mAudioMetadataProcessor.process(audioPacket); + mBroadcaster.broadcast(audioPacket); + } + } + }); + } + + public void start() + { + mLog.info("Audio packet manager started"); + mBufferProcessor.start(); + } + + public void stop() + { + mLog.info("Audio packet manager stopped"); + mBufferProcessor.stop(); + } + + /** + * Primary processing method for receiving audio packets from all processing channels. + */ + @Override + public void receive(ReusableAudioPacket audioPacket) + { + mBufferProcessor.receive(audioPacket); + } + + /** + * Adds the listener to receive enriched audio packets from this manager + */ + public void addListener(Listener listener) + { + mBroadcaster.addListener(listener); + } + + /** + * Removes the listener from receiving enriched audio packets from this manager + */ + public void removeListener(Listener listener) + { + mBroadcaster.removeListener(listener); + } +} diff --git a/src/main/java/io/github/dsheirer/audio/IAudioController.java b/src/main/java/io/github/dsheirer/audio/IAudioController.java index a6fe007b..e374b3ce 100644 --- a/src/main/java/io/github/dsheirer/audio/IAudioController.java +++ b/src/main/java/io/github/dsheirer/audio/IAudioController.java @@ -1,6 +1,6 @@ package io.github.dsheirer.audio; -import io.github.dsheirer.audio.output.AudioOutput; +import io.github.dsheirer.audio.playback.AudioOutput; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.source.mixer.MixerChannelConfiguration; diff --git a/src/main/java/io/github/dsheirer/audio/InversionFrequency.java b/src/main/java/io/github/dsheirer/audio/InversionFrequency.java deleted file mode 100644 index 071dc8bd..00000000 --- a/src/main/java/io/github/dsheirer/audio/InversionFrequency.java +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.audio; - -/** - * Standard voice inversion foldover frequencies - * - * Reference: http://en.wikipedia.org/wiki/Voice_inversion - */ -public enum InversionFrequency -{ - HZ_2500( 2500 ), - HZ_2550( 2550 ), - HZ_2600( 2600 ), - HZ_2632( 2632 ), - HZ_2675( 2675 ), - HZ_2718( 2718 ), - HZ_2800( 2800 ), - HZ_2868( 2868 ), - HZ_2950( 2950 ), - HZ_3023( 3023 ), - HZ_3075( 3075 ), - HZ_3107( 3107 ), - HZ_3150( 3150 ), - HZ_3196( 3196 ), - HZ_3333( 3333 ), - HZ_3339( 3339 ), - HZ_3400( 3400 ), - HZ_3496( 3496 ), - HZ_3729( 3729 ), - HZ_4096( 4096 ); - - private int mFrequency; - - private InversionFrequency( int frequency ) - { - mFrequency = frequency; - } - - public int getFrequency() - { - return mFrequency; - } -} diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/AudioBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/AudioBroadcaster.java index 876e2ea9..2c48963f 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/AudioBroadcaster.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/AudioBroadcaster.java @@ -19,7 +19,7 @@ package io.github.dsheirer.audio.broadcast; import io.github.dsheirer.audio.convert.ISilenceGenerator; -import io.github.dsheirer.channel.metadata.Metadata; +import io.github.dsheirer.identifier.IdentifierCollection; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.util.ThreadPool; import org.slf4j.Logger; @@ -99,15 +99,15 @@ public abstract class AudioBroadcaster implements Listener /** * Broadcasts the next song's audio metadata prior to streaming the next song. * - * @param metadata for the next recording that will be streamed + * @param identifierCollection for the next recording that will be streamed */ - protected void broadcastMetadata(Metadata metadata) + protected void broadcastMetadata(IdentifierCollection identifierCollection) { IBroadcastMetadataUpdater metadataUpdater = getMetadataUpdater(); if(metadataUpdater != null) { - metadataUpdater.update(metadata); + metadataUpdater.update(identifierCollection); } } @@ -442,7 +442,7 @@ public abstract class AudioBroadcaster implements Listener if(connected()) { - broadcastMetadata(nextRecording.getMetadata()); + broadcastMetadata(nextRecording.getIdentifierCollection()); } metadataUpdateRequired = false; diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/AudioRecording.java b/src/main/java/io/github/dsheirer/audio/broadcast/AudioRecording.java index 5edbe272..aa672063 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/AudioRecording.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/AudioRecording.java @@ -18,7 +18,7 @@ ******************************************************************************/ package io.github.dsheirer.audio.broadcast; -import io.github.dsheirer.channel.metadata.Metadata; +import io.github.dsheirer.identifier.IdentifierCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,20 +33,20 @@ public class AudioRecording implements Comparable private long mStartTime; private long mRecordingLength; private AtomicInteger mPendingReplayCount = new AtomicInteger(); - private Metadata mMetadata; + private IdentifierCollection mIdentifierCollection; /** * Audio recording that is ready to be streamed * * @param path to the audio recording file - * @param metadata associated with the recording + * @param identifierCollection associated with the recording * @param start time of recording in milliseconds since epoch * @param recordingLength in milliseconds */ - public AudioRecording(Path path, Metadata metadata, long start, long recordingLength) + public AudioRecording(Path path, IdentifierCollection identifierCollection, long start, long recordingLength) { mPath = path; - mMetadata = metadata; + mIdentifierCollection = identifierCollection; mStartTime = start; mRecordingLength = recordingLength; } @@ -60,11 +60,19 @@ public class AudioRecording implements Comparable } /** - * Optional audio metadata for the recording. + * Optional audio metadata/identifiers for the recording. */ - public Metadata getMetadata() + public IdentifierCollection getIdentifierCollection() { - return mMetadata; + return mIdentifierCollection; + } + + /** + * Indicates if this recording contains an optional identifier collection. + */ + public boolean hasIdentifierCollection() + { + return mIdentifierCollection != null; } /** diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastFactory.java b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastFactory.java index 85f6e43c..bacd227b 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastFactory.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastFactory.java @@ -60,7 +60,7 @@ public class BroadcastFactory * @param configuration describing the server and audio types * @return configured broadcaster or null */ - public static AudioBroadcaster getBroadcaster(BroadcastConfiguration configuration) + public static AudioBroadcaster getBroadcaster(BroadcastConfiguration configuration, AliasModel aliasModel) { if(configuration != null) { @@ -71,15 +71,15 @@ public class BroadcastFactory switch(configuration.getBroadcastServerType()) { case BROADCASTIFY: - return new IcecastTCPAudioBroadcaster((BroadcastifyConfiguration) configuration); + return new IcecastTCPAudioBroadcaster((BroadcastifyConfiguration) configuration, aliasModel); case ICECAST_TCP: - return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration); + return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration, aliasModel); case ICECAST_HTTP: - return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration); + return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration, aliasModel); case SHOUTCAST_V1: - return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration); + return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration, aliasModel); case SHOUTCAST_V2: - return new ShoutcastV2AudioBroadcaster((ShoutcastV2Configuration) configuration); + return new ShoutcastV2AudioBroadcaster((ShoutcastV2Configuration) configuration, aliasModel); case UNKNOWN: default: mLog.info("Unrecognized broadcastAudio configuration: " + configuration.getBroadcastFormat().name()); diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastModel.java b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastModel.java index 986306fd..30d482e2 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastModel.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastModel.java @@ -18,8 +18,9 @@ ******************************************************************************/ package io.github.dsheirer.audio.broadcast; +import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.alias.id.broadcast.BroadcastChannel; -import io.github.dsheirer.channel.metadata.Metadata; import io.github.dsheirer.icon.IconManager; import io.github.dsheirer.properties.SystemProperties; import io.github.dsheirer.sample.Broadcaster; @@ -72,13 +73,15 @@ public class BroadcastModel extends AbstractTableModel implements Listener mBroadcasterMap = new HashMap<>(); private IconManager mIconManager; private StreamManager mStreamManager; + private AliasModel mAliasModel; private Broadcaster mBroadcastEventBroadcaster = new Broadcaster<>(); /** * Model for managing Broadcast configurations and any associated broadcaster instances. */ - public BroadcastModel(IconManager iconManager) + public BroadcastModel(AliasModel aliasModel, IconManager iconManager) { + mAliasModel = aliasModel; mIconManager = iconManager; mStreamManager = new StreamManager(new CompletedRecordingListener(), BroadcastFormat.MP3, SystemProperties.getInstance().getApplicationFolder(TEMPORARY_STREAM_DIRECTORY)); @@ -278,9 +281,9 @@ public class BroadcastModel extends AbstractTableModel implements Listener @Override public void receive(ReusableAudioPacket audioPacket) { - if(mRunning.get() && audioPacket.hasMetadata()) + if(mRunning.get() && audioPacket.hasIdentifierCollection()) { synchronized(mStreamRecorders) { - int channelMetadataID = audioPacket.getMetadata().getMetadataID(); + int channelMetadataID = audioPacket.getAudioChannelId(); ReusableAudioPacket.Type type = audioPacket.getType(); @@ -139,8 +139,7 @@ public class StreamManager implements Listener } else { - AudioRecorder recorder = BroadcastFactory.getAudioRecorder(getTemporaryRecordingPath(), - mBroadcastFormat); + AudioRecorder recorder = BroadcastFactory.getAudioRecorder(getTemporaryRecordingPath(), mBroadcastFormat); recorder.start(); audioPacket.incrementUserCount(); recorder.receive(audioPacket); @@ -181,7 +180,7 @@ public class StreamManager implements Listener public void receive(AudioRecorder audioRecorder) { AudioRecording audioRecording = - new AudioRecording(audioRecorder.getPath(), audioRecorder.getMetadata(), + new AudioRecording(audioRecorder.getPath(), audioRecorder.getIdentifierCollection(), audioRecorder.getTimeRecordingStart(), audioRecorder.getRecordingLength()); if(mAudioRecordingListener != null) diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastAudioBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastAudioBroadcaster.java index 87bc5dae..3ce48058 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastAudioBroadcaster.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastAudioBroadcaster.java @@ -18,6 +18,7 @@ ******************************************************************************/ package io.github.dsheirer.audio.broadcast.icecast; +import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.audio.broadcast.AudioBroadcaster; import io.github.dsheirer.audio.broadcast.BroadcastConfiguration; import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater; @@ -25,10 +26,12 @@ import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater; public abstract class IcecastAudioBroadcaster extends AudioBroadcaster { private IBroadcastMetadataUpdater mMetadataUpdater; + private AliasModel mAliasModel; - public IcecastAudioBroadcaster(BroadcastConfiguration broadcastConfiguration) + public IcecastAudioBroadcaster(BroadcastConfiguration broadcastConfiguration, AliasModel aliasModel) { super(broadcastConfiguration); + mAliasModel = aliasModel; } /** @@ -44,7 +47,7 @@ public abstract class IcecastAudioBroadcaster extends AudioBroadcaster { if(mMetadataUpdater == null) { - mMetadataUpdater = new IcecastBroadcastMetadataUpdater(getConfiguration()); + mMetadataUpdater = new IcecastBroadcastMetadataUpdater(getConfiguration(), mAliasModel); } return mMetadataUpdater; diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastBroadcastMetadataUpdater.java b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastBroadcastMetadataUpdater.java index 924fd8ec..3c084c2b 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastBroadcastMetadataUpdater.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastBroadcastMetadataUpdater.java @@ -18,8 +18,15 @@ ******************************************************************************/ package io.github.dsheirer.audio.broadcast.icecast; +import io.github.dsheirer.alias.Alias; +import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater; -import io.github.dsheirer.channel.metadata.Metadata; +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.identifier.Role; import io.github.dsheirer.properties.SystemProperties; import io.github.dsheirer.util.ThreadPool; import org.apache.mina.core.RuntimeIoException; @@ -52,7 +59,7 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate private final static String UTF8 = "UTF-8"; private IcecastConfiguration mIcecastConfiguration; - + private AliasModel mAliasModel; private NioSocketConnector mSocketConnector; private AtomicBoolean mUpdating = new AtomicBoolean(); private Queue mMetadataQueue = new LinkedTransferQueue<>(); @@ -65,9 +72,10 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate * broadcaster. When multiple metadata updates are received prior to completion of the current ongoing update * sequence, those updates will be queued and processed in the order received. */ - public IcecastBroadcastMetadataUpdater(IcecastConfiguration icecastConfiguration) + public IcecastBroadcastMetadataUpdater(IcecastConfiguration icecastConfiguration, AliasModel aliasModel) { mIcecastConfiguration = icecastConfiguration; + mAliasModel = aliasModel; } /** @@ -113,9 +121,9 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate * and will remain in the update queue until the next metadata update is requested. However, this is a design * trade-off to avoid having a scheduled runnable repeatedly processing the update queue. */ - public void update(Metadata metadata) + public void update(IdentifierCollection identifierCollection) { - mMetadataQueue.offer(getSong(metadata)); + mMetadataQueue.offer(getSong(identifierCollection)); if(!mUpdating.get() && !mMetadataQueue.isEmpty()) { @@ -181,45 +189,54 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate /** * Creates the song information for a metadata update */ - private static String getSong(Metadata metadata) + private String getSong(IdentifierCollection identifierCollection) { StringBuilder sb = new StringBuilder(); - if(metadata != null) + if(identifierCollection != null) { - String to = metadata.getPrimaryAddressTo().getIdentifier(); + AliasList aliasList = mAliasModel.getAliasList(identifierCollection); - sb.append("TO:"); + Identifier to = identifierCollection.getIdentifier(IdentifierClass.USER, Form.PATCH_GROUP, Role.TO); - if(metadata.getPrimaryAddressTo().hasAlias()) + if(to == null) { - sb.append(metadata.getPrimaryAddressTo().getAlias().getName()); + to = identifierCollection.getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.TO); } - else if(metadata.getPrimaryAddressTo().hasIdentifier()) + + if(to != null) { - sb.append(metadata.getPrimaryAddressTo().getIdentifier()); + sb.append("TO:").append(to); + + Alias toAlias = aliasList != null ? aliasList.getAlias(to) : null; + + if(toAlias != null) + { + sb.append(" ").append(toAlias.getName()); + } } else { - sb.append("UNKNOWN"); + sb.append("TO:UNKNOWN"); } + Identifier from = identifierCollection.getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.FROM); - sb.append(" FROM:"); + if(from != null) + { + sb.append(" FROM:").append(from); - if(metadata.getPrimaryAddressFrom().hasAlias()) - { - sb.append(metadata.getPrimaryAddressFrom().getAlias().getName()); - } - else if(metadata.getPrimaryAddressFrom().hasIdentifier()) - { - sb.append(metadata.getPrimaryAddressFrom().getIdentifier()); + Alias fromAlias = aliasList != null ? aliasList.getAlias(from) : null; + + if(fromAlias != null) + { + sb.append(" ").append(fromAlias.getName()); + } } else { - sb.append("UNKNOWN"); + sb.append(" FROM:UNKNOWN"); } - } else { diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java index 3655dc20..2f72a8f6 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java @@ -18,6 +18,7 @@ ******************************************************************************/ package io.github.dsheirer.audio.broadcast.icecast; +import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.audio.broadcast.BroadcastState; import io.github.dsheirer.audio.convert.MP3AudioConverter; import io.github.dsheirer.properties.SystemProperties; @@ -69,9 +70,9 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster * * @param configuration for the Icecast stream */ - public IcecastHTTPAudioBroadcaster(IcecastHTTPConfiguration configuration) + public IcecastHTTPAudioBroadcaster(IcecastHTTPConfiguration configuration, AliasModel aliasModel) { - super(configuration); + super(configuration, aliasModel); } /** diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java index 8618d6b2..fe496762 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java @@ -18,6 +18,7 @@ ******************************************************************************/ package io.github.dsheirer.audio.broadcast.icecast; +import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.audio.broadcast.BroadcastState; import io.github.dsheirer.audio.broadcast.icecast.codec.IcecastCodecFactory; import io.github.dsheirer.audio.convert.MP3AudioConverter; @@ -65,9 +66,9 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster * * @param configuration for the Icecast stream */ - public IcecastTCPAudioBroadcaster(IcecastTCPConfiguration configuration) + public IcecastTCPAudioBroadcaster(IcecastTCPConfiguration configuration, AliasModel aliasModel) { - super(configuration); + super(configuration, aliasModel); } /** diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java index 7dea307a..e3d3f0c9 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java @@ -18,6 +18,7 @@ ******************************************************************************/ package io.github.dsheirer.audio.broadcast.shoutcast.v1; +import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.audio.broadcast.AudioBroadcaster; import io.github.dsheirer.audio.broadcast.BroadcastState; import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater; @@ -47,7 +48,7 @@ public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster private NioSocketConnector mSocketConnector; private IoSession mStreamingSession = null; private IBroadcastMetadataUpdater mMetadataUpdater; - + private AliasModel mAliasModel; private long mLastConnectionAttempt = 0; private AtomicBoolean mConnecting = new AtomicBoolean(); @@ -61,9 +62,10 @@ public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster * * @param configuration for the Shoutcast stream */ - public ShoutcastV1AudioBroadcaster(ShoutcastV1Configuration configuration) + public ShoutcastV1AudioBroadcaster(ShoutcastV1Configuration configuration, AliasModel aliasModel) { super(configuration); + mAliasModel = aliasModel; } /** @@ -79,7 +81,7 @@ public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster { if(mMetadataUpdater == null) { - mMetadataUpdater = new ShoutcastV1BroadcastMetadataUpdater(getConfiguration()); + mMetadataUpdater = new ShoutcastV1BroadcastMetadataUpdater(getConfiguration(), mAliasModel); } return mMetadataUpdater; diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1BroadcastMetadataUpdater.java b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1BroadcastMetadataUpdater.java index faf672d1..f1865ba4 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1BroadcastMetadataUpdater.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1BroadcastMetadataUpdater.java @@ -18,8 +18,15 @@ ******************************************************************************/ package io.github.dsheirer.audio.broadcast.shoutcast.v1; +import io.github.dsheirer.alias.Alias; +import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater; -import io.github.dsheirer.channel.metadata.Metadata; +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.identifier.Role; import io.github.dsheirer.util.ThreadPool; import org.apache.mina.core.future.ConnectFuture; import org.apache.mina.core.service.IoHandlerAdapter; @@ -50,7 +57,7 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp private final static String UTF8 = "UTF-8"; private ShoutcastV1Configuration mShoutcastV1Configuration; - + private AliasModel mAliasModel; private NioSocketConnector mSocketConnector; private AtomicBoolean mUpdating = new AtomicBoolean(); private Queue mMetadataQueue = new LinkedTransferQueue<>(); @@ -62,9 +69,10 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp * broadcaster. When multiple metadata updates are received prior to completion of the current ongoing update * sequence, those updates will be queued and processed in the order received. */ - public ShoutcastV1BroadcastMetadataUpdater(ShoutcastV1Configuration shoutcastV1Configuration) + public ShoutcastV1BroadcastMetadataUpdater(ShoutcastV1Configuration shoutcastV1Configuration, AliasModel aliasModel) { mShoutcastV1Configuration = shoutcastV1Configuration; + mAliasModel = aliasModel; } /** @@ -111,9 +119,9 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp * and will remain in the update queue until the next metadata update is requested. However, this is a design * trade-off to avoid having a scheduled runnable repeatedly processing the update queue. */ - public void update(Metadata metadata) + public void update(IdentifierCollection identifierCollection) { - mMetadataQueue.offer(getSong(metadata)); + mMetadataQueue.offer(getSong(identifierCollection)); if(mUpdating.compareAndSet(false, true)) { @@ -177,45 +185,52 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp /** * Creates the song information for a metadata update */ - private static String getSong(Metadata metadata) + private String getSong(IdentifierCollection identifierCollection) { StringBuilder sb = new StringBuilder(); - if(metadata != null) + if(identifierCollection != null) { - String to = metadata.getPrimaryAddressTo().getIdentifier(); + AliasList aliasList = mAliasModel.getAliasList(identifierCollection); - sb.append("TO:"); + Identifier to = identifierCollection.getIdentifier(IdentifierClass.USER, Form.PATCH_GROUP, Role.TO); - if(metadata.getPrimaryAddressTo().hasAlias()) + if(to == null) { - sb.append(metadata.getPrimaryAddressTo().getAlias().getName()); + to = identifierCollection.getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.TO); } - else if(metadata.getPrimaryAddressTo().hasIdentifier()) + + Alias toAlias = aliasList != null ? aliasList.getAlias(to) : null; + + if(toAlias != null) { - sb.append(metadata.getPrimaryAddressTo().getIdentifier()); + sb.append("TO:").append(toAlias.getName()); + } + else if(to != null) + { + sb.append("TO:").append(to.toString()); } else { - sb.append("UNKNOWN"); + sb.append("TO:UNKNOWN"); } + Identifier from = identifierCollection.getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.FROM); - sb.append(" FROM:"); + Alias fromAlias = aliasList != null ? aliasList.getAlias(from) : null; - if(metadata.getPrimaryAddressFrom().hasAlias()) + if(fromAlias != null) { - sb.append(metadata.getPrimaryAddressFrom().getAlias().getName()); + sb.append(" FROM:").append(fromAlias.getName()); } - else if(metadata.getPrimaryAddressFrom().hasIdentifier()) + else if(from != null) { - sb.append(metadata.getPrimaryAddressFrom().getIdentifier()); + sb.append(" FROM:").append(from.toString()); } else { - sb.append("UNKNOWN"); + sb.append(" FROM:UNKNOWN"); } - } else { diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioBroadcaster.java index b0d3c377..ea31260a 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioBroadcaster.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioBroadcaster.java @@ -18,6 +18,9 @@ ******************************************************************************/ package io.github.dsheirer.audio.broadcast.shoutcast.v2; +import io.github.dsheirer.alias.Alias; +import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.audio.broadcast.AudioBroadcaster; import io.github.dsheirer.audio.broadcast.BroadcastState; import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater; @@ -36,7 +39,11 @@ import io.github.dsheirer.audio.broadcast.shoutcast.v2.ultravox.UltravoxMessageF import io.github.dsheirer.audio.broadcast.shoutcast.v2.ultravox.UltravoxMessageType; import io.github.dsheirer.audio.broadcast.shoutcast.v2.ultravox.UltravoxMetadata; import io.github.dsheirer.audio.broadcast.shoutcast.v2.ultravox.UltravoxProtocolFactory; -import io.github.dsheirer.channel.metadata.Metadata; +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.identifier.Role; import io.github.dsheirer.properties.SystemProperties; import io.github.dsheirer.util.ThreadPool; import org.apache.mina.core.RuntimeIoException; @@ -66,7 +73,7 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr private NioSocketConnector mSocketConnector; private IoSession mStreamingSession = null; - + private AliasModel mAliasModel; private long mLastConnectionAttempt = 0; private AtomicBoolean mConnecting = new AtomicBoolean(); private LinkedTransferQueue mMetadataMessageQueue = new LinkedTransferQueue<>(); @@ -83,9 +90,10 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr * * @param configuration for the Shoutcast V2 stream */ - public ShoutcastV2AudioBroadcaster(ShoutcastV2Configuration configuration) + public ShoutcastV2AudioBroadcaster(ShoutcastV2Configuration configuration, AliasModel aliasModel) { super(configuration); + mAliasModel = aliasModel; } public ShoutcastV2Configuration getConfiguration() @@ -119,9 +127,9 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr } @Override - public void update(Metadata metadata) + public void update(IdentifierCollection identifierCollection) { - List metadataMessages = getMetadataMessages(metadata); + List metadataMessages = getMetadataMessages(identifierCollection); if(!metadataMessages.isEmpty()) { @@ -241,10 +249,10 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr * * See UltravoxMetadata.TAG.asXML(String value) * - * @param metadata containing audio metadata tags and attributes + * @param identifierCollection containing audio metadata tags and attributes * @return a sequence of CacheableXMLMetadata messages sufficient to carry the complete set of metadata */ - private List getMetadataMessages(Metadata metadata) + private List getMetadataMessages(IdentifierCollection identifierCollection) { StringBuilder sb = new StringBuilder(); @@ -266,24 +274,37 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr sb.append(UltravoxMetadata.URL.asXML(getConfiguration().getURL())); } - if(metadata != null) + if(identifierCollection != null) { - if(metadata.getPrimaryAddressTo().hasAlias()) + AliasList aliasList = mAliasModel.getAliasList(identifierCollection); + + Identifier to = identifierCollection.getIdentifier(IdentifierClass.USER, Form.PATCH_GROUP, Role.TO); + if(to == null) { - sb.append(UltravoxMetadata.TITLE_2.asXML(metadata.getPrimaryAddressTo().getAlias().getName())); - } - else if(metadata.getPrimaryAddressTo().hasIdentifier()) - { - sb.append(UltravoxMetadata.TITLE_2.asXML(metadata.getPrimaryAddressTo().getIdentifier())); + to = identifierCollection.getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.TO); } - if(metadata.getPrimaryAddressFrom().hasAlias()) + Alias toAlias = aliasList != null ? aliasList.getAlias(to) : null; + + if(toAlias != null) { - sb.append(UltravoxMetadata.TITLE_3.asXML("From:" + metadata.getPrimaryAddressFrom().getAlias().getName())); + sb.append(UltravoxMetadata.TITLE_2.asXML(toAlias.getName())); } - else if(metadata.getPrimaryAddressFrom().hasIdentifier()) + else if(to != null) { - sb.append(UltravoxMetadata.TITLE_3.asXML("From:" + metadata.getPrimaryAddressFrom().getIdentifier())); + sb.append(UltravoxMetadata.TITLE_2.asXML(to.toString())); + } + + Identifier from = identifierCollection.getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.FROM); + Alias fromAlias = aliasList != null ? aliasList.getAlias(from) : null; + + if(fromAlias != null) + { + sb.append(UltravoxMetadata.TITLE_3.asXML("From:" + fromAlias.getName())); + } + else if(from != null) + { + sb.append(UltravoxMetadata.TITLE_3.asXML("From:" + from.toString())); } } else diff --git a/src/main/java/io/github/dsheirer/audio/invert/AudioInverter.java b/src/main/java/io/github/dsheirer/audio/invert/AudioInverter.java index 63b6b6d9..f91ae5f7 100644 --- a/src/main/java/io/github/dsheirer/audio/invert/AudioInverter.java +++ b/src/main/java/io/github/dsheirer/audio/invert/AudioInverter.java @@ -17,7 +17,6 @@ ******************************************************************************/ package io.github.dsheirer.audio.invert; -import io.github.dsheirer.audio.InversionFrequency; import io.github.dsheirer.dsp.filter.Filters; import io.github.dsheirer.dsp.filter.FloatFIRFilter; import io.github.dsheirer.dsp.mixer.IOscillator; diff --git a/src/main/java/io/github/dsheirer/audio/invert/InversionFrequency.java b/src/main/java/io/github/dsheirer/audio/invert/InversionFrequency.java new file mode 100644 index 00000000..5c0f4957 --- /dev/null +++ b/src/main/java/io/github/dsheirer/audio/invert/InversionFrequency.java @@ -0,0 +1,61 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.audio.invert; + +/** + * Standard voice inversion foldover frequencies + * + * Reference: http://en.wikipedia.org/wiki/Voice_inversion + */ +public enum InversionFrequency +{ + HZ_2500( 2500 ), + HZ_2550( 2550 ), + HZ_2600( 2600 ), + HZ_2632( 2632 ), + HZ_2675( 2675 ), + HZ_2718( 2718 ), + HZ_2800( 2800 ), + HZ_2868( 2868 ), + HZ_2950( 2950 ), + HZ_3023( 3023 ), + HZ_3075( 3075 ), + HZ_3107( 3107 ), + HZ_3150( 3150 ), + HZ_3196( 3196 ), + HZ_3333( 3333 ), + HZ_3339( 3339 ), + HZ_3400( 3400 ), + HZ_3496( 3496 ), + HZ_3729( 3729 ), + HZ_4096( 4096 ); + + private int mFrequency; + + private InversionFrequency( int frequency ) + { + mFrequency = frequency; + } + + public int getFrequency() + { + return mFrequency; + } +} diff --git a/src/main/java/io/github/dsheirer/audio/AudioChannelPanel.java b/src/main/java/io/github/dsheirer/audio/playback/AudioChannelPanel.java similarity index 53% rename from src/main/java/io/github/dsheirer/audio/AudioChannelPanel.java rename to src/main/java/io/github/dsheirer/audio/playback/AudioChannelPanel.java index 7e6c1aa3..fbf05360 100644 --- a/src/main/java/io/github/dsheirer/audio/AudioChannelPanel.java +++ b/src/main/java/io/github/dsheirer/audio/playback/AudioChannelPanel.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,14 +15,24 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ -package io.github.dsheirer.audio; + * ***************************************************************************** + */ +package io.github.dsheirer.audio.playback; +import com.google.common.eventbus.Subscribe; import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.audio.output.AudioOutput; -import io.github.dsheirer.channel.metadata.Metadata; +import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.alias.AliasModel; +import io.github.dsheirer.audio.AudioEvent; +import io.github.dsheirer.eventbus.MyEventBus; import io.github.dsheirer.icon.IconManager; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.preference.PreferenceType; +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference; import io.github.dsheirer.properties.SystemProperties; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.settings.ColorSetting; @@ -32,11 +43,15 @@ import net.miginfocom.swing.MigLayout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.swing.*; -import java.awt.*; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import java.awt.Color; +import java.awt.EventQueue; +import java.awt.Font; +import java.util.List; -public class AudioChannelPanel extends JPanel - implements Listener, SettingChangeListener +public class AudioChannelPanel extends JPanel implements Listener, SettingChangeListener { private static final long serialVersionUID = 1L; private static final Logger mLog = LoggerFactory.getLogger(AudioChannelPanel.class); @@ -56,27 +71,35 @@ public class AudioChannelPanel extends JPanel private IconManager mIconManager; private SettingsManager mSettingsManager; + private UserPreferences mUserPreferences; + private TalkgroupFormatPreference mTalkgroupFormatPreference; private AudioOutput mAudioOutput; private JLabel mMutedLabel = new JLabel("M"); private JLabel mChannelName = new JLabel(" "); - private JLabel mToAlias = new JLabel(" "); - private JLabel mTo = new JLabel("-----"); + private JLabel mIconLabel = new JLabel(" "); + private JLabel mIdentifierLabel = new JLabel("-----"); + private Identifier mIdentifier; + private Alias mAlias; private boolean mConfigured = false; + private AliasModel mAliasModel; - public AudioChannelPanel(IconManager iconManager, SettingsManager settingsManager, AudioOutput audioOutput) + public AudioChannelPanel(IconManager iconManager, UserPreferences userPreferences, SettingsManager settingsManager, + AudioOutput audioOutput, AliasModel aliasModel) { mIconManager = iconManager; mSettingsManager = settingsManager; mSettingsManager.addListener(this); - + mAliasModel = aliasModel; + mUserPreferences = userPreferences; + mTalkgroupFormatPreference = mUserPreferences.getTalkgroupFormatPreference(); mAudioOutput = audioOutput; if(mAudioOutput != null) { mAudioOutput.addAudioEventListener(this); - mAudioOutput.setMetadataListener(new AudioMetadataProcessor()); + mAudioOutput.setIdentifierCollectionListener(new AudioMetadataProcessor()); } mBackgroundColor = SystemProperties.getInstance().get(PROPERTY_COLOR_BACKGROUND, Color.BLACK); @@ -87,8 +110,30 @@ public class AudioChannelPanel extends JPanel init(); } + /** + * Receives preference update notifications via the event bus + * @param preferenceType that was updated + */ + @Subscribe + public void preferenceUpdated(PreferenceType preferenceType) + { + if(preferenceType == PreferenceType.IDENTIFIER) + { + updateLabels(); + } + } + + public void dispose() + { + //Deregister from receiving preference update notifications + MyEventBus.getEventBus().unregister(this); + } + private void init() { + //Register to receive preference updates + MyEventBus.getEventBus().register(this); + setLayout(new MigLayout("align center center, insets 0 0 0 0", "[][][align right]0[grow,fill]", "")); setBackground(mBackgroundColor); @@ -103,13 +148,13 @@ public class AudioChannelPanel extends JPanel mChannelName.setForeground(mLabelColor); add(mChannelName); - mToAlias.setFont(mFont); - mToAlias.setForeground(mValueColor); - add(mToAlias); + mIconLabel.setFont(mFont); + mIconLabel.setForeground(mValueColor); + add(mIconLabel); - mTo.setFont(mFont); - mTo.setForeground(mValueColor); - add(mTo,"wmin 10lp"); + mIdentifierLabel.setFont(mFont); + mIdentifierLabel.setForeground(mValueColor); + add(mIdentifierLabel, "wmin 10lp"); } @Override @@ -149,72 +194,67 @@ public class AudioChannelPanel extends JPanel */ private void resetLabels() { - updateLabel(mTo, null, mToAlias, null); - + mIdentifier = null; + mAlias = null; + updateLabels(); mConfigured = false; } + /** + * Truncates the string to the specified length using an ellipses at the end + * @param value to optionally truncate + * @param length of the final formatted string + * @return formatted string or null + */ + private static String truncate(String value, int length) + { + if(value != null && value.length() > length) + { + return value.substring(0, length - 3) + "..."; + } + + return value; + } + /** * Updates the alias label with text and icon from the alias. Note: this * does not occur on the Swing event thread -- wrap any calls to this * method with an event thread call. */ - private void updateLabel(JLabel textLabel, String value, JLabel iconLabel, Alias alias) + private void updateLabels() { - if(value != null) + String identifier = mAlias != null ? mAlias.getName() : mTalkgroupFormatPreference.format(mIdentifier); + final ImageIcon icon = mAlias != null ? mIconManager.getIcon(mAlias.getIconName(), 18) : null; + final String identifierText = (identifier != null ? truncate(identifier, 33) : "-----"); + + EventQueue.invokeLater(new Runnable() { - - if(alias != null) + @Override + public void run() { - iconLabel.setIcon(mIconManager.getIcon(alias.getIconName(), 18)); - - String aliasName = alias.getName(); - - //Truncate label if length exceeds 33 characters - hack! - if(aliasName.length() > 33) - { - textLabel.setText(aliasName.substring(0,30) + "..."); - } - else - { - textLabel.setText(aliasName); - } + mIdentifierLabel.setText(identifierText); + mIconLabel.setIcon(icon); + mConfigured = true; } - else - { - iconLabel.setIcon(null); - textLabel.setText(value); - } - } - else - { - iconLabel.setIcon(null); - textLabel.setText("-----"); - } + }); + } /** * Processes audio metadata to update this panel's display values */ - public class AudioMetadataProcessor implements Listener + public class AudioMetadataProcessor implements Listener { @Override - public void receive(final Metadata metadata) + public void receive(final IdentifierCollection identifierCollection) { - if(metadata.isUpdated() || !mConfigured) + if(identifierCollection != null && (identifierCollection.isUpdated() || !mConfigured)) { - EventQueue.invokeLater(new Runnable() - { - @Override - public void run() - { - updateLabel(mTo, metadata.getPrimaryAddressTo().getIdentifier(), - mToAlias, metadata.getPrimaryAddressTo().getAlias()); - - mConfigured = true; - } - }); - + List toIds = identifierCollection.getIdentifiers(IdentifierClass.USER, Role.TO); + mIdentifier = !toIds.isEmpty() ? toIds.get(0) : null; + AliasList aliasList = mAliasModel.getAliasList(identifierCollection); + mAlias = (aliasList != null ? aliasList.getAlias(mIdentifier) : null); + updateLabels(); } } } @@ -225,18 +265,18 @@ public class AudioChannelPanel extends JPanel { if(setting instanceof ColorSetting) { - ColorSetting colorSetting = (ColorSetting) setting; + ColorSetting colorSetting = (ColorSetting)setting; switch(colorSetting.getColorSettingName()) { case CHANNEL_STATE_LABEL_DECODER: - if(mTo != null) + if(mIdentifierLabel != null) { - mTo.setForeground(mLabelColor); + mIdentifierLabel.setForeground(mLabelColor); } - if(mToAlias != null) + if(mIconLabel != null) { - mToAlias.setForeground(mLabelColor); + mIconLabel.setForeground(mLabelColor); } break; default: diff --git a/src/main/java/io/github/dsheirer/audio/AudioChannelsPanel.java b/src/main/java/io/github/dsheirer/audio/playback/AudioChannelsPanel.java similarity index 58% rename from src/main/java/io/github/dsheirer/audio/AudioChannelsPanel.java rename to src/main/java/io/github/dsheirer/audio/playback/AudioChannelsPanel.java index a3db071b..42e77155 100644 --- a/src/main/java/io/github/dsheirer/audio/AudioChannelsPanel.java +++ b/src/main/java/io/github/dsheirer/audio/playback/AudioChannelsPanel.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,24 +15,29 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ -package io.github.dsheirer.audio; + * ***************************************************************************** + */ +package io.github.dsheirer.audio.playback; -import io.github.dsheirer.audio.output.AudioOutput; +import io.github.dsheirer.alias.AliasModel; +import io.github.dsheirer.audio.IAudioController; import io.github.dsheirer.icon.IconManager; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.settings.SettingsManager; import net.miginfocom.swing.MigLayout; -import javax.swing.*; -import java.awt.*; +import javax.swing.JPanel; +import javax.swing.JSeparator; +import java.awt.Color; +import java.awt.Component; import java.util.List; public class AudioChannelsPanel extends JPanel { private static final long serialVersionUID = 1L; - public AudioChannelsPanel(IconManager iconManager, SettingsManager settingsManager, IAudioController controller) + public AudioChannelsPanel(IconManager iconManager, UserPreferences userPreferences, SettingsManager settingsManager, + IAudioController controller, AliasModel aliasModel) { setLayout(new MigLayout("insets 0 0 0 0", "[][sg abc,grow,fill][][sg abc,grow,fill]", "[grow,fill]")); @@ -43,7 +49,7 @@ public class AudioChannelsPanel extends JPanel for(int x = 0; x < outputs.size(); x++) { - add(new AudioChannelPanel(iconManager, settingsManager, outputs.get(x))); + add(new AudioChannelPanel(iconManager, userPreferences, settingsManager, outputs.get(x), aliasModel)); if(x < outputs.size() - 1) { @@ -56,7 +62,21 @@ public class AudioChannelsPanel extends JPanel if(outputs.size() == 1) { addSeparator(); - add(new AudioChannelPanel(iconManager, settingsManager, null), "growx"); + add(new AudioChannelPanel(iconManager, userPreferences, settingsManager, null, aliasModel), "growx"); + } + } + + /** + * Prepares for dispose to allow deregistering from services + */ + public void dispose() + { + for(Component component: getComponents()) + { + if(component instanceof AudioChannelPanel) + { + ((AudioChannelPanel)component).dispose(); + } } } diff --git a/src/main/java/io/github/dsheirer/audio/output/AudioOutput.java b/src/main/java/io/github/dsheirer/audio/playback/AudioOutput.java similarity index 92% rename from src/main/java/io/github/dsheirer/audio/output/AudioOutput.java rename to src/main/java/io/github/dsheirer/audio/playback/AudioOutput.java index fa0af9e6..45504cfd 100644 --- a/src/main/java/io/github/dsheirer/audio/output/AudioOutput.java +++ b/src/main/java/io/github/dsheirer/audio/playback/AudioOutput.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,12 +15,12 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ -package io.github.dsheirer.audio.output; + * ***************************************************************************** + */ +package io.github.dsheirer.audio.playback; import io.github.dsheirer.audio.AudioEvent; -import io.github.dsheirer.channel.metadata.Metadata; +import io.github.dsheirer.identifier.IdentifierCollection; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.buffer.ReusableAudioPacket; @@ -54,7 +55,7 @@ public abstract class AudioOutput implements Listener, Line private int mBufferStartThreshold; private int mBufferStopThreshold; - private Listener mMetadataListener; + private Listener mIdentifierCollectionListener; private Broadcaster mAudioEventBroadcaster = new Broadcaster<>(); private ScheduledFuture mProcessorTask; @@ -85,10 +86,7 @@ public abstract class AudioOutput implements Listener, Line * @param lineInfo to use when obtaining the source data line * @param requestedBufferSize of approximately 1 second of audio */ - public AudioOutput(Mixer mixer, - MixerChannel mixerChannel, - AudioFormat audioFormat, - Line.Info lineInfo, + public AudioOutput(Mixer mixer, MixerChannel mixerChannel, AudioFormat audioFormat, Line.Info lineInfo, int requestedBufferSize) { mMixer = mixer; @@ -174,7 +172,7 @@ public abstract class AudioOutput implements Listener, Line mAudioEventBroadcaster.dispose(); mAudioEventBroadcaster = null; - mMetadataListener = null; + mIdentifierCollectionListener = null; if(mOutput != null) { @@ -233,24 +231,24 @@ public abstract class AudioOutput implements Listener, Line * Registers a single listener to receive the audio metadata from each * audio packet */ - public void setMetadataListener(Listener listener) + public void setIdentifierCollectionListener(Listener listener) { - mMetadataListener = listener; + mIdentifierCollectionListener = listener; } public void removeAudioMetadataListener() { - mMetadataListener = null; + mIdentifierCollectionListener = null; } /** - * Broadcasts audio metadata to the registered listener + * Broadcasts audio identifier collection metadata to the registered listener */ - private void broadcast(Metadata metadata) + private void broadcast(IdentifierCollection identifierCollection) { - if(mMetadataListener != null) + if(mIdentifierCollectionListener != null) { - mMetadataListener.receive(metadata); + mIdentifierCollectionListener.receive(identifierCollection); } } @@ -312,7 +310,7 @@ public abstract class AudioOutput implements Listener, Line { if(packet.getType() == ReusableAudioPacket.Type.AUDIO) { - broadcast(packet.getMetadata()); + broadcast(packet.getIdentifierCollection()); ByteBuffer buffer = convert(packet); diff --git a/src/main/java/io/github/dsheirer/audio/AudioPanel.java b/src/main/java/io/github/dsheirer/audio/playback/AudioPanel.java similarity index 92% rename from src/main/java/io/github/dsheirer/audio/AudioPanel.java rename to src/main/java/io/github/dsheirer/audio/playback/AudioPanel.java index 66d80147..5b2333f4 100644 --- a/src/main/java/io/github/dsheirer/audio/AudioPanel.java +++ b/src/main/java/io/github/dsheirer/audio/playback/AudioPanel.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,12 +15,16 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ -package io.github.dsheirer.audio; + * ***************************************************************************** + */ +package io.github.dsheirer.audio.playback; -import io.github.dsheirer.audio.output.AudioOutput; +import io.github.dsheirer.alias.AliasModel; +import io.github.dsheirer.audio.AudioEvent; +import io.github.dsheirer.audio.AudioException; +import io.github.dsheirer.audio.IAudioController; import io.github.dsheirer.icon.IconManager; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.settings.SettingsManager; import io.github.dsheirer.source.SourceManager; @@ -60,17 +65,21 @@ public class AudioPanel extends JPanel implements Listener private SettingsManager mSettingsManager; private SourceManager mSourceManager; private IAudioController mController; + private AliasModel mAliasModel; + private UserPreferences mUserPreferences; private JButton mMuteButton; private AudioChannelsPanel mAudioChannelsPanel; - public AudioPanel(IconManager iconManager, SettingsManager settingsManager, SourceManager sourceManager, - IAudioController controller) + public AudioPanel(IconManager iconManager, UserPreferences userPreferences, SettingsManager settingsManager, + SourceManager sourceManager, IAudioController controller, AliasModel aliasModel) { mIconManager = iconManager; mSettingsManager = settingsManager; mSourceManager = sourceManager; mController = controller; + mAliasModel = aliasModel; + mUserPreferences = userPreferences; mController.addControllerListener(this); @@ -86,7 +95,7 @@ public class AudioPanel extends JPanel implements Listener mMuteButton.setBackground(getBackground()); add(mMuteButton); - mAudioChannelsPanel = new AudioChannelsPanel(mIconManager, mSettingsManager, mController); + mAudioChannelsPanel = new AudioChannelsPanel(mIconManager, mUserPreferences, mSettingsManager, mController, mAliasModel); add(mAudioChannelsPanel); @@ -107,13 +116,10 @@ public class AudioPanel extends JPanel implements Listener public void run() { remove(mAudioChannelsPanel); - - mAudioChannelsPanel = new AudioChannelsPanel(mIconManager, mSettingsManager, mController); - + mAudioChannelsPanel.dispose(); + mAudioChannelsPanel = new AudioChannelsPanel(mIconManager, mUserPreferences, mSettingsManager, mController, mAliasModel); add(mAudioChannelsPanel); - mAudioChannelsPanel.repaint(); - revalidate(); repaint(); } diff --git a/src/main/java/io/github/dsheirer/audio/AudioManager.java b/src/main/java/io/github/dsheirer/audio/playback/AudioPlaybackManager.java similarity index 89% rename from src/main/java/io/github/dsheirer/audio/AudioManager.java rename to src/main/java/io/github/dsheirer/audio/playback/AudioPlaybackManager.java index e48cf36a..449354d6 100644 --- a/src/main/java/io/github/dsheirer/audio/AudioManager.java +++ b/src/main/java/io/github/dsheirer/audio/playback/AudioPlaybackManager.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,13 +15,13 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ -package io.github.dsheirer.audio; + * ***************************************************************************** + */ +package io.github.dsheirer.audio.playback; -import io.github.dsheirer.audio.output.AudioOutput; -import io.github.dsheirer.audio.output.MonoAudioOutput; -import io.github.dsheirer.audio.output.StereoAudioOutput; +import io.github.dsheirer.audio.AudioEvent; +import io.github.dsheirer.audio.AudioException; +import io.github.dsheirer.audio.IAudioController; import io.github.dsheirer.properties.SystemProperties; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; @@ -44,9 +45,9 @@ import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -public class AudioManager implements Listener, IAudioController +public class AudioPlaybackManager implements Listener, IAudioController { - private static final Logger mLog = LoggerFactory.getLogger(AudioManager.class); + private static final Logger mLog = LoggerFactory.getLogger(AudioPlaybackManager.class); public static final int AUDIO_TIMEOUT = 1000; //1 second @@ -79,7 +80,7 @@ public class AudioManager implements Listener, IAudioContro * packets to any combination of outputs based on any alias audio routing * options specified by the user. */ - public AudioManager(MixerManager mixerManager) + public AudioPlaybackManager(MixerManager mixerManager) { mMixerManager = mixerManager; @@ -185,9 +186,9 @@ public class AudioManager implements Listener, IAudioContro for(AudioOutputConnection connection : mAudioOutputConnections) { - if(connection.isInactive() && mChannelConnectionMap.containsKey(connection.getChannelMetadataID())) + if(connection.isInactive() && mChannelConnectionMap.containsKey(connection.getAudioChannelId())) { - mChannelConnectionMap.remove(connection.getChannelMetadataID()); + mChannelConnectionMap.remove(connection.getAudioChannelId()); connection.disconnect(); mAvailableConnectionCount++; changed = true; @@ -365,12 +366,12 @@ public class AudioManager implements Listener, IAudioContro */ private AudioOutputConnection getConnection(ReusableAudioPacket audioPacket) { - int channelMetadataID = audioPacket.getMetadata().getMetadataID(); + int audioChannelId = audioPacket.getAudioChannelId(); //Use an existing connection - if(mChannelConnectionMap.containsKey(channelMetadataID)) + if(mChannelConnectionMap.containsKey(audioChannelId)) { - return mChannelConnectionMap.get(channelMetadataID); + return mChannelConnectionMap.get(audioChannelId); } //Connect to an unused, available connection @@ -380,8 +381,8 @@ public class AudioManager implements Listener, IAudioContro { if(connection.isDisconnected()) { - connection.connect(channelMetadataID, audioPacket.getMetadata().getAudioPriority()); - mChannelConnectionMap.put(channelMetadataID, connection); + connection.connect(audioChannelId, audioPacket.getMonitoringPriority()); + mChannelConnectionMap.put(audioChannelId, connection); mAvailableConnectionCount--; return connection; } @@ -390,17 +391,17 @@ public class AudioManager implements Listener, IAudioContro //Preempt an existing lower priority connection and connect when this is a higher priority packet else { - int priority = audioPacket.getMetadata().getAudioPriority(); + int priority = audioPacket.getMonitoringPriority(); AudioOutputConnection connection = mLowestPriorityConnection; if(connection != null && priority < connection.getPriority()) { - mChannelConnectionMap.remove(connection.getChannelMetadataID()); + mChannelConnectionMap.remove(connection.getAudioChannelId()); - connection.connect(channelMetadataID, priority); + connection.connect(audioChannelId, priority); - mChannelConnectionMap.put(channelMetadataID, connection); + mChannelConnectionMap.put(audioChannelId, connection); return connection; } @@ -427,7 +428,7 @@ public class AudioManager implements Listener, IAudioContro for(ReusableAudioPacket packet : mPackets) { /* Don't process any packet's marked as do not monitor */ - if(!packet.getMetadata().isDoNotMonitor() && packet.getType() == ReusableAudioPacket.Type.AUDIO) + if(!packet.isDoNotMonitor() && packet.getType() == ReusableAudioPacket.Type.AUDIO) { AudioOutputConnection connection = getConnection(packet); @@ -469,11 +470,11 @@ public class AudioManager implements Listener, IAudioContro */ public class AudioOutputConnection { - private static final int DISCONNECTED = -1; + private static final int DISCONNECTED = Integer.MIN_VALUE; private AudioOutput mAudioOutput; private int mPriority = 0; - private int mChannelMetadataID = DISCONNECTED; + private int mAudioChannelId = DISCONNECTED; public AudioOutputConnection(AudioOutput audioOutput) { @@ -482,9 +483,9 @@ public class AudioManager implements Listener, IAudioContro public void receive(ReusableAudioPacket packet) { - if(packet.hasMetadata() && packet.getMetadata().getMetadataID() == mChannelMetadataID) + if(packet.hasIdentifierCollection() && packet.getAudioChannelId() == mAudioChannelId) { - int priority = packet.getMetadata().getAudioPriority(); + int priority = packet.getMonitoringPriority(); if(mPriority != priority) { @@ -499,10 +500,10 @@ public class AudioManager implements Listener, IAudioContro } else { - if(packet.hasMetadata()) + if(packet.hasIdentifierCollection()) { - mLog.error("Received audio packet from channel metadata [" + packet.getMetadata().getMetadataID() + - "] however this assignment is currently connected to metadata [" + mChannelMetadataID + "]"); + mLog.error("Received audio packet from channel metadata [" + packet.getAudioChannelId() + + "] however this assignment is currently connected to metadata [" + mAudioChannelId + "]"); } else { @@ -525,7 +526,7 @@ public class AudioManager implements Listener, IAudioContro */ public boolean isDisconnected() { - return mChannelMetadataID == DISCONNECTED; + return mAudioChannelId == DISCONNECTED; } /** @@ -542,7 +543,7 @@ public class AudioManager implements Listener, IAudioContro */ public void connect(int source, int priority) { - mChannelMetadataID = source; + mAudioChannelId = source; mPriority = priority; updateLowestPriorityAssignment(); @@ -552,9 +553,9 @@ public class AudioManager implements Listener, IAudioContro /** * Currently connected source or -1 if disconnected */ - public int getChannelMetadataID() + public int getAudioChannelId() { - return mChannelMetadataID; + return mAudioChannelId; } /** @@ -563,7 +564,7 @@ public class AudioManager implements Listener, IAudioContro */ public void disconnect() { - mChannelMetadataID = DISCONNECTED; + mAudioChannelId = DISCONNECTED; mPriority = 0; } diff --git a/src/main/java/io/github/dsheirer/audio/output/MonoAudioOutput.java b/src/main/java/io/github/dsheirer/audio/playback/MonoAudioOutput.java similarity index 98% rename from src/main/java/io/github/dsheirer/audio/output/MonoAudioOutput.java rename to src/main/java/io/github/dsheirer/audio/playback/MonoAudioOutput.java index b1722b5f..2fad8bfc 100644 --- a/src/main/java/io/github/dsheirer/audio/output/MonoAudioOutput.java +++ b/src/main/java/io/github/dsheirer/audio/playback/MonoAudioOutput.java @@ -16,7 +16,7 @@ * along with this program. If not, see * ******************************************************************************/ -package io.github.dsheirer.audio.output; +package io.github.dsheirer.audio.playback; import io.github.dsheirer.audio.AudioFormats; import io.github.dsheirer.sample.buffer.ReusableAudioPacket; diff --git a/src/main/java/io/github/dsheirer/audio/output/StereoAudioOutput.java b/src/main/java/io/github/dsheirer/audio/playback/StereoAudioOutput.java similarity index 98% rename from src/main/java/io/github/dsheirer/audio/output/StereoAudioOutput.java rename to src/main/java/io/github/dsheirer/audio/playback/StereoAudioOutput.java index 7a7cc828..c8538c7f 100644 --- a/src/main/java/io/github/dsheirer/audio/output/StereoAudioOutput.java +++ b/src/main/java/io/github/dsheirer/audio/playback/StereoAudioOutput.java @@ -16,7 +16,7 @@ * along with this program. If not, see * ******************************************************************************/ -package io.github.dsheirer.audio.output; +package io.github.dsheirer.audio.playback; import io.github.dsheirer.audio.AudioFormats; import io.github.dsheirer.sample.buffer.ReusableAudioPacket; diff --git a/src/main/java/io/github/dsheirer/bits/BinaryMessage.java b/src/main/java/io/github/dsheirer/bits/BinaryMessage.java index 8ce03554..3a3e2cc6 100644 --- a/src/main/java/io/github/dsheirer/bits/BinaryMessage.java +++ b/src/main/java/io/github/dsheirer/bits/BinaryMessage.java @@ -1,17 +1,17 @@ /******************************************************************************* * 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 ******************************************************************************/ @@ -24,14 +24,14 @@ import java.util.BitSet; public class BinaryMessage extends BitSet { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; /** * Logical (ie constructed) size of this bitset, despite the actual size of * the super bitset that this class is based on */ private int mSize = 0; - + /** * Pointer to the next fill index location, when adding bits to this bitset * one at a time. @@ -41,49 +41,49 @@ public class BinaryMessage extends BitSet /** * Bitset that buffers bits added one at a time, up to the size of the this * bitset. - * + * * Note: the super class bitset behind this class may have a size larger * that the size parameter specified. * @param size */ - + /** * Used for temporary storage of CRC check results when we're passing this * message to an EDAC function. */ private CRC mCRC; - - public BinaryMessage( int size ) + + public BinaryMessage(int size) { - super( size ); + super(size); mSize = size; } - + /** * Constructs a bitset buffer and preloads it with the bits contained in * the bitsToPreload parameter. If the bitsToPreload are longer than the * size of the bitset, only those bits that fit will be preloaded - * + * * @param size * @param bitsToPreload */ - public BinaryMessage( int size, boolean[] bitsToPreload ) + public BinaryMessage(int size, boolean[] bitsToPreload) { - this( size ); - + this(size); + int pointer = 0; - - while( !this.isFull() && pointer < bitsToPreload.length ) + + while(!this.isFull() && pointer < bitsToPreload.length) { try { - this.add( bitsToPreload[ pointer ] ); + this.add(bitsToPreload[pointer]); } - catch( BitSetFullException e ) + catch(BitSetFullException e) { e.printStackTrace(); } - + pointer++; } } @@ -91,17 +91,17 @@ public class BinaryMessage extends BitSet /** * Constructs a new BitSetBuffer from an existing one */ - private BinaryMessage( BinaryMessage toCopyFrom ) + private BinaryMessage(BinaryMessage toCopyFrom) { - this( toCopyFrom.size() ); - this.or( toCopyFrom ); + this(toCopyFrom.size()); + this.or(toCopyFrom); this.mPointer = toCopyFrom.pointer(); } - public BinaryMessage( BitSet bitset, int size ) + public BinaryMessage(BitSet bitset, int size) { - this( size ); - this.or( bitset ); + this(size); + this.or(bitset); this.mPointer = size - 1; } @@ -109,43 +109,43 @@ public class BinaryMessage extends BitSet { this(BitSet.valueOf(data), data.length * 8); } - + /** * Returns a mew binary message containing the bits from (inclusive) to * end (exclusive). - * + * * @param start * @param end * @return */ - public BinaryMessage getSubMessage( int start, int end ) + public BinaryMessage getSubMessage(int start, int end) { - BitSet subset = this.get( start, end ); - - return new BinaryMessage( subset, end - start ); + BitSet subset = this.get(start, end); + + return new BinaryMessage(subset, end - start); } - + public CRC getCRC() { - return mCRC; + return mCRC; } - - public void setCRC( CRC crc ) + + public void setCRC(CRC crc) { - mCRC = crc; + mCRC = crc; } /** * Indicates the number of bit errors that were corrected in this message. * - * @see CorrectedBinaryMessage subclass that supports setting this value * @return number of corrected bits in this message + * @see CorrectedBinaryMessage subclass that supports setting this value */ public int getCorrectedBitCount() { return 0; } - + /** * Current pointer index */ @@ -156,64 +156,66 @@ public class BinaryMessage extends BitSet /** * Sets the pointer to a specific value + * * @param index */ - public void setPointer( int index ) + public void setPointer(int index) { - mPointer = index; + mPointer = index; } /** - * Moves the current pointer position left (negative adjustment) or + * Moves the current pointer position left (negative adjustment) or * right (positive adjustment) */ - public void adjustPointer( int adjustment ) + public void adjustPointer(int adjustment) { - mPointer += adjustment; + mPointer += adjustment; } /** * Static method to construct a new BitSetBuffer, preloaded with the bits * from the preload parameter, and then filled with the bits from the * second bitsetbuffer parameter. - * + * * @param preloadBits - boolean array of bits to be prepended to the new - * bitset + * bitset * @param bitsetToAppend - full bitset to be appended to the residual bits array * @return - new Bitset preloaded with residual bits and new bitset */ - public static BinaryMessage merge( boolean[] preloadBits, BinaryMessage bitsetToAppend ) + public static BinaryMessage merge(boolean[] preloadBits, BinaryMessage bitsetToAppend) { - BinaryMessage returnValue = new BinaryMessage( preloadBits.length + bitsetToAppend.size(), preloadBits ); + BinaryMessage returnValue = new BinaryMessage(preloadBits.length + bitsetToAppend.size(), preloadBits); int pointer = 0; - - while( pointer < bitsetToAppend.size() && !returnValue.isFull() ) + + while(pointer < bitsetToAppend.size() && !returnValue.isFull()) { try { - returnValue.add( bitsetToAppend.get( pointer ) ); + returnValue.add(bitsetToAppend.get(pointer)); } - catch( BitSetFullException e ) + catch(BitSetFullException e) { e.printStackTrace(); } - + pointer++; } - + return returnValue; } - + /** * Returns a (new) copy of this bitsetbuffer + * * @return */ public BinaryMessage copy() { - return new BinaryMessage( this ); + return new BinaryMessage(this); } - + public boolean isFull() { return mPointer >= mSize; @@ -229,10 +231,10 @@ public class BinaryMessage extends BitSet { return mSize; } - - public void setSize( int size ) + + public void setSize(int size) { - mSize = size; + mSize = size; } /** @@ -242,41 +244,57 @@ public class BinaryMessage extends BitSet @Override public void clear() { - this.clear( 0, mSize ); + this.clear(0, mSize); mPointer = 0; } /** - * Adds a the bit parameters to this bitset, placing it in the index + * Adds a the bit parameters to this bitset, placing it in the index * specified by mPointer, and incrementing mPointer to prepare for the next * call to this method + * * @param value * @throws BitSetFullException - if the size specified at construction is - * exceeded. Invoke full() to determine if the bitset is full either before - * adding a new bit, or after adding a bit. + * exceeded. Invoke full() to determine if the bitset is full either before + * adding a new bit, or after adding a bit. */ - public void add( boolean value ) throws BitSetFullException + public void add(boolean value) throws BitSetFullException { - if( !isFull() ) + if(!isFull()) { - this.set( mPointer++, value ); + this.set(mPointer++, value); } else { - throw new BitSetFullException( "bitset is full -- contains " + - ( mPointer + 1 ) + "bits"); + throw new BitSetFullException("bitset is full -- contains " + + (mPointer + 1) + "bits"); } } - + public String toString() { StringBuilder sb = new StringBuilder(); - - for( int x = 0; x < mSize; x++ ) + + for(int x = 0; x < mSize; x++) { - sb.append( ( this.get( x ) ? "1" : "0" ) ); + sb.append((this.get(x) ? "1" : "0")); } - + + return sb.toString(); + } + + public String toHexString() + { + int pointer = 0; + + StringBuilder sb = new StringBuilder(); + + while(pointer < size()) + { + sb.append(getHex(pointer, pointer + 3, 1)); + pointer += 4; + } + return sb.toString(); } @@ -285,19 +303,20 @@ public class BinaryMessage extends BitSet */ public int[] toIntegerArray() { - int[] values = new int[ mSize ]; - - for (int i = nextSetBit( 0 ); i >= 0 && i < mSize; i = nextSetBit( i+1 ) ) - { - values[ i ] = 1; - } - - return values; + int[] values = new int[mSize]; + + for(int i = nextSetBit(0); i >= 0 && i < mSize; i = nextSetBit(i + 1)) + { + values[i] = 1; + } + + return values; } /** * Returns this message as a little endian byte array. Extra 0 bits will be padded to the end to make the overall * length a multiple of 8. + * * @return little endian message byte array */ public byte[] toByteArray() @@ -306,7 +325,7 @@ public class BinaryMessage extends BitSet if(length * 8 < size()) { - length ++; + length++; } byte[] bytes = new byte[length]; @@ -321,59 +340,59 @@ public class BinaryMessage extends BitSet return bytes; } - + /** * Returns this bitset as a reversed bit order array of integer ones and zeros * from the specified index range */ - public int[] toReverseIntegerArray( int start, int end ) + public int[] toReverseIntegerArray(int start, int end) { - int[] values = new int[ end - start + 1 ]; - - for (int i = nextSetBit( start ); i >= start && i <= end; i = nextSetBit( i+1 ) ) - { - values[ end - i ] = 1; - } - - return values; + int[] values = new int[end - start + 1]; + + for(int i = nextSetBit(start); i >= start && i <= end; i = nextSetBit(i + 1)) + { + values[end - i] = 1; + } + + return values; } - + /** * Returns a boolean array from startIndex to end of the bitset */ - public boolean[] getBits( int startIndex ) + public boolean[] getBits(int startIndex) { - return getBits( startIndex, mSize - 1 ); + return getBits(startIndex, mSize - 1); } - + /** * Returns a boolean array of the right-most bitCount number of bits */ - public boolean[] right( int bitCount ) + public boolean[] right(int bitCount) { - return getBits( mSize - bitCount - 1 ); + return getBits(mSize - bitCount - 1); } - + /** * Returns a boolean array representing the bits located from startIndex * through endIndex */ - public boolean[] getBits( int startIndex, int endIndex ) + public boolean[] getBits(int startIndex, int endIndex) { boolean[] returnValue = null; - - if( startIndex >= 0 && - startIndex < endIndex && - endIndex < mSize ) + + if(startIndex >= 0 && + startIndex < endIndex && + endIndex < mSize) { - returnValue = new boolean[ endIndex - startIndex + 1 ]; + returnValue = new boolean[endIndex - startIndex + 1]; int bitsetPointer = startIndex; int returnPointer = 0; - - while( bitsetPointer <= endIndex ) + + while(bitsetPointer <= endIndex) { - returnValue[ returnPointer ] = this.get( bitsetPointer ); + returnValue[returnPointer] = this.get(bitsetPointer); bitsetPointer++; returnPointer++; } @@ -381,42 +400,75 @@ public class BinaryMessage extends BitSet return returnValue; } - + /** * Returns the integer value represented by the bit array + * * @param bits - an array of bit positions that will be treated as if they - * were contiguous bits, with index 0 being the MSB and index - * length - 1 being the LSB + * were contiguous bits, with index 0 being the MSB and index + * length - 1 being the LSB * @return - integer value of the bit array */ - public int getInt( int[] bits ) + public int getInt(int[] bits) { - if( bits.length > 32 ) - { - throw new IllegalArgumentException( "Overflow - must be 32 bits " - + "or less to fit into a primitive integer value" ); - } + if(bits.length > 32) + { + throw new IllegalArgumentException("Overflow - must be 32 bits " + + "or less to fit into a primitive integer value"); + } - int value = 0; + int value = 0; - for( int index: bits ) - { - value = Integer.rotateLeft( value, 1 ); - - if( get( index ) ) - { - value++; - } - } - - return value; + for(int index : bits) + { + value = Integer.rotateLeft(value, 1); + + if(get(index)) + { + value++; + } + } + + return value; + } + + /** + * Returns the integer value represented by the bit array + * + * @param bits - an array of bit positions that will be treated as if they + * were contiguous bits, with index 0 being the MSB and index + * length - 1 being the LSB + * @param offset to apply to each of the bit position in the bits array. + * @return - integer value of the bit array + */ + public int getInt(int[] bits, int offset) + { + if(bits.length > 32) + { + throw new IllegalArgumentException("Overflow - must be 32 bits " + + "or less to fit into a primitive integer value"); + } + + int value = 0; + + for(int index : bits) + { + value = Integer.rotateLeft(value, 1); + + if(get(index + offset)) + { + value++; + } + } + + return value; } public void setInt(int value, int[] indices) { for(int x = 0; x < indices.length; x++) { - int mask = 1<<(indices.length - x - 1); + int mask = 1 << (indices.length - x - 1); if((value & mask) == mask) { @@ -431,36 +483,70 @@ public class BinaryMessage extends BitSet /** * Returns the byte value represented by the bit array + * * @param bits - an array of bit positions that will be treated as if they - * were contiguous bits, with index 0 being the MSB and index - * length - 1 being the LSB + * were contiguous bits, with index 0 being the MSB and index + * length - 1 being the LSB * @return - byte value of the bit array */ - public byte getByte( int[] bits ) + public byte getByte(int[] bits) { - if( bits.length != 8 ) - { - throw new IllegalArgumentException( "Invalid - there must be 8" - + "indexes to form a proper byte" ); - } + if(bits.length != 8) + { + throw new IllegalArgumentException("Invalid - there must be 8" + + "indexes to form a proper byte"); + } - int value = 0; + int value = 0; - for( int index: bits ) - { - value = Integer.rotateLeft( value, 1 ); - - if( get( index ) ) - { - value++; - } - } - - return (byte)( value & 0xFF ); + for(int index : bits) + { + value = Integer.rotateLeft(value, 1); + + if(get(index)) + { + value++; + } + } + + return (byte)(value & 0xFF); + } + + /** + * Returns the byte value represented by the bit array + * + * @param bits - an array of bit positions that will be treated as if they + * were contiguous bits, with index 0 being the MSB and index + * length - 1 being the LSB + * @param offset to apply to each of the bit positions + * @return - byte value of the bit array + */ + public byte getByte(int[] bits, int offset) + { + if(bits.length != 8) + { + throw new IllegalArgumentException("Invalid - there must be 8" + + "indexes to form a proper byte"); + } + + int value = 0; + + for(int index : bits) + { + value = Integer.rotateLeft(value, 1); + + if(get(index + offset)) + { + value++; + } + } + + return (byte)(value & 0xFF); } /** * Returns the byte value contained between index and index + 7 bit positions + * * @param index specifying the start of the byte value * @return byte value contained at index <> index + 7 bit positions */ @@ -512,390 +598,404 @@ public class BinaryMessage extends BitSet /** * Returns the long value represented by the bit array + * * @param bits - an array of bit positions that will be treated as if they - * were contiguous bits, with index 0 being the MSB and index - * length - 1 being the LSB + * were contiguous bits, with index 0 being the MSB and index + * length - 1 being the LSB * @return - integer value of the bit array */ - public long getLong( int[] bits ) + public long getLong(int[] bits) { - if( bits.length > 64 ) - { - throw new IllegalArgumentException( "Overflow - must be 64 bits " - + "or less to fit into a primitive long value" ); - } + if(bits.length > 64) + { + throw new IllegalArgumentException("Overflow - must be 64 bits " + + "or less to fit into a primitive long value"); + } - long value = 0; + long value = 0; - for( int index: bits ) - { - value = Long.rotateLeft( value, 1 ); - - if( get( index ) ) - { - value++; - } - } - - return value; + for(int index : bits) + { + value = Long.rotateLeft(value, 1); + + if(get(index)) + { + value++; + } + } + + return value; } - - + /** * Converts up to 63 bits from the bit array into an integer and then * formats the value into hexadecimal, prefixing the value with zeros to * provide a total length of digitDisplayCount; - * + * * @param bits * @param digitDisplayCount * @return */ - public String getHex( int[] bits, int digitDisplayCount ) + public String getHex(int[] bits, int digitDisplayCount) { - if( bits.length <= 32 ) - { - int value = getInt( bits ); - - return String.format( "%0" + digitDisplayCount + "X", value ); - } - else if( bits.length <= 64 ) - { - long value = getLong( bits ); - - return String.format( "%0" + digitDisplayCount + "X", value ); - } - else - { - throw new IllegalArgumentException( "BitSetBuffer.getHex() " - + "maximum array length is 63 bits" ); - } + if(bits.length <= 32) + { + int value = getInt(bits); + + return String.format("%0" + digitDisplayCount + "X", value); + } + else if(bits.length <= 64) + { + long value = getLong(bits); + + return String.format("%0" + digitDisplayCount + "X", value); + } + else + { + throw new IllegalArgumentException("BitSetBuffer.getHex() " + + "maximum array length is 63 bits"); + } } - public String getHex( int msb, int lsb, int digitDisplayCount ) + public String getHex(int msb, int lsb, int digitDisplayCount) { - int length = lsb - msb; - - if( length <= 32 ) - { - int value = getInt( msb, lsb ); - - return String.format( "%0" + digitDisplayCount + "X", value ); - } - else if( length <= 64 ) - { - long value = getLong( msb, lsb ); - - return String.format( "%0" + digitDisplayCount + "X", value ); - } - else - { - throw new IllegalArgumentException( "BitSetBuffer.getHex() " - + "maximum array length is 64 bits" ); - } + int length = lsb - msb; + + if(length <= 32) + { + int value = getInt(msb, lsb); + + return String.format("%0" + digitDisplayCount + "X", value); + } + else if(length <= 64) + { + long value = getLong(msb, lsb); + + return String.format("%0" + digitDisplayCount + "X", value); + } + else + { + throw new IllegalArgumentException("BitSetBuffer.getHex() " + + "maximum array length is 64 bits"); + } } /** * Returns the int value represented by the bit range. This method will * parse the bits in big endian or little endian format. The start value * represents the MSB and the end value represents the LSB of the value. - * + * * start < end: little endian interpretation * end < start: big endian interpretation - * + * * @param start - MSB of the value * @param end - LSB of the value - * * @return - int value of the bit range */ - public int getInt( int start, int end ) + public int getInt(int start, int end) { - if( Math.abs( end - start ) > 32 ) - { - throw new IllegalArgumentException( "Overflow - must be 32 bits " - + "or less to fit into a primitive integer value" ); - } - - int value = 0; + if(Math.abs(end - start) > 32) + { + throw new IllegalArgumentException("Overflow - must be 32 bits " + + "or less to fit into a primitive integer value"); + } - if( start < end ) - { - for( int x = start; x <= end; x++ ) - { - value = Integer.rotateLeft( value, 1 ); + int value = 0; - if( get( x ) ) - { - value++;; - } - } - } - else - { - for( int x = end; x >= start; x-- ) - { - value = Integer.rotateLeft( value, 1 ); + if(start < end) + { + for(int x = start; x <= end; x++) + { + value = Integer.rotateLeft(value, 1); - if( get( x ) ) - { - value++;; - } - } - } - - return value; + if(get(x)) + { + value++; + ; + } + } + } + else + { + for(int x = end; x >= start; x--) + { + value = Integer.rotateLeft(value, 1); + + if(get(x)) + { + value++; + ; + } + } + } + + return value; } - + /** * Returns the long value represented by the bit range. This method will * parse the bits in big endian or little endian format. The start value * represents the MSB and the end value represents the LSB of the value. - * + * * start < end: little endian interpretation * end < start: big endian interpretation - * + * * @param start - MSB of the value * @param end - LSB of the value - * * @return - long value of the bit range */ - public long getLong( int start, int end ) + public long getLong(int start, int end) { - if( Math.abs( end - start ) > 64 ) - { - throw new IllegalArgumentException( "Overflow - must be 64 bits " - + "or less to fit into a primitive long value" ); - } - - long value = 0; + if(Math.abs(end - start) > 64) + { + throw new IllegalArgumentException("Overflow - must be 64 bits " + + "or less to fit into a primitive long value"); + } - if( start < end ) - { - for( int x = start; x <= end; x++ ) - { - value = Long.rotateLeft( value, 1 ); - - if( get( x ) ) - { - value++; - } - } - } - else - { - for( int x = end; x >= start; x-- ) - { - value = Long.rotateLeft( value, 1 ); - - if( get( x ) ) - { - value++; - } - } - } - - return value; + long value = 0; + + if(start < end) + { + for(int x = start; x <= end; x++) + { + value = Long.rotateLeft(value, 1); + + if(get(x)) + { + value++; + } + } + } + else + { + for(int x = end; x >= start; x--) + { + value = Long.rotateLeft(value, 1); + + if(get(x)) + { + value++; + } + } + } + + return value; } /** * Creates a buffer of size=width and fills the buffer with the fill value + * * @param width - size of the buffer * @param fill - initial fill value * @return - filled buffer */ - public static BinaryMessage getBuffer( int width, long fill ) - { - BinaryMessage buffer = new BinaryMessage( width ); - - buffer.load( 0, width, fill ); - - return buffer; - } - - /** - * Loads the value into the buffer starting at the offset index, and - * assuming that the value represents (width) number of bits. The MSB of - * the value will be located at the offset and the LSB of the value will - * be located at ( offset + width ). - * - * @param offset - starting bit index for the MSB of the value - * @param width - representative bit width of the value - * @param value - value to be loaded into the buffer - */ - public void load( int offset, int width, long value ) - { - for( int x = 0; x < width; x++ ) - { - long mask = Long.rotateLeft( 1, width - x - 1 ); - - if( ( mask & value ) == mask ) - { - set( offset + x ); - } - else - { - clear( offset + x ); - } - } - } - - /** - * Generates an array of message bit position indexes to support accessing - * a contiguous field value - * - * @param start - starting bit position of the field - * @param length - field length - * @return - array of field indexes - */ - public static int[] getFieldIndexes( int start, int length, boolean bigEndian ) - { - int[] checksumIndexes = new int[ length ]; - - for( int x = 0; x < length; x++ ) - { - if( bigEndian ) - { - checksumIndexes[ length - x - 1 ] = start + x; - } - else - { - checksumIndexes[ x ] = start + x; - } - } - - return checksumIndexes; - } - - /** - * Creates a bitsetbuffer loaded from a string of zeros and ones - * - * @param message - string containing only zeros and ones - * @return - loaded buffer - */ - public static BinaryMessage load( String message ) - { - if( !message.matches( "[01]*" ) ) - { - throw new IllegalArgumentException( - "Message must contain only zeros and ones" ); - } - - BinaryMessage buffer = new BinaryMessage( message.length() ); - - for( int x = 0; x < message.length(); x++ ) - { - if( message.substring( x, x + 1 ).contentEquals( "1" ) ) - { - buffer.set( x ); - } - } - - return buffer; - } - - /** - * Left rotates the bits between start and end indices, number of places. - */ - public void rotateLeft( int places, int startIndex, int endIndex ) - { - for( int x = 0; x < places; x++ ) - { - rotateLeft( startIndex, endIndex ); - } - } - - /** - * Left rotates the bits between start and end and wraps the left-most - * bit around to the end. - */ - public void rotateLeft( int startIndex, int endIndex ) - { - boolean wrapBit = get( startIndex ); - - for( int x = startIndex; x < endIndex; x++ ) - { - if( get( x + 1 ) ) - { - set( x ); - } - else - { - clear( x ); - } - } - - if( wrapBit ) - { - set( endIndex ); - } - else - { - clear( endIndex ); - } - } - - /** - * Right rotates the bits between start and end indices, number of places. - */ - public void rotateRight( int places, int startIndex, int endIndex ) - { - for( int x = 0; x < places; x++ ) - { - rotateRight( startIndex, endIndex ); - } - } - - /** - * Right rotates the bits between start and end and wraps the right-most - * bit around to the start. - */ - public void rotateRight( int startIndex, int endIndex ) - { - boolean wrapBit = get( endIndex ); - - for( int x = endIndex - 1; x >= startIndex; x-- ) - { - if( get( x ) ) - { - set( x + 1 ); - } - else - { - clear( x + 1 ); - } - } - - if( wrapBit ) - { - set( startIndex ); - } - else - { - clear( startIndex ); - } - } - - /** - * Performs exclusive or of the value against this bitset starting at the - * offset position using width bits from the value. - */ - public void xor( int offset, int width, int value ) - { - BinaryMessage mask = new BinaryMessage( this.size() ); - - mask.load( offset, width, value ); - - this.xor( mask ); - } - - public static void main(String[] args) + public static BinaryMessage getBuffer(int width, long fill) { - BinaryMessage b = new BinaryMessage(32); + BinaryMessage buffer = new BinaryMessage(width); - int[] indices = {2,3,7,8}; + buffer.load(0, width, fill); - b.setInt(0xF, indices); + return buffer; + } - System.out.println(b.toString()); + /** + * Loads the value into the buffer starting at the offset index, and + * assuming that the value represents (width) number of bits. The MSB of + * the value will be located at the offset and the LSB of the value will + * be located at ( offset + width ). + * + * @param offset - starting bit index for the MSB of the value + * @param width - representative bit width of the value + * @param value - value to be loaded into the buffer + */ + public void load(int offset, int width, long value) + { + for(int x = 0; x < width; x++) + { + long mask = Long.rotateLeft(1, width - x - 1); + + if((mask & value) == mask) + { + set(offset + x); + } + else + { + clear(offset + x); + } + } + } + + /** + * Loads the binary message into this message starting at the offset. Note: this method + * does not do any length checking for the message being loaded and if that message is longer + * than this message, it would be loaded outside of the size (length) boundary of this message, but will not + * produce an error. + * + * @param offset into this message to load the binary message + * @param binaryMessage to load + */ + public void load(int offset, BinaryMessage binaryMessage) + { + for(int x = 0; x < binaryMessage.size(); x++) + { + if(binaryMessage.get(x)) + { + set(x + offset); + } + else + { + clear(x + offset); + } + } + } + + /** + * Generates an array of message bit position indexes to support accessing + * a contiguous field value + * + * @param start - starting bit position of the field + * @param length - field length + * @return - array of field indexes + */ + public static int[] getFieldIndexes(int start, int length, boolean bigEndian) + { + int[] checksumIndexes = new int[length]; + + for(int x = 0; x < length; x++) + { + if(bigEndian) + { + checksumIndexes[length - x - 1] = start + x; + } + else + { + checksumIndexes[x] = start + x; + } + } + + return checksumIndexes; + } + + /** + * Creates a bitsetbuffer loaded from a string of zeros and ones + * + * @param message - string containing only zeros and ones + * @return - loaded buffer + */ + public static BinaryMessage load(String message) + { + if(!message.matches("[01]*")) + { + throw new IllegalArgumentException( + "Message must contain only zeros and ones"); + } + + BinaryMessage buffer = new BinaryMessage(message.length()); + + for(int x = 0; x < message.length(); x++) + { + if(message.substring(x, x + 1).contentEquals("1")) + { + buffer.set(x); + } + } + + return buffer; + } + + /** + * Left rotates the bits between start and end indices, number of places. + */ + public void rotateLeft(int places, int startIndex, int endIndex) + { + for(int x = 0; x < places; x++) + { + rotateLeft(startIndex, endIndex); + } + } + + /** + * Left rotates the bits between start and end and wraps the left-most + * bit around to the end. + */ + public void rotateLeft(int startIndex, int endIndex) + { + boolean wrapBit = get(startIndex); + + for(int x = startIndex; x < endIndex; x++) + { + if(get(x + 1)) + { + set(x); + } + else + { + clear(x); + } + } + + if(wrapBit) + { + set(endIndex); + } + else + { + clear(endIndex); + } + } + + /** + * Right rotates the bits between start and end indices, number of places. + */ + public void rotateRight(int places, int startIndex, int endIndex) + { + for(int x = 0; x < places; x++) + { + rotateRight(startIndex, endIndex); + } + } + + /** + * Right rotates the bits between start and end and wraps the right-most + * bit around to the start. + */ + public void rotateRight(int startIndex, int endIndex) + { + boolean wrapBit = get(endIndex); + + for(int x = endIndex - 1; x >= startIndex; x--) + { + if(get(x)) + { + set(x + 1); + } + else + { + clear(x + 1); + } + } + + if(wrapBit) + { + set(startIndex); + } + else + { + clear(startIndex); + } + } + + /** + * Performs exclusive or of the value against this bitset starting at the + * offset position using width bits from the value. + */ + public void xor(int offset, int width, int value) + { + BinaryMessage mask = new BinaryMessage(this.size()); + + mask.load(offset, width, value); + + this.xor(mask); } } diff --git a/src/main/java/io/github/dsheirer/bits/CorrectedBinaryMessage.java b/src/main/java/io/github/dsheirer/bits/CorrectedBinaryMessage.java index a06f45af..a0465be1 100644 --- a/src/main/java/io/github/dsheirer/bits/CorrectedBinaryMessage.java +++ b/src/main/java/io/github/dsheirer/bits/CorrectedBinaryMessage.java @@ -62,4 +62,14 @@ public class CorrectedBinaryMessage extends BinaryMessage { mCorrectedBitCount = count; } + + /** + * Increases the current corrected bit count by the argument value. + * @param additionalCount to add to the current corrected bit count + */ + public void incrementCorrectedBitCount(int additionalCount) + { + mCorrectedBitCount += additionalCount; + } + } diff --git a/src/main/java/io/github/dsheirer/bits/IBinarySymbolProcessor.java b/src/main/java/io/github/dsheirer/bits/IBinarySymbolProcessor.java index a46185ad..83c71e75 100644 --- a/src/main/java/io/github/dsheirer/bits/IBinarySymbolProcessor.java +++ b/src/main/java/io/github/dsheirer/bits/IBinarySymbolProcessor.java @@ -1,21 +1,25 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.bits; public interface IBinarySymbolProcessor { - void receive(boolean symbol); + void process(boolean symbol); } diff --git a/src/main/java/io/github/dsheirer/bits/MessageFramer.java b/src/main/java/io/github/dsheirer/bits/MessageFramer.java index 27bdcabe..97fb43e4 100644 --- a/src/main/java/io/github/dsheirer/bits/MessageFramer.java +++ b/src/main/java/io/github/dsheirer/bits/MessageFramer.java @@ -1,18 +1,22 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.bits; import io.github.dsheirer.dsp.symbol.ISyncDetectListener; @@ -37,9 +41,9 @@ public class MessageFramer implements IBinarySymbolProcessor, Listener, private boolean[] mSyncPattern; private int mMessageLength; private ISyncDetectListener mSyncDetectListener; - private Broadcaster mBroadcaster = new Broadcaster(); - private List mMessageAssemblers = new ArrayList(); - private List mCompletedMessageAssemblers = new ArrayList(); + private Broadcaster mBroadcaster = new Broadcaster<>(); + private List mMessageAssemblers = new ArrayList<>(); + private List mCompletedMessageAssemblers = new ArrayList<>(); private SyncPatternMatcher mMatcher; public MessageFramer(boolean[] syncPattern, int messageLength) @@ -63,10 +67,9 @@ public class MessageFramer implements IBinarySymbolProcessor, Listener, { mBroadcaster.dispose(); mCompletedMessageAssemblers.clear(); - mMessageAssemblers.clear(); } - public void receive(boolean bit) + public void process(boolean bit) { mMatcher.receive(bit); @@ -99,7 +102,7 @@ public class MessageFramer implements IBinarySymbolProcessor, Listener, /* Notify any sync detect listener(s) */ if(mSyncDetectListener != null) { - mSyncDetectListener.syncDetected(); + mSyncDetectListener.syncDetected(0); } } } @@ -108,20 +111,7 @@ public class MessageFramer implements IBinarySymbolProcessor, Listener, @Override public void receive(Boolean bit) { - receive(bit); - } - - /** - * Causes all messages currently under assembly to be forcibly - * sent (ie flushed) to all registered message listeners, and - * subsequently, all assemblers to be deleted - */ - public void flush() - { - for(MessageAssembler assembler : mMessageAssemblers) - { - assembler.flush(); - } + process(bit); } @Override @@ -134,12 +124,12 @@ public class MessageFramer implements IBinarySymbolProcessor, Listener, * Allow a message listener to register with this framer to receive * all framed messages */ - public void addMessageListener(Listener listener) + public void addMessageListener(Listener listener) { mBroadcaster.addListener(listener); } - public void removeMessageListener(Listener listener) + public void removeMessageListener(Listener listener) { mBroadcaster.removeListener(listener); } @@ -152,7 +142,6 @@ public class MessageFramer implements IBinarySymbolProcessor, Listener, mMessageAssemblers.add(assembler); } - @SuppressWarnings("unused") private void removeMessageAssembler(MessageAssembler assembler) { mMessageAssemblers.remove(assembler); @@ -169,12 +158,12 @@ public class MessageFramer implements IBinarySymbolProcessor, Listener, */ private class MessageAssembler implements Listener { - BinaryMessage mMessage; + CorrectedBinaryMessage mMessage; boolean mComplete = false; MessageAssembler(int messageLength) { - mMessage = new BinaryMessage(messageLength); + mMessage = new CorrectedBinaryMessage(messageLength); } MessageAssembler(int messageLength, boolean[] initialFill) diff --git a/src/main/java/io/github/dsheirer/bits/MultiSyncPatternMatcher.java b/src/main/java/io/github/dsheirer/bits/MultiSyncPatternMatcher.java index 4b4e8621..89e939d3 100644 --- a/src/main/java/io/github/dsheirer/bits/MultiSyncPatternMatcher.java +++ b/src/main/java/io/github/dsheirer/bits/MultiSyncPatternMatcher.java @@ -88,7 +88,6 @@ public class MultiSyncPatternMatcher { if(processor.checkSync(mBits)) { - mSyncDetectListener.syncDetected(); mBitCount = 0; } } @@ -100,6 +99,15 @@ public class MultiSyncPatternMatcher } } + /** + * Returns the number of bit errors for the current value against the sync pattern. + * @return + */ + public long getCurrentValue() + { + return mBits; + } + /** * Processes one bit before checking sync processors for a match. */ diff --git a/src/main/java/io/github/dsheirer/bits/SoftSyncDetector.java b/src/main/java/io/github/dsheirer/bits/SoftSyncDetector.java index 6362e160..4322dbad 100644 --- a/src/main/java/io/github/dsheirer/bits/SoftSyncDetector.java +++ b/src/main/java/io/github/dsheirer/bits/SoftSyncDetector.java @@ -16,22 +16,19 @@ package io.github.dsheirer.bits; import io.github.dsheirer.dsp.symbol.ISyncDetectListener; +import org.apache.commons.lang3.Validate; public class SoftSyncDetector implements ISyncProcessor { private ISyncDetectListener mListener; private long mPattern; private int mThreshold; + private int mBitErrorCount; - public SoftSyncDetector(ISyncDetectListener listener, long pattern, int threshold) + public SoftSyncDetector(long pattern, int threshold, ISyncDetectListener listener) { - this(pattern, threshold); - + Validate.notNull(listener, "Sync detect listener cannot be null"); mListener = listener; - } - - public SoftSyncDetector(long pattern, int threshold) - { mPattern = pattern; mThreshold = threshold; } @@ -46,12 +43,21 @@ public class SoftSyncDetector implements ISyncProcessor { long difference = value ^ mPattern; - if((difference == 0 || Long.bitCount(difference) <= mThreshold) && - mListener != null) + if(difference == 0) { - mListener.syncDetected(); + mListener.syncDetected(0); return true; } + else + { + mBitErrorCount = Long.bitCount(difference); + + if(mBitErrorCount <= mThreshold) + { + mListener.syncDetected(mBitErrorCount); + return true; + } + } return false; } diff --git a/src/main/java/io/github/dsheirer/bits/SyncDetector.java b/src/main/java/io/github/dsheirer/bits/SyncDetector.java index aedaceec..25a8d30e 100644 --- a/src/main/java/io/github/dsheirer/bits/SyncDetector.java +++ b/src/main/java/io/github/dsheirer/bits/SyncDetector.java @@ -46,7 +46,7 @@ public class SyncDetector implements ISyncProcessor { if(mListener != null) { - mListener.syncDetected(); + mListener.syncDetected(0); } return true; diff --git a/src/main/java/io/github/dsheirer/channel/IChannelDescriptor.java b/src/main/java/io/github/dsheirer/channel/IChannelDescriptor.java new file mode 100644 index 00000000..18a35963 --- /dev/null +++ b/src/main/java/io/github/dsheirer/channel/IChannelDescriptor.java @@ -0,0 +1,67 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.channel; + +import io.github.dsheirer.module.decode.p25.message.IFrequencyBand; +import io.github.dsheirer.protocol.Protocol; + +/** + * Channel descriptor + */ +public interface IChannelDescriptor +{ + /** + * Downlink frequency for the channel + * @return frequency in hertz + */ + long getDownlinkFrequency(); + + /** + * Uplink frequency for the channel + * @return frequency in hertz + */ + long getUplinkFrequency(); + + /** + * Frequency band identifiers (0 - 15) + */ + int[] getFrequencyBandIdentifiers(); + + /** + * Assigns the frequency band message to the channel to use in calculating the up/downlink frequencies + * @param bandIdentifier + */ + void setFrequencyBand(IFrequencyBand bandIdentifier); + + /** + * Indicates if the timeslot count is greater than 1; + */ + boolean isTDMAChannel(); + + /** + * Number of timeslots. FDMA has 1 timeslot and TDMA has at least 2 timeslots + */ + int getTimeslotCount(); + + /** + * Protocol associated with the channel + */ + Protocol getProtocol(); +} diff --git a/src/main/java/io/github/dsheirer/channel/metadata/AliasedIdentifier.java b/src/main/java/io/github/dsheirer/channel/metadata/AliasedIdentifier.java deleted file mode 100644 index a9bf91f2..00000000 --- a/src/main/java/io/github/dsheirer/channel/metadata/AliasedIdentifier.java +++ /dev/null @@ -1,111 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.channel.metadata; - -import io.github.dsheirer.alias.Alias; - -public class AliasedIdentifier -{ - private String mIdentifier; - private Alias mAlias; - - /** - * Identifier and corresponding optional alias - */ - public AliasedIdentifier() - { - } - - /** - * Resets alias and value to null - * - * Package Private - should only be changed within MutableMetadata - */ - void reset() - { - mIdentifier = null; - mAlias = null; - } - - /** - * Identifier - */ - public String getIdentifier() - { - return mIdentifier; - } - - /** - * Sets the identifier to the argument value - * - * Package Private - should only be changed within MutableMetadata - */ - void setIdentifier(String identifier) - { - mIdentifier = identifier; - } - - /** - * Indicates if there is a non-null, non-empty identifier - */ - public boolean hasIdentifier() - { - return mIdentifier != null && !mIdentifier.isEmpty(); - } - - /** - * Alias for the identifier. - */ - public Alias getAlias() - { - return mAlias; - } - - /** - * Sets the alias to the argument - * - * Package Private - should only be changed within MutableMetadata - */ - void setAlias(Alias alias) - { - mAlias = alias; - } - - /** - * Indicates if there is an alias corresponding to the identifier - */ - public boolean hasAlias() - { - return mAlias != null; - } - - public AliasedIdentifier copyOf() - { - AliasedIdentifier copy = new AliasedIdentifier(); - - copy.setAlias(mAlias); - - if(mIdentifier != null) - { - copy.setIdentifier(new String(mIdentifier)); - } - - return copy; - } -} diff --git a/src/main/java/io/github/dsheirer/channel/metadata/AliasedIntegerAttributeMonitor.java b/src/main/java/io/github/dsheirer/channel/metadata/AliasedIntegerAttributeMonitor.java deleted file mode 100644 index a79bfaf7..00000000 --- a/src/main/java/io/github/dsheirer/channel/metadata/AliasedIntegerAttributeMonitor.java +++ /dev/null @@ -1,101 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.channel.metadata; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.alias.id.AliasIDType; -import io.github.dsheirer.sample.Listener; - -import static io.github.dsheirer.alias.id.AliasIDType.LTR_NET_UID; -import static io.github.dsheirer.alias.id.AliasIDType.STATUS; - - -public class AliasedIntegerAttributeMonitor extends AttributeMonitor -{ - private static final AliasIDType[] VALID_ID_TYPES = {STATUS, LTR_NET_UID}; - - private AliasList mAliasList; - private AliasIDType mAliasIDType; - - /** - * Provides monitoring of a String attribute's values with occurrence heuristics enabled and alias lookup with each - * change in the attribute's value. - * - * @param attribute to monitor - * @param listener to receive change requests - * @param aliasList to lookup alias from - * @param aliasIDType to determine correct alias lookup method in alias list - */ - public AliasedIntegerAttributeMonitor(Attribute attribute, Listener listener, AliasList aliasList, - AliasIDType aliasIDType) - { - super(attribute, listener); - - if(!isValidIDType(aliasIDType)) - { - throw new IllegalArgumentException("Alias ID Type [" + aliasIDType.name() + "] is not supported"); - } - mAliasList = aliasList; - mAliasIDType = aliasIDType; - } - - /** - * Indicates if the alias ID type is a supported/valid type for this monitor. - */ - public static boolean isValidIDType(AliasIDType type) - { - for(AliasIDType validType: VALID_ID_TYPES) - { - if(type == validType) - { - return true; - } - } - - return false; - } - - /** - * Lookup of the alias that corresponds to the value's alias ID type - */ - @Override - protected Alias getAlias() - { - Alias alias = null; - - if(mAliasList != null) - { - if(hasValue()) - { - switch(mAliasIDType) - { - case STATUS: - alias = mAliasList.getStatus(getValue()); - break; - case LTR_NET_UID: - alias = mAliasList.getUniqueID(getValue()); - break; - } - } - } - - return alias; - } -} diff --git a/src/main/java/io/github/dsheirer/channel/metadata/AliasedStringAttributeMonitor.java b/src/main/java/io/github/dsheirer/channel/metadata/AliasedStringAttributeMonitor.java deleted file mode 100644 index 1f224478..00000000 --- a/src/main/java/io/github/dsheirer/channel/metadata/AliasedStringAttributeMonitor.java +++ /dev/null @@ -1,121 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.channel.metadata; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.alias.id.AliasIDType; -import io.github.dsheirer.sample.Listener; - -import static io.github.dsheirer.alias.id.AliasIDType.ESN; -import static io.github.dsheirer.alias.id.AliasIDType.FLEETSYNC; -import static io.github.dsheirer.alias.id.AliasIDType.MDC1200; -import static io.github.dsheirer.alias.id.AliasIDType.MIN; -import static io.github.dsheirer.alias.id.AliasIDType.MPT1327; -import static io.github.dsheirer.alias.id.AliasIDType.SITE; -import static io.github.dsheirer.alias.id.AliasIDType.TALKGROUP; - - -public class AliasedStringAttributeMonitor extends AttributeMonitor -{ - private static final AliasIDType[] VALID_ID_TYPES = {ESN, FLEETSYNC, MDC1200, MIN, MPT1327, SITE, TALKGROUP}; - - private AliasList mAliasList; - private AliasIDType mAliasIDType; - - /** - * Provides monitoring of a String attribute's values with occurrence heuristics enabled and alias lookup with each - * change in the attribute's value. - * - * @param attribute to monitor - * @param listener to receive change requests - * @param aliasList to lookup alias from - * @param aliasIDType to determine correct alias lookup method in alias list - */ - public AliasedStringAttributeMonitor(Attribute attribute, Listener listener, AliasList aliasList, - AliasIDType aliasIDType) - { - super(attribute, listener); - - if(!isValidIDType(aliasIDType)) - { - throw new IllegalArgumentException("Alias ID Type [" + aliasIDType.name() + "] is not supported"); - } - mAliasList = aliasList; - mAliasIDType = aliasIDType; - } - - /** - * Indicates if the alias ID type is a supported/valid type for this monitor. - */ - public static boolean isValidIDType(AliasIDType type) - { - for(AliasIDType validType: VALID_ID_TYPES) - { - if(type == validType) - { - return true; - } - } - - return false; - } - - /** - * Lookup of the alias that corresponds to the value's alias ID type - */ - @Override - protected Alias getAlias() - { - Alias alias = null; - - if(mAliasList != null) - { - if(hasValue()) - { - switch(mAliasIDType) - { - case ESN: - alias = mAliasList.getESNAlias(getValue()); - break; - case FLEETSYNC: - alias = mAliasList.getFleetsyncAlias(getValue()); - break; - case MDC1200: - alias = mAliasList.getMDC1200Alias(getValue()); - break; - case MIN: - alias = mAliasList.getMobileIDNumberAlias(getValue()); - break; - case MPT1327: - alias = mAliasList.getMPT1327Alias(getValue()); - break; - case SITE: - alias = mAliasList.getSiteID(getValue()); - break; - case TALKGROUP: - alias = mAliasList.getTalkgroupAlias(getValue()); - break; - } - } - } - - return alias; - } -} diff --git a/src/main/java/io/github/dsheirer/channel/metadata/AttributeMonitor.java b/src/main/java/io/github/dsheirer/channel/metadata/AttributeMonitor.java deleted file mode 100644 index 6bc79da3..00000000 --- a/src/main/java/io/github/dsheirer/channel/metadata/AttributeMonitor.java +++ /dev/null @@ -1,159 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.channel.metadata; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.sample.Listener; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class AttributeMonitor -{ - private Attribute mAttribute; - private Listener mListener; - private boolean mHeuristicsEnabled = true; - private Map mOccurrenceCounts = new HashMap<>(); - private T mCurrentValue; - private List mIllegalValues = new ArrayList<>(); - - public AttributeMonitor(Attribute attribute, Listener listener) - { - mAttribute = attribute; - mListener = listener; - } - - /** - * Processes the value and emits a change request if the value needs to be changed. If heuristics are enabled, - * an internal mapping of values to occurrence counts is updated and a change is emitted once a value that is - * different from the current value, has a higher occurrence count than the current value. If heuristics are - * disabled, then a change is emitted at each change in the value. - * - * @param t type - * @return true if the value was changed after processing - */ - public boolean process(T t) - { - boolean changed = false; - - if(t != null && !mIllegalValues.contains(t)) - { - if(mHeuristicsEnabled) - { - if(mOccurrenceCounts.containsKey(t)) - { - mOccurrenceCounts.put(t, mOccurrenceCounts.get(t) + 1); - } - else - { - mOccurrenceCounts.put(t, 1); - } - } - - if(mCurrentValue == null) - { - mCurrentValue = t; - changed = true; - } - else if(!mCurrentValue.equals(t)) - { - if(mHeuristicsEnabled) - { - int count = mOccurrenceCounts.get(mCurrentValue); - - for(Map.Entry entry : mOccurrenceCounts.entrySet()) - { - if(entry.getValue() > count) - { - count = entry.getValue(); - mCurrentValue = entry.getKey(); - changed = true; - } - } - } - else - { - mCurrentValue = t; - changed = true; - } - } - - if(changed && mListener != null) - { - mListener.receive(new AttributeChangeRequest(mAttribute, getValue(), getAlias())); - } - } - - return changed; - } - - /** - * Alias for the current value. This class does not return an alias, but subclasses can return an alias that - * is appropriate for the monitored attribute value. - * - * @return an optional alias for the current value - */ - protected Alias getAlias() - { - return null; - } - - /** - * Resets the attribute value to null and resets occurrence heuristics. - */ - public void reset() - { - mCurrentValue = null; - mOccurrenceCounts.clear(); - - if(mListener != null) - { - mListener.receive(new AttributeChangeRequest(mAttribute, null, null)); - } - } - - /** - * Adds a value to the internal list of illegal values. The monitor will not allow the attribute to be changed to - * any values contained in this list. - * - * @param illegalValue to prevent for the monitored alias - */ - public void addIllegalValue(T illegalValue) - { - mIllegalValues.add(illegalValue); - } - - /** - * Returns the current value or null if the monitor is newly started or reset. - */ - public T getValue() - { - return mCurrentValue; - } - - /** - * Indicates if the current value is non-null - */ - public boolean hasValue() - { - return mCurrentValue != null; - } -} diff --git a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadata.java b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadata.java new file mode 100644 index 00000000..591b1cbc --- /dev/null +++ b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadata.java @@ -0,0 +1,336 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.channel.metadata; + +import io.github.dsheirer.alias.Alias; +import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.alias.AliasModel; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierUpdateListener; +import io.github.dsheirer.identifier.IdentifierUpdateNotification; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier; +import io.github.dsheirer.identifier.configuration.ChannelNameConfigurationIdentifier; +import io.github.dsheirer.identifier.configuration.DecoderTypeConfigurationIdentifier; +import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier; +import io.github.dsheirer.identifier.configuration.SiteConfigurationIdentifier; +import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier; +import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier; +import io.github.dsheirer.identifier.decoder.DecoderStateIdentifier; +import io.github.dsheirer.sample.Listener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Channel metadata containing details about the channel configuration, decoder state and current + * set of decoded user identifiers (ie TO and FROM). + */ +public class ChannelMetadata implements Listener, IdentifierUpdateListener +{ + private final static Logger mLog = LoggerFactory.getLogger(ChannelMetadata.class); + + private SystemConfigurationIdentifier mSystemConfigurationIdentifier; + private SiteConfigurationIdentifier mSiteConfigurationIdentifier; + private ChannelNameConfigurationIdentifier mChannelNameConfigurationIdentifier; + private FrequencyConfigurationIdentifier mFrequencyConfigurationIdentifier; + private DecoderStateIdentifier mDecoderStateIdentifier = DecoderStateIdentifier.IDLE; + private DecoderLogicalChannelNameIdentifier mDecoderLogicalChannelNameIdentifier; + private DecoderTypeConfigurationIdentifier mDecoderTypeConfigurationIdentifier; + private Identifier mFromIdentifier; + private Alias mFromIdentifierAlias; + private Identifier mToIdentifier; + private Alias mToIdentifierAlias; + + private IChannelMetadataUpdateListener mIChannelMetadataUpdateListener; + private AliasModel mAliasModel; + private AliasList mAliasList; + + public ChannelMetadata(AliasModel aliasModel) + { + mAliasModel = aliasModel; + } + + @Override + public Listener getIdentifierUpdateListener() + { + return this; + } + + /** + * System configuration identifier + */ + public SystemConfigurationIdentifier getSystemConfigurationIdentifier() + { + return mSystemConfigurationIdentifier; + } + + public boolean hasSystemConfigurationIdentifier() + { + return mSystemConfigurationIdentifier != null; + } + + /** + * Site configuration identifier + */ + public SiteConfigurationIdentifier getSiteConfigurationIdentifier() + { + return mSiteConfigurationIdentifier; + } + + public boolean hasSiteConfigurationIdentifier() + { + return mSiteConfigurationIdentifier != null; + } + + /** + * Channel configuration identifier + */ + public ChannelNameConfigurationIdentifier getChannelNameConfigurationIdentifier() + { + return mChannelNameConfigurationIdentifier; + } + + public boolean hasChannelConfigurationIdentifier() + { + return mChannelNameConfigurationIdentifier != null; + } + + /** + * Logical channel name/number expoded by the decoder + */ + public DecoderLogicalChannelNameIdentifier getDecoderLogicalChannelNameIdentifier() + { + return mDecoderLogicalChannelNameIdentifier; + } + + public boolean hasDecoderLogicalChannelNameIdentifier() + { + return mDecoderLogicalChannelNameIdentifier != null; + } + + /** + * Frequency configuration identifier + */ + public FrequencyConfigurationIdentifier getFrequencyConfigurationIdentifier() + { + return mFrequencyConfigurationIdentifier; + } + + public boolean hasFrequencyConfigurationIdentifier() + { + return mFrequencyConfigurationIdentifier != null; + } + + /** + * Decoder state identifier + */ + public DecoderStateIdentifier getDecoderStateIdentifier() + { + return mDecoderStateIdentifier; + } + + public boolean hasDecoderStateIdentifier() + { + return mDecoderStateIdentifier != null; + } + + /** + * Decoder type identifier + */ + public DecoderTypeConfigurationIdentifier getDecoderTypeConfigurationIdentifier() + { + return mDecoderTypeConfigurationIdentifier; + } + + public boolean hasDecoderTypeIdentifier() + { + return mDecoderTypeConfigurationIdentifier != null; + } + + /** + * Current call event FROM identifier + */ + public Identifier getFromIdentifier() + { + return mFromIdentifier; + } + + public boolean hasFromIdentifier() + { + return mFromIdentifier != null; + } + + /** + * Optional alias associated with the FROM identifier + */ + public Alias getFromIdentifierAlias() + { + return mFromIdentifierAlias; + } + + /** + * Current call event TO identifier + */ + public Identifier getToIdentifier() + { + return mToIdentifier; + } + + public boolean hasToIdentifier() + { + return mToIdentifier != null; + } + + /** + * Optional alias associated with the TO identifier + */ + public Alias getToIdentifierAlias() + { + return mToIdentifierAlias; + } + + /** + * Registers the listener for receiving field update events + */ + public void setUpdateEventListener(IChannelMetadataUpdateListener listener) + { + mIChannelMetadataUpdateListener = listener; + } + + /** + * Deregisters the listener from receiving field update events + */ + public void removeUpdateEventListener() + { + mIChannelMetadataUpdateListener = null; + } + + /** + * Broadcasts this metadata and the field that has been updated + * + * @param field that has been updated + */ + private void broadcastUpdate(ChannelMetadataField field) + { + if(mIChannelMetadataUpdateListener != null) + { + mIChannelMetadataUpdateListener.updated(this, field); + } + } + + @Override + public void receive(IdentifierUpdateNotification update) + { + Identifier identifier = update.getIdentifier(); + + switch(identifier.getIdentifierClass()) + { + case CONFIGURATION: + switch(identifier.getForm()) + { + case ALIAS_LIST: + if(update.isAdded()) + { + mAliasList = mAliasModel.getAliasList((AliasListConfigurationIdentifier)identifier); + + if(mAliasList != null) + { + if(mToIdentifier != null) + { + mToIdentifierAlias = mAliasList.getAlias(mToIdentifier); + } + if(mFromIdentifier != null) + { + mFromIdentifierAlias = mAliasList.getAlias(mFromIdentifier); + } + } + } + break; + case CHANNEL: + mChannelNameConfigurationIdentifier = update.isAdded() ? (ChannelNameConfigurationIdentifier)identifier : null; + broadcastUpdate(ChannelMetadataField.CONFIGURATION_CHANNEL); + break; + case CHANNEL_FREQUENCY: + mFrequencyConfigurationIdentifier = update.isAdded() ? (FrequencyConfigurationIdentifier)identifier : null; + broadcastUpdate(ChannelMetadataField.CONFIGURATION_FREQUENCY); + break; + case DECODER_TYPE: + mDecoderTypeConfigurationIdentifier = update.isAdded() ? (DecoderTypeConfigurationIdentifier)identifier : null; + broadcastUpdate(ChannelMetadataField.DECODER_TYPE); + break; + case SITE: + mSiteConfigurationIdentifier = update.isAdded() ? (SiteConfigurationIdentifier)identifier : null; + broadcastUpdate(ChannelMetadataField.CONFIGURATION_SITE); + break; + case SYSTEM: + mSystemConfigurationIdentifier = update.isAdded() ? (SystemConfigurationIdentifier)identifier : null; + broadcastUpdate(ChannelMetadataField.CONFIGURATION_SYSTEM); + break; + } + break; + case DECODER: + switch(identifier.getForm()) + { + case CHANNEL_NAME: + mDecoderLogicalChannelNameIdentifier = update.isAdded() ? (DecoderLogicalChannelNameIdentifier)identifier : null; + broadcastUpdate(ChannelMetadataField.DECODER_CHANNEL_NAME); + break; + case STATE: + mDecoderStateIdentifier = update.isAdded() ? (DecoderStateIdentifier)identifier : null; + broadcastUpdate(ChannelMetadataField.DECODER_STATE); + break; + } + break; + case USER: + if(identifier.getRole() == Role.FROM) + { + mFromIdentifier = update.isAdded() ? identifier : null; + + if(mAliasList != null && mFromIdentifier != null) + { + mFromIdentifierAlias = mAliasList.getAlias(mFromIdentifier); + } + else + { + mFromIdentifierAlias = null; + } + + broadcastUpdate(ChannelMetadataField.USER_FROM); + } + else if(identifier.getRole() == Role.TO) + { + mToIdentifier = update.isAdded() ? identifier : null; + + if(mAliasList != null && mToIdentifier != null) + { + mToIdentifierAlias = mAliasList.getAlias(mToIdentifier); + } + else + { + mToIdentifierAlias = null; + } + + broadcastUpdate(ChannelMetadataField.USER_TO); + } + break; + } + } +} diff --git a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataField.java b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataField.java new file mode 100644 index 00000000..c68e800f --- /dev/null +++ b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataField.java @@ -0,0 +1,34 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.channel.metadata; + +public enum ChannelMetadataField +{ + CONFIGURATION_CHANNEL, + CONFIGURATION_FREQUENCY, + CONFIGURATION_SITE, + CONFIGURATION_SYSTEM, + DECODER_CHANNEL_NAME, + DECODER_STATE, + DECODER_TYPE, + USER_FROM, + USER_TO; +} diff --git a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataModel.java b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataModel.java index 6e400936..1be1ccef 100644 --- a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataModel.java +++ b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataModel.java @@ -15,44 +15,65 @@ ******************************************************************************/ package io.github.dsheirer.channel.metadata; -import io.github.dsheirer.channel.state.State; +import com.google.common.eventbus.Subscribe; +import io.github.dsheirer.alias.Alias; import io.github.dsheirer.controller.channel.Channel; -import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.preference.PreferenceType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.table.AbstractTableModel; import java.awt.EventQueue; -import java.text.DecimalFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -public class ChannelMetadataModel extends AbstractTableModel implements Listener +public class ChannelMetadataModel extends AbstractTableModel implements IChannelMetadataUpdateListener { private final static Logger mLog = LoggerFactory.getLogger(ChannelMetadataModel.class); - private final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat( "#.00000" ); + public static final int COLUMN_DECODER_STATE = 0; + public static final int COLUMN_DECODER_TYPE = 1; + public static final int COLUMN_USER_FROM = 2; + public static final int COLUMN_USER_FROM_ALIAS = 3; + public static final int COLUMN_USER_TO = 4; + public static final int COLUMN_USER_TO_ALIAS = 5; + public static final int COLUMN_DECODER_LOGICAL_CHANNEL_NAME = 6; + public static final int COLUMN_CONFIGURATION_FREQUENCY = 7; + public static final int COLUMN_CONFIGURATION_CHANNEL = 8; - public static final int COLUMN_STATE = 0; - public static final int COLUMN_DECODER = 1; - public static final int COLUMN_CHANNEL = 2; - public static final int COLUMN_FREQUENCY = 3; - public static final int COLUMN_PRIMARY_FROM = 4; - public static final int COLUMN_PRIMARY_TO = 5; - public static final int COLUMN_SECONDARY_FROM = 6; - public static final int COLUMN_SECONDARY_TO = 7; - public static final int COLUMN_CONFIGURATION_NAME = 8; - public static final int COLUMN_MESSAGE = 9; + private static final String[] COLUMNS = {"Status", "Decoder", "From", "Alias", "To", "Alias", "Channel", "Frequency", "Channel Name"}; - private static final String[] COLUMNS = {"Status", "Decoder", "Channel", "Frequency", "Primary From", "Primary To", - "Secondary From", "Secondary To", "Channel Name", "Message"}; + private List mChannelMetadata = new ArrayList(); + private Map mMetadataChannelMap = new HashMap(); - private List mChannelMetadata = new ArrayList(); - private Map mMetadataChannelMap = new HashMap(); + public ChannelMetadataModel() + { + MyEventBus.getEventBus().register(this); + } - public void add(MutableMetadata metadata, Channel channel) + /** + * Receives preference update notifications via the event bus + * @param preferenceType that was updated + */ + @Subscribe + public void preferenceUpdated(PreferenceType preferenceType) + { + if(preferenceType == PreferenceType.IDENTIFIER) + { + for(int row = 0; row < mChannelMetadata.size(); row++) + { + fireTableCellUpdated(row, COLUMN_USER_FROM); + fireTableCellUpdated(row, COLUMN_USER_TO); + } + } + } + + + public void add(ChannelMetadata channelMetadata, Channel channel) { //Execute on the swing thread to avoid threading issues EventQueue.invokeLater(new Runnable() @@ -60,19 +81,16 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener @Override public void run() { - mChannelMetadata.add(metadata); - mMetadataChannelMap.put(metadata, channel); - - int index = mChannelMetadata.indexOf(metadata); - + mChannelMetadata.add(channelMetadata); + mMetadataChannelMap.put(channelMetadata, channel); + int index = mChannelMetadata.indexOf(channelMetadata); fireTableRowsInserted(index, index); - - metadata.addListener(ChannelMetadataModel.this); + channelMetadata.setUpdateEventListener(ChannelMetadataModel.this); } }); } - public void remove(MutableMetadata metadata) + public void remove(ChannelMetadata channelMetadata) { //Execute on the swing thread to avoid threading issues EventQueue.invokeLater(new Runnable() @@ -80,13 +98,10 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener @Override public void run() { - metadata.removeListener(ChannelMetadataModel.this); - - int index = mChannelMetadata.indexOf(metadata); - - mChannelMetadata.remove(metadata); - mMetadataChannelMap.remove(metadata); - + channelMetadata.removeUpdateEventListener(); + int index = mChannelMetadata.indexOf(channelMetadata); + mChannelMetadata.remove(channelMetadata); + mMetadataChannelMap.remove(channelMetadata); fireTableRowsDeleted(index, index); } }); @@ -95,7 +110,7 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener /** * Get the channel metadata at the specified model row index */ - public Metadata getMetadata(int row) + public ChannelMetadata getChannelMetadata(int row) { if(row < mChannelMetadata.size()) { @@ -108,9 +123,9 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener /** * Returns the channel that matches the metadata or null */ - public Channel getChannelFromMetadata(Metadata metadata) + public Channel getChannelFromMetadata(ChannelMetadata channelMetadata) { - return mMetadataChannelMap.get(metadata); + return mMetadataChannelMap.get(channelMetadata); } @Override @@ -136,24 +151,14 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener { switch(columnIndex) { - case COLUMN_STATE: - return State.class; - - case COLUMN_DECODER: - case COLUMN_CHANNEL: - case COLUMN_FREQUENCY: - case COLUMN_MESSAGE: - case COLUMN_CONFIGURATION_NAME: - return String.class; - - case COLUMN_PRIMARY_TO: - case COLUMN_PRIMARY_FROM: - case COLUMN_SECONDARY_TO: - case COLUMN_SECONDARY_FROM: - return MutableMetadata.class; - + case COLUMN_USER_FROM: + case COLUMN_USER_TO: + return ChannelMetadata.class; + case COLUMN_USER_FROM_ALIAS: + case COLUMN_USER_TO_ALIAS: + return Alias.class; default: - return String.class; + return Identifier.class; } } @@ -162,60 +167,28 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener { if(rowIndex <= mChannelMetadata.size()) { - Metadata metadata = mChannelMetadata.get(rowIndex); + ChannelMetadata channelMetadata = mChannelMetadata.get(rowIndex); switch(columnIndex) { - case COLUMN_STATE: - return metadata.getState(); - case COLUMN_DECODER: - if(metadata.hasPrimaryDecoderType()) - { - return metadata.getPrimaryDecoderType().getShortDisplayString(); - } - - return null; - case COLUMN_CHANNEL: - return metadata.getChannelFrequencyLabel(); - case COLUMN_FREQUENCY: - if(metadata.hasChannelFrequency()) - { - return FREQUENCY_FORMATTER.format((double)metadata.getChannelFrequency() / 1E6d); - } - - return null; - case COLUMN_MESSAGE: - if(metadata.isBufferOverflow()) - { - return "**OVERFLOW**"; - } - else if(metadata.hasMessageType() || metadata.hasMessage()) - { - StringBuilder sb = new StringBuilder(); - if(metadata.hasMessageType()) - { - sb.append(metadata.getMessageType()).append(" "); - } - - if(metadata.hasMessage()) - { - sb.append(metadata.getMessage()); - } - - return sb.toString(); - } - - return null; - case COLUMN_CONFIGURATION_NAME: - return metadata.getChannelConfigurationName(); - case COLUMN_PRIMARY_TO: - case COLUMN_PRIMARY_FROM: - case COLUMN_SECONDARY_TO: - case COLUMN_SECONDARY_FROM: - return metadata; - - default: - return String.class; + case COLUMN_DECODER_STATE: + return channelMetadata.getDecoderStateIdentifier(); + case COLUMN_DECODER_TYPE: + return channelMetadata.getDecoderTypeConfigurationIdentifier(); + case COLUMN_DECODER_LOGICAL_CHANNEL_NAME: + return channelMetadata.getDecoderLogicalChannelNameIdentifier(); + case COLUMN_CONFIGURATION_FREQUENCY: + return channelMetadata.getFrequencyConfigurationIdentifier(); + case COLUMN_CONFIGURATION_CHANNEL: + return channelMetadata.getChannelNameConfigurationIdentifier(); + case COLUMN_USER_TO: + return channelMetadata; + case COLUMN_USER_FROM: + return channelMetadata; + case COLUMN_USER_FROM_ALIAS: + return channelMetadata.getFromIdentifierAlias(); + case COLUMN_USER_TO_ALIAS: + return channelMetadata.getToIdentifierAlias(); } } @@ -223,59 +196,46 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener } @Override - public void receive(MutableMetadataChangeEvent mutableMetadataChangeEvent) + public void updated(ChannelMetadata channelMetadata, ChannelMetadataField channelMetadataField) { - //Execute on the swing thread to avoid threading issues - EventQueue.invokeLater(new Runnable() - { - @Override - public void run() - { - int rowIndex = mChannelMetadata.indexOf(mutableMetadataChangeEvent.getMetadata()); + final int rowIndex = mChannelMetadata.indexOf(channelMetadata); - if(rowIndex >= 0) + if(rowIndex >= 0) + { + //Execute on the swing thread to avoid threading issues + EventQueue.invokeLater(new Runnable() + { + @Override + public void run() { - switch(mutableMetadataChangeEvent.getAttribute()) + switch(channelMetadataField) { - case CHANNEL_CONFIGURATION_SYSTEM: + case CONFIGURATION_CHANNEL: + fireTableCellUpdated(rowIndex, COLUMN_CONFIGURATION_CHANNEL); break; - case CHANNEL_CONFIGURATION_SITE: + case CONFIGURATION_FREQUENCY: + fireTableCellUpdated(rowIndex, COLUMN_CONFIGURATION_FREQUENCY); break; - case CHANNEL_CONFIGURATION_NAME: - fireTableCellUpdated(rowIndex, COLUMN_CONFIGURATION_NAME); + case DECODER_CHANNEL_NAME: + fireTableCellUpdated(rowIndex, COLUMN_DECODER_LOGICAL_CHANNEL_NAME); break; - case CHANNEL_FREQUENCY: - fireTableCellUpdated(rowIndex, COLUMN_FREQUENCY); + case DECODER_TYPE: + fireTableCellUpdated(rowIndex, COLUMN_DECODER_TYPE); break; - case CHANNEL_FREQUENCY_LABEL: - fireTableCellUpdated(rowIndex, COLUMN_CHANNEL); + case DECODER_STATE: + fireTableCellUpdated(rowIndex, COLUMN_DECODER_STATE); break; - case CHANNEL_STATE: - fireTableCellUpdated(rowIndex, COLUMN_STATE); + case USER_FROM: + fireTableCellUpdated(rowIndex, COLUMN_USER_FROM); + fireTableCellUpdated(rowIndex, COLUMN_USER_FROM_ALIAS); break; - case BUFFER_OVERFLOW: - case MESSAGE: - case MESSAGE_TYPE: - fireTableCellUpdated(rowIndex, COLUMN_MESSAGE); - break; - case PRIMARY_ADDRESS_FROM: - fireTableCellUpdated(rowIndex, COLUMN_PRIMARY_FROM); - break; - case PRIMARY_ADDRESS_TO: - fireTableCellUpdated(rowIndex, COLUMN_PRIMARY_TO); - break; - case PRIMARY_DECODER_TYPE: - fireTableCellUpdated(rowIndex, COLUMN_DECODER); - break; - case SECONDARY_ADDRESS_FROM: - fireTableCellUpdated(rowIndex, COLUMN_SECONDARY_FROM); - break; - case SECONDARY_ADDRESS_TO: - fireTableCellUpdated(rowIndex, COLUMN_SECONDARY_TO); + case USER_TO: + fireTableCellUpdated(rowIndex, COLUMN_USER_TO); + fireTableCellUpdated(rowIndex, COLUMN_USER_TO_ALIAS); break; } } - } - }); + }); + } } } diff --git a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataPanel.java b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataPanel.java index 1159360e..75f1cc28 100644 --- a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataPanel.java +++ b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataPanel.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,21 +15,26 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.channel.metadata; import io.github.dsheirer.alias.Alias; import io.github.dsheirer.channel.state.State; import io.github.dsheirer.controller.channel.Channel; +import io.github.dsheirer.controller.channel.ChannelModel; import io.github.dsheirer.controller.channel.ChannelProcessingManager; import io.github.dsheirer.controller.channel.ChannelUtils; import io.github.dsheirer.icon.IconManager; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier; +import io.github.dsheirer.identifier.decoder.DecoderStateIdentifier; import io.github.dsheirer.module.ProcessingChain; +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; import net.miginfocom.swing.MigLayout; -import org.apache.commons.lang3.Validate; import javax.swing.JLabel; import javax.swing.JMenuItem; @@ -44,13 +50,16 @@ import java.awt.Color; import java.awt.Component; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.text.DecimalFormat; import java.util.HashMap; import java.util.Map; public class ChannelMetadataPanel extends JPanel implements ListSelectionListener { + private ChannelModel mChannelModel; private ChannelProcessingManager mChannelProcessingManager; private IconManager mIconManager; + private UserPreferences mUserPreferences; private JTable mTable; private Broadcaster mSelectedProcessingChainBroadcaster = new Broadcaster<>(); private Map mBackgroundColors = new HashMap<>(); @@ -59,11 +68,13 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene /** * Table view for currently decoding channel metadata */ - public ChannelMetadataPanel(ChannelProcessingManager channelProcessingManager, IconManager iconManager) + public ChannelMetadataPanel(ChannelModel channelModel, ChannelProcessingManager channelProcessingManager, + IconManager iconManager, UserPreferences userPreferences) { - mIconManager = iconManager; + mChannelModel = channelModel; mChannelProcessingManager = channelProcessingManager; - + mIconManager = iconManager; + mUserPreferences = userPreferences; init(); } @@ -82,16 +93,18 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene mTable.getSelectionModel().addListSelectionListener(this); mTable.addMouseListener(new MouseSupport()); - mTable.getColumnModel().getColumn(ChannelMetadataModel.COLUMN_STATE) + mTable.getColumnModel().getColumn(ChannelMetadataModel.COLUMN_DECODER_STATE) .setCellRenderer(new ColoredStateCellRenderer()); - mTable.getColumnModel().getColumn(ChannelMetadataModel.COLUMN_PRIMARY_TO) - .setCellRenderer(new AliasedValueCellRenderer(Attribute.PRIMARY_ADDRESS_TO)); - mTable.getColumnModel().getColumn(ChannelMetadataModel.COLUMN_PRIMARY_FROM) - .setCellRenderer(new AliasedValueCellRenderer(Attribute.PRIMARY_ADDRESS_FROM)); - mTable.getColumnModel().getColumn(ChannelMetadataModel.COLUMN_SECONDARY_TO) - .setCellRenderer(new AliasedValueCellRenderer(Attribute.SECONDARY_ADDRESS_TO)); - mTable.getColumnModel().getColumn(ChannelMetadataModel.COLUMN_SECONDARY_FROM) - .setCellRenderer(new AliasedValueCellRenderer(Attribute.SECONDARY_ADDRESS_FROM)); + mTable.getColumnModel().getColumn(ChannelMetadataModel.COLUMN_USER_FROM) + .setCellRenderer(new FromCellRenderer(mUserPreferences.getTalkgroupFormatPreference())); + mTable.getColumnModel().getColumn(ChannelMetadataModel.COLUMN_USER_TO) + .setCellRenderer(new ToCellRenderer(mUserPreferences.getTalkgroupFormatPreference())); + mTable.getColumnModel().getColumn(ChannelMetadataModel.COLUMN_USER_FROM_ALIAS) + .setCellRenderer(new AliasCellRenderer()); + mTable.getColumnModel().getColumn(ChannelMetadataModel.COLUMN_USER_TO_ALIAS) + .setCellRenderer(new AliasCellRenderer()); + mTable.getColumnModel().getColumn(ChannelMetadataModel.COLUMN_CONFIGURATION_FREQUENCY) + .setCellRenderer(new FrequencyCellRenderer()); JScrollPane scrollPane = new JScrollPane(mTable, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); @@ -139,14 +152,16 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene { int selectedModelRow = mTable.convertRowIndexToModel(selectedViewRow); - Metadata selectedMetadata = mChannelProcessingManager.getChannelMetadataModel() - .getMetadata(selectedModelRow); + ChannelMetadata selectedMetadata = mChannelProcessingManager.getChannelMetadataModel() + .getChannelMetadata(selectedModelRow); if(selectedMetadata != null) { Channel selectedChannel = mChannelProcessingManager.getChannelMetadataModel() .getChannelFromMetadata(selectedMetadata); + + processingChain = mChannelProcessingManager.getProcessingChain(selectedChannel); } } @@ -164,35 +179,148 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene mSelectedProcessingChainBroadcaster.addListener(listener); } - public class AliasedValueCellRenderer extends DefaultTableCellRenderer + /** + * Cell renderer for frequency values + */ + public class FrequencyCellRenderer extends DefaultTableCellRenderer { - private Attribute mAttribute; + private final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat( "#.00000" ); - public AliasedValueCellRenderer(Attribute attribute) + public FrequencyCellRenderer() { - Validate.isTrue(attribute != null); - mAttribute = attribute; setHorizontalAlignment(JLabel.CENTER); } @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, - int row, int column) + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - Metadata metadata = (Metadata)value; - String value1 = metadata.getValue(mAttribute); - Alias alias = metadata.getAlias(mAttribute); - - label.setText(alias != null ? alias.getName() : value1); - label.setIcon(alias != null ? mIconManager.getIcon(alias.getIconName(), IconManager.DEFAULT_ICON_SIZE) : null); - label.setForeground(alias != null ? alias.getDisplayColor() : table.getForeground()); + if(value instanceof FrequencyConfigurationIdentifier) + { + long frequency = ((FrequencyConfigurationIdentifier)value).getValue(); + label.setText(FREQUENCY_FORMATTER.format(frequency / 1e6d)); + } + else + { + label.setText(null); + } return label; } } + /** + * Alias cell renderer + */ + public class AliasCellRenderer extends DefaultTableCellRenderer + { + public AliasCellRenderer() + { + setHorizontalAlignment(JLabel.LEFT); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + if(value instanceof Alias) + { + Alias alias = (Alias)value; + + label.setText(alias.getName()); + label.setIcon(mIconManager.getIcon(alias.getIconName(), IconManager.DEFAULT_ICON_SIZE)); + label.setForeground(alias.getDisplayColor()); + + } + else + { + label.setText(null); + label.setIcon(null); + label.setForeground(table.getForeground()); + } + + return label; + } + } + + /** + * Abstract cell renderer for identifiers + */ + public abstract class IdentifierCellRenderer extends DefaultTableCellRenderer + { + private final static String EMPTY_VALUE = "-----"; + private TalkgroupFormatPreference mTalkgroupFormatPreference; + + public IdentifierCellRenderer(TalkgroupFormatPreference talkgroupFormatPreference) + { + mTalkgroupFormatPreference = talkgroupFormatPreference; + setHorizontalAlignment(JLabel.CENTER); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + if(value instanceof ChannelMetadata) + { + ChannelMetadata channelMetadata = (ChannelMetadata)value; + Identifier identifier = getIdentifier(channelMetadata); + String text = mTalkgroupFormatPreference.format(identifier); + if(text == null || text.isEmpty()) + { + text = EMPTY_VALUE; + } + + label.setText(text); + } + else + { + label.setText(EMPTY_VALUE); + } + + return label; + } + + public abstract Identifier getIdentifier(ChannelMetadata channelMetadata); + } + + /** + * Cell renderer for the FROM identifier + */ + public class FromCellRenderer extends IdentifierCellRenderer + { + public FromCellRenderer(TalkgroupFormatPreference talkgroupFormatPreference) + { + super(talkgroupFormatPreference); + } + + @Override + public Identifier getIdentifier(ChannelMetadata channelMetadata) + { + return channelMetadata.getFromIdentifier(); + } + } + + /** + * Cell renderer for the TO identifier + */ + public class ToCellRenderer extends IdentifierCellRenderer + { + public ToCellRenderer(TalkgroupFormatPreference talkgroupFormatPreference) + { + super(talkgroupFormatPreference); + } + + @Override + public Identifier getIdentifier(ChannelMetadata channelMetadata) + { + return channelMetadata.getToIdentifier(); + } + } + public class ColoredStateCellRenderer extends DefaultTableCellRenderer { public ColoredStateCellRenderer() @@ -209,9 +337,9 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene Color background = table.getBackground(); Color foreground = table.getForeground(); - if(value instanceof State) + if(value instanceof DecoderStateIdentifier) { - State state = (State)value; + State state = ((DecoderStateIdentifier)value).getValue(); label.setText(state.getDisplayValue()); if(mBackgroundColors.containsKey(state)) @@ -226,7 +354,7 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene } else { - setText(""); + setText("----"); } setBackground(background); @@ -255,7 +383,7 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene if(modelRowIndex >= 0) { - Metadata metadata = mChannelProcessingManager.getChannelMetadataModel().getMetadata(modelRowIndex); + ChannelMetadata metadata = mChannelProcessingManager.getChannelMetadataModel().getChannelMetadata(modelRowIndex); if(metadata != null) { @@ -264,9 +392,7 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene if(channel != null) { - popupMenu.add(ChannelUtils.getContextMenu(mChannelProcessingManager.getChannelModel(), - mChannelProcessingManager, channel, mTable)); - + popupMenu.add(ChannelUtils.getContextMenu(mChannelModel, mChannelProcessingManager, channel, mTable)); populated = true; } } diff --git a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataViewer.java b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataViewer.java index d0f3fd23..b7b3c65c 100644 --- a/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataViewer.java +++ b/src/main/java/io/github/dsheirer/channel/metadata/ChannelMetadataViewer.java @@ -20,12 +20,14 @@ package io.github.dsheirer.channel.metadata; import com.jidesoft.swing.JideSplitPane; import com.jidesoft.swing.JideTabbedPane; +import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.channel.details.ChannelDetailPanel; +import io.github.dsheirer.controller.channel.ChannelModel; import io.github.dsheirer.controller.channel.ChannelProcessingManager; import io.github.dsheirer.icon.IconManager; -import io.github.dsheirer.module.decode.event.CallEventPanel; +import io.github.dsheirer.module.decode.event.DecodeEventPanel; import io.github.dsheirer.module.decode.event.MessageActivityPanel; -import io.github.dsheirer.settings.SettingsManager; +import io.github.dsheirer.preference.UserPreferences; import net.miginfocom.swing.MigLayout; import javax.swing.JPanel; @@ -36,20 +38,20 @@ public class ChannelMetadataViewer extends JPanel private ChannelMetadataPanel mChannelMetadataPanel; private ChannelDetailPanel mChannelDetailPanel; - private CallEventPanel mCallEventPanel; + private DecodeEventPanel mDecodeEventPanel; private MessageActivityPanel mMessageActivityPanel; /** * GUI panel that combines the currently decoding channels metadata table and viewers for channel details, * messages, events, and spectral view. */ - public ChannelMetadataViewer(ChannelProcessingManager channelProcessingManager, IconManager iconManager, - SettingsManager settingsManager) + public ChannelMetadataViewer(ChannelModel channelModel, ChannelProcessingManager channelProcessingManager, + IconManager iconManager, AliasModel aliasModel, UserPreferences userPreferences) { mChannelDetailPanel = new ChannelDetailPanel(channelProcessingManager); - mCallEventPanel = new CallEventPanel(iconManager); + mDecodeEventPanel = new DecodeEventPanel(iconManager, userPreferences, aliasModel); mMessageActivityPanel = new MessageActivityPanel(channelProcessingManager); - mChannelMetadataPanel = new ChannelMetadataPanel(channelProcessingManager, iconManager); + mChannelMetadataPanel = new ChannelMetadataPanel(channelModel, channelProcessingManager, iconManager, userPreferences); init(); } @@ -60,7 +62,7 @@ public class ChannelMetadataViewer extends JPanel JideTabbedPane tabbedPane = new JideTabbedPane(); tabbedPane.addTab("Details", mChannelDetailPanel); - tabbedPane.addTab("Events", mCallEventPanel); + tabbedPane.addTab("Events", mDecodeEventPanel); tabbedPane.addTab("Messages", mMessageActivityPanel); // tabbedPane.addTab("Spectrum", mChannelSpectrumPanel); tabbedPane.setFont(this.getFont()); @@ -73,7 +75,7 @@ public class ChannelMetadataViewer extends JPanel add(splitPane); mChannelMetadataPanel.addProcessingChainSelectionListener(mChannelDetailPanel); - mChannelMetadataPanel.addProcessingChainSelectionListener(mCallEventPanel); + mChannelMetadataPanel.addProcessingChainSelectionListener(mDecodeEventPanel); mChannelMetadataPanel.addProcessingChainSelectionListener(mMessageActivityPanel); } } diff --git a/src/main/java/io/github/dsheirer/channel/metadata/IChannelMetadataUpdateListener.java b/src/main/java/io/github/dsheirer/channel/metadata/IChannelMetadataUpdateListener.java new file mode 100644 index 00000000..1e0f8df1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/channel/metadata/IChannelMetadataUpdateListener.java @@ -0,0 +1,29 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.channel.metadata; + +/** + * Interface to receive channel metadata update notifications + */ +public interface IChannelMetadataUpdateListener +{ + void updated(ChannelMetadata channelMetadata, ChannelMetadataField channelMetadataField); +} diff --git a/src/main/java/io/github/dsheirer/channel/metadata/Metadata.java b/src/main/java/io/github/dsheirer/channel/metadata/Metadata.java deleted file mode 100644 index 80bffc1a..00000000 --- a/src/main/java/io/github/dsheirer/channel/metadata/Metadata.java +++ /dev/null @@ -1,664 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.channel.metadata; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.id.broadcast.BroadcastChannel; -import io.github.dsheirer.alias.id.priority.Priority; -import io.github.dsheirer.channel.state.State; -import io.github.dsheirer.module.decode.DecoderType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Set; -import java.util.TreeSet; - -public class Metadata -{ - private final static Logger mLog = LoggerFactory.getLogger(Metadata.class); - - // Static unique metadata identifier tracking - private static int UNIQUE_METADATA_ID_GENERATOR = 0; - - private int mMetadataID; - - protected boolean mUpdated; - protected DecoderType mPrimaryDecoderType; - protected boolean mSelected; - protected boolean mBufferOverflow; - protected State mState = State.IDLE; - protected String mChannelConfigurationSystem; - protected String mChannelConfigurationSite; - protected String mChannelConfigurationName; - protected String mChannelFrequencyLabel; - protected long mChannelFrequency; - protected AliasedIdentifier mNetworkID1 = new AliasedIdentifier(); - protected AliasedIdentifier mNetworkID2 = new AliasedIdentifier(); - - protected String mMessage; - protected String mMessageType; - protected AliasedIdentifier mPrimaryAddressFrom = new AliasedIdentifier(); - protected AliasedIdentifier mPrimaryAddressTo = new AliasedIdentifier(); - protected AliasedIdentifier mSecondaryAddressFrom = new AliasedIdentifier(); - protected AliasedIdentifier mSecondaryAddressTo = new AliasedIdentifier(); - - //Lazily constructed member variables. - private Integer mAudioPriority; - protected Boolean mRecordable = false; - protected Boolean mDoNotRecord; - private Set mBroadcastChannels; - - private long mTimestamp = System.currentTimeMillis(); - - - /** - * Channel metadata. Contains all attributes that reflect the state and current attribute values for a channel - * that is currently decoding. This metadata is intended to support any decoding channel gui components to - * graphically convey the current state of a decoding channel and to provide audio metadata - */ - public Metadata() - { - mMetadataID = UNIQUE_METADATA_ID_GENERATOR++; - } - - /** - * Protected constructor. Primarily used by the copyOf() method to create a metadata copy with the same ID - */ - protected Metadata(int metadataID) - { - mMetadataID = metadataID; - } - - /** - * Timestamp when this metadata was created - * @return milliseconds since epoch - */ - public long getTimestamp() - { - return mTimestamp; - } - /** - * Unique string identifier for this metadata that is comprised of the channel ID and the primary TO address. - * - * This identifier can be used to uniquely identify a channel audio stream and the primary communicant. - */ - public String getUniqueIdentifier() - { - return "SRC:" + mMetadataID + - " ID:" + (mPrimaryAddressTo.hasIdentifier() ? mPrimaryAddressTo.getIdentifier() : "UNKNOWN"); - } - - /** - * Indicates if any of the fields of this metadata have been updated since the last time a copy was made from this - * metadata. This method is primarily used by downstream audio playback and audio recording to signal when changes - * are made to the metadata that requires the downstream component to reinspect the metadata. - * - * This flag is reset to false immediately after a copy is made of this metadata via the copyOf() method. - */ - public boolean isUpdated() - { - return mUpdated; - } - - /** - * Audio Priority as the highest audio priority value from across the primary and secondary identifier aliases. - * - * A default priority will be used if there are no aliased values. If there is a conflict between audio priority - * and 'Do Not Monitor' among the aliases, the highest audio priority will be used (prefer to monitor). - * - * -1 Do Not Monitor - * 0 Channel Selected for immediate monitor - * 1 Highest Priority - * 100 Lowest Priority (default) - * - * @return audio priority - */ - public int getAudioPriority() - { - if(isSelected()) - { - return Priority.SELECTED_PRIORITY; - } - - if(mAudioPriority == null) - { - determineAudioPriority(); - } - - return mAudioPriority; - } - - /** - * Indicates if the audio priority is set to 'Do Not Monitor' - */ - public boolean isDoNotMonitor() - { - return getAudioPriority() == Priority.DO_NOT_MONITOR; - } - - /** - * Indicates if any of the primary or secondary TO/FROM aliases are identified as recordable - */ - public boolean isRecordable() - { - if(mDoNotRecord == null) - { - determineRecordable(); - } - - return mRecordable && !mDoNotRecord; - } - - /** - * Indicates that this metadata has been selected by the user and any audio produced by this channel will be set - * to the highest playback priority (immediate monitor). - */ - public boolean isSelected() - { - return mSelected; - } - - /** - * Indicates if the channel for this metadata is in a buffer overflow state. - */ - public boolean isBufferOverflow() - { - return mBufferOverflow; - } - - /** - * List of de-duplicated broadcast channels aggregated from the primary and secondary TO/FROM aliases. - */ - public Set getBroadcastChannels() - { - if(mBroadcastChannels == null) - { - determineBroadcastChannels(); - } - - return mBroadcastChannels; - } - - /** - * Indicates if this metadata is streamable, meaning that it contains one ore more broadcast channels - */ - public boolean isStreamable() - { - return !getBroadcastChannels().isEmpty(); - } - - /** - * Unique identifier for the source channel that produces this metadata - */ - public int getMetadataID() - { - return mMetadataID; - } - - /** - * Decoder type (ie protocol) for the primary decoder - */ - public DecoderType getPrimaryDecoderType() - { - return mPrimaryDecoderType; - } - - /** - * Indicates if this metadata has a primary decoder type defined - */ - public boolean hasPrimaryDecoderType() - { - return mPrimaryDecoderType != null; - } - - /** - * Returns the alias associated with the attribute or null - */ - public Alias getAlias(Attribute attribute) - { - switch(attribute) - { - case NETWORK_ID_1: - return getNetworkID1().getAlias(); - case NETWORK_ID_2: - return getNetworkID2().getAlias(); - case PRIMARY_ADDRESS_FROM: - return getPrimaryAddressFrom().getAlias(); - case PRIMARY_ADDRESS_TO: - return getPrimaryAddressTo().getAlias(); - case SECONDARY_ADDRESS_FROM: - return getSecondaryAddressFrom().getAlias(); - case SECONDARY_ADDRESS_TO: - return getSecondaryAddressTo().getAlias(); - } - - return null; - } - - /** - * Returns the string value for the specified attribute or null - */ - public String getValue(Attribute attribute) - { - switch(attribute) - { - case CHANNEL_CONFIGURATION_SYSTEM: - return mChannelConfigurationSystem; - case CHANNEL_CONFIGURATION_SITE: - return mChannelConfigurationSite; - case CHANNEL_FREQUENCY_LABEL: - return mChannelFrequencyLabel; - case CHANNEL_STATE: - return mState.getDisplayValue(); - case MESSAGE: - return mMessage; - case MESSAGE_TYPE: - return mMessageType; - case NETWORK_ID_1: - return mNetworkID1.getIdentifier(); - case NETWORK_ID_2: - return mNetworkID2.getIdentifier(); - case PRIMARY_ADDRESS_FROM: - return mPrimaryAddressFrom.getIdentifier(); - case PRIMARY_ADDRESS_TO: - return mPrimaryAddressTo.getIdentifier(); - case SECONDARY_ADDRESS_FROM: - return mSecondaryAddressFrom.getIdentifier(); - case SECONDARY_ADDRESS_TO: - return mSecondaryAddressTo.getIdentifier(); - } - - return null; - } - - /** - * Current channel state - */ - public State getState() - { - return mState; - } - - /** - * System name from the channel configuration - */ - public String getChannelConfigurationSystem() - { - return mChannelConfigurationSystem; - } - - /** - * Indicates if there is a non-null, non-empty channel configuration system value - */ - public boolean hasChannelConfigurationSystem() - { - return mChannelConfigurationSystem != null && !mChannelConfigurationSystem.isEmpty(); - } - - /** - * Site name from the channel configuration - */ - public String getChannelConfigurationSite() - { - return mChannelConfigurationSite; - } - - /** - * Indicates if there is a non-null, non-empty channel configuration site value - */ - public boolean hasChannelConfigurationSite() - { - return mChannelConfigurationSite != null && !mChannelConfigurationSite.isEmpty(); - } - - /** - * Site name from the channel configuration - */ - public String getChannelConfigurationName() - { - return mChannelConfigurationName; - } - - /** - * Indicates if there is a non-null, non-empty channel configuration name value - */ - public boolean hasChannelConfigurationName() - { - return mChannelConfigurationSite != null && !mChannelConfigurationSite.isEmpty(); - } - - /** - * Channel ID or Channel Number - */ - public String getChannelFrequencyLabel() - { - return mChannelFrequencyLabel; - } - - /** - * Indicates if there is a non-null, non-empty channel identifier value - */ - public boolean hasChannelFrequencyLabel() - { - return mChannelFrequencyLabel != null && !mChannelFrequencyLabel.isEmpty(); - } - - /** - * Channel frequency - */ - public long getChannelFrequency() - { - return mChannelFrequency; - } - - /** - * Indicates if there is a non-zero channel frequency value - */ - public boolean hasChannelFrequency() - { - return mChannelFrequency > 0; - } - - /** - * Network Identifier 1 - */ - public AliasedIdentifier getNetworkID1() - { - return mNetworkID1; - } - - /** - * Network Identifier 2 - */ - public AliasedIdentifier getNetworkID2() - { - return mNetworkID2; - } - - /** - * Message - */ - public String getMessage() - { - return mMessage; - } - - /** - * Indicates if there is a non-null, non-empty message value - */ - public boolean hasMessage() - { - return mMessage != null && !mMessage.isEmpty(); - } - - /** - * Message and Decoder type - */ - public String getMessageType() - { - return mMessageType; - } - - /** - * Indicates if there is a non-null, non-empty message and decoder type value - */ - public boolean hasMessageType() - { - return mMessageType != null && !mMessageType.isEmpty(); - } - - /** - * Primary FROM Address - */ - public AliasedIdentifier getPrimaryAddressFrom() - { - return mPrimaryAddressFrom; - } - - /** - * Primary TO Address - */ - public AliasedIdentifier getPrimaryAddressTo() - { - return mPrimaryAddressTo; - } - - /** - * Secondary FROM Address - */ - public AliasedIdentifier getSecondaryAddressFrom() - { - return mSecondaryAddressFrom; - } - - /** - * Secondary TO Address - */ - public AliasedIdentifier getSecondaryAddressTo() - { - return mSecondaryAddressTo; - } - - /** - * Determines the audio priority from across the primary and secondary TO/FROM aliases when they exist - */ - private void determineAudioPriority() - { - int priority = Priority.DEFAULT_PRIORITY; - - boolean doNotMonitorFound = false; - boolean audioPriorityFound = false; - - if(getPrimaryAddressTo().hasAlias()) - { - Alias primaryTo = getPrimaryAddressTo().getAlias(); - - if(primaryTo.hasCallPriority()) - { - if(primaryTo.getCallPriority() == Priority.DO_NOT_MONITOR) - { - doNotMonitorFound = true; - } - else - { - audioPriorityFound = true; - - if(primaryTo.getCallPriority() < priority) - { - priority = primaryTo.getCallPriority(); - } - } - } - } - - if(getPrimaryAddressFrom().hasAlias()) - { - Alias primaryFrom = getPrimaryAddressFrom().getAlias(); - - if(primaryFrom.hasCallPriority()) - { - if(primaryFrom.getCallPriority() == Priority.DO_NOT_MONITOR) - { - doNotMonitorFound = true; - } - else - { - audioPriorityFound = true; - - if(primaryFrom.getCallPriority() < priority) - { - priority = primaryFrom.getCallPriority(); - } - } - } - } - - if(getSecondaryAddressTo().hasAlias()) - { - Alias secondaryTo = getSecondaryAddressTo().getAlias(); - - if(secondaryTo.hasCallPriority()) - { - if(secondaryTo.getCallPriority() == Priority.DO_NOT_MONITOR) - { - doNotMonitorFound = true; - } - else - { - audioPriorityFound = true; - - if(secondaryTo.getCallPriority() < priority) - { - priority = secondaryTo.getCallPriority(); - } - } - } - } - - if(getSecondaryAddressFrom().hasAlias()) - { - Alias secondaryFrom = getSecondaryAddressFrom().getAlias(); - - if(secondaryFrom.hasCallPriority()) - { - if(secondaryFrom.getCallPriority() == Priority.DO_NOT_MONITOR) - { - doNotMonitorFound = true; - } - else - { - audioPriorityFound = true; - - if(secondaryFrom.getCallPriority() < priority) - { - priority = secondaryFrom.getCallPriority(); - } - } - } - } - - //If we found a 'Do Not Monitor' and no other audio priority, then set to do not monitor - if(doNotMonitorFound && !audioPriorityFound) - { - mAudioPriority = Priority.DO_NOT_MONITOR; - } - else - { - mAudioPriority = priority; - } - } - - /** - * Determines if any of the primary or secondary to/from aliases are recordable. - */ - private void determineRecordable() - { - mDoNotRecord = false; - - if(mPrimaryAddressTo.hasAlias() && !mPrimaryAddressTo.getAlias().isRecordable()) - { - mDoNotRecord = true; - return; - } - - if(mPrimaryAddressFrom.hasAlias() && !mPrimaryAddressFrom.getAlias().isRecordable()) - { - mDoNotRecord = true; - return; - } - - if(mSecondaryAddressTo.hasAlias() && !mSecondaryAddressTo.getAlias().isRecordable()) - { - mDoNotRecord = true; - return; - } - - if(mSecondaryAddressFrom.hasAlias() && !mSecondaryAddressFrom.getAlias().isRecordable()) - { - mDoNotRecord = true; - return; - } - } - - /** - * Aggregates a de-duplicated list of broadcast channels from among the primary/secondary to/from aliases - */ - private void determineBroadcastChannels() - { - mBroadcastChannels = new TreeSet<>(); - - if(mPrimaryAddressTo.hasAlias() && mPrimaryAddressTo.getAlias().isStreamable()) - { - mBroadcastChannels.addAll(mPrimaryAddressTo.getAlias().getBroadcastChannels()); - } - - if(mPrimaryAddressFrom.hasAlias() && mPrimaryAddressFrom.getAlias().isStreamable()) - { - mBroadcastChannels.addAll(mPrimaryAddressFrom.getAlias().getBroadcastChannels()); - } - - if(mSecondaryAddressTo.hasAlias() && mSecondaryAddressTo.getAlias().isStreamable()) - { - mBroadcastChannels.addAll(mSecondaryAddressTo.getAlias().getBroadcastChannels()); - } - - if(mSecondaryAddressFrom.hasAlias() && mSecondaryAddressFrom.getAlias().isStreamable()) - { - mBroadcastChannels.addAll(mSecondaryAddressFrom.getAlias().getBroadcastChannels()); - } - } - - /** - * Creates a deep/full snapshot copy of this metadata set. - * - * The streamable, recordable, audio priority and broadcast channels are not copied. These are lazily determined - * when initially requested. - */ - public Metadata copyOf() - { - Metadata copy = new Metadata(mMetadataID); - - copy.mDoNotRecord = mDoNotRecord; - copy.mRecordable = mRecordable; - copy.mState = mState; - copy.mPrimaryDecoderType = mPrimaryDecoderType; - copy.mChannelFrequency = mChannelFrequency; - - copy.mChannelConfigurationSystem = hasChannelConfigurationSystem() ? new String(mChannelConfigurationSystem) : null; - copy.mChannelConfigurationSite = hasChannelConfigurationSite() ? new String(mChannelConfigurationSite) : null; - copy.mChannelConfigurationName = hasChannelConfigurationName() ? new String(mChannelConfigurationName) : null; - copy.mChannelFrequencyLabel = hasChannelFrequencyLabel() ? new String(mChannelFrequencyLabel) : null; - copy.mMessage = hasMessage() ? new String(mMessage) : null; - copy.mMessageType = hasMessageType() ? new String(mMessageType) : null; - - copy.mNetworkID1 = mNetworkID1.copyOf(); - copy.mNetworkID2 = mNetworkID2.copyOf(); - copy.mPrimaryAddressFrom = mPrimaryAddressFrom.copyOf(); - copy.mPrimaryAddressTo = mPrimaryAddressTo.copyOf(); - copy.mSecondaryAddressFrom = mSecondaryAddressFrom.copyOf(); - copy.mSecondaryAddressTo = mSecondaryAddressTo.copyOf(); - - copy.mUpdated = mUpdated; - - //Reset the updated flag - mUpdated = false; - - return copy; - } -} diff --git a/src/main/java/io/github/dsheirer/channel/metadata/MutableMetadata.java b/src/main/java/io/github/dsheirer/channel/metadata/MutableMetadata.java deleted file mode 100644 index df4e297d..00000000 --- a/src/main/java/io/github/dsheirer/channel/metadata/MutableMetadata.java +++ /dev/null @@ -1,294 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.channel.metadata; - -import io.github.dsheirer.channel.state.State; -import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.sample.Broadcaster; -import io.github.dsheirer.sample.Listener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MutableMetadata extends Metadata implements Listener -{ - private final static Logger mLog = LoggerFactory.getLogger(MutableMetadata.class); - - private Broadcaster mMetadataChangeEventBroadcaster = new Broadcaster<>(); - - /** - * Mutable channel metadata. Contains all attributes that reflect the state and current attribute values for a - * channel that is currently decoding. This metadata is intended to support any decoding channel gui components to - * graphically convey the current state of a decoding channel and to provide audio metadata - */ - public MutableMetadata() - { - super(); - } - - /** - * Sets the selected state for this metadata. Selected indicates that the user has selected this metadata for - * immediate monitor in the audio panel. When true, this overrides the audio priority for any audio produced - * by the channel and sets that audio to the highest priority. - * - * Any component that toggles the selected state for this metadata is also responsible for clearing the flag. - */ - public void setSelected(boolean selected) - { - mSelected = selected; - mUpdated = true; - } - - /** - * Sets the recordable flag to the argument value and overrides any recordable settings in the enclosed aliases. - */ - public void setRecordable(boolean recordable) - { - mRecordable = recordable; - } - - /** - * Sets the primary decoder type - */ - public void setPrimaryDecoderType(DecoderType decoderType) - { - mPrimaryDecoderType = decoderType; - mUpdated = true; - } - - /** - * Request to change an attribute value for this metadata. This is the primary method for updating all attributes - * in this metadata set. - */ - @Override - public void receive(AttributeChangeRequest request) - { - switch(request.getAttribute()) - { - case BUFFER_OVERFLOW: - mBufferOverflow = request.getBooleanValue(); - broadcast(Attribute.BUFFER_OVERFLOW); - break; - case CHANNEL_CONFIGURATION_SYSTEM: - mChannelConfigurationSystem = request.getStringValue(); - broadcast(Attribute.CHANNEL_CONFIGURATION_SYSTEM); - break; - case CHANNEL_CONFIGURATION_SITE: - mChannelConfigurationSite = request.getStringValue(); - broadcast(Attribute.CHANNEL_CONFIGURATION_SITE); - break; - case CHANNEL_CONFIGURATION_NAME: - mChannelConfigurationName = request.getStringValue(); - broadcast(Attribute.CHANNEL_CONFIGURATION_NAME); - break; - case CHANNEL_FREQUENCY: - if(request.hasValue()) - { - mChannelFrequency = request.getLongValue(); - } - broadcast(Attribute.CHANNEL_FREQUENCY); - break; - case CHANNEL_FREQUENCY_LABEL: - mChannelFrequencyLabel = request.getStringValue(); - broadcast(Attribute.CHANNEL_FREQUENCY_LABEL); - break; - case MESSAGE: - mMessage = request.getStringValue(); - broadcast(Attribute.MESSAGE); - break; - case MESSAGE_TYPE: - mMessageType = request.getStringValue(); - broadcast(Attribute.MESSAGE_TYPE); - break; - case NETWORK_ID_1: - mNetworkID1.setIdentifier(request.getStringValue()); - mNetworkID1.setAlias(request.getAlias()); - broadcast(Attribute.NETWORK_ID_1); - break; - case NETWORK_ID_2: - mNetworkID2.setIdentifier(request.getStringValue()); - mNetworkID2.setAlias(request.getAlias()); - broadcast(Attribute.NETWORK_ID_2); - break; - case PRIMARY_ADDRESS_FROM: - mPrimaryAddressFrom.setIdentifier(request.getStringValue()); - mPrimaryAddressFrom.setAlias(request.getAlias()); - broadcast(Attribute.PRIMARY_ADDRESS_FROM); - break; - case PRIMARY_ADDRESS_TO: - mPrimaryAddressTo.setIdentifier(request.getStringValue()); - mPrimaryAddressTo.setAlias(request.getAlias()); - broadcast(Attribute.PRIMARY_ADDRESS_TO); - break; - case PRIMARY_DECODER_TYPE: - mPrimaryDecoderType = request.getDecoderTypeValue(); - broadcast(Attribute.PRIMARY_DECODER_TYPE); - break; - case SECONDARY_ADDRESS_FROM: - mSecondaryAddressFrom.setIdentifier(request.getStringValue()); - mSecondaryAddressFrom.setAlias(request.getAlias()); - broadcast(Attribute.SECONDARY_ADDRESS_FROM); - break; - case SECONDARY_ADDRESS_TO: - mSecondaryAddressTo.setIdentifier(request.getStringValue()); - mSecondaryAddressTo.setAlias(request.getAlias()); - broadcast(Attribute.SECONDARY_ADDRESS_TO); - break; - case CHANNEL_STATE: - mState = (State) request.getValue(); - broadcast(Attribute.CHANNEL_STATE); - break; - default: - throw new IllegalArgumentException("Unrecognized Metadata Attribute: " + - request.getAttribute().name()); - } - - mUpdated = true; - } - - /** - * Resets temporal attributes to null value. This only resets attributes that have a non-ull value in order to - * limit the number of change events that are produced. - */ - public void resetTemporalAttributes() - { - mDoNotRecord = null; - mBufferOverflow = false; - - if(hasMessage()) - { - mMessage = null; - broadcast(Attribute.MESSAGE); - } - - if(hasMessageType()) - { - mMessageType = null; - broadcast(Attribute.MESSAGE_TYPE); - } - - if(mPrimaryAddressFrom.hasIdentifier()) - { - mPrimaryAddressFrom.reset(); - broadcast(Attribute.PRIMARY_ADDRESS_FROM); - } - - if(mPrimaryAddressTo.hasIdentifier()) - { - mPrimaryAddressTo.reset(); - broadcast(Attribute.PRIMARY_ADDRESS_TO); - } - - if(mSecondaryAddressFrom.hasIdentifier()) - { - mSecondaryAddressFrom.reset(); - broadcast(Attribute.SECONDARY_ADDRESS_FROM); - } - - if(mSecondaryAddressTo.hasIdentifier()) - { - mSecondaryAddressTo.reset(); - broadcast(Attribute.SECONDARY_ADDRESS_TO); - } - - mUpdated = true; - } - - /** - * Resets all attributes to empty values - */ - public void resetAllAttributes() - { - resetTemporalAttributes(); - - if(hasChannelConfigurationSystem()) - { - mChannelConfigurationSystem = null; - broadcast(Attribute.CHANNEL_CONFIGURATION_SYSTEM); - } - - if(hasChannelConfigurationSite()) - { - mChannelConfigurationSite = null; - broadcast(Attribute.CHANNEL_CONFIGURATION_SITE); - } - - if(hasChannelConfigurationName()) - { - mChannelConfigurationSite = null; - broadcast(Attribute.CHANNEL_CONFIGURATION_NAME); - } - - if(hasChannelFrequencyLabel()) - { - mChannelFrequencyLabel = null; - broadcast(Attribute.CHANNEL_FREQUENCY_LABEL); - } - - if(hasChannelFrequency()) - { - mChannelFrequency = 0; - broadcast(Attribute.CHANNEL_FREQUENCY); - } - - if(hasPrimaryDecoderType()) - { - mPrimaryDecoderType = null; - broadcast(Attribute.PRIMARY_DECODER_TYPE); - } - - if(mNetworkID1.hasIdentifier()) - { - mNetworkID1.reset(); - broadcast(Attribute.NETWORK_ID_1); - } - - if(mNetworkID2.hasIdentifier()) - { - mNetworkID2.reset(); - broadcast(Attribute.NETWORK_ID_2); - } - - mUpdated = true; - } - - - /** - * Broadcasts to registered listeners that an attribute has changed for this metadata - */ - private void broadcast(Attribute attribute) - { - mMetadataChangeEventBroadcaster.broadcast(new MutableMetadataChangeEvent(this, attribute)); - } - - /** - * Adds the listener to receive metadata change events for this channel metadata - */ - public void addListener(Listener listener) - { - mMetadataChangeEventBroadcaster.addListener(listener); - } - - /** - * Removes the listener from receiving metadata change events for this channel metadata - */ - public void removeListener(Listener listener) - { - mMetadataChangeEventBroadcaster.removeListener(listener); - } -} diff --git a/src/main/java/io/github/dsheirer/channel/metadata/MutableMetadataChangeEvent.java b/src/main/java/io/github/dsheirer/channel/metadata/MutableMetadataChangeEvent.java deleted file mode 100644 index c083140d..00000000 --- a/src/main/java/io/github/dsheirer/channel/metadata/MutableMetadataChangeEvent.java +++ /dev/null @@ -1,53 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.channel.metadata; - -public class MutableMetadataChangeEvent -{ - private MutableMetadata mMetadata; - private Attribute mAttribute; - - /** - * Channel metadata changed attribute event - * - * @param metadata where the change occurred - * @param attribute that changed - */ - public MutableMetadataChangeEvent(MutableMetadata metadata, Attribute attribute) - { - mMetadata = metadata; - mAttribute = attribute; - } - - /** - * Channel metadata where the change occurred - */ - public MutableMetadata getMetadata() - { - return mMetadata; - } - - /** - * Channel metadata attribute that has changed - */ - public Attribute getAttribute() - { - return mAttribute; - } -} diff --git a/src/main/java/io/github/dsheirer/channel/state/AlwaysUnsquelchedDecoderState.java b/src/main/java/io/github/dsheirer/channel/state/AlwaysUnsquelchedDecoderState.java index 1d3795e8..4d145b90 100644 --- a/src/main/java/io/github/dsheirer/channel/state/AlwaysUnsquelchedDecoderState.java +++ b/src/main/java/io/github/dsheirer/channel/state/AlwaysUnsquelchedDecoderState.java @@ -1,26 +1,26 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,2015 Dennis Sheirer +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 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. + * 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 - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.channel.state; -import io.github.dsheirer.channel.metadata.Attribute; -import io.github.dsheirer.channel.metadata.AttributeChangeRequest; import io.github.dsheirer.channel.state.DecoderStateEvent.Event; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,8 +39,6 @@ public class AlwaysUnsquelchedDecoderState extends DecoderState public AlwaysUnsquelchedDecoderState(DecoderType decoderType, String channelName) { - super(null); - mDecoderType = decoderType; mChannelName = channelName; } @@ -69,9 +67,9 @@ public class AlwaysUnsquelchedDecoderState extends DecoderState } @Override - public void receive(Message t) + public void receive(IMessage t) { - /* Not implemented */ + /* Not implemented */ } @Override @@ -81,7 +79,7 @@ public class AlwaysUnsquelchedDecoderState extends DecoderState { //Each time we're reset, set the PRIMARY TO attribute back to the channel name, otherwise we won't have //a primary ID for any audio produced by this state. - broadcast(new AttributeChangeRequest(Attribute.PRIMARY_ADDRESS_TO, NO_SQUELCH)); +// broadcast(new AttributeChangeRequest(Attribute.PRIMARY_ADDRESS_TO, NO_SQUELCH)); } } @@ -94,7 +92,7 @@ public class AlwaysUnsquelchedDecoderState extends DecoderState @Override public void start() { - broadcast(new AttributeChangeRequest(Attribute.PRIMARY_ADDRESS_TO, NO_SQUELCH)); +// broadcast(new AttributeChangeRequest(Attribute.PRIMARY_ADDRESS_TO, NO_SQUELCH)); broadcast(new DecoderStateEvent(this, Event.ALWAYS_UNSQUELCH, State.IDLE)); } diff --git a/src/main/java/io/github/dsheirer/channel/state/ChannelState.java b/src/main/java/io/github/dsheirer/channel/state/ChannelState.java index c688035b..ee0c3fda 100644 --- a/src/main/java/io/github/dsheirer/channel/state/ChannelState.java +++ b/src/main/java/io/github/dsheirer/channel/state/ChannelState.java @@ -18,51 +18,64 @@ ******************************************************************************/ package io.github.dsheirer.channel.state; +import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.audio.squelch.ISquelchStateProvider; import io.github.dsheirer.audio.squelch.SquelchState; -import io.github.dsheirer.channel.metadata.Attribute; -import io.github.dsheirer.channel.metadata.AttributeChangeRequest; -import io.github.dsheirer.channel.metadata.IAttributeChangeRequestListener; -import io.github.dsheirer.channel.metadata.MutableMetadata; +import io.github.dsheirer.channel.metadata.ChannelMetadata; import io.github.dsheirer.channel.state.DecoderStateEvent.Event; -import io.github.dsheirer.channel.traffic.TrafficChannelManager; +import io.github.dsheirer.controller.channel.Channel; import io.github.dsheirer.controller.channel.Channel.ChannelType; +import io.github.dsheirer.controller.channel.ChannelEvent; +import io.github.dsheirer.controller.channel.IChannelEventProvider; +import io.github.dsheirer.identifier.IdentifierUpdateListener; +import io.github.dsheirer.identifier.IdentifierUpdateNotification; +import io.github.dsheirer.identifier.IdentifierUpdateProvider; +import io.github.dsheirer.identifier.MutableIdentifierCollection; +import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier; +import io.github.dsheirer.identifier.configuration.ChannelNameConfigurationIdentifier; +import io.github.dsheirer.identifier.configuration.DecoderTypeConfigurationIdentifier; +import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier; +import io.github.dsheirer.identifier.configuration.SiteConfigurationIdentifier; +import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier; +import io.github.dsheirer.identifier.decoder.DecoderStateIdentifier; import io.github.dsheirer.module.Module; -import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.config.DecodeConfiguration; -import io.github.dsheirer.module.decode.event.CallEvent; -import io.github.dsheirer.module.decode.event.ICallEventProvider; +import io.github.dsheirer.module.decode.event.IDecodeEvent; +import io.github.dsheirer.module.decode.event.IDecodeEventProvider; import io.github.dsheirer.sample.IOverflowListener; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.ISourceEventProvider; import io.github.dsheirer.source.SourceEvent; +import io.github.dsheirer.source.SourceType; +import io.github.dsheirer.source.config.SourceConfigTuner; import io.github.dsheirer.source.heartbeat.Heartbeat; import io.github.dsheirer.source.heartbeat.IHeartbeatListener; +import io.github.dsheirer.source.tuner.channel.TunerChannel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener, - IDecoderStateEventProvider, ISourceEventListener, ISourceEventProvider, IHeartbeatListener, - ISquelchStateProvider, IAttributeChangeRequestListener, IOverflowListener +public class ChannelState extends Module implements IChannelEventProvider, IDecodeEventProvider, + IDecoderStateEventListener, IDecoderStateEventProvider, ISourceEventListener, ISourceEventProvider, + IHeartbeatListener, ISquelchStateProvider, IOverflowListener, IdentifierUpdateListener, IdentifierUpdateProvider { private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class); public static final long FADE_TIMEOUT_DELAY = 1200; public static final long RESET_TIMEOUT_DELAY = 2000; - private MutableMetadata mMutableMetadata = new MutableMetadata(); private State mState = State.IDLE; - private Listener mCallEventListener; + private MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection(); + private Listener mChannelEventListener; + private Listener mDecodeEventListener; private Listener mDecoderStateListener; private Listener mSquelchStateListener; private Listener mExternalSourceEventListener; private DecoderStateEventReceiver mDecoderStateEventReceiver = new DecoderStateEventReceiver(); private HeartbeatReceiver mHeartbeatReceiver = new HeartbeatReceiver(); - private ChannelType mChannelType; - private TrafficChannelManager mTrafficChannelEndListener; - private CallEvent mTrafficChannelCallEvent; + private Channel mChannel; private SourceEventListener mInternalSourceEventListener; + private ChannelMetadata mChannelMetadata; private boolean mSquelchLocked = false; private boolean mSelected = false; @@ -87,25 +100,85 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder * IDLE: Normal state. No voice or data call activity * CALL/DATA/ENCRYPTED/CONTROL: Decoding states. * FADE: The phase after a voice or data call when either an explicit call end has been received, or when no new - * signalling updates have been received, and the fade timer has expired. This phase allows for gui updates to - * signal to the user that the call is ended, while continuing to display the call details for the user + * signalling updates have been received, and the fade timer has expired. This phase allows for gui updates to + * signal to the user that the call is ended, while continuing to display the call details for the user * TEARDOWN: Indicates a traffic channel that will be torn down for reuse. */ - public ChannelState(ChannelType channelType) + public ChannelState(Channel channel, AliasModel aliasModel) { - mChannelType = channelType; + mChannel = channel; + mChannelMetadata = new ChannelMetadata(aliasModel); + createConfigurationIdentifiers(channel); } + /** + * Creates configuration identifiers for the channel name, system, site and alias list name. + */ + private void createConfigurationIdentifiers(Channel channel) + { + mIdentifierCollection.update(DecoderTypeConfigurationIdentifier.create(channel.getDecodeConfiguration().getDecoderType())); + + if(channel.hasSystem()) + { + mIdentifierCollection.update(SystemConfigurationIdentifier.create(channel.getSystem())); + } + if(channel.hasSite()) + { + mIdentifierCollection.update(SiteConfigurationIdentifier.create(channel.getSite())); + } + if(channel.getName() != null && !channel.getName().isEmpty()) + { + mIdentifierCollection.update(ChannelNameConfigurationIdentifier.create(channel.getName())); + } + if(channel.getAliasListName() != null && !channel.getAliasListName().isEmpty()) + { + mIdentifierCollection.update(AliasListConfigurationIdentifier.create(channel.getAliasListName())); + } + if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER) + { + TunerChannel tunerChannel = ((SourceConfigTuner)channel.getSourceConfiguration()).getTunerChannel(); + mIdentifierCollection.update(FrequencyConfigurationIdentifier.create(tunerChannel.getFrequency())); + } + } /** - * Metadata for this channel containing channel details, primary and secondary entities for call events and any - * decoded messages. This metadata is primarily intended to be used by graphical components to convey the - * details of the current channel state and to provide metadata backing any decoded audio packets produced by - * any audio streams for this channel. + * Interface to receive channel identifier updates from this channel state and from any + * decoder states. */ - public MutableMetadata getMutableMetadata() + @Override + public Listener getIdentifierUpdateListener() { - return mMutableMetadata; + return mChannelMetadata; + } + + /** + * Updates the channel state identifier collection using the update notification. This update will be reflected + * in the internal channel state and will also be broadcast to any listeners, including the channel metadata for + * this channel state. + */ + public void updateChannelStateIdentifiers(IdentifierUpdateNotification notification) + { + mIdentifierCollection.receive(notification); + } + + @Override + public void setIdentifierUpdateListener(Listener listener) + { + mIdentifierCollection.setIdentifierUpdateListener(listener); + } + + @Override + public void removeIdentifierUpdateListener() + { + mIdentifierCollection.removeIdentifierUpdateListener(); + } + + /** + * Channel metadata for this channel. + */ + public ChannelMetadata getChannelMetadata() + { + return mChannelMetadata; } /** @@ -114,21 +187,18 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder @Override public void reset() { - getMutableMetadata().resetTemporalAttributes(); - - broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE)); - mState = State.IDLE; - mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_STATE, mState)); + broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE)); + mIdentifierCollection.update(DecoderStateIdentifier.IDLE); mSourceOverflow = false; } @Override public void start() { - mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_STATE, mState)); + mIdentifierCollection.broadcastIdentifiers(); - if(mTrafficChannelEndListener != null) + if(mChannel.getChannelType() == ChannelType.TRAFFIC) { setState(State.CALL); } @@ -143,7 +213,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder public void dispose() { - mCallEventListener = null; + mDecodeEventListener = null; mDecoderStateListener = null; mSquelchStateListener = null; } @@ -159,21 +229,11 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder return mInternalSourceEventListener; } - private boolean isStandardChannel() - { - return mChannelType == ChannelType.STANDARD; - } - - private boolean isTrafficChannel() - { - return mChannelType == ChannelType.TRAFFIC; - } - public void setStandardChannelTimeout(long milliseconds) { mStandardChannelFadeTimeout = milliseconds; - if(mChannelType == ChannelType.STANDARD) + if(mChannel.isStandardChannel()) { mFadeTimeout = mStandardChannelFadeTimeout; } @@ -183,7 +243,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder { mTrafficChannelFadeTimeout = milliseconds; - if(mChannelType == ChannelType.TRAFFIC) + if(mChannel.isTrafficChannel()) { mFadeTimeout = System.currentTimeMillis() + mTrafficChannelFadeTimeout; } @@ -209,7 +269,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder */ private void updateFadeTimeout() { - if(isTrafficChannel()) + if(mChannel.isTrafficChannel()) { mFadeTimeout = System.currentTimeMillis() + mTrafficChannelFadeTimeout; } @@ -225,7 +285,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder */ private void updateResetTimeout() { - if(isTrafficChannel()) + if(mChannel.isTrafficChannel()) { mEndTimeout = System.currentTimeMillis(); } @@ -298,26 +358,35 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder broadcast(SquelchState.SQUELCH); updateFadeTimeout(); mState = state; + mIdentifierCollection.update(DecoderStateIdentifier.ACTIVE); break; case CONTROL: //Don't allow traffic channels to be control channels, otherwise they can't transition to teardown - if(isStandardChannel()) + if(mChannel.isStandardChannel()) { broadcast(SquelchState.SQUELCH); updateFadeTimeout(); mState = state; + mIdentifierCollection.update(DecoderStateIdentifier.CONTROL); } break; case DATA: + broadcast(SquelchState.SQUELCH); + updateFadeTimeout(); + mState = state; + mIdentifierCollection.update(DecoderStateIdentifier.DATA); + break; case ENCRYPTED: broadcast(SquelchState.SQUELCH); updateFadeTimeout(); mState = state; + mIdentifierCollection.update(DecoderStateIdentifier.ENCRYPTED); break; case CALL: broadcast(SquelchState.UNSQUELCH); updateFadeTimeout(); mState = state; + mIdentifierCollection.update(DecoderStateIdentifier.CALL); break; case FADE: processFadeState(); @@ -330,12 +399,11 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder break; case RESET: mState = State.IDLE; + mIdentifierCollection.update(DecoderStateIdentifier.IDLE); break; default: break; } - - mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_STATE, mState)); } } @@ -351,14 +419,13 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder /** * This method is invoked if the source buffer provider goes into overflow state. Since this is an external state, * we use the mSourceOverflow variable to override the internal state reported to external listeners. + * * @param overflow true to indicate an overflow state */ @Override public void sourceOverflow(boolean overflow) { mSourceOverflow = overflow; - - mMutableMetadata.receive(new AttributeChangeRequest(Attribute.BUFFER_OVERFLOW, mSourceOverflow)); } /** @@ -380,9 +447,9 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder { updateResetTimeout(); mState = State.FADE; + mIdentifierCollection.update(DecoderStateIdentifier.FADE); broadcast(SquelchState.SQUELCH); - mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_STATE, mState)); } private void processIdleState() @@ -391,12 +458,11 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder if(mState == State.FADE) { - getMutableMetadata().resetTemporalAttributes(); broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE)); } mState = State.IDLE; - getMutableMetadata().receive(new AttributeChangeRequest(Attribute.CHANNEL_STATE, mState)); + mIdentifierCollection.update(DecoderStateIdentifier.IDLE); } private void processTeardownState() @@ -404,48 +470,58 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder broadcast(SquelchState.SQUELCH); mState = State.TEARDOWN; + mIdentifierCollection.update(DecoderStateIdentifier.TEARDOWN); - mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_STATE, mState)); - - if(mTrafficChannelEndListener != null && mTrafficChannelCallEvent != null) + if(mChannel.isTrafficChannel()) { - mTrafficChannelCallEvent.end(); - broadcast(mTrafficChannelCallEvent); - mTrafficChannelEndListener.callEnd(mTrafficChannelCallEvent.getChannel()); - mTrafficChannelEndListener = null; - mTrafficChannelCallEvent = null; + broadcast(new ChannelEvent(mChannel, ChannelEvent.Event.REQUEST_DISABLE)); } } /** * Broadcasts the call event to the registered listener */ - protected void broadcast(CallEvent event) + protected void broadcast(IDecodeEvent event) { - if(mCallEventListener != null) + if(mDecodeEventListener != null) { - mCallEventListener.receive(event); + mDecodeEventListener.receive(event); + } + } + + /** + * Broadcasts the channel event to a registered listener + */ + private void broadcast(ChannelEvent channelEvent) + { + if(mChannelEventListener != null) + { + mChannelEventListener.receive(channelEvent); } } @Override - public void addCallEventListener(Listener listener) + public void setChannelEventListener(Listener listener) { - mCallEventListener = listener; + mChannelEventListener = listener; } @Override - public void removeCallEventListener(Listener listener) + public void removeChannelEventListener() { - mCallEventListener = null; + mChannelEventListener = null; } - - //NEW: @Override - public Listener getAttributeChangeRequestListener() + public void addDecodeEventListener(Listener listener) { - return mMutableMetadata; + mDecodeEventListener = listener; + } + + @Override + public void removeDecodeEventListener(Listener listener) + { + mDecodeEventListener = null; } /** @@ -483,57 +559,6 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder return mDecoderStateEventReceiver; } - /** - * Registers a listener to be notified when a traffic channel call event is - * completed, so that the listener can perform call tear-down - */ - public void configureAsTrafficChannel(TrafficChannelManager manager, CallEvent callEvent) - { - mTrafficChannelEndListener = manager; - - mTrafficChannelCallEvent = callEvent; - - /* Broadcast the call event details as metadata for the audio manager */ - String channel = mTrafficChannelCallEvent.getChannel(); - - if(channel != null) - { - mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_FREQUENCY_LABEL, channel)); - } - - long frequency = mTrafficChannelCallEvent.getFrequency(); - - if(frequency > 0) - { - mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_FREQUENCY, frequency)); - } - - mMutableMetadata.receive(new AttributeChangeRequest(Attribute.PRIMARY_DECODER_TYPE, - mTrafficChannelCallEvent.getDecoderType())); - - mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_CONFIGURATION_NAME, "TRAFFIC")); - - String from = mTrafficChannelCallEvent.getFromID(); - - if(from != null) - { - mMutableMetadata.receive(new AttributeChangeRequest(Attribute.PRIMARY_ADDRESS_FROM, from, - mTrafficChannelCallEvent.getFromIDAlias())); - } - - String to = mTrafficChannelCallEvent.getToID(); - - if(to != null) - { - mMutableMetadata.receive(new AttributeChangeRequest(Attribute.PRIMARY_ADDRESS_TO, to, - mTrafficChannelCallEvent.getToIDAlias())); - } - - /* Rebroadcast the allocation event so that the internal decoder states - * can self-configure with the call event details */ - broadcast(mTrafficChannelCallEvent); - } - /** * Registers the listener to receive source events from the channel state */ @@ -572,7 +597,8 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder //in an active (ie sync locked) state. if(getState().isActiveState()) { - broadcast(SourceEvent.frequencyErrorMeasurementSyncLocked(sourceEvent.getValue().longValue(), mChannelType.name())); + broadcast(SourceEvent.frequencyErrorMeasurementSyncLocked(sourceEvent.getValue().longValue(), + mChannel.getChannelType().name())); } break; } @@ -598,7 +624,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder case CHANGE_CALL_TIMEOUT: if(event instanceof ChangeChannelTimeoutEvent) { - ChangeChannelTimeoutEvent timeout = (ChangeChannelTimeoutEvent) event; + ChangeChannelTimeoutEvent timeout = (ChangeChannelTimeoutEvent)event; if(timeout.getChannelType() == ChannelType.STANDARD) { @@ -618,7 +644,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder } break; case END: - if(isTrafficChannel()) + if(mChannel.isTrafficChannel()) { setState(State.TEARDOWN); } @@ -628,7 +654,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder } break; case RESET: - /* Channel State does not respond to reset events */ + /* Channel State does not respond to reset events */ break; default: break; @@ -660,7 +686,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder } else if(mState == State.FADE && mEndTimeout <= System.currentTimeMillis()) { - if(isTrafficChannel()) + if(mChannel.isTrafficChannel()) { processTeardownState(); } diff --git a/src/main/java/io/github/dsheirer/channel/state/DecoderState.java b/src/main/java/io/github/dsheirer/channel/state/DecoderState.java index 0ce9cfc2..8eead5af 100644 --- a/src/main/java/io/github/dsheirer/channel/state/DecoderState.java +++ b/src/main/java/io/github/dsheirer/channel/state/DecoderState.java @@ -1,15 +1,23 @@ package io.github.dsheirer.channel.state; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.channel.metadata.AttributeChangeRequest; -import io.github.dsheirer.channel.metadata.IAttributeChangeRequestProvider; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.IdentifierUpdateListener; +import io.github.dsheirer.identifier.IdentifierUpdateNotification; +import io.github.dsheirer.identifier.IdentifierUpdateProvider; +import io.github.dsheirer.identifier.MutableIdentifierCollection; +import io.github.dsheirer.identifier.configuration.ChannelDescriptorConfigurationIdentifier; +import io.github.dsheirer.identifier.configuration.DecoderTypeConfigurationIdentifier; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.IMessageListener; -import io.github.dsheirer.message.Message; import io.github.dsheirer.module.Module; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.event.ActivitySummaryProvider; import io.github.dsheirer.module.decode.event.CallEvent; -import io.github.dsheirer.module.decode.event.ICallEventProvider; +import io.github.dsheirer.module.decode.event.IDecodeEvent; +import io.github.dsheirer.module.decode.event.IDecodeEventProvider; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; @@ -19,47 +27,87 @@ import io.github.dsheirer.sample.Listener; * * Provides access to a textual activity summary of events observed. */ -public abstract class DecoderState extends Module implements ActivitySummaryProvider, Listener, - IAttributeChangeRequestProvider, ICallEventProvider, IDecoderStateEventListener, - IDecoderStateEventProvider, IMessageListener +public abstract class DecoderState extends Module implements ActivitySummaryProvider, Listener, + IDecodeEventProvider, IDecoderStateEventListener, IDecoderStateEventProvider, IMessageListener, + IdentifierUpdateProvider, IdentifierUpdateListener { +// private final static Logger mLog = LoggerFactory.getLogger(DecoderState.class); + protected String DIVIDER1 = "======================================================\n"; protected String DIVIDER2 = "------------------------------------------------------\n"; /* This has to be a broadcaster in order for references to persist */ - private Broadcaster mCallEventBroadcaster = new Broadcaster<>(); - private Broadcaster mAttributeChangeRequestBroadcaster = new Broadcaster<>(); + private Broadcaster mDecodeEventBroadcaster = new Broadcaster<>(); private Listener mDecoderStateListener; - private DecoderStateEventListener mDecoderStateEventListener = new DecoderStateEventListener(); + private MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection(); + private ConfigurationIdentifierListener mConfigurationIdentifierListener = new ConfigurationIdentifierListener(); + private IChannelDescriptor mCurrentChannel; protected CallEvent mCurrentCallEvent; - private AliasList mAliasList; - - public DecoderState(AliasList aliasList) + public DecoderState() { - mAliasList = aliasList; + mIdentifierCollection.update(new DecoderTypeConfigurationIdentifier(getDecoderType())); + } + + @Override + public void start() + { + //Broadcast the existing identifiers (as add events) so that they can be received by external listeners + mIdentifierCollection.broadcastIdentifiers(); } public abstract DecoderType getDecoderType(); - public AliasList getAliasList() + /** + * Current collection of identifiers managed by the decoder state. + */ + public MutableIdentifierCollection getIdentifierCollection() { - return mAliasList; + return mIdentifierCollection; } - public boolean hasAliasList() + /** + * Registers the listener to receive identifier update notifications + */ + @Override + public void setIdentifierUpdateListener(Listener listener) { - return mAliasList != null; + getIdentifierCollection().setIdentifierUpdateListener(listener); + } + + /** + * Removes the listener from receiving identifier update notifications + */ + @Override + public void removeIdentifierUpdateListener() + { + getIdentifierCollection().removeIdentifierUpdateListener(); } /** * Provides subclass reference to the call event broadcaster */ - protected Broadcaster getCallEventBroadcaster() + protected Broadcaster getDecodeEventBroadcaster() { - return mCallEventBroadcaster; + return mDecodeEventBroadcaster; + } + + + @Override + public Listener getMessageListener() + { + return this; + } + + /** + * Resets the current temporal state variables to end the current event and prepare for the next. + */ + protected void resetState() + { + mIdentifierCollection.remove(IdentifierClass.USER); + mCurrentChannel = null; } /** @@ -85,11 +133,9 @@ public abstract class DecoderState extends Module implements ActivitySummaryProv */ public void dispose() { - mCallEventBroadcaster.dispose(); - mCallEventBroadcaster = null; + mDecodeEventBroadcaster.dispose(); + mDecodeEventBroadcaster = null; mDecoderStateListener = null; - - mAliasList = null; } /** @@ -98,29 +144,29 @@ public abstract class DecoderState extends Module implements ActivitySummaryProv public abstract String getActivitySummary(); /** - * Broadcasts a call event to any registered listeners + * Broadcasts a decode event to any registered listeners */ - protected void broadcast(CallEvent event) + protected void broadcast(IDecodeEvent event) { - mCallEventBroadcaster.broadcast(event); + mDecodeEventBroadcaster.broadcast(event); } /** * Adds a call event listener */ @Override - public void addCallEventListener(Listener listener) + public void addDecodeEventListener(Listener listener) { - mCallEventBroadcaster.addListener(listener); + mDecodeEventBroadcaster.addListener(listener); } /** * Removes the call event listener */ @Override - public void removeCallEventListener(Listener listener) + public void removeDecodeEventListener(Listener listener) { - mCallEventBroadcaster.removeListener(listener); + mDecodeEventBroadcaster.removeListener(listener); } @Override @@ -167,43 +213,66 @@ public abstract class DecoderState extends Module implements ActivitySummaryProv mDecoderStateListener = null; } - @Override - public Listener getMessageListener() - { - return this; - } - /** - * Broadcasts the attribute change request to the registered listener - */ - protected void broadcast(AttributeChangeRequest request) - { - mAttributeChangeRequestBroadcaster.broadcast(request); - } - - /** - * Sets the listener to receive attribute change requests from this decoder state + * Listens for configuration identifiers and adds them to this decoder state's identifier collection. + * @return */ @Override - public void setAttributeChangeRequestListener(Listener listener) + public Listener getIdentifierUpdateListener() { - mAttributeChangeRequestBroadcaster.addListener(listener); + return mConfigurationIdentifierListener; } /** - * Removes any listener from receiving attribute change requests + * Configuration identifier listener. */ - @Override - public void removeAttributeChangeRequestListener(Listener listener) + public ConfigurationIdentifierListener getConfigurationIdentifierListener() { - mAttributeChangeRequestBroadcaster.removeListener(listener); + return mConfigurationIdentifierListener; } /** - * Broadaster so that Attribute monitors can broadcast attribute change requests + * Optional current channel descriptor */ - protected Listener getAttributeChangeRequestListener() + protected IChannelDescriptor getCurrentChannel() { - return mAttributeChangeRequestBroadcaster; + return mCurrentChannel; + } + + /** + * Sets the current channel descriptor + */ + protected void setCurrentChannel(IChannelDescriptor channel) + { + mCurrentChannel = channel; + } + + /** + * Listener for configuration type identifier updates sent from the channel state. Adds configuration + * identifiers to this decoder state so that decode events will contain configuration details in the + * event's identifier collection. + */ + public class ConfigurationIdentifierListener implements Listener + { + @Override + public void receive(IdentifierUpdateNotification identifierUpdateNotification) + { + if(identifierUpdateNotification.getIdentifier().getIdentifierClass() == IdentifierClass.CONFIGURATION && + identifierUpdateNotification.getIdentifier().getForm() != Form.DECODER_TYPE && + identifierUpdateNotification.getIdentifier().getForm() != Form.CHANNEL_DESCRIPTOR) + { + getIdentifierCollection().update(identifierUpdateNotification.getIdentifier()); + } + + if(identifierUpdateNotification.getOperation() == IdentifierUpdateNotification.Operation.ADD) + { + Identifier identifier = identifierUpdateNotification.getIdentifier(); + + if(identifier instanceof ChannelDescriptorConfigurationIdentifier) + { + mCurrentChannel = ((ChannelDescriptorConfigurationIdentifier)identifier).getValue(); + } + } + } } } diff --git a/src/main/java/io/github/dsheirer/channel/state/DecoderStateEvent.java b/src/main/java/io/github/dsheirer/channel/state/DecoderStateEvent.java index ad853865..90d868a1 100644 --- a/src/main/java/io/github/dsheirer/channel/state/DecoderStateEvent.java +++ b/src/main/java/io/github/dsheirer/channel/state/DecoderStateEvent.java @@ -62,7 +62,6 @@ public class DecoderStateEvent END, RESET, SOURCE_FREQUENCY, - START, - TRAFFIC_CHANNEL_ALLOCATION; + START; } } \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/channel/state/IDecoderStateEventProvider.java b/src/main/java/io/github/dsheirer/channel/state/IDecoderStateEventProvider.java index 0e387e2e..e0b27fe7 100644 --- a/src/main/java/io/github/dsheirer/channel/state/IDecoderStateEventProvider.java +++ b/src/main/java/io/github/dsheirer/channel/state/IDecoderStateEventProvider.java @@ -4,6 +4,6 @@ import io.github.dsheirer.sample.Listener; public interface IDecoderStateEventProvider { - public void setDecoderStateListener( Listener listener ); - public void removeDecoderStateListener(); + void setDecoderStateListener( Listener listener ); + void removeDecoderStateListener(); } diff --git a/src/main/java/io/github/dsheirer/channel/traffic/TrafficChannelAllocationEvent.java b/src/main/java/io/github/dsheirer/channel/traffic/TrafficChannelAllocationEvent.java deleted file mode 100644 index 935e60e9..00000000 --- a/src/main/java/io/github/dsheirer/channel/traffic/TrafficChannelAllocationEvent.java +++ /dev/null @@ -1,45 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.channel.traffic; - -import io.github.dsheirer.channel.state.DecoderStateEvent; -import io.github.dsheirer.channel.state.State; -import io.github.dsheirer.module.decode.event.CallEvent; - -/** - * Traffic channel allocation event wraps a call event indicating the channel - * and frequency for a traffic channel allocation event, so that the traffic - * channel manager can respond and allocate a decoder channel and source if - * available, or change the wrapped call event to a call detect event and log it. - */ -public class TrafficChannelAllocationEvent extends DecoderStateEvent -{ - private CallEvent mCallEvent; - - public TrafficChannelAllocationEvent( Object source, CallEvent callEvent ) - { - super( source, Event.TRAFFIC_CHANNEL_ALLOCATION, State.CALL ); - mCallEvent = callEvent; - } - - public CallEvent getCallEvent() - { - return mCallEvent; - } -} diff --git a/src/main/java/io/github/dsheirer/channel/traffic/TrafficChannelManager.java b/src/main/java/io/github/dsheirer/channel/traffic/TrafficChannelManager.java deleted file mode 100644 index b81b2869..00000000 --- a/src/main/java/io/github/dsheirer/channel/traffic/TrafficChannelManager.java +++ /dev/null @@ -1,391 +0,0 @@ -/******************************************************************************* - * sdr-trunk - * 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 io.github.dsheirer.channel.traffic; - -import io.github.dsheirer.channel.state.DecoderStateEvent; -import io.github.dsheirer.channel.state.IDecoderStateEventListener; -import io.github.dsheirer.controller.channel.Channel; -import io.github.dsheirer.controller.channel.Channel.ChannelType; -import io.github.dsheirer.controller.channel.ChannelEvent; -import io.github.dsheirer.controller.channel.ChannelEvent.Event; -import io.github.dsheirer.controller.channel.ChannelModel; -import io.github.dsheirer.controller.channel.TrafficChannelEvent; -import io.github.dsheirer.module.Module; -import io.github.dsheirer.module.decode.config.DecodeConfiguration; -import io.github.dsheirer.module.decode.event.CallEvent; -import io.github.dsheirer.module.decode.event.CallEvent.CallEventType; -import io.github.dsheirer.module.decode.event.ICallEventProvider; -import io.github.dsheirer.record.config.RecordConfiguration; -import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.source.config.SourceConfigTuner; -import io.github.dsheirer.source.tuner.channel.TunerChannel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class TrafficChannelManager extends Module implements ICallEventProvider, IDecoderStateEventListener -{ - private final static Logger mLog = LoggerFactory.getLogger(TrafficChannelManager.class); - public static final String CHANNEL_START_REJECTED = "CHANNEL START REJECTED"; - public static final String NO_TUNER_AVAILABLE = "NO TUNER AVAILABLE"; - public static final String UNKNOWN_FREQUENCY = "UNKNOWN FREQUENCY"; - - private int mTrafficChannelPoolMaximumSize = DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_DEFAULT; - private List mTrafficChannelPool = new ArrayList(); - private Map mTrafficChannelsInUse = new ConcurrentHashMap(); - - private DecoderStateEventListener mEventListener = new DecoderStateEventListener(); - private Listener mCallEventListener; - - private ChannelModel mChannelModel; - private DecodeConfiguration mDecodeConfiguration; - private RecordConfiguration mRecordConfiguration; - private String mSystem; - private String mSite; - private String mAliasListName; - - /** - * Monitors call events and allocates traffic decoder channels in response - * to traffic channel allocation call events. Manages a pool of reusable - * traffic channel allocations. - * - * @param channelModel containing channels currently in use - * @param decodeConfiguration - decoder configuration to use for each - * traffic channel allocation. - * @param recordConfiguration - recording options for each traffic channel - * @param system label to use to describe the system - * @param site label to use to describe the site - * @param aliasListName designated for the channel - * @param trafficChannelPoolSize - maximum number of allocated traffic channels - * in the pool - */ - public TrafficChannelManager(ChannelModel channelModel, - DecodeConfiguration decodeConfiguration, - RecordConfiguration recordConfiguration, - String system, - String site, - String aliasListName, - int trafficChannelPoolSize) - { - mChannelModel = channelModel; - mDecodeConfiguration = decodeConfiguration; - mRecordConfiguration = recordConfiguration; - mSystem = system; - mSite = site; - mAliasListName = aliasListName; - mTrafficChannelPoolMaximumSize = trafficChannelPoolSize; - } - - @Override - public void dispose() - { - for(Channel trafficChannel : mTrafficChannelPool) - { - mChannelModel.broadcast(new ChannelEvent(trafficChannel, Event.REQUEST_DISABLE)); - } - - mTrafficChannelPool.clear(); - - mTrafficChannelsInUse.clear(); - - mCallEventListener = null; - mDecodeConfiguration = null; - } - - /** - * Provides a channel by either reusing an existing channel or constructing - * a new one, limited by the total number of constructed channels allowed. - * - * Note: you must enforce thread safety on the mTrafficChannelsInUse - * external to this method. - * - * @param channelNumber - * @param tunerChannel - * @return - */ - private Channel getChannel(String channelNumber, TunerChannel tunerChannel) - { - Channel channel = null; - - if(!mTrafficChannelsInUse.containsKey(channelNumber)) - { - for(Channel configuredChannel : mTrafficChannelPool) - { - if(!configuredChannel.isProcessing()) - { - channel = configuredChannel; - break; - } - } - - if(channel == null && mTrafficChannelPool.size() < mTrafficChannelPoolMaximumSize) - { - channel = new Channel("Traffic", ChannelType.TRAFFIC); - - channel.setDecodeConfiguration(mDecodeConfiguration); - - channel.setRecordConfiguration(mRecordConfiguration); - - channel.setAliasListName(mAliasListName); - - mChannelModel.addChannel(channel); - - mTrafficChannelPool.add(channel); - } - - /* If we have a configured channel, update metadata */ - if(channel != null) - { - channel.setSourceConfiguration(new SourceConfigTuner(tunerChannel)); - channel.setSystem(mSystem); - channel.setSite(mSite); - channel.setName(channelNumber); - - mChannelModel.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_CONFIGURATION_CHANGE)); - } - } - - return channel; - } - - /** - * Processes the event and creates a traffic channel is resources are - * available - */ - private void process(TrafficChannelAllocationEvent event) - { - CallEvent callEvent = event.getCallEvent(); - - /* Check for duplicate events and suppress */ - synchronized(mTrafficChannelsInUse) - { - if(mTrafficChannelsInUse.containsKey(callEvent.getChannel())) - { - return; - } - - long frequency = callEvent.getFrequency(); - - if(frequency > 0) - { - Channel channel = getChannel(callEvent.getChannel(), new TunerChannel(frequency, - mDecodeConfiguration.getChannelSpecification().getBandwidth())); - - if(channel != null) - { - TrafficChannelEvent trafficChannelEvent = - new TrafficChannelEvent(this, channel, Event.REQUEST_ENABLE, callEvent); - - //Request to enable the channel - mChannelModel.broadcast(trafficChannelEvent); - - if(channel.isProcessing()) - { - mTrafficChannelsInUse.put(callEvent.getChannel(), channel); - } - else - { - callEvent.setCallEventType(CallEventType.CALL_DETECT); - - String details = callEvent.getDetails(); - - if(details == null || details.isEmpty()) - { - callEvent.setDetails(CHANNEL_START_REJECTED); - } - else if(!details.contains(CHANNEL_START_REJECTED)) - { - callEvent.setDetails(new StringBuilder(CHANNEL_START_REJECTED).append(" : ") - .append(callEvent.getDetails()).toString()); - } - } - } - else - { - callEvent.setCallEventType(CallEventType.CALL_DETECT); - - String details = callEvent.getDetails(); - - if(details == null || details.isEmpty()) - { - callEvent.setDetails(NO_TUNER_AVAILABLE); - } - else if(!details.contains(NO_TUNER_AVAILABLE)) - { - callEvent.setDetails(new StringBuilder(NO_TUNER_AVAILABLE).append(" : ") - .append(callEvent.getDetails()).toString()); - } - } - } - else - { - callEvent.setCallEventType(CallEventType.CALL_DETECT); - - String details = callEvent.getDetails(); - - if(details == null || details.isEmpty()) - { - callEvent.setDetails(UNKNOWN_FREQUENCY); - } - else if(!details.contains(UNKNOWN_FREQUENCY)) - { - callEvent.setDetails(new StringBuilder(UNKNOWN_FREQUENCY).append(" : ") - .append(callEvent.getDetails()).toString()); - } - } - - final Listener listener = mCallEventListener; - - if(listener != null) - { - listener.receive(callEvent); - } - } - } - - /** - * Compares the call type, channel and to fields for equivalence and the - * from field for either both null, or equivalence. - * - * @param e1 - * @param e2 - * @return - */ - public static boolean isSameCallEvent(CallEvent e1, CallEvent e2) - { - if(e1 == null || e2 == null) - { - return false; - } - - if(e1.getCallEventType() != e2.getCallEventType()) - { - return false; - } - - if(e1.getChannel() == null || e2.getChannel() == null || !e1.getChannel().contentEquals(e2.getChannel())) - { - return false; - } - - if(e1.getToID() == null || e2.getToID() == null || !e1.getToID().contentEquals(e2.getToID())) - { - return false; - } - - if(e1.getFromID() == null || e2.getFromID() == null) - { - return (e1.getFromID() == null && e2.getFromID() == null); - } - else if(e1.getFromID().contentEquals(e2.getFromID())) - { - return true; - } - - return false; - } - - @Override - public Listener getDecoderStateListener() - { - return mEventListener; - } - - @Override - public void addCallEventListener(Listener listener) - { - mCallEventListener = listener; - } - - @Override - public void removeCallEventListener(Listener listener) - { - mCallEventListener = null; - } - - @Override - public void reset() - { - } - - @Override - public void start() - { - } - - @Override - public void stop() - { - if(!mTrafficChannelsInUse.isEmpty()) - { - List channels = new ArrayList<>(); - - /* Copy the keyset so we don't get concurrent modification of the map */ - channels.addAll(mTrafficChannelsInUse.keySet()); - - for(String channel : channels) - { - callEnd(channel); - } - } - } - - /** - * Callback used by the TrafficChannelStatusListener class to signal the - * end of a an allocated traffic channel call event - * - * @param channelNumber - channel number from the call event that signaled - * the start of a traffic channel allocation - */ - public void callEnd(String channelNumber) - { - synchronized(mTrafficChannelsInUse) - { - if(channelNumber != null && mTrafficChannelsInUse.containsKey(channelNumber)) - { - Channel channel = mTrafficChannelsInUse.get(channelNumber); - - mChannelModel.broadcast(new ChannelEvent(channel, Event.REQUEST_DISABLE)); - - mTrafficChannelsInUse.remove(channelNumber); - } - } - } - - /** - * Wrapper class for the decoder state event listener interface to catch - * traffic channel allocation requests - */ - public class DecoderStateEventListener implements Listener - { - @Override - public void receive(DecoderStateEvent event) - { - switch(event.getEvent()) - { - case TRAFFIC_CHANNEL_ALLOCATION: - process((TrafficChannelAllocationEvent) event); - break; - default: - break; - } - } - } -} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/controller/ControllerPanel.java b/src/main/java/io/github/dsheirer/controller/ControllerPanel.java index 95bcd3d3..64f0eb32 100644 --- a/src/main/java/io/github/dsheirer/controller/ControllerPanel.java +++ b/src/main/java/io/github/dsheirer/controller/ControllerPanel.java @@ -21,10 +21,10 @@ package io.github.dsheirer.controller; import com.jidesoft.swing.JideTabbedPane; import io.github.dsheirer.alias.AliasController; import io.github.dsheirer.alias.AliasModel; -import io.github.dsheirer.audio.AudioManager; -import io.github.dsheirer.audio.AudioPanel; import io.github.dsheirer.audio.broadcast.BroadcastModel; import io.github.dsheirer.audio.broadcast.BroadcastPanel; +import io.github.dsheirer.audio.playback.AudioPanel; +import io.github.dsheirer.audio.playback.AudioPlaybackManager; import io.github.dsheirer.channel.metadata.ChannelMetadataViewer; import io.github.dsheirer.controller.channel.ChannelController; import io.github.dsheirer.controller.channel.ChannelModel; @@ -33,14 +33,17 @@ import io.github.dsheirer.controller.channel.map.ChannelMapModel; import io.github.dsheirer.icon.IconManager; import io.github.dsheirer.map.MapPanel; import io.github.dsheirer.map.MapService; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.settings.SettingsManager; import io.github.dsheirer.source.SourceManager; import io.github.dsheirer.source.tuner.TunerModel; import io.github.dsheirer.source.tuner.TunerViewPanel; import net.miginfocom.swing.MigLayout; -import javax.swing.*; -import java.awt.*; +import javax.swing.JPanel; +import javax.swing.JTable; +import java.awt.Color; +import java.awt.Dimension; public class ControllerPanel extends JPanel { @@ -59,7 +62,7 @@ public class ControllerPanel extends JPanel private JideTabbedPane mTabbedPane; protected JTable mChannelActivityTable = new JTable(); - public ControllerPanel(AudioManager audioManager, + public ControllerPanel(AudioPlaybackManager audioPlaybackManager, AliasModel aliasModel, BroadcastModel broadcastModel, ChannelModel channelModel, @@ -69,20 +72,24 @@ public class ControllerPanel extends JPanel MapService mapService, SettingsManager settingsManager, SourceManager sourceManager, - TunerModel tunerModel) + TunerModel tunerModel, + UserPreferences userPreferences) { mBroadcastModel = broadcastModel; mChannelModel = channelModel; - mAudioPanel = new AudioPanel(iconManager, settingsManager, sourceManager, audioManager); + mAudioPanel = new AudioPanel(iconManager, userPreferences, settingsManager, sourceManager, audioPlaybackManager, + aliasModel); - mChannelMetadataViewer = new ChannelMetadataViewer(channelProcessingManager, iconManager, settingsManager); + mChannelMetadataViewer = new ChannelMetadataViewer(channelModel, channelProcessingManager, iconManager, + aliasModel, userPreferences); - mMapPanel = new MapPanel(mapService, iconManager, settingsManager); + mMapPanel = new MapPanel(mapService, aliasModel, iconManager, settingsManager); mBroadcastPanel = new BroadcastPanel(broadcastModel, aliasModel, iconManager); - mChannelController = new ChannelController(channelModel, channelMapModel, sourceManager, aliasModel); + mChannelController = new ChannelController(channelModel, channelProcessingManager, channelMapModel, + sourceManager, aliasModel); mAliasController = new AliasController(aliasModel, broadcastModel, iconManager); diff --git a/src/main/java/io/github/dsheirer/controller/channel/Channel.java b/src/main/java/io/github/dsheirer/controller/channel/Channel.java index df432101..59f41908 100644 --- a/src/main/java/io/github/dsheirer/controller/channel/Channel.java +++ b/src/main/java/io/github/dsheirer/controller/channel/Channel.java @@ -1,4 +1,5 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk * Copyright (C) 2014-2018 Dennis Sheirer * @@ -14,8 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.controller.channel; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -38,10 +39,16 @@ import io.github.dsheirer.source.config.SourceConfigRecording; import io.github.dsheirer.source.config.SourceConfigTuner; import io.github.dsheirer.source.config.SourceConfiguration; import io.github.dsheirer.source.tuner.channel.TunerChannel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Objects; @JacksonXmlRootElement(localName = "channel") public class Channel extends Configuration implements Listener { + private final static Logger mLog = LoggerFactory.getLogger(Channel.class); + // Standard channels are persisted and traffic channels are temporary public enum ChannelType { @@ -189,6 +196,18 @@ public class Channel extends Configuration implements Listener return mChannelType; } + @JsonIgnore + public boolean isTrafficChannel() + { + return mChannelType == ChannelType.TRAFFIC; + } + + @JsonIgnore + public boolean isStandardChannel() + { + return mChannelType == ChannelType.STANDARD; + } + /** * Returns the owning system for this channel. */ @@ -365,6 +384,14 @@ public class Channel extends Configuration implements Listener mAliasListName = name; } + /** + * Indicates if this channel has a non-null, non-empty alias list name specified. + */ + public boolean hasAliasList() + { + return mAliasListName != null && !mAliasListName.isEmpty(); + } + /** * Gets the primary decoder configuration used by this channel */ @@ -547,4 +574,25 @@ public class Channel extends Configuration implements Listener { mChannelFrequencyCorrection = 0; } + + @Override + public boolean equals(Object o) + { + if(this == o) + { + return true; + } + if(o == null || getClass() != o.getClass()) + { + return false; + } + Channel channel = (Channel)o; + return getChannelID() == channel.getChannelID(); + } + + @Override + public int hashCode() + { + return Objects.hash(getChannelID()); + } } diff --git a/src/main/java/io/github/dsheirer/controller/channel/ChannelAutoStartFrame.java b/src/main/java/io/github/dsheirer/controller/channel/ChannelAutoStartFrame.java index 92dbc945..5971a1ca 100644 --- a/src/main/java/io/github/dsheirer/controller/channel/ChannelAutoStartFrame.java +++ b/src/main/java/io/github/dsheirer/controller/channel/ChannelAutoStartFrame.java @@ -1,20 +1,25 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.controller.channel; +import io.github.dsheirer.sample.Listener; import io.github.dsheirer.util.ThreadPool; import net.miginfocom.swing.MigLayout; import org.slf4j.Logger; @@ -42,7 +47,7 @@ public class ChannelAutoStartFrame extends JFrame private final static Logger mLog = LoggerFactory.getLogger(ChannelAutoStartFrame.class); private static final int COUNTDOWN_SECONDS = 10; - private ChannelEventListener mChannelEventListener; + private Listener mChannelEventListener; private List mChannels; private JLabel mCountdownLabel; @@ -61,27 +66,22 @@ public class ChannelAutoStartFrame extends JFrame * @param listener to receive channel start/enable request(s) * @param channels to auto-start */ - public ChannelAutoStartFrame(ChannelEventListener listener, List channels) + public ChannelAutoStartFrame(Listener listener, List channels) { mChannelEventListener = listener; mChannels = channels; init(); - EventQueue.invokeLater(new Runnable() - { - @Override - public void run() - { - setVisible(true); - startTimer(); - } + EventQueue.invokeLater(() -> { + setVisible(true); + startTimer(); }); } private void init() { setTitle("Auto-Start Channels"); - setSize(new Dimension(400,300)); + setSize(new Dimension(400, 300)); setLocationRelativeTo(null); addWindowListener(new WindowAdapter() { @@ -95,7 +95,7 @@ public class ChannelAutoStartFrame extends JFrame }); JPanel panel = new JPanel(); - panel.setLayout(new MigLayout("","[grow,fill][grow,fill]", + panel.setLayout(new MigLayout("", "[grow,fill][grow,fill]", "[][][grow,fill][]")); panel.add(new JLabel("The following channels will be automatically"), "span"); @@ -138,6 +138,7 @@ public class ChannelAutoStartFrame extends JFrame /** * Updates the countdown text label on the swing event thread + * * @param value for the countdown */ private void updateCountdownText(final int value) @@ -180,9 +181,9 @@ public class ChannelAutoStartFrame extends JFrame { if(mChannelEventListener != null) { - for(Channel channel: mChannels) + for(Channel channel : mChannels) { - mChannelEventListener.channelChanged(new ChannelEvent(channel, ChannelEvent.Event.REQUEST_ENABLE)); + mChannelEventListener.receive(new ChannelEvent(channel, ChannelEvent.Event.REQUEST_ENABLE)); } } } @@ -210,14 +211,7 @@ public class ChannelAutoStartFrame extends JFrame mTimerFuture = null; } - EventQueue.invokeLater(new Runnable() - { - @Override - public void run() - { - dispose(); - } - }); + EventQueue.invokeLater(() -> dispose()); } /** diff --git a/src/main/java/io/github/dsheirer/controller/channel/ChannelController.java b/src/main/java/io/github/dsheirer/controller/channel/ChannelController.java index 06d1becb..9963ea7f 100644 --- a/src/main/java/io/github/dsheirer/controller/channel/ChannelController.java +++ b/src/main/java/io/github/dsheirer/controller/channel/ChannelController.java @@ -41,254 +41,257 @@ import java.awt.event.ActionListener; public class ChannelController extends JPanel implements ActionListener, ListSelectionListener { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - private ChannelModel mChannelModel; - private JTable mChannelTable; - private TableFilterHeader mTableFilterHeader; - private ChannelEditor mEditor; - - private static final String NEW_CHANNEL = "New"; - private static final String COPY_CHANNEL = "Copy"; - private static final String DELETE_CHANNEL = "Delete"; - - private JButton mNewChannelButton = new JButton( NEW_CHANNEL ); - private JButton mCopyChannelButton = new JButton( COPY_CHANNEL ); - private JButton mDeleteChannelButton = new JButton( DELETE_CHANNEL ); + private ChannelModel mChannelModel; + private ChannelProcessingManager mChannelProcessingManager; + private JTable mChannelTable; + private TableFilterHeader mTableFilterHeader; + private ChannelEditor mEditor; - public ChannelController( ChannelModel channelModel, - ChannelMapModel channelMapModel, - SourceManager sourceManager, - AliasModel aliasModel ) - { - mChannelModel = channelModel; + private static final String NEW_CHANNEL = "New"; + private static final String COPY_CHANNEL = "Copy"; + private static final String DELETE_CHANNEL = "Delete"; - mEditor = new ChannelEditor( channelModel, channelMapModel, sourceManager, aliasModel ); - mChannelModel.addListener( mEditor ); + private JButton mNewChannelButton = new JButton(NEW_CHANNEL); + private JButton mCopyChannelButton = new JButton(COPY_CHANNEL); + private JButton mDeleteChannelButton = new JButton(DELETE_CHANNEL); - init(); - } - - private void init() - { - setLayout( new MigLayout( "", "[grow,fill]", "[grow,fill]") ); + public ChannelController(ChannelModel channelModel, + ChannelProcessingManager channelProcessingManager, + ChannelMapModel channelMapModel, + SourceManager sourceManager, + AliasModel aliasModel) + { + mChannelModel = channelModel; + mChannelProcessingManager = channelProcessingManager; + mEditor = new ChannelEditor(channelModel, channelProcessingManager, channelMapModel, sourceManager, aliasModel); + mChannelModel.addListener(mEditor); + mChannelProcessingManager.addChannelEventListener(mEditor); - //System Configuration View and Editor - mChannelTable = new JTable( mChannelModel ); - mChannelTable.setDefaultRenderer( String.class, new ChannelTableCellRenderer() ); - mChannelTable.setSelectionMode( ListSelectionModel.SINGLE_SELECTION ); - mChannelTable.getSelectionModel().addListSelectionListener( this ); - mChannelTable.setAutoCreateRowSorter( true ); - - mTableFilterHeader = new TableFilterHeader( mChannelTable, AutoChoices.ENABLED ); - mTableFilterHeader.setFilterOnUpdates( true ); - - JScrollPane channelScroller = new JScrollPane( mChannelTable ); + init(); + } - JPanel buttonsPanel = new JPanel(); - - buttonsPanel.setLayout( - new MigLayout( "insets 0 0 0 0", "[grow,fill][grow,fill][grow,fill]", "[]") ); + private void init() + { + setLayout(new MigLayout("", "[grow,fill]", "[grow,fill]")); - mNewChannelButton.addActionListener( this ); - mNewChannelButton.setToolTipText( "Create a new channel configuration" ); - buttonsPanel.add( mNewChannelButton ); - - mCopyChannelButton.addActionListener( this ); - mCopyChannelButton.setEnabled( false ); - mCopyChannelButton.setToolTipText( "Create a copy of the currently selected channel configuration" ); - buttonsPanel.add( mCopyChannelButton ); + //System Configuration View and Editor + mChannelTable = new JTable(mChannelModel); + mChannelTable.setDefaultRenderer(String.class, new ChannelTableCellRenderer()); + mChannelTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + mChannelTable.getSelectionModel().addListSelectionListener(this); + mChannelTable.setAutoCreateRowSorter(true); - mDeleteChannelButton.addActionListener( this ); - mDeleteChannelButton.setEnabled( false ); - mDeleteChannelButton.setToolTipText( "Delete the currently selected channel configuration" ); - buttonsPanel.add( mDeleteChannelButton ); - - JPanel listAndButtonsPanel = new JPanel(); + mTableFilterHeader = new TableFilterHeader(mChannelTable, AutoChoices.ENABLED); + mTableFilterHeader.setFilterOnUpdates(true); - listAndButtonsPanel.setLayout( - new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[grow,fill][]") ); + JScrollPane channelScroller = new JScrollPane(mChannelTable); - listAndButtonsPanel.add( channelScroller, "wrap" ); - listAndButtonsPanel.add( buttonsPanel ); - - JideSplitPane splitPane = new JideSplitPane( JideSplitPane.VERTICAL_SPLIT ); - splitPane.setDividerSize( 5 ); - splitPane.setShowGripper( true ); + JPanel buttonsPanel = new JPanel(); - //Shrink this guy so that he'll eventually be expanded to the scroller preferred size - listAndButtonsPanel.setPreferredSize( new Dimension( 10, 10 ) ); + buttonsPanel.setLayout( + new MigLayout("insets 0 0 0 0", "[grow,fill][grow,fill][grow,fill]", "[]")); - //Attempt to get a 60:40 vertical sizing preference - JScrollPane listScroller = new JScrollPane( listAndButtonsPanel ); - listScroller.setPreferredSize( new Dimension( 800, 58 ) ); - - JScrollPane editorScroller = new JScrollPane( mEditor ); - editorScroller.setPreferredSize( new Dimension( 800, 42 ) ); + mNewChannelButton.addActionListener(this); + mNewChannelButton.setToolTipText("Create a new channel configuration"); + buttonsPanel.add(mNewChannelButton); - splitPane.add( listScroller ); - splitPane.add( editorScroller ); + mCopyChannelButton.addActionListener(this); + mCopyChannelButton.setEnabled(false); + mCopyChannelButton.setToolTipText("Create a copy of the currently selected channel configuration"); + buttonsPanel.add(mCopyChannelButton); - add( splitPane ); - } + mDeleteChannelButton.addActionListener(this); + mDeleteChannelButton.setEnabled(false); + mDeleteChannelButton.setToolTipText("Delete the currently selected channel configuration"); + buttonsPanel.add(mDeleteChannelButton); - @Override - public void valueChanged( ListSelectionEvent event ) - { - //This limits event firing to only when selection is complete - if( !event.getValueIsAdjusting() ) - { - int selectedRow = mChannelTable.getSelectedRow(); - - if( selectedRow != -1 ) - { - int index = mChannelTable.convertRowIndexToModel( mChannelTable.getSelectedRow() ); - - Channel channel = mChannelModel.getChannelAtIndex( index ); - - if( channel != null ) - { - mEditor.setItem( channel ); - mCopyChannelButton.setEnabled( true ); - mDeleteChannelButton.setEnabled( true ); - } - } - else - { - mEditor.setItem( null ); - mCopyChannelButton.setEnabled( false ); - mDeleteChannelButton.setEnabled( false ); - } - } - } + JPanel listAndButtonsPanel = new JPanel(); - /** - * Adds the channel to the channel model/table and scrolls to view it - */ - private void addChannel( Channel channel ) - { - //HACK: when inserting a row to the model, the JTable gets - //notified and attempts to tell the coderazzi table filter - //adaptive choices filter to refresh before the table filter is - //notified of the row additions, causing an index out of bounds - //exception. We turn off adaptive choices temporarily, add the - //channel, and turn on adaptive choices again. - mTableFilterHeader.setAdaptiveChoices( false ); - - int index = mChannelModel.addChannel( channel ); + listAndButtonsPanel.setLayout( + new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill][]")); - mTableFilterHeader.setAdaptiveChoices( true ); + listAndButtonsPanel.add(channelScroller, "wrap"); + listAndButtonsPanel.add(buttonsPanel); - if( index >= 0 ) - { - int translatedIndex = mChannelTable.convertRowIndexToView( index ); + JideSplitPane splitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT); + splitPane.setDividerSize(5); + splitPane.setShowGripper(true); - if(translatedIndex >= 0) - { - mChannelTable.setRowSelectionInterval( translatedIndex, translatedIndex ); - mChannelTable.scrollRectToVisible( - new Rectangle( mChannelTable.getCellRect( translatedIndex, 0, true ) ) ); - } - } - } - - private Channel getSelectedChannel() - { - int index = mChannelTable.getSelectedRow(); - - if( index >= 0 ) - { - return mChannelModel.getChannelAtIndex( - mChannelTable.convertRowIndexToModel( index ) ); - } - - return null; - } + //Shrink this guy so that he'll eventually be expanded to the scroller preferred size + listAndButtonsPanel.setPreferredSize(new Dimension(10, 10)); - /** - * Responds to New, Copy and Delete Channel button invocations - */ - @Override - public void actionPerformed( ActionEvent event ) - { - switch( event.getActionCommand() ) - { - case NEW_CHANNEL: - addChannel( new Channel( "New Channel" ) ); - break; - case COPY_CHANNEL: - Channel selected = getSelectedChannel(); - - if( selected != null ) - { - addChannel( selected.copyOf() ); - } - break; - case DELETE_CHANNEL: - Channel toDelete = getSelectedChannel(); + //Attempt to get a 60:40 vertical sizing preference + JScrollPane listScroller = new JScrollPane(listAndButtonsPanel); + listScroller.setPreferredSize(new Dimension(800, 58)); - if( toDelete != null ) - { - int choice = JOptionPane.showConfirmDialog( ChannelController.this, - "Do you want to delete this channel?", "Delete Channel?", - JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE ); - - if( choice == JOptionPane.YES_OPTION ) - { - mChannelModel.removeChannel( toDelete ); - } - } - break; - default: - break; - } - } - - public class ChannelTableCellRenderer extends DefaultTableCellRenderer - { - private static final long serialVersionUID = 1L; + JScrollPane editorScroller = new JScrollPane(mEditor); + editorScroller.setPreferredSize(new Dimension(800, 42)); - @Override - public Component getTableCellRendererComponent( JTable table, - Object value, boolean isSelected, boolean hasFocus, int row, - int column ) - { - Component component = super.getTableCellRendererComponent( - table, value, isSelected, hasFocus, row, column ); + splitPane.add(listScroller); + splitPane.add(editorScroller); - ChannelModel model = (ChannelModel)table.getModel(); + add(splitPane); + } - int index = table.convertRowIndexToModel( row ); - - Channel channel = model.getChannelAtIndex( index ); - - boolean enabled = channel.isProcessing(); - - if( isSelected ) - { - if( enabled ) - { - component.setBackground( Color.YELLOW ); - } - else - { - component.setBackground( table.getSelectionBackground() ); - } - } - else - { - if( enabled ) - { - component.setBackground( Color.GREEN ); - } - else - { - component.setBackground( table.getBackground() ); - } - } - - return component; - } - - } + @Override + public void valueChanged(ListSelectionEvent event) + { + //This limits event firing to only when selection is complete + if(!event.getValueIsAdjusting()) + { + int selectedRow = mChannelTable.getSelectedRow(); + + if(selectedRow != -1) + { + int index = mChannelTable.convertRowIndexToModel(mChannelTable.getSelectedRow()); + + Channel channel = mChannelModel.getChannelAtIndex(index); + + if(channel != null) + { + mEditor.setItem(channel); + mCopyChannelButton.setEnabled(true); + mDeleteChannelButton.setEnabled(true); + } + } + else + { + mEditor.setItem(null); + mCopyChannelButton.setEnabled(false); + mDeleteChannelButton.setEnabled(false); + } + } + } + + /** + * Adds the channel to the channel model/table and scrolls to view it + */ + private void addChannel(Channel channel) + { + //HACK: when inserting a row to the model, the JTable gets + //notified and attempts to tell the coderazzi table filter + //adaptive choices filter to refresh before the table filter is + //notified of the row additions, causing an index out of bounds + //exception. We turn off adaptive choices temporarily, add the + //channel, and turn on adaptive choices again. + mTableFilterHeader.setAdaptiveChoices(false); + + int index = mChannelModel.addChannel(channel); + + mTableFilterHeader.setAdaptiveChoices(true); + + if(index >= 0) + { + int translatedIndex = mChannelTable.convertRowIndexToView(index); + + if(translatedIndex >= 0) + { + mChannelTable.setRowSelectionInterval(translatedIndex, translatedIndex); + mChannelTable.scrollRectToVisible( + new Rectangle(mChannelTable.getCellRect(translatedIndex, 0, true))); + } + } + } + + private Channel getSelectedChannel() + { + int index = mChannelTable.getSelectedRow(); + + if(index >= 0) + { + return mChannelModel.getChannelAtIndex( + mChannelTable.convertRowIndexToModel(index)); + } + + return null; + } + + /** + * Responds to New, Copy and Delete Channel button invocations + */ + @Override + public void actionPerformed(ActionEvent event) + { + switch(event.getActionCommand()) + { + case NEW_CHANNEL: + addChannel(new Channel("New Channel")); + break; + case COPY_CHANNEL: + Channel selected = getSelectedChannel(); + + if(selected != null) + { + addChannel(selected.copyOf()); + } + break; + case DELETE_CHANNEL: + Channel toDelete = getSelectedChannel(); + + if(toDelete != null) + { + int choice = JOptionPane.showConfirmDialog(ChannelController.this, + "Do you want to delete this channel?", "Delete Channel?", + JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); + + if(choice == JOptionPane.YES_OPTION) + { + mChannelModel.removeChannel(toDelete); + } + } + break; + default: + break; + } + } + + public class ChannelTableCellRenderer extends DefaultTableCellRenderer + { + private static final long serialVersionUID = 1L; + + @Override + public Component getTableCellRendererComponent(JTable table, + Object value, boolean isSelected, boolean hasFocus, int row, + int column) + { + Component component = super.getTableCellRendererComponent( + table, value, isSelected, hasFocus, row, column); + + ChannelModel model = (ChannelModel)table.getModel(); + + int index = table.convertRowIndexToModel(row); + + Channel channel = model.getChannelAtIndex(index); + + boolean enabled = channel.isProcessing(); + + if(isSelected) + { + if(enabled) + { + component.setBackground(Color.YELLOW); + } + else + { + component.setBackground(table.getSelectionBackground()); + } + } + else + { + if(enabled) + { + component.setBackground(Color.GREEN); + } + else + { + component.setBackground(table.getBackground()); + } + } + + return component; + } + + } } diff --git a/src/main/java/io/github/dsheirer/controller/channel/ChannelEditor.java b/src/main/java/io/github/dsheirer/controller/channel/ChannelEditor.java index 65d2cade..7fa551c4 100644 --- a/src/main/java/io/github/dsheirer/controller/channel/ChannelEditor.java +++ b/src/main/java/io/github/dsheirer/controller/channel/ChannelEditor.java @@ -1,20 +1,22 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 Dennis Sheirer +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 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. + * 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 - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.controller.channel; import com.jidesoft.swing.JideTabbedPane; @@ -26,6 +28,7 @@ import io.github.dsheirer.module.decode.AuxDecodeConfigurationEditor; import io.github.dsheirer.module.decode.DecodeConfigurationEditor; import io.github.dsheirer.module.log.EventLogConfigurationEditor; import io.github.dsheirer.record.RecordConfigurationEditor; +import io.github.dsheirer.sample.Listener; import io.github.dsheirer.source.SourceConfigurationEditor; import io.github.dsheirer.source.SourceManager; import net.miginfocom.swing.MigLayout; @@ -37,7 +40,7 @@ import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -public class ChannelEditor extends Editor implements ActionListener, ChannelEventListener +public class ChannelEditor extends Editor implements ActionListener, Listener { private static final long serialVersionUID = 1L; private static final String ACTION_START = "Start"; @@ -56,17 +59,20 @@ public class ChannelEditor extends Editor implements ActionListener, Ch private JLabel mChannelName = new JLabel("Channel:"); private ChannelModel mChannelModel; + private ChannelProcessingManager mChannelProcessingManager; private ChannelMapModel mChannelMapModel; private SourceManager mSourceManager; private boolean mChannelStartRequested = false; public ChannelEditor(ChannelModel channelModel, + ChannelProcessingManager channelProcessingManager, ChannelMapModel channelMapModel, SourceManager sourceManager, AliasModel aliasModel) { mChannelModel = channelModel; + mChannelProcessingManager = channelProcessingManager; mChannelMapModel = channelMapModel; mSourceManager = sourceManager; mNameConfigurationEditor = new NameConfigurationEditor(aliasModel, mChannelModel); @@ -126,7 +132,7 @@ public class ChannelEditor extends Editor implements ActionListener, Ch } @Override - public void channelChanged(ChannelEvent event) + public void receive(ChannelEvent event) { if(hasItem() && getItem() == event.getChannel()) { @@ -141,7 +147,7 @@ public class ChannelEditor extends Editor implements ActionListener, Ch case NOTIFICATION_DELETE: setItem(null); break; - case NOTIFICATION_START_PROCESSING_REJECTED: + case NOTIFICATION_PROCESSING_START_REJECTED: if(mChannelStartRequested) { JOptionPane.showMessageDialog(ChannelEditor.this, "Channel could not be " @@ -169,14 +175,14 @@ public class ChannelEditor extends Editor implements ActionListener, Ch save(); mChannelStartRequested = true; - mChannelModel.broadcast(new ChannelEvent(getItem(), ChannelEvent.Event.REQUEST_ENABLE)); + mChannelProcessingManager.receive(new ChannelEvent(getItem(), ChannelEvent.Event.REQUEST_ENABLE)); } } else if(command.contentEquals(ACTION_STOP)) { if(hasItem()) { - mChannelModel.broadcast(new ChannelEvent(getItem(), ChannelEvent.Event.REQUEST_DISABLE)); + mChannelProcessingManager.receive(new ChannelEvent(getItem(), ChannelEvent.Event.REQUEST_DISABLE)); } } else if(command.contentEquals(ACTION_SAVE)) @@ -246,9 +252,9 @@ public class ChannelEditor extends Editor implements ActionListener, Ch return; } + ChannelEvent update = new ChannelEvent(getItem(), ChannelEvent.Event.NOTIFICATION_CONFIGURATION_CHANGE); - mChannelModel.broadcast(new ChannelEvent(getItem(), - ChannelEvent.Event.NOTIFICATION_CONFIGURATION_CHANGE)); + mChannelModel.receive(update); } setModified(false); diff --git a/src/main/java/io/github/dsheirer/controller/channel/ChannelEvent.java b/src/main/java/io/github/dsheirer/controller/channel/ChannelEvent.java index 42679a38..1e565688 100644 --- a/src/main/java/io/github/dsheirer/controller/channel/ChannelEvent.java +++ b/src/main/java/io/github/dsheirer/controller/channel/ChannelEvent.java @@ -1,83 +1,115 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2018 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.controller.channel; public class ChannelEvent { - private Channel mChannel; - private Event mEvent; + private Channel mChannel; + private Event mEvent; + private String mDescription; - /** - * ChannelEvent - event describing any changes to channels - * @param channel - channel that changed - * @param event - change event - */ - public ChannelEvent( Channel channel, Event event ) - { - mChannel = channel; - mEvent = event; - } - - public Channel getChannel() - { - return mChannel; - } - - public Event getEvent() - { - return mEvent; - } + /** + * ChannelEvent - event describing channel related events + * + * @param channel for the event + * @param event that occurred + * @param description of the event + */ + public ChannelEvent(Channel channel, Event event, String description) + { + mChannel = channel; + mEvent = event; + mDescription = description; + } - public String toString() - { - return getEvent().name() + " Channel:" + getChannel().getName(); - } - - /** - * Channel events to describe the specific event - */ - public enum Event - { - //Channel is added - NOTIFICATION_ADD, - //Channel configuration has changed - NOTIFICATION_CONFIGURATION_CHANGE, - //Channel is deleted/removed - NOTIFICATION_DELETE, - //Channel enable request was rejected - NOTIFICATION_START_PROCESSING_REJECTED, - //Channel has started processing/decoding - NOTIFICATION_PROCESSING_START, - //Channel has stopped processing/decoding - NOTIFICATION_PROCESSING_STOP, - //Channel's selection state has changed - NOTIFICATION_SELECTION_CHANGE, - //Channel's channel-state has been reset - NOTIFICATION_STATE_RESET, - //Request to delete the channel - REQUEST_DELETE, - //Request to deselect the channel - REQUEST_DESELECT, - //Request to disable a channel - response will be a PROCESSING_STOP_NOTIFICATION - REQUEST_DISABLE, - //Request to enable a channel - response will be a PROCESSING_START_NOTIFICATION - REQUEST_ENABLE, - //Request to select the channel - REQUEST_SELECT; - } + /** + * ChannelEvent - event describing any changes to channels + * + * @param channel - channel that changed + * @param event - change event + */ + public ChannelEvent(Channel channel, Event event) + { + this(channel, event, null); + } + + /** + * Channel for the event + */ + public Channel getChannel() + { + return mChannel; + } + + /** + * Describes the event that happened to the channel + */ + public Event getEvent() + { + return mEvent; + } + + /** + * Optional description of the event + */ + public String getDescription() + { + return mDescription; + } + + public String toString() + { + return getEvent().name() + " Channel:" + getChannel().getName() + + (mDescription != null ? " Description:" + mDescription : ""); + } + + /** + * Channel events to describe the specific event + */ + public enum Event + { + //Channel is added + NOTIFICATION_ADD, + //Channel configuration has changed + NOTIFICATION_CONFIGURATION_CHANGE, + //Channel is deleted/removed + NOTIFICATION_DELETE, + //Channel enable request was rejected + NOTIFICATION_PROCESSING_START_REJECTED, + //Channel has started processing/decoding + NOTIFICATION_PROCESSING_START, + //Channel has stopped processing/decoding + NOTIFICATION_PROCESSING_STOP, + //Channel's selection state has changed + NOTIFICATION_SELECTION_CHANGE, + //Channel's channel-state has been reset + NOTIFICATION_STATE_RESET, + //Request to delete the channel + REQUEST_DELETE, + //Request to deselect the channel + REQUEST_DESELECT, + //Request to disable a channel - response will be a PROCESSING_STOP_NOTIFICATION + REQUEST_DISABLE, + //Request to enable a channel - response will be a PROCESSING_START_NOTIFICATION + REQUEST_ENABLE, + //Request to select the channel + REQUEST_SELECT; + } } diff --git a/src/main/java/io/github/dsheirer/controller/channel/ChannelGrantEvent.java b/src/main/java/io/github/dsheirer/controller/channel/ChannelGrantEvent.java new file mode 100644 index 00000000..1666df2d --- /dev/null +++ b/src/main/java/io/github/dsheirer/controller/channel/ChannelGrantEvent.java @@ -0,0 +1,64 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.controller.channel; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.IdentifierCollection; + +/** + * Channel grant event with a channel descriptor that identifies the channel that is being created + */ +public class ChannelGrantEvent extends ChannelEvent +{ + private IChannelDescriptor mChannelDescriptor; + private IdentifierCollection mIdentifierCollection; + + /** + * Constructs a channel grant event + * @param channel with setup/configuration details + * @param event to convey for the channel + * @param channelDescriptor for the channel that is to be created + * @param identifierCollection containing identifiers to preload into the channel + */ + public ChannelGrantEvent(Channel channel, Event event, IChannelDescriptor channelDescriptor, + IdentifierCollection identifierCollection) + { + super(channel, event); + mChannelDescriptor = channelDescriptor; + mIdentifierCollection = identifierCollection; + } + + /** + * Channel descriptor that contains uplink and downlink information + */ + public IChannelDescriptor getChannelDescriptor() + { + return mChannelDescriptor; + } + + /** + * Identifier collection to use in preloading the channel state for the allocated channel + */ + public IdentifierCollection getIdentifierCollection() + { + return mIdentifierCollection; + } +} diff --git a/src/main/java/io/github/dsheirer/controller/channel/ChannelModel.java b/src/main/java/io/github/dsheirer/controller/channel/ChannelModel.java index 30939c9c..3dedb42a 100644 --- a/src/main/java/io/github/dsheirer/controller/channel/ChannelModel.java +++ b/src/main/java/io/github/dsheirer/controller/channel/ChannelModel.java @@ -21,6 +21,8 @@ package io.github.dsheirer.controller.channel; import io.github.dsheirer.controller.channel.Channel.ChannelType; import io.github.dsheirer.controller.channel.ChannelEvent.Event; import io.github.dsheirer.module.decode.DecoderType; +import io.github.dsheirer.sample.Broadcaster; +import io.github.dsheirer.sample.Listener; import javax.swing.table.AbstractTableModel; import java.util.ArrayList; @@ -32,7 +34,7 @@ import java.util.concurrent.CopyOnWriteArrayList; /** * Channel Model */ -public class ChannelModel extends AbstractTableModel implements IChannelEventBroadcaster +public class ChannelModel extends AbstractTableModel implements Listener { private static final long serialVersionUID = 1L; @@ -51,7 +53,7 @@ public class ChannelModel extends AbstractTableModel implements IChannelEventBro private List mChannels = new CopyOnWriteArrayList<>(); private List mTrafficChannels = new CopyOnWriteArrayList<>(); - private List mListeners = new CopyOnWriteArrayList<>(); + private Broadcaster mChannelEventBroadcaster = new Broadcaster(); public ChannelModel() { @@ -75,25 +77,6 @@ public class ChannelModel extends AbstractTableModel implements IChannelEventBro return null; } - /** - * Returns the channel identified by the id argument - */ - public Channel getChannelFromChannelID(Integer id) - { - if(id != null) - { - for(Channel channel: mChannels) - { - if(channel.getChannelID() == id) - { - return channel; - } - } - } - - return null; - } - /** * Returns a list of unique system values from across the channel set */ @@ -144,24 +127,30 @@ public class ChannelModel extends AbstractTableModel implements IChannelEventBro /** * Broadcasts the channel event to all registered listeners */ - @Override - public void broadcast(ChannelEvent event) + public void receive(ChannelEvent event) { - for(ChannelEventListener listener : mListeners) - { - listener.channelChanged(event); - } - if(event.getChannel().getChannelType() == ChannelType.STANDARD) { + int index; switch(event.getEvent()) { case NOTIFICATION_CONFIGURATION_CHANGE: + case NOTIFICATION_SELECTION_CHANGE: + index = mChannels.indexOf(event.getChannel()); + if(index > 0) + { + fireTableRowsUpdated(index, index); + } + //rebroadcast this event to any listeners + mChannelEventBroadcaster.broadcast(event); + break; case NOTIFICATION_PROCESSING_START: case NOTIFICATION_PROCESSING_STOP: - case NOTIFICATION_SELECTION_CHANGE: - int index = mChannels.indexOf(event.getChannel()); - fireTableRowsUpdated(index, index); + index = mChannels.indexOf(event.getChannel()); + if(index > 0) + { + fireTableRowsUpdated(index, index); + } break; default: break; @@ -210,7 +199,7 @@ public class ChannelModel extends AbstractTableModel implements IChannelEventBro break; } - broadcast(new ChannelEvent(channel, Event.NOTIFICATION_ADD)); + mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_ADD)); return index; } @@ -241,7 +230,7 @@ public class ChannelModel extends AbstractTableModel implements IChannelEventBro break; } - broadcast(new ChannelEvent(channel, Event.NOTIFICATION_DELETE)); + mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_DELETE)); } } @@ -317,14 +306,20 @@ public class ChannelModel extends AbstractTableModel implements IChannelEventBro return channels; } - public void addListener(ChannelEventListener listener) + /** + * Adds a listener to receive channel events + */ + public void addListener(Listener listener) { - mListeners.add(listener); + mChannelEventBroadcaster.addListener(listener); } - public void removeListener(ChannelEventListener listener) + /** + * Removes listener from receiving channel events + */ + public void removeListener(Listener listener) { - mListeners.remove(listener); + mChannelEventBroadcaster.removeListener(listener); } //Table Model Interface Methods - standard channels only diff --git a/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java b/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java index 8d01a8d0..2b7a1809 100644 --- a/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java +++ b/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,72 +15,86 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.controller.channel; import io.github.dsheirer.alias.AliasModel; -import io.github.dsheirer.channel.metadata.Attribute; -import io.github.dsheirer.channel.metadata.AttributeChangeRequest; +import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.channel.metadata.ChannelMetadataModel; import io.github.dsheirer.controller.channel.map.ChannelMapModel; import io.github.dsheirer.filter.FilterSet; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.identifier.IdentifierUpdateNotification; +import io.github.dsheirer.identifier.configuration.ChannelDescriptorConfigurationIdentifier; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.Module; import io.github.dsheirer.module.ProcessingChain; import io.github.dsheirer.module.decode.DecoderFactory; -import io.github.dsheirer.module.decode.DecoderType; +import io.github.dsheirer.module.decode.event.IDecodeEvent; import io.github.dsheirer.module.decode.event.MessageActivityModel; import io.github.dsheirer.module.log.EventLogManager; import io.github.dsheirer.record.RecorderManager; import io.github.dsheirer.record.RecorderType; import io.github.dsheirer.record.binary.BinaryRecorder; +import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.buffer.ReusableAudioPacket; import io.github.dsheirer.source.Source; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.SourceManager; -import io.github.dsheirer.source.SourceType; -import io.github.dsheirer.source.config.SourceConfigTuner; -import io.github.dsheirer.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; -public class ChannelProcessingManager implements ChannelEventListener +/** + * Channel processing manager handles all starting and stopping of channel decoding. A processing chain is created + * for each channel that is enabled. The processing chain contains all of the components needed to decode a specific + * channel and protocol along with all logging and baseband or bitstream recording. Audio recording is handled outside + * of this class by the RecorderManager. + */ +public class ChannelProcessingManager implements Listener { private final static Logger mLog = LoggerFactory.getLogger(ChannelProcessingManager.class); - - private Map mProcessingChains = new HashMap<>(); + private static final String TUNER_UNAVAILABLE_DESCRIPTION = "TUNER UNAVAILABLE"; + private Map mProcessingChains = new HashMap<>(); private List> mAudioPacketListeners = new CopyOnWriteArrayList<>(); - private List> mMessageListeners = new CopyOnWriteArrayList<>(); + private List> mDecodeEventListeners = new CopyOnWriteArrayList<>(); + private Broadcaster mChannelEventBroadcaster = new Broadcaster(); - private ChannelModel mChannelModel; private ChannelMapModel mChannelMapModel; - private ChannelMetadataModel mChannelMetadataModel = new ChannelMetadataModel(); - private AliasModel mAliasModel; + private ChannelMetadataModel mChannelMetadataModel; private EventLogManager mEventLogManager; private RecorderManager mRecorderManager; private SourceManager mSourceManager; + private AliasModel mAliasModel; - public ChannelProcessingManager(ChannelModel channelModel, - ChannelMapModel channelMapModel, - AliasModel aliasModel, - EventLogManager eventLogManager, - RecorderManager recorderManager, - SourceManager sourceManager) + /** + * Constructs the channel processing manager + * + * @param channelMapModel containing channel maps defined by the user + * @param eventLogManager for adding event loggers to channels + * @param recorderManager for receiving audio packets produced by the channel + * @param sourceManager for obtaining a tuner channel source for the channel + * @param aliasModel for aliasing of identifiers produced by the channel + */ + public ChannelProcessingManager(ChannelMapModel channelMapModel, EventLogManager eventLogManager, + RecorderManager recorderManager, SourceManager sourceManager, AliasModel aliasModel) { - mChannelModel = channelModel; mChannelMapModel = channelMapModel; - mAliasModel = aliasModel; mEventLogManager = eventLogManager; mRecorderManager = recorderManager; mSourceManager = sourceManager; + mAliasModel = aliasModel; + mChannelMetadataModel = new ChannelMetadataModel(); } /** @@ -90,22 +105,13 @@ public class ChannelProcessingManager implements ChannelEventListener return mChannelMetadataModel; } - /** - * Channel model - */ - public ChannelModel getChannelModel() - { - return mChannelModel; - } - /** * Indicates if a processing chain is constructed for the channel and that * the processing chain is currently processing. */ private boolean isProcessing(Channel channel) { - return mProcessingChains.containsKey(channel.getChannelID()) && - mProcessingChains.get(channel.getChannelID()).isProcessing(); + return mProcessingChains.containsKey(channel) && mProcessingChains.get(channel).isProcessing(); } /** @@ -114,27 +120,20 @@ public class ChannelProcessingManager implements ChannelEventListener */ public ProcessingChain getProcessingChain(Channel channel) { - return mProcessingChains.get(channel.getChannelID()); + return mProcessingChains.get(channel); } /** * Returns the channel associated with the processing chain + * * @param processingChain * @return channel associated with the processing chain or null */ public Channel getChannel(ProcessingChain processingChain) - { - return mChannelModel.getChannelFromChannelID(getChannelID(processingChain)); - } - - /** - * Returns the channel ID associated with the processing chain - */ - private Integer getChannelID(ProcessingChain processingChain) { if(processingChain != null) { - for(Map.Entry entry: mProcessingChains.entrySet()) + for(Map.Entry entry : mProcessingChains.entrySet()) { if(entry.getValue() == processingChain) { @@ -146,16 +145,20 @@ public class ChannelProcessingManager implements ChannelEventListener return null; } + /** + * Primary method for receiving requests to start and stop a channel + * + * @param event that requests either enable/start or disable/stop a channel. + */ @Override - public synchronized void channelChanged(ChannelEvent event) + public synchronized void receive(ChannelEvent event) { Channel channel = event.getChannel(); switch(event.getEvent()) { case REQUEST_ENABLE: - if(!mProcessingChains.containsKey(channel.getChannelID()) || - !mProcessingChains.get(channel.getChannelID()).isProcessing()) + if(!isProcessing(channel)) { startProcessing(event); } @@ -195,11 +198,16 @@ public class ChannelProcessingManager implements ChannelEventListener } } + /** + * Starts a channel/processing chain + * + * @param event that requested the channel start + */ private void startProcessing(ChannelEvent event) { Channel channel = event.getChannel(); - ProcessingChain processingChain = mProcessingChains.get(channel.getChannelID()); + ProcessingChain processingChain = mProcessingChains.get(channel); //If we're already processing, ignore the request if(processingChain != null && processingChain.isProcessing()) @@ -224,71 +232,48 @@ public class ChannelProcessingManager implements ChannelEventListener { channel.setProcessing(false); - mChannelModel.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_START_PROCESSING_REJECTED)); + mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_START_REJECTED, + TUNER_UNAVAILABLE_DESCRIPTION)); return; } if(processingChain == null) { - processingChain = new ProcessingChain(channel.getChannelType()); + processingChain = new ProcessingChain(channel, mAliasModel); + mChannelEventBroadcaster.addListener(processingChain); - /* Register global listeners */ + /* Register global listeners */ for(Listener listener : mAudioPacketListeners) { processingChain.addAudioPacketListener(listener); } - for(Listener listener : mMessageListeners) + for(Listener listener : mDecodeEventListeners) { - processingChain.addMessageListener(listener); + processingChain.addDecodeEventListener(listener); } + //Register this manager to receive channel events from traffic channel manager modules within + //the processing chain + processingChain.addChannelEventListener(this); + //Register channel to receive frequency correction events to show in the spectral display (hack!) processingChain.addFrequencyChangeListener(channel); - /* Processing Modules */ - List modules = DecoderFactory.getModules(mChannelModel, mChannelMapModel, this, - mAliasModel, channel, processingChain.getChannelState().getMutableMetadata()); + /* Processing Modules */ + List modules = DecoderFactory.getModules(mChannelMapModel, channel, mAliasModel); processingChain.addModules(modules); - /* Setup message activity model with filtering */ - FilterSet messageFilter = DecoderFactory.getMessageFilters(modules); + /* Setup message activity model with filtering */ + FilterSet messageFilter = DecoderFactory.getMessageFilters(modules); MessageActivityModel messageModel = new MessageActivityModel(messageFilter); processingChain.setMessageActivityModel(messageModel); } - //Set the recordable flag to true if the user has requested recording. The metadata class can still - //override recordability if any of the aliased values has 'Do Not Record' alias identifier. - boolean recordable = channel.getRecordConfiguration() != null && - channel.getRecordConfiguration().getRecorders().contains(RecorderType.AUDIO); - - processingChain.getChannelState().getMutableMetadata().setRecordable(recordable); - - //Inject channel metadata that will be inserted into audio packets for recorder manager and streaming - processingChain.getChannelState().getMutableMetadata().receive( - new AttributeChangeRequest(Attribute.CHANNEL_CONFIGURATION_SYSTEM, channel.getSystem())); - processingChain.getChannelState().getMutableMetadata().receive( - new AttributeChangeRequest(Attribute.CHANNEL_CONFIGURATION_SITE, channel.getSite())); - processingChain.getChannelState().getMutableMetadata().receive( - new AttributeChangeRequest(Attribute.CHANNEL_CONFIGURATION_NAME, channel.getName())); - - if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER) - { - long frequency = ((SourceConfigTuner)channel.getSourceConfiguration()).getFrequency(); - - processingChain.getChannelState().getMutableMetadata().receive( - new AttributeChangeRequest(Attribute.CHANNEL_FREQUENCY, frequency)); - } - - processingChain.getChannelState().getMutableMetadata().receive( - new AttributeChangeRequest(Attribute.PRIMARY_DECODER_TYPE, - channel.getDecodeConfiguration().getDecoderType())); - /* Setup event logging */ - String cleanedChannelName = StringUtils.replaceIllegalCharacters(channel.getName()); - List loggers = mEventLogManager.getLoggers(channel.getEventLogConfiguration(), cleanedChannelName); + List loggers = mEventLogManager.getLoggers(channel); if(!loggers.isEmpty()) { @@ -319,7 +304,7 @@ public class ChannelProcessingManager implements ChannelEventListener channel.getChannelType() == Channel.ChannelType.STANDARD)) { processingChain.addModule(new BinaryRecorder(mRecorderManager.getRecordingBasePath(), - channel.toString())); + channel.toString(), channel.getDecodeConfiguration().getDecoderType().getProtocol())); } /* Add traffic channel decoded bit stream recorder */ @@ -327,42 +312,62 @@ public class ChannelProcessingManager implements ChannelEventListener channel.getChannelType() == Channel.ChannelType.TRAFFIC) { processingChain.addModule(new BinaryRecorder(mRecorderManager.getRecordingBasePath(), - channel.toString())); + channel.toString(), channel.getDecodeConfiguration().getDecoderType().getProtocol())); } } } processingChain.setSource(source); - if(event instanceof TrafficChannelEvent) + //Inject the channel identifier for traffic channels and preload user identifiers + if(channel.isTrafficChannel() && event instanceof ChannelGrantEvent) { - TrafficChannelEvent trafficChannelEvent = (TrafficChannelEvent) event; + ChannelGrantEvent channelGrantEvent = (ChannelGrantEvent)event; + IChannelDescriptor channelDescriptor = channelGrantEvent.getChannelDescriptor(); - processingChain.getChannelState().configureAsTrafficChannel( - trafficChannelEvent.getTrafficChannelManager(), - trafficChannelEvent.getCallEvent()); + if(channelDescriptor != null) + { + ChannelDescriptorConfigurationIdentifier identifier = new ChannelDescriptorConfigurationIdentifier(channelDescriptor); + IdentifierUpdateNotification notification = new IdentifierUpdateNotification(identifier, + IdentifierUpdateNotification.Operation.ADD); + processingChain.getChannelState().updateChannelStateIdentifiers(notification); + } + + IdentifierCollection identifierCollection = channelGrantEvent.getIdentifierCollection(); + + for(Identifier userIdentifier : identifierCollection.getIdentifiers(IdentifierClass.USER)) + { + IdentifierUpdateNotification notification = new IdentifierUpdateNotification(userIdentifier, + IdentifierUpdateNotification.Operation.ADD); + processingChain.getChannelState().updateChannelStateIdentifiers(notification); + } } processingChain.start(); - - getChannelMetadataModel().add(processingChain.getChannelState().getMutableMetadata(), channel); - channel.setProcessing(true); - mProcessingChains.put(channel.getChannelID(), processingChain); + getChannelMetadataModel().add(processingChain.getChannelState().getChannelMetadata(), channel); - mChannelModel.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_START)); + mProcessingChains.put(channel, processingChain); + + mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_START)); } + /** + * Stops the channel/processing chain. + * + * @param channel to stop + * @param remove set to true to remove the associated processing chain. + */ private void stopProcessing(Channel channel, boolean remove) { channel.setProcessing(false); - if(mProcessingChains.containsKey(channel.getChannelID())) + if(mProcessingChains.containsKey(channel)) { - ProcessingChain processingChain = mProcessingChains.get(channel.getChannelID()); + ProcessingChain processingChain = mProcessingChains.get(channel); - getChannelMetadataModel().remove(processingChain.getChannelState().getMutableMetadata()); + getChannelMetadataModel().remove(processingChain.getChannelState().getChannelMetadata()); processingChain.stop(); @@ -374,12 +379,12 @@ public class ChannelProcessingManager implements ChannelEventListener processingChain.removeFrequencyChangeListener(channel); channel.resetFrequencyCorrection(); - mChannelModel.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_STOP)); + mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_STOP)); if(remove) { - mProcessingChains.remove(channel.getChannelID()); - + mChannelEventBroadcaster.removeListener(processingChain); + mProcessingChains.remove(channel); processingChain.dispose(); } } @@ -391,13 +396,13 @@ public class ChannelProcessingManager implements ChannelEventListener public void shutdown() { mLog.debug("Stopping Channels ..."); - for(Channel channel: mChannelModel.getChannels()) + + List channelsToStop = new ArrayList<>(mProcessingChains.keySet()); + + for(Channel channel : channelsToStop) { - if(channel.isProcessing()) - { - mLog.debug("Stopping channel: " + channel.toString()); - stopProcessing(channel, true); - } + mLog.debug("Stopping channel: " + channel.toString()); + stopProcessing(channel, true); } } @@ -422,16 +427,32 @@ public class ChannelProcessingManager implements ChannelEventListener * Adds a message listener that will be added to all channels to receive * any messages. */ - public void addMessageListener(Listener listener) + public void addDecodeEventListener(Listener listener) { - mMessageListeners.add(listener); + mDecodeEventListeners.add(listener); } /** * Removes a message listener. */ - public void removeMessageListener(Listener listener) + public void removeDecodeEventListener(Listener listener) { - mMessageListeners.remove(listener); + mDecodeEventListeners.remove(listener); + } + + /** + * Adds a listener to receive channel events from this manager + */ + public void addChannelEventListener(Listener listener) + { + mChannelEventBroadcaster.addListener(listener); + } + + /** + * Removes the listener from receiving channel events from this manager + */ + public void removeChannelEventListener(Listener listener) + { + mChannelEventBroadcaster.removeListener(listener); } } diff --git a/src/main/java/io/github/dsheirer/controller/channel/ChannelSelectionManager.java b/src/main/java/io/github/dsheirer/controller/channel/ChannelSelectionManager.java index 05c9fd4a..bc64ae91 100644 --- a/src/main/java/io/github/dsheirer/controller/channel/ChannelSelectionManager.java +++ b/src/main/java/io/github/dsheirer/controller/channel/ChannelSelectionManager.java @@ -1,69 +1,69 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.controller.channel; -public class ChannelSelectionManager implements ChannelEventListener +import io.github.dsheirer.sample.Listener; + +public class ChannelSelectionManager implements Listener { - private IChannelEventBroadcaster mChannelEventBroadcaster; - private Channel mSelectedChannel = null; + private Listener mChannelEventListener; + private Channel mSelectedChannel = null; - /** - * Manages channel selection state to ensure that only one channel is ever - * in a selected state. - */ - public ChannelSelectionManager( IChannelEventBroadcaster broadcaster ) - { - mChannelEventBroadcaster = broadcaster; - } + /** + * Manages channel selection state to ensure that only one channel is ever + * in a selected state. + */ + public ChannelSelectionManager(Listener listener) + { + mChannelEventListener = listener; + } - @Override - public void channelChanged( ChannelEvent event ) - { - switch( event.getEvent() ) - { - case REQUEST_SELECT: - if( mSelectedChannel != null ) - { - mSelectedChannel.setSelected( false ); - mChannelEventBroadcaster.broadcast( new ChannelEvent( mSelectedChannel, - ChannelEvent.Event.NOTIFICATION_SELECTION_CHANGE ) ); - mSelectedChannel = null; - } - - mSelectedChannel = event.getChannel(); - mSelectedChannel.setSelected( true ); - mChannelEventBroadcaster.broadcast( new ChannelEvent( event.getChannel(), - ChannelEvent.Event.NOTIFICATION_SELECTION_CHANGE ) ); - break; - case NOTIFICATION_PROCESSING_STOP: - case REQUEST_DESELECT: - if( mSelectedChannel != null && - mSelectedChannel == event.getChannel() ) - { - mSelectedChannel.setSelected( false ); - mSelectedChannel = null; - mChannelEventBroadcaster.broadcast( new ChannelEvent( event.getChannel(), - ChannelEvent.Event.NOTIFICATION_SELECTION_CHANGE ) ); - } - break; - default: - break; - } - } + @Override + public void receive(ChannelEvent event) + { + switch(event.getEvent()) + { + case REQUEST_SELECT: + if(mSelectedChannel != null) + { + mSelectedChannel.setSelected(false); + mChannelEventListener.receive(new ChannelEvent(mSelectedChannel, ChannelEvent.Event.NOTIFICATION_SELECTION_CHANGE)); + mSelectedChannel = null; + } + mSelectedChannel = event.getChannel(); + mSelectedChannel.setSelected(true); + mChannelEventListener.receive(new ChannelEvent(event.getChannel(), ChannelEvent.Event.NOTIFICATION_SELECTION_CHANGE)); + break; + case NOTIFICATION_PROCESSING_STOP: + case REQUEST_DESELECT: + if(mSelectedChannel != null && + mSelectedChannel == event.getChannel()) + { + mSelectedChannel.setSelected(false); + mSelectedChannel = null; + mChannelEventListener.receive(new ChannelEvent(event.getChannel(), ChannelEvent.Event.NOTIFICATION_SELECTION_CHANGE)); + } + break; + default: + break; + } + } } diff --git a/src/main/java/io/github/dsheirer/controller/channel/ChannelUtils.java b/src/main/java/io/github/dsheirer/controller/channel/ChannelUtils.java index 69cff222..5cc173d3 100644 --- a/src/main/java/io/github/dsheirer/controller/channel/ChannelUtils.java +++ b/src/main/java/io/github/dsheirer/controller/channel/ChannelUtils.java @@ -1,3 +1,23 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.controller.channel; import io.github.dsheirer.channel.state.DecoderState; @@ -14,104 +34,101 @@ import java.awt.event.ActionListener; public class ChannelUtils { - /** - * Creates a context menu for the channel argument - */ - public static JMenu getContextMenu( final ChannelModel channelModel, - final ChannelProcessingManager channelProcessingManager, - final Channel channel, - final Component anchor ) - { - if( channel != null ) - { - JMenu menu = new JMenu( "Channel: " + channel.getName() ); - - if( channel.isProcessing() ) - { - JMenuItem disable = new JMenuItem( "Disable" ); - disable.addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent e ) - { - channelModel.broadcast( - new ChannelEvent( channel, ChannelEvent.Event.REQUEST_DISABLE ) ); - } - } ); - - menu.add( disable ); - - menu.add( new JSeparator() ); + /** + * Creates a context menu for the channel argument + */ + public static JMenu getContextMenu(final ChannelModel channelModel, + final ChannelProcessingManager channelProcessingManager, + final Channel channel, + final Component anchor) + { + if(channel != null) + { + JMenu menu = new JMenu("Channel: " + channel.getName()); - JMenuItem actySummaryItem = - new JMenuItem( "Activity Summary" ); + if(channel.isProcessing()) + { + JMenuItem disable = new JMenuItem("Disable"); + disable.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + channelProcessingManager.receive(new ChannelEvent(channel, ChannelEvent.Event.REQUEST_DISABLE)); + } + }); + + menu.add(disable); + + menu.add(new JSeparator()); + + JMenuItem actySummaryItem = + new JMenuItem("Activity Summary"); + + actySummaryItem.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + StringBuilder sb = new StringBuilder(); + + ProcessingChain chain = channelProcessingManager.getProcessingChain(channel); + + if(chain != null) + { + for(DecoderState decoderState : chain.getDecoderStates()) + { + sb.append(decoderState.getActivitySummary()); + } + } + + new ActivitySummaryFrame(sb.toString(), anchor); + } + }); + + menu.add(actySummaryItem); + } + else + { + JMenuItem enable = new JMenuItem("Enable"); + enable.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + channelProcessingManager.receive(new ChannelEvent(channel, ChannelEvent.Event.REQUEST_ENABLE)); + } + }); + + menu.add(enable); + } + + menu.add(new JSeparator()); + + JMenuItem deleteItem = new JMenuItem("Delete"); + deleteItem.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + int response = JOptionPane.showConfirmDialog(anchor, + "Do you want to delete channel " + channel.getName() + "?", + "Are you sure?", JOptionPane.YES_NO_CANCEL_OPTION); + + if(response == JOptionPane.YES_OPTION) + { + channelModel.receive(new ChannelEvent(channel, ChannelEvent.Event.REQUEST_DELETE)); + } + } + }); + + menu.add(deleteItem); + + return menu; + + } + + return null; + } - actySummaryItem.addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent e ) - { - StringBuilder sb = new StringBuilder(); - - ProcessingChain chain = channelProcessingManager.getProcessingChain( channel ); - - if( chain != null ) - { - for( DecoderState decoderState: chain.getDecoderStates() ) - { - sb.append( decoderState.getActivitySummary() ); - } - } - - new ActivitySummaryFrame( sb.toString(), anchor ); - } - } ); - - menu.add( actySummaryItem ); - } - else - { - JMenuItem enable = new JMenuItem( "Enable" ); - enable.addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent e ) - { - channelModel.broadcast( - new ChannelEvent( channel, ChannelEvent.Event.REQUEST_ENABLE ) ); - } - } ); - - menu.add( enable ); - } - - menu.add( new JSeparator() ); - - JMenuItem deleteItem = new JMenuItem( "Delete" ); - deleteItem.addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent e ) - { - int response = JOptionPane.showConfirmDialog( anchor, - "Do you want to delete channel " + channel.getName() + "?", - "Are you sure?", JOptionPane.YES_NO_CANCEL_OPTION ); - - if( response == JOptionPane.YES_OPTION ) - { - channelModel.broadcast( - new ChannelEvent( channel, ChannelEvent.Event.REQUEST_DELETE ) ); - } - } - } ); - - menu.add( deleteItem ); - - return menu; - - } - - return null; - } - } diff --git a/src/main/java/io/github/dsheirer/controller/channel/IChannelEventBroadcaster.java b/src/main/java/io/github/dsheirer/controller/channel/IChannelEventBroadcaster.java deleted file mode 100644 index 2b365072..00000000 --- a/src/main/java/io/github/dsheirer/controller/channel/IChannelEventBroadcaster.java +++ /dev/null @@ -1,29 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2016 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 io.github.dsheirer.controller.channel; - -/** - * Provides channel event broadcasting capability. - */ -public interface IChannelEventBroadcaster -{ - /** - * Broadcasts the channel event - */ - public void broadcast( ChannelEvent event ); -} diff --git a/src/main/java/io/github/dsheirer/controller/channel/IChannelEventProvider.java b/src/main/java/io/github/dsheirer/controller/channel/IChannelEventProvider.java index d6acb8ad..a465a5fc 100644 --- a/src/main/java/io/github/dsheirer/controller/channel/IChannelEventProvider.java +++ b/src/main/java/io/github/dsheirer/controller/channel/IChannelEventProvider.java @@ -4,6 +4,6 @@ import io.github.dsheirer.sample.Listener; public interface IChannelEventProvider { - public void setChannelEventListener( Listener listener ); - public void removeChannelEventListener(); + void setChannelEventListener( Listener listener ); + void removeChannelEventListener(); } diff --git a/src/main/java/io/github/dsheirer/controller/channel/TrafficChannelEvent.java b/src/main/java/io/github/dsheirer/controller/channel/TrafficChannelEvent.java deleted file mode 100644 index f00ddcc4..00000000 --- a/src/main/java/io/github/dsheirer/controller/channel/TrafficChannelEvent.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.github.dsheirer.controller.channel; - -import io.github.dsheirer.channel.traffic.TrafficChannelManager; -import io.github.dsheirer.module.decode.event.CallEvent; - -public class TrafficChannelEvent extends ChannelEvent -{ - private TrafficChannelManager mTrafficChannelManager; - private CallEvent mCallEvent; - - /** - * Call event with traffic channel manager for call back, and original creation event. - */ - public TrafficChannelEvent( TrafficChannelManager trafficChannelManager, - Channel channel, - Event event, - CallEvent callEvent ) - { - super( channel, event ); - - mTrafficChannelManager = trafficChannelManager; - mCallEvent = callEvent; - } - - public TrafficChannelManager getTrafficChannelManager() - { - return mTrafficChannelManager; - } - - public CallEvent getCallEvent() - { - return mCallEvent; - } -} diff --git a/src/main/java/io/github/dsheirer/dsp/NRZDecoder.java b/src/main/java/io/github/dsheirer/dsp/NRZDecoder.java index a93e9c01..10adc784 100644 --- a/src/main/java/io/github/dsheirer/dsp/NRZDecoder.java +++ b/src/main/java/io/github/dsheirer/dsp/NRZDecoder.java @@ -1,18 +1,22 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.dsp; import io.github.dsheirer.bits.IBinarySymbolProcessor; @@ -52,13 +56,13 @@ public class NRZDecoder implements IBinarySymbolProcessor } @Override - public void receive(boolean symbol) + public void process(boolean symbol) { boolean result = mPrevious ^ symbol; if(mListener != null) { - mListener.receive(mMode ? result : !result); + mListener.process(mMode ? result : !result); } mPrevious = result; diff --git a/src/main/java/io/github/dsheirer/dsp/afsk/AFSK1200Decoder.java b/src/main/java/io/github/dsheirer/dsp/afsk/AFSK1200Decoder.java index 1602986a..f2ca56bc 100644 --- a/src/main/java/io/github/dsheirer/dsp/afsk/AFSK1200Decoder.java +++ b/src/main/java/io/github/dsheirer/dsp/afsk/AFSK1200Decoder.java @@ -1,18 +1,22 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.dsp.afsk; import io.github.dsheirer.bits.IBinarySymbolProcessor; @@ -119,7 +123,7 @@ public class AFSK1200Decoder implements Listener { if(mBinarySymbolProcessor != null) { - mBinarySymbolProcessor.receive(mNormalOutput ? symbol : !symbol); + mBinarySymbolProcessor.process(mNormalOutput ? symbol : !symbol); } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousBufferProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousBufferProcessor.java index 75835c57..2ffe2587 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousBufferProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousBufferProcessor.java @@ -33,10 +33,11 @@ public class ContinuousBufferProcessor implements Listener { private final static Logger mLog = LoggerFactory.getLogger(ContinuousBufferProcessor.class); - private OverflowableTransferQueue mQueue; + protected OverflowableTransferQueue mQueue; private Listener> mListener; private ScheduledFuture mScheduledFuture; private AtomicBoolean mRunning = new AtomicBoolean(); + private long mProcessingPeriod = 5; //milliseconds /** * Scheduled Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing task @@ -57,6 +58,24 @@ public class ContinuousBufferProcessor implements Listener this(new OverflowableTransferQueue<>(maximumSize, resetThreshold)); } + /** + * Listener to receive the queued buffers each time this processor runs. + */ + protected Listener> getListener() + { + return mListener; + } + + /** + * Sets the processing periodicity or run interval for how often the buffer queue is processed. + * + * @param milliseconds (default = 5 milliseconds) + */ + public void setProcessingPeriod(long milliseconds) + { + mProcessingPeriod = milliseconds; + } + /** * Scheduled Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing task * for periodically distributing internally queued elements to the registered listener. This processor provides @@ -115,7 +134,7 @@ public class ContinuousBufferProcessor implements Listener { if(mRunning.compareAndSet(false, true)) { - mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new Processor(), 0, 5, TimeUnit.MILLISECONDS); + mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new Processor(), 0, mProcessingPeriod, TimeUnit.MILLISECONDS); } } @@ -157,7 +176,7 @@ public class ContinuousBufferProcessor implements Listener /** * Distributes queued buffers to the listener */ - private void process() + protected void process() { List buffers = new ArrayList<>(); diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousReusableBufferProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousReusableBufferProcessor.java index 57f9f2fb..9f3b9cb9 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousReusableBufferProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ContinuousReusableBufferProcessor.java @@ -17,9 +17,16 @@ package io.github.dsheirer.dsp.filter.channelizer; import io.github.dsheirer.sample.buffer.AbstractReusableBuffer; import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; public class ContinuousReusableBufferProcessor extends ContinuousBufferProcessor { + private final static Logger mLog = LoggerFactory.getLogger(ContinuousReusableBufferProcessor.class); + /** * Scheduled Reusable Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing * task for periodically distributing internally queued elements to the registered listener. This processor @@ -38,4 +45,44 @@ public class ContinuousReusableBufferProcessor { super(new OverflowableReusableBufferTransferQueue(maximumSize, resetThreshold)); } + + /** + * Distributes queued buffers to the listener + */ + @Override + protected void process() + { + List buffers = new ArrayList<>(); + + mQueue.drainTo(buffers); + + try + { + if(getListener() != null) + { + getListener().receive(buffers); + } + + buffers.clear(); + } + catch(Throwable throwable) + { + mLog.error("Error while dispatching buffers to listener. Performing buffer user count cleanup", throwable); + + if(!buffers.isEmpty()) + { + for(T buffer: buffers) + { + try + { + buffer.decrementUserCount(); + } + catch(IllegalStateException ise) + { + mLog.error("Error while performing user count cleanup on reusable buffers."); + } + } + } + } + } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java index a21a244e..de590798 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java @@ -1,18 +1,22 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.dsp.filter.channelizer; import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor; @@ -28,15 +32,13 @@ import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.tuner.channel.ChannelSpecification; import io.github.dsheirer.source.tuner.channel.TunerChannel; import io.github.dsheirer.source.tuner.channel.TunerChannelSource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.Map; public class PolyphaseChannelSource extends TunerChannelSource { - private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class); +// private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class); private ReusableComplexBufferAssembler mReusableComplexBufferAssembler; private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor; diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/SampleTimestampManager.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/SampleTimestampManager.java index 38c8a943..39c02649 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/SampleTimestampManager.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/SampleTimestampManager.java @@ -1,18 +1,22 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.dsp.filter.channelizer; public class SampleTimestampManager @@ -29,6 +33,7 @@ public class SampleTimestampManager */ public SampleTimestampManager(double sampleRate) { + setSampleRate(sampleRate); } /** diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java index 335e957d..95862798 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java @@ -80,7 +80,6 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP if(mChannelResultsQueue != null) { mChannelResultsQueue.dispose(); - mChannelResultsQueue = null; } } diff --git a/src/main/java/io/github/dsheirer/dsp/fsk/LTRDecoder.java b/src/main/java/io/github/dsheirer/dsp/fsk/LTRDecoder.java index cda51b67..c502341d 100644 --- a/src/main/java/io/github/dsheirer/dsp/fsk/LTRDecoder.java +++ b/src/main/java/io/github/dsheirer/dsp/fsk/LTRDecoder.java @@ -1,18 +1,22 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.dsp.fsk; import io.github.dsheirer.bits.MessageFramer; @@ -129,9 +133,9 @@ public class LTRDecoder implements Listener, ISyncDetectLis * synchronization has been achieved. */ @Override - public void syncDetected() + public void syncDetected(int bitErrors) { - mSynchronizationMonitor.syncDetected(); + mSynchronizationMonitor.syncDetected(bitErrors); } @Override @@ -164,7 +168,7 @@ public class LTRDecoder implements Listener, ISyncDetectLis { if(mMessageFramer != null) { - mMessageFramer.receive(mSampleBuffer.getSymbol()); + mMessageFramer.process(mSampleBuffer.getSymbol()); } mSampleBuffer.resetAndAdjust(-mTimingErrorDetector.getError()); diff --git a/src/main/java/io/github/dsheirer/dsp/fsk/SynchronizationMonitor.java b/src/main/java/io/github/dsheirer/dsp/fsk/SynchronizationMonitor.java index 7d9746b7..f8c514c5 100644 --- a/src/main/java/io/github/dsheirer/dsp/fsk/SynchronizationMonitor.java +++ b/src/main/java/io/github/dsheirer/dsp/fsk/SynchronizationMonitor.java @@ -121,7 +121,7 @@ public class SynchronizationMonitor implements ISyncDetectListener * sync word in the symbol stream. */ @Override - public void syncDetected() + public void syncDetected(int bitErrors) { mSymbolCount = 0; mSyncCount++; diff --git a/src/main/java/io/github/dsheirer/dsp/psk/pll/AdaptivePLLGainMonitor.java b/src/main/java/io/github/dsheirer/dsp/psk/pll/AdaptivePLLGainMonitor.java index eb48da17..6b94d677 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/pll/AdaptivePLLGainMonitor.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/pll/AdaptivePLLGainMonitor.java @@ -53,7 +53,7 @@ public class AdaptivePLLGainMonitor implements ISyncDetectListener, IFrequencyEr * Sync detection event. Updates the running sync count and updates the PLL gain level. */ @Override - public void syncDetected() + public void syncDetected(int bitErrors) { mSyncCount++; update(); diff --git a/src/main/java/io/github/dsheirer/dsp/symbol/BinaryToByteBufferAssembler.java b/src/main/java/io/github/dsheirer/dsp/symbol/BinaryToByteBufferAssembler.java new file mode 100644 index 00000000..7a87c079 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/symbol/BinaryToByteBufferAssembler.java @@ -0,0 +1,120 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.symbol; + +import io.github.dsheirer.bits.IBinarySymbolProcessor; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider; +import io.github.dsheirer.sample.buffer.ReusableByteBuffer; +import io.github.dsheirer.sample.buffer.ReusableByteBufferQueue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Assembles reusable byte buffers from an incoming stream of boolean values. + */ +public class BinaryToByteBufferAssembler implements IBinarySymbolProcessor, IReusableByteBufferProvider +{ + private final static Logger mLog = LoggerFactory.getLogger(BinaryToByteBufferAssembler.class); + + private ReusableByteBufferQueue mBufferQueue = new ReusableByteBufferQueue("BinaryToByteBufferAssembler"); + private ReusableByteBuffer mCurrentBuffer; + private int mBufferPointer; + private int mBufferSize; + private byte mCurrentByte; + private int mBitCount; + private Listener mBufferListener; + + /** + * Constructs an assembler to produce reusable byte buffers of the specified size + * + * @param bufferSize + */ + public BinaryToByteBufferAssembler(int bufferSize) + { + mBufferSize = bufferSize; + getNextBuffer(); + } + + /** + * Broadcasts the current buffer to the registered listener and creates a new buffer, resetting + * the buffer pointer to zero so that new dibits can be loaded. + */ + private void getNextBuffer() + { + if(mCurrentBuffer != null && mBufferListener != null) + { + mBufferListener.receive(mCurrentBuffer); + } + + mCurrentBuffer = mBufferQueue.getBuffer(mBufferSize); + mBufferPointer = 0; + } + + @Override + public void process(boolean symbol) + { + mCurrentByte <<= 1; + + if(symbol) + { + mCurrentByte++; + } + + mBitCount++; + + if(mBitCount >= 8) + { + mCurrentBuffer.getBytes()[mBufferPointer++] = mCurrentByte; + mCurrentByte = 0; + mBitCount = 0; + + if(mBufferPointer >= mBufferSize) + { + getNextBuffer(); + } + } + + } + + /** + * Registers the listener to receive fully assembled byte buffers from this assembler. + */ + @Override + public void setBufferListener(Listener listener) + { + mBufferListener = listener; + } + + /** + * Removes the listener from receiving buffers from this assembler + */ + @Override + public void removeBufferListener(Listener listener) + { + mBufferListener = null; + } + + @Override + public boolean hasBufferListeners() + { + return mBufferListener != null; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/symbol/Dibit.java b/src/main/java/io/github/dsheirer/dsp/symbol/Dibit.java index 8ec42ec7..d912109e 100644 --- a/src/main/java/io/github/dsheirer/dsp/symbol/Dibit.java +++ b/src/main/java/io/github/dsheirer/dsp/symbol/Dibit.java @@ -2,111 +2,146 @@ package io.github.dsheirer.dsp.symbol; public enum Dibit { - D01_PLUS_3( false, true, 1, 4 ), - D00_PLUS_1( false, false, 0, 0 ), - D10_MINUS_1( true, false, 2, 8 ), - D11_MINUS_3( true, true, 3, 12 ); - - private boolean mBit1; - private boolean mBit2; - private int mLowValue; - private int mHighValue; - - private Dibit( boolean bit1, boolean bit2, int lowValue, int highValue ) - { - mBit1 = bit1; - mBit2 = bit2; - mLowValue = lowValue; - mHighValue = highValue; - } + D01_PLUS_3(false, true, 1, 4), + D00_PLUS_1(false, false, 0, 0), + D10_MINUS_1(true, false, 2, 8), + D11_MINUS_3(true, true, 3, 12); - /** - * Allowable transition state 1 for trellis coded modulation. Indicates - * the dibit that differs from this dibit in the LSB bit position. - */ - public Dibit getAllowableTransition1() - { - switch( this ) - { - case D00_PLUS_1: - return D01_PLUS_3; - case D01_PLUS_3: - return D00_PLUS_1; - case D10_MINUS_1: - return D11_MINUS_3; - case D11_MINUS_3: - return D10_MINUS_1; - default: - /* We'll never get here */ - return D00_PLUS_1; - } - } - - /** - * Allowable transition state 2 for trellis coded modulation. Indicates - * the dibit that differs from this dibit in the MSB bit position. - */ - public Dibit getAllowableTransition2() - { - switch( this ) - { - case D00_PLUS_1: - return D10_MINUS_1; - case D01_PLUS_3: - return D11_MINUS_3; - case D10_MINUS_1: - return D00_PLUS_1; - case D11_MINUS_3: - return D01_PLUS_3; - default: - /* We'll never get here */ - return D00_PLUS_1; - } - } + private boolean mBit1; + private boolean mBit2; + private int mLowValue; + private int mHighValue; - /** - * Indicates if the trellis coded modulation dibit is allowed to transition - * to the dibit argument. - */ - public boolean isAllowableTransition( Dibit dibit ) - { - return dibit == getAllowableTransition1() || - dibit == getAllowableTransition2(); - } - - public boolean getBit1() - { - return mBit1; - } - - public boolean getBit2() - { - return mBit2; - } - - public int getLowValue() - { - return mLowValue; - } - - public int getHighValue() - { - return mHighValue; - } - - public static Dibit inverted( Dibit symbol ) - { - switch( symbol ) - { - case D10_MINUS_1: - return D11_MINUS_3; - case D11_MINUS_3: - return D01_PLUS_3; - case D00_PLUS_1: - return D10_MINUS_1; - case D01_PLUS_3: - default: - return D00_PLUS_1; - } - } + private Dibit(boolean bit1, boolean bit2, int lowValue, int highValue) + { + mBit1 = bit1; + mBit2 = bit2; + mLowValue = lowValue; + mHighValue = highValue; + } + + /** + * Allowable transition state 1 for trellis coded modulation. Indicates + * the dibit that differs from this dibit in the LSB bit position. + */ + public Dibit getAllowableTransition1() + { + switch(this) + { + case D00_PLUS_1: + return D01_PLUS_3; + case D01_PLUS_3: + return D00_PLUS_1; + case D10_MINUS_1: + return D11_MINUS_3; + case D11_MINUS_3: + return D10_MINUS_1; + default: + /* We'll never get here */ + return D00_PLUS_1; + } + } + + /** + * Allowable transition state 2 for trellis coded modulation. Indicates + * the dibit that differs from this dibit in the MSB bit position. + */ + public Dibit getAllowableTransition2() + { + switch(this) + { + case D00_PLUS_1: + return D10_MINUS_1; + case D01_PLUS_3: + return D11_MINUS_3; + case D10_MINUS_1: + return D00_PLUS_1; + case D11_MINUS_3: + return D01_PLUS_3; + default: + /* We'll never get here */ + return D00_PLUS_1; + } + } + + /** + * Indicates if the trellis coded modulation dibit is allowed to transition + * to the dibit argument. + */ + public boolean isAllowableTransition(Dibit dibit) + { + return dibit == getAllowableTransition1() || + dibit == getAllowableTransition2(); + } + + public boolean getBit1() + { + return mBit1; + } + + public boolean getBit2() + { + return mBit2; + } + + public int getLowValue() + { + return mLowValue; + } + + public int getHighValue() + { + return mHighValue; + } + + public static Dibit inverted(Dibit symbol) + { + switch(symbol) + { + case D10_MINUS_1: + return D11_MINUS_3; + case D11_MINUS_3: + return D01_PLUS_3; + case D00_PLUS_1: + return D10_MINUS_1; + case D01_PLUS_3: + default: + return D00_PLUS_1; + } + } + + /** + * Parses a dibit from the byte value at the specified index + * @param value to parse + * @param index index of dibit, 0 - 3, where 0 is the most significant dibit and 3 is the least. + * @return dibit parsed from the byte. + */ + public static Dibit parse(byte value, int index) + { + switch(value >> ((3 - index) * 2) & 0x3) + { + case 0: + return Dibit.D00_PLUS_1; + case 1: + return Dibit.D01_PLUS_3; + case 2: + return Dibit.D10_MINUS_1; + case 3: + return Dibit.D11_MINUS_3; + } + + return Dibit.D00_PLUS_1; + } + + public static void main(String[] args) + { + byte a = (byte)0x1B; + + Dibit d1 = Dibit.parse(a, 0); + Dibit d2 = Dibit.parse(a, 1); + Dibit d3 = Dibit.parse(a, 2); + Dibit d4 = Dibit.parse(a, 3); + + System.out.println("1: " + d1 + " 2:" + d2 + " 3:" + d3 + " 4:" + d4); + } } diff --git a/src/main/java/io/github/dsheirer/dsp/symbol/ISyncDetectListener.java b/src/main/java/io/github/dsheirer/dsp/symbol/ISyncDetectListener.java index 139deefb..e560c1e6 100644 --- a/src/main/java/io/github/dsheirer/dsp/symbol/ISyncDetectListener.java +++ b/src/main/java/io/github/dsheirer/dsp/symbol/ISyncDetectListener.java @@ -21,7 +21,16 @@ package io.github.dsheirer.dsp.symbol; */ public interface ISyncDetectListener { - void syncDetected(); + /** + * Indicates that a sync pattern has been detected. + * + * @param bitErrors count for soft sync matching to indicate the number of bit positions + * of the sequence that didn't fully match the sync pattern + */ + void syncDetected(int bitErrors); + /** + * Indicates that sync has been lost + */ void syncLost(); } diff --git a/src/main/java/io/github/dsheirer/edac/BerlekempMassey_63.java b/src/main/java/io/github/dsheirer/edac/BerlekempMassey_63.java index a21006e5..7467d651 100644 --- a/src/main/java/io/github/dsheirer/edac/BerlekempMassey_63.java +++ b/src/main/java/io/github/dsheirer/edac/BerlekempMassey_63.java @@ -55,7 +55,7 @@ public class BerlekempMassey_63 private static final int NN = 63; /* Hamming distance between codewords: NN - KK + 1 = 2 * TT + 1 */ - private static int KK; + private int KK; /* Maximum number of errors that can be corrected */ int TT; @@ -222,7 +222,7 @@ public class BerlekempMassey_63 /* put recd[i] into index form (ie as powers of alpha) */ for( int i = 0; i < NN; i++ ) { - output[ i ] = index_of[ input[ i ] ]; + output[ i ] = index_of[ input[ i ] ]; } /* first form the syndromes */ diff --git a/src/main/java/io/github/dsheirer/edac/CRC.java b/src/main/java/io/github/dsheirer/edac/CRC.java index 15c8cc26..15c22cb1 100644 --- a/src/main/java/io/github/dsheirer/edac/CRC.java +++ b/src/main/java/io/github/dsheirer/edac/CRC.java @@ -1,73 +1,82 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.edac; public enum CRC { - PASSED ( "*", "PASS" ), - PASSED_INV ( "#", "PASS INVERTED" ), - FAILED_CRC ( "f", "FAIL CRC" ), - FAILED_PARITY( "p", "FAIL PARITY" ), - CORRECTED ( "C", "CORRECTED" ), - UNKNOWN ( "-", "UNKNOWN"); + PASSED("*", "PASS", true), + PASSED_INV("#", "PASS INVERTED", true), + FAILED_CRC("f", "FAIL CRC", false), + FAILED_PARITY("p", "FAIL PARITY", false), + CORRECTED("C", "CORRECTED", true), + UNKNOWN("-", "UNKNOWN", false); - private String mAbbreviation; - private String mDisplayText; - - CRC( String abbreviation, String displayText ) - { - mAbbreviation = abbreviation; - mDisplayText = displayText; - } - - public String getAbbreviation() - { - return mAbbreviation; - } - - public String getDisplayText() - { - return mDisplayText; - } + private String mAbbreviation; + private String mDisplayText; + private boolean mPass; - public static String format( CRC[] checks ) - { - if( checks == null ) - { - return CRC.UNKNOWN.getAbbreviation(); - } - - StringBuilder sb = new StringBuilder(); - - for( int x = 0; x < checks.length; x++ ) - { - CRC check = checks[ x ]; - - if( check != null ) - { - sb.append( check.getAbbreviation() ); - } - else - { - sb.append( CRC.UNKNOWN.getAbbreviation() ); - } - } - - return sb.toString(); - } + CRC(String abbreviation, String displayText, boolean pass) + { + mAbbreviation = abbreviation; + mDisplayText = displayText; + mPass = pass; + } + + public String getAbbreviation() + { + return mAbbreviation; + } + + public String getDisplayText() + { + return mDisplayText; + } + + public boolean passes() + { + return mPass; + } + + public static String format(CRC[] checks) + { + if(checks == null) + { + return CRC.UNKNOWN.getAbbreviation(); + } + + StringBuilder sb = new StringBuilder(); + + for(int x = 0; x < checks.length; x++) + { + CRC check = checks[x]; + + if(check != null) + { + sb.append(check.getAbbreviation()); + } + else + { + sb.append(CRC.UNKNOWN.getAbbreviation()); + } + } + + return sb.toString(); + } } diff --git a/src/main/java/io/github/dsheirer/edac/CRCP25.java b/src/main/java/io/github/dsheirer/edac/CRCP25.java index f98c5a74..a5b9c389 100644 --- a/src/main/java/io/github/dsheirer/edac/CRCP25.java +++ b/src/main/java/io/github/dsheirer/edac/CRCP25.java @@ -1,23 +1,22 @@ /******************************************************************************* - * 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 + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.edac; import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,445 +25,485 @@ import org.slf4j.LoggerFactory; */ public class CRCP25 { - private final static Logger mLog = LoggerFactory.getLogger( CRCP25.class ); - - /** - * CRC-CCITT 16-bit checksums for a message length of 80 bits plus 16 - * additional checksums representing CRC checksum bit errors - * - * Generated by: - * CRCUtil.generate( 80, 16, 0x11021l, 0xFFFFl, true ); - */ - public static final int[] CCITT_80_CHECKSUMS = new int[] - { - 0x1BCB, 0x8DE5, 0xC6F2, 0x6B69, 0xB5B4, 0x52CA, 0x2175, 0x90BA, 0x404D, - 0xA026, 0x5803, 0xAC01, 0xD600, 0x6310, 0x3998, 0x14DC, 0x27E, 0x92F, - 0x8497, 0xC24B, 0xE125, 0xF092, 0x7059, 0xB82C, 0x5406, 0x2213, 0x9109, - 0xC884, 0x6C52, 0x3E39, 0x9F1C, 0x479E, 0x2BDF, 0x95EF, 0xCAF7, 0xE57B, - 0xF2BD, 0xF95E, 0x74BF, 0xBA5F, 0xDD2F, 0xEE97, 0xF74B, 0xFBA5, 0xFDD2, - 0x76F9, 0xBB7C, 0x55AE, 0x22C7, 0x9163, 0xC8B1, 0xE458, 0x7A3C, 0x350E, - 0x1297, 0x894B, 0xC4A5, 0xE252, 0x7939, 0xBC9C, 0x565E, 0x233F, 0x919F, - 0xC8CF, 0xE467, 0xF233, 0xF919, 0xFC8C, 0x7656, 0x333B, 0x999D, 0xCCCE, - 0x6E77, 0xB73B, 0xDB9D, 0xEDCE, 0x7EF7, 0xBF7B, 0xDFBD, 0xEFDE, 0x0001, - 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, 0x0100, 0x0200, - 0x0400, 0x0800, 0x1000, 0x2000, 0x4000, 0x8000 - }; - - /** - * Confirmed Packet Data Unit CRC-9 checksums, generated by: - * - * long[] table = generate( 135, 9, 0x259l, 0x1FF, false, Parity.NONE ); - */ - public static final int[] CRC9_CHECKSUMS = new int[] - { - 0x1E7, 0x1F3, 0x1F9, 0x1FC, 0x0D2, 0x045, 0x122, 0x0BD, 0x15E, 0x083, - 0x141, 0x1A0, 0x0FC, 0x052, 0x005, 0x102, 0x0AD, 0x156, 0x087, 0x143, - 0x1A1, 0x1D0, 0x0C4, 0x04E, 0x00B, 0x105, 0x182, 0x0ED, 0x176, 0x097, - 0x14B, 0x1A5, 0x1D2, 0x0C5, 0x162, 0x09D, 0x14E, 0x08B, 0x145, 0x1A2, - 0x0FD, 0x17E, 0x093, 0x149, 0x1A4, 0x0FE, 0x053, 0x129, 0x194, 0x0E6, - 0x05F, 0x12F, 0x197, 0x1CB, 0x1E5, 0x1F2, 0x0D5, 0x16A, 0x099, 0x14C, - 0x08A, 0x069, 0x134, 0x0B6, 0x077, 0x13B, 0x19D, 0x1CE, 0x0CB, 0x165, - 0x1B2, 0x0F5, 0x17A, 0x091, 0x148, 0x088, 0x068, 0x018, 0x020, 0x03C, - 0x302, 0x035, 0x11A, 0x0A1, 0x150, 0x084, 0x06E, 0x01B, 0x10D, 0x186, - 0x0EF, 0x177, 0x1BB, 0x1DD, 0x1EE, 0x0DB, 0x16D, 0x1B6, 0x0F7, 0x17B, - 0x1BD, 0x1DE, 0x0C3, 0x161, 0x1B0, 0x0F4, 0x056, 0x007, 0x103, 0x181, - 0x1C0, 0x0CC, 0x04A, 0x009, 0x104, 0x0AE, 0x07B, 0x13D, 0x19E, 0x0E3, - 0x171, 0x1B8, 0x0F0, 0x054, 0x006, 0x02F, 0x117, 0x18B, 0x1C5, 0x1E2, - 0x0DD, 0x16E, 0x09B, 0x14D, 0x1A6 - }; - - /** - * CRC-32 checksums for PDU1 ( HEADER + 1 Block ) messages, generated by: - * - * CRCUtil.generate( 64, 32, 0x104C11DB7l, 0xFFFFFFFFl, true ); - */ - public static final long[] PDU1_CHECKSUMS = new long[] - { - 0x86FFAACCl, 0x411F5BBDl, 0xA08FADDEl, 0x52275834l, 0x2B7322C1l, - 0x95B99160l, 0x48BC466Bl, 0xA45E2335l, 0xD22F119Al, 0x6B770616l, - 0x37DB0DD0l, 0x198D0833l, 0x8CC68419l, 0xC663420Cl, 0x61512FDDl, - 0xB0A897EEl, 0x5A34C52Cl, 0x2F7AEC4Dl, 0x97BD7626l, 0x49BE35C8l, - 0x26BF943Fl, 0x935FCA1Fl, 0xC9AFE50Fl, 0xE4D7F287l, 0xF26BF943l, - 0xF935FCA1l, 0xFC9AFE50l, 0x7C2DF1F3l, 0xBE16F8F9l, 0xDF0B7C7Cl, - 0x6DE530E5l, 0xB6F29872l, 0x5919C2E2l, 0x2EEC6FAAl, 0x1516B90El, - 0x08EBD25Cl, 0x061567F5l, 0x830AB3FAl, 0x43E5D726l, 0x23926548l, - 0x13A9BC7Fl, 0x89D4DE3Fl, 0xC4EA6F1Fl, 0xE275378Fl, 0xF13A9BC7l, - 0xF89D4DE3l, 0xFC4EA6F1l, 0xFE275378l, 0x7D732767l, 0xBEB993B3l, - 0xDF5CC9D9l, 0xEFAE64ECl, 0x75B7BCADl, 0xBADBDE56l, 0x5F0D61F0l, - 0x2DE63E23l, 0x96F31F11l, 0xCB798F88l, 0x67DC491Fl, 0xB3EE248Fl, - 0xD9F71247l, 0xECFB8923l, 0xF67DC491l, 0xFB3EE248l, 0x00000001l, - 0x00000002l, 0x00000004l, 0x00000008l, 0x00000010l, 0x00000020l, - 0x00000040l, 0x00000080l, 0x00000100l, 0x00000200l, 0x00000400l, - 0x00000800l, 0x00001000l, 0x00002000l, 0x00004000l, 0x00008000l, - 0x00010000l, 0x00020000l, 0x00040000l, 0x00080000l, 0x00100000l, - 0x00200000l, 0x00400000l, 0x00800000l, 0x01000000l, 0x02000000l, - 0x04000000l, 0x08000000l, 0x10000000l, 0x20000000l, 0x40000000l, - 0x80000000l - }; - - /** - * CRC-32 checksums for PDU2 ( HEADER + 2 Blocks ) messages, generated by: - * - * CRCUtil.generate( 160, 32, 0x104C11DB7l, 0xFFFFFFFFl, true ); - */ - public static final long[] PDU2_CHECKSUMS = new long[] - { - 0x9D231959l, 0xCE918CACl, 0x6528488Dl, 0xB2942446l, 0x5B2A9CF8l, - 0x2FF5C0A7l, 0x97FAE053l, 0xCBFD7029l, 0xE5FEB814l, 0x709FD2D1l, - 0xB84FE968l, 0x5E477A6Fl, 0xAF23BD37l, 0xD791DE9Bl, 0xEBC8EF4Dl, - 0xF5E477A6l, 0x7892B508l, 0x3E29D45Fl, 0x9F14EA2Fl, 0xCF8A7517l, - 0xE7C53A8Bl, 0xF3E29D45l, 0xF9F14EA2l, 0x7E98298Al, 0x3D2C9A1El, - 0x1CF6C3D4l, 0x0C1BEF31l, 0x860DF798l, 0x41667517l, 0xA0B33A8Bl, - 0xD0599D45l, 0xE82CCEA2l, 0x7676E98Al, 0x395BFA1El, 0x1ECD73D4l, - 0x0D063731l, 0x86831B98l, 0x41210317l, 0xA090818Bl, 0xD04840C5l, - 0xE8242062l, 0x76729EEAl, 0x3959C1AEl, 0x1ECC6E0Cl, 0x0D06B9DDl, - 0x86835CEEl, 0x412120ACl, 0x22F01E8Dl, 0x91780F46l, 0x4ADC8978l, - 0x270ECA67l, 0x93876533l, 0xC9C3B299l, 0xE4E1D94Cl, 0x7010627Dl, - 0xB808313El, 0x5E649644l, 0x2D52C5F9l, 0x96A962FCl, 0x49343FA5l, - 0xA49A1FD2l, 0x502D8132l, 0x2A764E42l, 0x175BA9FAl, 0x09CD5A26l, - 0x068623C8l, 0x01239F3Fl, 0x8091CF9Fl, 0xC048E7CFl, 0xE02473E7l, - 0xF01239F3l, 0xF8091CF9l, 0xFC048E7Cl, 0x7C62C9E5l, 0xBE3164F2l, - 0x5D783CA2l, 0x2CDC908Al, 0x140EC69El, 0x0867ED94l, 0x06537811l, - 0x8329BC08l, 0x43F450DFl, 0xA1FA286Fl, 0xD0FD1437l, 0xE87E8A1Bl, - 0xF43F450Dl, 0xFA1FA286l, 0x7F6F5F98l, 0x3DD72117l, 0x9EEB908Bl, - 0xCF75C845l, 0xE7BAE422l, 0x71BDFCCAl, 0x3ABE70BEl, 0x1F3FB684l, - 0x0DFF5599l, 0x86FFAACCl, 0x411F5BBDl, 0xA08FADDEl, 0x52275834l, - 0x2B7322C1l, 0x95B99160l, 0x48BC466Bl, 0xA45E2335l, 0xD22F119Al, - 0x6B770616l, 0x37DB0DD0l, 0x198D0833l, 0x8CC68419l, 0xC663420Cl, - 0x61512FDDl, 0xB0A897EEl, 0x5A34C52Cl, 0x2F7AEC4Dl, 0x97BD7626l, - 0x49BE35C8l, 0x26BF943Fl, 0x935FCA1Fl, 0xC9AFE50Fl, 0xE4D7F287l, - 0xF26BF943l, 0xF935FCA1l, 0xFC9AFE50l, 0x7C2DF1F3l, 0xBE16F8F9l, - 0xDF0B7C7Cl, 0x6DE530E5l, 0xB6F29872l, 0x5919C2E2l, 0x2EEC6FAAl, - 0x1516B90El, 0x08EBD25Cl, 0x061567F5l, 0x830AB3FAl, 0x43E5D726l, - 0x23926548l, 0x13A9BC7Fl, 0x89D4DE3Fl, 0xC4EA6F1Fl, 0xE275378Fl, - 0xF13A9BC7l, 0xF89D4DE3l, 0xFC4EA6F1l, 0xFE275378l, 0x7D732767l, - 0xBEB993B3l, 0xDF5CC9D9l, 0xEFAE64ECl, 0x75B7BCADl, 0xBADBDE56l, - 0x5F0D61F0l, 0x2DE63E23l, 0x96F31F11l, 0xCB798F88l, 0x67DC491Fl, - 0xB3EE248Fl, 0xD9F71247l, 0xECFB8923l, 0xF67DC491l, 0xFB3EE248l, - 0x00000001l, 0x00000002l, 0x00000004l, 0x00000008l, 0x00000010l, - 0x00000020l, 0x00000040l, 0x00000080l, 0x00000100l, 0x00000200l, - 0x00000400l, 0x00000800l, 0x00001000l, 0x00002000l, 0x00004000l, - 0x00008000l, 0x00010000l, 0x00020000l, 0x00040000l, 0x00080000l, - 0x00100000l, 0x00200000l, 0x00400000l, 0x00800000l, 0x01000000l, - 0x02000000l, 0x04000000l, 0x08000000l, 0x10000000l, 0x20000000l, - 0x40000000l, 0x80000000l - }; - - /** - * CRC-32 checksums for PDU3 ( HEADER + 3 Blocks ) messages, generated by: - * - * CRCUtil.generate( 256, 32, 0x104C11DB7l, 0xFFFFFFFFl, true ); - */ - public static final long[] PDU3_CHECKSUMS = new long[] - { - 0xAA5FA470l, 0x574F5CE3l, 0xABA7AE71l, 0xD5D3D738l, 0x68896547l, - 0xB444B2A3l, 0xDA225951l, 0xED112CA8l, 0x74E8188Fl, 0xBA740C47l, - 0xDD3A0623l, 0xEE9D0311l, 0xF74E8188l, 0x79C7CE1Fl, 0xBCE3E70Fl, - 0xDE71F387l, 0xEF38F9C3l, 0xF79C7CE1l, 0xFBCE3E70l, 0x7F8791E3l, - 0xBFC3C8F1l, 0xDFE1E478l, 0x6D907CE7l, 0xB6C83E73l, 0xDB641F39l, - 0xEDB20F9Cl, 0x74B98915l, 0xBA5CC48Al, 0x5F4EEC9El, 0x2DC7F894l, - 0x14837291l, 0x8A41B948l, 0x4740527Fl, 0xA3A0293Fl, 0xD1D0149Fl, - 0xE8E80A4Fl, 0xF4740527l, 0xFA3A0293l, 0xFD1D0149l, 0xFE8E80A4l, - 0x7D27CE89l, 0xBE93E744l, 0x5D297D79l, 0xAE94BEBCl, 0x552AD185l, - 0xAA9568C2l, 0x572A3ABAl, 0x29F59386l, 0x169A4718l, 0x092DAD57l, - 0x8496D6ABl, 0xC24B6B55l, 0xE125B5AAl, 0x72F2540El, 0x3B19A4DCl, - 0x1FEC5CB5l, 0x8FF62E5Al, 0x459B99F6l, 0x20AD4220l, 0x12362FCBl, - 0x891B17E5l, 0xC48D8BF2l, 0x60264B22l, 0x3273AB4Al, 0x1B595B7El, - 0x0FCC2364l, 0x05869F69l, 0x82C34FB4l, 0x43012901l, 0xA1809480l, - 0x52A0C49Bl, 0xA950624Dl, 0xD4A83126l, 0x68349648l, 0x367AC5FFl, - 0x9B3D62FFl, 0xCD9EB17Fl, 0xE6CF58BFl, 0xF367AC5Fl, 0xF9B3D62Fl, - 0xFCD9EB17l, 0xFE6CF58Bl, 0xFF367AC5l, 0xFF9B3D62l, 0x7DAD106Al, - 0x3CB606EEl, 0x1C3B8DACl, 0x0C7D480Dl, 0x863EA406l, 0x417FDCD8l, - 0x22DF60B7l, 0x916FB05Bl, 0xC8B7D82Dl, 0xE45BEC16l, 0x704D78D0l, - 0x3A4632B3l, 0x9D231959l, 0xCE918CACl, 0x6528488Dl, 0xB2942446l, - 0x5B2A9CF8l, 0x2FF5C0A7l, 0x97FAE053l, 0xCBFD7029l, 0xE5FEB814l, - 0x709FD2D1l, 0xB84FE968l, 0x5E477A6Fl, 0xAF23BD37l, 0xD791DE9Bl, - 0xEBC8EF4Dl, 0xF5E477A6l, 0x7892B508l, 0x3E29D45Fl, 0x9F14EA2Fl, - 0xCF8A7517l, 0xE7C53A8Bl, 0xF3E29D45l, 0xF9F14EA2l, 0x7E98298Al, - 0x3D2C9A1El, 0x1CF6C3D4l, 0x0C1BEF31l, 0x860DF798l, 0x41667517l, - 0xA0B33A8Bl, 0xD0599D45l, 0xE82CCEA2l, 0x7676E98Al, 0x395BFA1El, - 0x1ECD73D4l, 0x0D063731l, 0x86831B98l, 0x41210317l, 0xA090818Bl, - 0xD04840C5l, 0xE8242062l, 0x76729EEAl, 0x3959C1AEl, 0x1ECC6E0Cl, - 0x0D06B9DDl, 0x86835CEEl, 0x412120ACl, 0x22F01E8Dl, 0x91780F46l, - 0x4ADC8978l, 0x270ECA67l, 0x93876533l, 0xC9C3B299l, 0xE4E1D94Cl, - 0x7010627Dl, 0xB808313El, 0x5E649644l, 0x2D52C5F9l, 0x96A962FCl, - 0x49343FA5l, 0xA49A1FD2l, 0x502D8132l, 0x2A764E42l, 0x175BA9FAl, - 0x09CD5A26l, 0x068623C8l, 0x01239F3Fl, 0x8091CF9Fl, 0xC048E7CFl, - 0xE02473E7l, 0xF01239F3l, 0xF8091CF9l, 0xFC048E7Cl, 0x7C62C9E5l, - 0xBE3164F2l, 0x5D783CA2l, 0x2CDC908Al, 0x140EC69El, 0x0867ED94l, - 0x06537811l, 0x8329BC08l, 0x43F450DFl, 0xA1FA286Fl, 0xD0FD1437l, - 0xE87E8A1Bl, 0xF43F450Dl, 0xFA1FA286l, 0x7F6F5F98l, 0x3DD72117l, - 0x9EEB908Bl, 0xCF75C845l, 0xE7BAE422l, 0x71BDFCCAl, 0x3ABE70BEl, - 0x1F3FB684l, 0x0DFF5599l, 0x86FFAACCl, 0x411F5BBDl, 0xA08FADDEl, - 0x52275834l, 0x2B7322C1l, 0x95B99160l, 0x48BC466Bl, 0xA45E2335l, - 0xD22F119Al, 0x6B770616l, 0x37DB0DD0l, 0x198D0833l, 0x8CC68419l, - 0xC663420Cl, 0x61512FDDl, 0xB0A897EEl, 0x5A34C52Cl, 0x2F7AEC4Dl, - 0x97BD7626l, 0x49BE35C8l, 0x26BF943Fl, 0x935FCA1Fl, 0xC9AFE50Fl, - 0xE4D7F287l, 0xF26BF943l, 0xF935FCA1l, 0xFC9AFE50l, 0x7C2DF1F3l, - 0xBE16F8F9l, 0xDF0B7C7Cl, 0x6DE530E5l, 0xB6F29872l, 0x5919C2E2l, - 0x2EEC6FAAl, 0x1516B90El, 0x08EBD25Cl, 0x061567F5l, 0x830AB3FAl, - 0x43E5D726l, 0x23926548l, 0x13A9BC7Fl, 0x89D4DE3Fl, 0xC4EA6F1Fl, - 0xE275378Fl, 0xF13A9BC7l, 0xF89D4DE3l, 0xFC4EA6F1l, 0xFE275378l, - 0x7D732767l, 0xBEB993B3l, 0xDF5CC9D9l, 0xEFAE64ECl, 0x75B7BCADl, - 0xBADBDE56l, 0x5F0D61F0l, 0x2DE63E23l, 0x96F31F11l, 0xCB798F88l, - 0x67DC491Fl, 0xB3EE248Fl, 0xD9F71247l, 0xECFB8923l, 0xF67DC491l, - 0xFB3EE248l, 0x00000001l, 0x00000002l, 0x00000004l, 0x00000008l, - 0x00000010l, 0x00000020l, 0x00000040l, 0x00000080l, 0x00000100l, - 0x00000200l, 0x00000400l, 0x00000800l, 0x00001000l, 0x00002000l, - 0x00004000l, 0x00008000l, 0x00010000l, 0x00020000l, 0x00040000l, - 0x00080000l, 0x00100000l, 0x00200000l, 0x00400000l, 0x00800000l, - 0x01000000l, 0x02000000l, 0x04000000l, 0x08000000l, 0x10000000l, - 0x20000000l, 0x40000000l, 0x80000000l - }; - - /** - * Performs error detection and single-bit error correction against the - * data blocks of a PDU1 message. - */ - public static BinaryMessage correctPDU1( BinaryMessage message ) - { - return correctPDU( message, PDU1_CHECKSUMS, 224 ); - } + private final static Logger mLog = LoggerFactory.getLogger(CRCP25.class); - /** - * Performs error detection and single-bit error correction against the - * data blocks of a PDU2 message. - */ - public static BinaryMessage correctPDU2( BinaryMessage message ) - { - return correctPDU( message, PDU2_CHECKSUMS, 320 ); - } + /** + * CRC-CCITT 16-bit checksums for a message length of 80 bits plus 16 + * additional checksums representing CRC checksum bit errors + * + * Generated by: + * CRCUtil.generate( 80, 16, 0x11021l, 0xFFFFl, true ); + */ + public static final int[] CCITT_80_CHECKSUMS = new int[] + { + 0x1BCB, 0x8DE5, 0xC6F2, 0x6B69, 0xB5B4, 0x52CA, 0x2175, 0x90BA, 0x404D, + 0xA026, 0x5803, 0xAC01, 0xD600, 0x6310, 0x3998, 0x14DC, 0x27E, 0x92F, + 0x8497, 0xC24B, 0xE125, 0xF092, 0x7059, 0xB82C, 0x5406, 0x2213, 0x9109, + 0xC884, 0x6C52, 0x3E39, 0x9F1C, 0x479E, 0x2BDF, 0x95EF, 0xCAF7, 0xE57B, + 0xF2BD, 0xF95E, 0x74BF, 0xBA5F, 0xDD2F, 0xEE97, 0xF74B, 0xFBA5, 0xFDD2, + 0x76F9, 0xBB7C, 0x55AE, 0x22C7, 0x9163, 0xC8B1, 0xE458, 0x7A3C, 0x350E, + 0x1297, 0x894B, 0xC4A5, 0xE252, 0x7939, 0xBC9C, 0x565E, 0x233F, 0x919F, + 0xC8CF, 0xE467, 0xF233, 0xF919, 0xFC8C, 0x7656, 0x333B, 0x999D, 0xCCCE, + 0x6E77, 0xB73B, 0xDB9D, 0xEDCE, 0x7EF7, 0xBF7B, 0xDFBD, 0xEFDE, 0x0001, + 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, 0x0100, 0x0200, + 0x0400, 0x0800, 0x1000, 0x2000, 0x4000, 0x8000 + }; - /** - * Performs error detection and single-bit error correction against the - * data blocks of a PDU3 message. - */ - public static BinaryMessage correctPDU3( BinaryMessage message ) - { - return correctPDU( message, PDU3_CHECKSUMS, 416 ); - } + /** + * Confirmed Packet Data Unit CRC-9 checksums, generated by: + * + * long[] table = CRCUtil.generate( 135, 9, 0x259l, 0x1FF, false, Parity.NONE ); + */ + public static final int[] CRC9_CHECKSUMS = new int[] + { + 0x1E7, 0x1F3, 0x1F9, 0x1FC, 0x0D2, 0x045, 0x122, 0x0BD, 0x15E, 0x083, + 0x141, 0x1A0, 0x0FC, 0x052, 0x005, 0x102, 0x0AD, 0x156, 0x087, 0x143, + 0x1A1, 0x1D0, 0x0C4, 0x04E, 0x00B, 0x105, 0x182, 0x0ED, 0x176, 0x097, + 0x14B, 0x1A5, 0x1D2, 0x0C5, 0x162, 0x09D, 0x14E, 0x08B, 0x145, 0x1A2, + 0x0FD, 0x17E, 0x093, 0x149, 0x1A4, 0x0FE, 0x053, 0x129, 0x194, 0x0E6, + 0x05F, 0x12F, 0x197, 0x1CB, 0x1E5, 0x1F2, 0x0D5, 0x16A, 0x099, 0x14C, + 0x08A, 0x069, 0x134, 0x0B6, 0x077, 0x13B, 0x19D, 0x1CE, 0x0CB, 0x165, + 0x1B2, 0x0F5, 0x17A, 0x091, 0x148, 0x088, 0x068, 0x018, 0x020, 0x03C, + 0x032, 0x035, 0x11A, 0x0A1, 0x150, 0x084, 0x06E, 0x01B, 0x10D, 0x186, + 0x0EF, 0x177, 0x1BB, 0x1DD, 0x1EE, 0x0DB, 0x16D, 0x1B6, 0x0F7, 0x17B, + 0x1BD, 0x1DE, 0x0C3, 0x161, 0x1B0, 0x0F4, 0x056, 0x007, 0x103, 0x181, + 0x1C0, 0x0CC, 0x04A, 0x009, 0x104, 0x0AE, 0x07B, 0x13D, 0x19E, 0x0E3, + 0x171, 0x1B8, 0x0F0, 0x054, 0x006, 0x02F, 0x117, 0x18B, 0x1C5, 0x1E2, + 0x0DD, 0x16E, 0x09B, 0x14D, 0x1A6 + }; - public static BinaryMessage correctPDU( BinaryMessage message, long[] checksums, int crcStart ) - { - long calculated = 0; //Starting value - - int messageStart = 160; + /** + * CRC-32 checksums for PDU1 ( HEADER + 1 Block ) messages, generated by: + * + * CRCUtil.generate( 64, 32, 0x104C11DB7l, 0xFFFFFFFFl, true ); + */ + public static final long[] PDU1_CHECKSUMS = new long[] + { + 0x86FFAACCl, 0x411F5BBDl, 0xA08FADDEl, 0x52275834l, 0x2B7322C1l, + 0x95B99160l, 0x48BC466Bl, 0xA45E2335l, 0xD22F119Al, 0x6B770616l, + 0x37DB0DD0l, 0x198D0833l, 0x8CC68419l, 0xC663420Cl, 0x61512FDDl, + 0xB0A897EEl, 0x5A34C52Cl, 0x2F7AEC4Dl, 0x97BD7626l, 0x49BE35C8l, + 0x26BF943Fl, 0x935FCA1Fl, 0xC9AFE50Fl, 0xE4D7F287l, 0xF26BF943l, + 0xF935FCA1l, 0xFC9AFE50l, 0x7C2DF1F3l, 0xBE16F8F9l, 0xDF0B7C7Cl, + 0x6DE530E5l, 0xB6F29872l, 0x5919C2E2l, 0x2EEC6FAAl, 0x1516B90El, + 0x08EBD25Cl, 0x061567F5l, 0x830AB3FAl, 0x43E5D726l, 0x23926548l, + 0x13A9BC7Fl, 0x89D4DE3Fl, 0xC4EA6F1Fl, 0xE275378Fl, 0xF13A9BC7l, + 0xF89D4DE3l, 0xFC4EA6F1l, 0xFE275378l, 0x7D732767l, 0xBEB993B3l, + 0xDF5CC9D9l, 0xEFAE64ECl, 0x75B7BCADl, 0xBADBDE56l, 0x5F0D61F0l, + 0x2DE63E23l, 0x96F31F11l, 0xCB798F88l, 0x67DC491Fl, 0xB3EE248Fl, + 0xD9F71247l, 0xECFB8923l, 0xF67DC491l, 0xFB3EE248l, 0x00000001l, + 0x00000002l, 0x00000004l, 0x00000008l, 0x00000010l, 0x00000020l, + 0x00000040l, 0x00000080l, 0x00000100l, 0x00000200l, 0x00000400l, + 0x00000800l, 0x00001000l, 0x00002000l, 0x00004000l, 0x00008000l, + 0x00010000l, 0x00020000l, 0x00040000l, 0x00080000l, 0x00100000l, + 0x00200000l, 0x00400000l, 0x00800000l, 0x01000000l, 0x02000000l, + 0x04000000l, 0x08000000l, 0x10000000l, 0x20000000l, 0x40000000l, + 0x80000000l + }; - /* Iterate the set bits and XOR running checksum with lookup value */ - for (int i = message.nextSetBit( messageStart ); - i >= messageStart && i < crcStart; - i = message.nextSetBit( i+1 ) ) - { - calculated ^= checksums[ i - messageStart ]; - } - - long checksum = getLongChecksum( message, crcStart, 32 ); + /** + * CRC-32 checksums for PDU2 ( HEADER + 2 Blocks ) messages, generated by: + * + * CRCUtil.generate( 160, 32, 0x104C11DB7l, 0xFFFFFFFFl, true ); + */ + public static final long[] PDU2_CHECKSUMS = new long[] + { + 0x9D231959l, 0xCE918CACl, 0x6528488Dl, 0xB2942446l, 0x5B2A9CF8l, + 0x2FF5C0A7l, 0x97FAE053l, 0xCBFD7029l, 0xE5FEB814l, 0x709FD2D1l, + 0xB84FE968l, 0x5E477A6Fl, 0xAF23BD37l, 0xD791DE9Bl, 0xEBC8EF4Dl, + 0xF5E477A6l, 0x7892B508l, 0x3E29D45Fl, 0x9F14EA2Fl, 0xCF8A7517l, + 0xE7C53A8Bl, 0xF3E29D45l, 0xF9F14EA2l, 0x7E98298Al, 0x3D2C9A1El, + 0x1CF6C3D4l, 0x0C1BEF31l, 0x860DF798l, 0x41667517l, 0xA0B33A8Bl, + 0xD0599D45l, 0xE82CCEA2l, 0x7676E98Al, 0x395BFA1El, 0x1ECD73D4l, + 0x0D063731l, 0x86831B98l, 0x41210317l, 0xA090818Bl, 0xD04840C5l, + 0xE8242062l, 0x76729EEAl, 0x3959C1AEl, 0x1ECC6E0Cl, 0x0D06B9DDl, + 0x86835CEEl, 0x412120ACl, 0x22F01E8Dl, 0x91780F46l, 0x4ADC8978l, + 0x270ECA67l, 0x93876533l, 0xC9C3B299l, 0xE4E1D94Cl, 0x7010627Dl, + 0xB808313El, 0x5E649644l, 0x2D52C5F9l, 0x96A962FCl, 0x49343FA5l, + 0xA49A1FD2l, 0x502D8132l, 0x2A764E42l, 0x175BA9FAl, 0x09CD5A26l, + 0x068623C8l, 0x01239F3Fl, 0x8091CF9Fl, 0xC048E7CFl, 0xE02473E7l, + 0xF01239F3l, 0xF8091CF9l, 0xFC048E7Cl, 0x7C62C9E5l, 0xBE3164F2l, + 0x5D783CA2l, 0x2CDC908Al, 0x140EC69El, 0x0867ED94l, 0x06537811l, + 0x8329BC08l, 0x43F450DFl, 0xA1FA286Fl, 0xD0FD1437l, 0xE87E8A1Bl, + 0xF43F450Dl, 0xFA1FA286l, 0x7F6F5F98l, 0x3DD72117l, 0x9EEB908Bl, + 0xCF75C845l, 0xE7BAE422l, 0x71BDFCCAl, 0x3ABE70BEl, 0x1F3FB684l, + 0x0DFF5599l, 0x86FFAACCl, 0x411F5BBDl, 0xA08FADDEl, 0x52275834l, + 0x2B7322C1l, 0x95B99160l, 0x48BC466Bl, 0xA45E2335l, 0xD22F119Al, + 0x6B770616l, 0x37DB0DD0l, 0x198D0833l, 0x8CC68419l, 0xC663420Cl, + 0x61512FDDl, 0xB0A897EEl, 0x5A34C52Cl, 0x2F7AEC4Dl, 0x97BD7626l, + 0x49BE35C8l, 0x26BF943Fl, 0x935FCA1Fl, 0xC9AFE50Fl, 0xE4D7F287l, + 0xF26BF943l, 0xF935FCA1l, 0xFC9AFE50l, 0x7C2DF1F3l, 0xBE16F8F9l, + 0xDF0B7C7Cl, 0x6DE530E5l, 0xB6F29872l, 0x5919C2E2l, 0x2EEC6FAAl, + 0x1516B90El, 0x08EBD25Cl, 0x061567F5l, 0x830AB3FAl, 0x43E5D726l, + 0x23926548l, 0x13A9BC7Fl, 0x89D4DE3Fl, 0xC4EA6F1Fl, 0xE275378Fl, + 0xF13A9BC7l, 0xF89D4DE3l, 0xFC4EA6F1l, 0xFE275378l, 0x7D732767l, + 0xBEB993B3l, 0xDF5CC9D9l, 0xEFAE64ECl, 0x75B7BCADl, 0xBADBDE56l, + 0x5F0D61F0l, 0x2DE63E23l, 0x96F31F11l, 0xCB798F88l, 0x67DC491Fl, + 0xB3EE248Fl, 0xD9F71247l, 0xECFB8923l, 0xF67DC491l, 0xFB3EE248l, + 0x00000001l, 0x00000002l, 0x00000004l, 0x00000008l, 0x00000010l, + 0x00000020l, 0x00000040l, 0x00000080l, 0x00000100l, 0x00000200l, + 0x00000400l, 0x00000800l, 0x00001000l, 0x00002000l, 0x00004000l, + 0x00008000l, 0x00010000l, 0x00020000l, 0x00040000l, 0x00080000l, + 0x00100000l, 0x00200000l, 0x00400000l, 0x00800000l, 0x01000000l, + 0x02000000l, 0x04000000l, 0x08000000l, 0x10000000l, 0x20000000l, + 0x40000000l, 0x80000000l + }; - long error = calculated ^ checksum; - - if( error == 0 || error == 0xFFFFFFFFl ) - { - message.setCRC( CRC.PASSED ); - - return message; - } - else - { - int errorLocation = getBitError( error, checksums ); - - if( errorLocation >= 0 ) - { - message.flip( errorLocation + messageStart ); + /** + * CRC-32 checksums for PDU3 ( HEADER + 3 Blocks ) messages, generated by: + * + * CRCUtil.generate( 256, 32, 0x104C11DB7l, 0xFFFFFFFFl, true ); + */ + public static final long[] PDU3_CHECKSUMS = new long[] + { + 0xAA5FA470l, 0x574F5CE3l, 0xABA7AE71l, 0xD5D3D738l, 0x68896547l, + 0xB444B2A3l, 0xDA225951l, 0xED112CA8l, 0x74E8188Fl, 0xBA740C47l, + 0xDD3A0623l, 0xEE9D0311l, 0xF74E8188l, 0x79C7CE1Fl, 0xBCE3E70Fl, + 0xDE71F387l, 0xEF38F9C3l, 0xF79C7CE1l, 0xFBCE3E70l, 0x7F8791E3l, + 0xBFC3C8F1l, 0xDFE1E478l, 0x6D907CE7l, 0xB6C83E73l, 0xDB641F39l, + 0xEDB20F9Cl, 0x74B98915l, 0xBA5CC48Al, 0x5F4EEC9El, 0x2DC7F894l, + 0x14837291l, 0x8A41B948l, 0x4740527Fl, 0xA3A0293Fl, 0xD1D0149Fl, + 0xE8E80A4Fl, 0xF4740527l, 0xFA3A0293l, 0xFD1D0149l, 0xFE8E80A4l, + 0x7D27CE89l, 0xBE93E744l, 0x5D297D79l, 0xAE94BEBCl, 0x552AD185l, + 0xAA9568C2l, 0x572A3ABAl, 0x29F59386l, 0x169A4718l, 0x092DAD57l, + 0x8496D6ABl, 0xC24B6B55l, 0xE125B5AAl, 0x72F2540El, 0x3B19A4DCl, + 0x1FEC5CB5l, 0x8FF62E5Al, 0x459B99F6l, 0x20AD4220l, 0x12362FCBl, + 0x891B17E5l, 0xC48D8BF2l, 0x60264B22l, 0x3273AB4Al, 0x1B595B7El, + 0x0FCC2364l, 0x05869F69l, 0x82C34FB4l, 0x43012901l, 0xA1809480l, + 0x52A0C49Bl, 0xA950624Dl, 0xD4A83126l, 0x68349648l, 0x367AC5FFl, + 0x9B3D62FFl, 0xCD9EB17Fl, 0xE6CF58BFl, 0xF367AC5Fl, 0xF9B3D62Fl, + 0xFCD9EB17l, 0xFE6CF58Bl, 0xFF367AC5l, 0xFF9B3D62l, 0x7DAD106Al, + 0x3CB606EEl, 0x1C3B8DACl, 0x0C7D480Dl, 0x863EA406l, 0x417FDCD8l, + 0x22DF60B7l, 0x916FB05Bl, 0xC8B7D82Dl, 0xE45BEC16l, 0x704D78D0l, + 0x3A4632B3l, 0x9D231959l, 0xCE918CACl, 0x6528488Dl, 0xB2942446l, + 0x5B2A9CF8l, 0x2FF5C0A7l, 0x97FAE053l, 0xCBFD7029l, 0xE5FEB814l, + 0x709FD2D1l, 0xB84FE968l, 0x5E477A6Fl, 0xAF23BD37l, 0xD791DE9Bl, + 0xEBC8EF4Dl, 0xF5E477A6l, 0x7892B508l, 0x3E29D45Fl, 0x9F14EA2Fl, + 0xCF8A7517l, 0xE7C53A8Bl, 0xF3E29D45l, 0xF9F14EA2l, 0x7E98298Al, + 0x3D2C9A1El, 0x1CF6C3D4l, 0x0C1BEF31l, 0x860DF798l, 0x41667517l, + 0xA0B33A8Bl, 0xD0599D45l, 0xE82CCEA2l, 0x7676E98Al, 0x395BFA1El, + 0x1ECD73D4l, 0x0D063731l, 0x86831B98l, 0x41210317l, 0xA090818Bl, + 0xD04840C5l, 0xE8242062l, 0x76729EEAl, 0x3959C1AEl, 0x1ECC6E0Cl, + 0x0D06B9DDl, 0x86835CEEl, 0x412120ACl, 0x22F01E8Dl, 0x91780F46l, + 0x4ADC8978l, 0x270ECA67l, 0x93876533l, 0xC9C3B299l, 0xE4E1D94Cl, + 0x7010627Dl, 0xB808313El, 0x5E649644l, 0x2D52C5F9l, 0x96A962FCl, + 0x49343FA5l, 0xA49A1FD2l, 0x502D8132l, 0x2A764E42l, 0x175BA9FAl, + 0x09CD5A26l, 0x068623C8l, 0x01239F3Fl, 0x8091CF9Fl, 0xC048E7CFl, + 0xE02473E7l, 0xF01239F3l, 0xF8091CF9l, 0xFC048E7Cl, 0x7C62C9E5l, + 0xBE3164F2l, 0x5D783CA2l, 0x2CDC908Al, 0x140EC69El, 0x0867ED94l, + 0x06537811l, 0x8329BC08l, 0x43F450DFl, 0xA1FA286Fl, 0xD0FD1437l, + 0xE87E8A1Bl, 0xF43F450Dl, 0xFA1FA286l, 0x7F6F5F98l, 0x3DD72117l, + 0x9EEB908Bl, 0xCF75C845l, 0xE7BAE422l, 0x71BDFCCAl, 0x3ABE70BEl, + 0x1F3FB684l, 0x0DFF5599l, 0x86FFAACCl, 0x411F5BBDl, 0xA08FADDEl, + 0x52275834l, 0x2B7322C1l, 0x95B99160l, 0x48BC466Bl, 0xA45E2335l, + 0xD22F119Al, 0x6B770616l, 0x37DB0DD0l, 0x198D0833l, 0x8CC68419l, + 0xC663420Cl, 0x61512FDDl, 0xB0A897EEl, 0x5A34C52Cl, 0x2F7AEC4Dl, + 0x97BD7626l, 0x49BE35C8l, 0x26BF943Fl, 0x935FCA1Fl, 0xC9AFE50Fl, + 0xE4D7F287l, 0xF26BF943l, 0xF935FCA1l, 0xFC9AFE50l, 0x7C2DF1F3l, + 0xBE16F8F9l, 0xDF0B7C7Cl, 0x6DE530E5l, 0xB6F29872l, 0x5919C2E2l, + 0x2EEC6FAAl, 0x1516B90El, 0x08EBD25Cl, 0x061567F5l, 0x830AB3FAl, + 0x43E5D726l, 0x23926548l, 0x13A9BC7Fl, 0x89D4DE3Fl, 0xC4EA6F1Fl, + 0xE275378Fl, 0xF13A9BC7l, 0xF89D4DE3l, 0xFC4EA6F1l, 0xFE275378l, + 0x7D732767l, 0xBEB993B3l, 0xDF5CC9D9l, 0xEFAE64ECl, 0x75B7BCADl, + 0xBADBDE56l, 0x5F0D61F0l, 0x2DE63E23l, 0x96F31F11l, 0xCB798F88l, + 0x67DC491Fl, 0xB3EE248Fl, 0xD9F71247l, 0xECFB8923l, 0xF67DC491l, + 0xFB3EE248l, 0x00000001l, 0x00000002l, 0x00000004l, 0x00000008l, + 0x00000010l, 0x00000020l, 0x00000040l, 0x00000080l, 0x00000100l, + 0x00000200l, 0x00000400l, 0x00000800l, 0x00001000l, 0x00002000l, + 0x00004000l, 0x00008000l, 0x00010000l, 0x00020000l, 0x00040000l, + 0x00080000l, 0x00100000l, 0x00200000l, 0x00400000l, 0x00800000l, + 0x01000000l, 0x02000000l, 0x04000000l, 0x08000000l, 0x10000000l, + 0x20000000l, 0x40000000l, 0x80000000l + }; - message.setCRC( CRC.CORRECTED ); - - return message; - } - } + /** + * Performs error detection and single-bit error correction against the + * data blocks of a PDU1 message. + */ + public static BinaryMessage correctPDU1(BinaryMessage message) + { + return correctPDU(message, PDU1_CHECKSUMS, 224); + } - message.setCRC( CRC.FAILED_CRC ); - - return message; - } + /** + * Performs error detection and single-bit error correction against the + * data blocks of a PDU2 message. + */ + public static BinaryMessage correctPDU2(BinaryMessage message) + { + return correctPDU(message, PDU2_CHECKSUMS, 320); + } - /** - * Error detection and correction of single-bit errors for CCITT 16-bit - * CRC protected 80-bit messages. - */ - public static BinaryMessage correctCCITT80( BinaryMessage message, - int messageStart, - int crcStart ) - { - int calculated = 0; //Starting value + /** + * Performs error detection and single-bit error correction against the + * data blocks of a PDU3 message. + */ + public static BinaryMessage correctPDU3(BinaryMessage message) + { + return correctPDU(message, PDU3_CHECKSUMS, 416); + } - /* Iterate the set bits and XOR running checksum with lookup value */ - for (int i = message.nextSetBit( messageStart ); - i >= messageStart && i < crcStart; - i = message.nextSetBit( i+1 ) ) - { - calculated ^= CCITT_80_CHECKSUMS[ i - messageStart ]; - } - - int checksum = getIntChecksum( message, crcStart, 16 ); + public static BinaryMessage correctPDU(BinaryMessage message, long[] checksums, int crcStart) + { + long calculated = 0; //Starting value - int residual = calculated ^ checksum; - - if( residual == 0 || residual == 0xFFFF ) - { - message.setCRC( CRC.PASSED ); - - return message; - } - else - { - int errorLocation = getBitError( residual, CCITT_80_CHECKSUMS ); - - if( errorLocation >= 0 ) - { - message.flip( errorLocation + messageStart ); - - message.setCRC( CRC.CORRECTED ); - - return message; - } - } + int messageStart = 160; - message.setCRC( CRC.FAILED_CRC ); - - return message; - } + /* Iterate the set bits and XOR running checksum with lookup value */ + for(int i = message.nextSetBit(messageStart); + i >= messageStart && i < crcStart; + i = message.nextSetBit(i + 1)) + { + calculated ^= checksums[i - messageStart]; + } - /** - * Error detection for CRC-9 protected Confirmed Packet Data blocks. These - * data blocks have a slightly complicated structure because the checksum - * is located between bits 7-15, within a 144 bit block. The checksums - * were generated assuming that the message is contiguous from 0 - 134 bits. - * No data correction is performed. - */ - public static CRC checkCRC9( BinaryMessage message, int messageStart ) - { - int calculated = 0x0; //Initial fill of all ones + long checksum = getLongChecksum(message, crcStart, 32); - /* Iterate the set bits and XOR running checksum with lookup value */ - for (int i = message.nextSetBit( messageStart ); - i >= messageStart && i < messageStart + 144; - i = message.nextSetBit( i+1 ) ) - { - /* message bits before the CRC */ - if( i < ( messageStart + 7 ) ) - { - calculated ^= CRC9_CHECKSUMS[ i - messageStart ]; - } - /* message bits after the CRC */ - else if( i > ( messageStart + 15 ) ) - { - calculated ^= CRC9_CHECKSUMS[ i - messageStart - 9 ]; - } - } - - int checksum = message.getInt( messageStart + 7, messageStart + 15 ); + long error = calculated ^ checksum; + + if(error == 0 || error == 0xFFFFFFFFl) + { + message.setCRC(CRC.PASSED); + + return message; + } + else + { + int errorLocation = getBitError(error, checksums); + + if(errorLocation >= 0) + { + message.flip(errorLocation + messageStart); + + message.setCRC(CRC.CORRECTED); + + return message; + } + } + + message.setCRC(CRC.FAILED_CRC); + + return message; + } + + /** + * Error detection and correction of single-bit errors for CCITT 16-bit + * CRC protected 80-bit messages. + */ + public static BinaryMessage correctCCITT80(BinaryMessage message, + int messageStart, + int crcStart) + { + int calculated = 0; //Starting value + + /* Iterate the set bits and XOR running checksum with lookup value */ + for(int i = message.nextSetBit(messageStart); + i >= messageStart && i < crcStart; + i = message.nextSetBit(i + 1)) + { + calculated ^= CCITT_80_CHECKSUMS[i - messageStart]; + } + + int checksum = getIntChecksum(message, crcStart, 16); + + int residual = calculated ^ checksum; + + if(residual == 0 || residual == 0xFFFF) + { + message.setCRC(CRC.PASSED); + + return message; + } + else + { + int errorLocation = getBitError(residual, CCITT_80_CHECKSUMS); + + if(errorLocation >= 0) + { + message.flip(errorLocation + messageStart); + + message.setCRC(CRC.CORRECTED); + + return message; + } + } + + message.setCRC(CRC.FAILED_CRC); + + return message; + } + + /** + * Error detection and correction of single-bit errors for CCITT 16-bit + * CRC protected 80-bit messages. + */ + public static int correctCCITT80(CorrectedBinaryMessage message, int messageStart, int crcStart) + { + int calculated = 0xFFFF; //Starting value + + /* Iterate the set bits and XOR running checksum with lookup value */ + for(int i = message.nextSetBit(messageStart); i >= messageStart && i < crcStart; i = message.nextSetBit(i + 1)) + { + calculated ^= CCITT_80_CHECKSUMS[i - messageStart]; + } + + int checksum = getIntChecksum(message, crcStart, 16); + + int residual = calculated ^ checksum; + + if(residual == 0 || residual == 0xFFFF) + { + return 0; + } + else + { + int errorLocation = getBitError(residual, CCITT_80_CHECKSUMS); + + if(errorLocation >= 0) + { + message.flip(errorLocation + messageStart); + message.incrementCorrectedBitCount(1); + return 1; + } + } + + //Message has at least 2 bit errors - ie uncorrectable + message.incrementCorrectedBitCount(2); + + return 2; + } + + /** + * Error detection for CRC-9 protected Confirmed Packet Data blocks. These + * data blocks have a slightly complicated structure because the checksum + * is located between bits 7-15, within a 144 bit block. The checksums + * were generated assuming that the message is contiguous from 0 - 134 bits. + * No data correction is performed. + */ + public static CRC checkCRC9(BinaryMessage message, int messageStart) + { + int calculated = 0x0; //Initial fill of all ones + + /* Iterate the set bits and XOR running checksum with lookup value */ + for(int i = message.nextSetBit(messageStart); + i >= messageStart && i < messageStart + 144; + i = message.nextSetBit(i + 1)) + { + /* message bits before the CRC */ + if(i < (messageStart + 7)) + { + calculated ^= CRC9_CHECKSUMS[i - messageStart]; + } + /* message bits after the CRC */ + else if(i > (messageStart + 15)) + { + calculated ^= CRC9_CHECKSUMS[i - messageStart - 9]; + } + } + + int checksum = message.getInt(messageStart + 7, messageStart + 15); + + int residual = calculated ^ checksum; - int residual = calculated ^ checksum; - // mLog.debug( "CALC:" + calculated + " CHECK:" + checksum + " RESID:" + residual ); - if( residual == 0 || residual == 0x1FF ) - { - return CRC.PASSED; - } - - return CRC.FAILED_CRC; - } - - - /** - * Performs Galois 24/12/7 error detection and correction against the 12 - * encoded 24-bit message segments following the 64-bit NID in the message - * - * @return - true if all 12 segments of the message can be checked/corrected - */ - public static boolean correctGalois24( BinaryMessage tdulc ) - { - boolean passes = true; - - int x = 64; - - while( x < tdulc.size() && passes ) - { - - tdulc = Golay24.checkAndCorrect( tdulc, x ); - - passes = tdulc.getCRC() == CRC.PASSED; + if(residual == 0 || residual == 0x1FF) + { + return CRC.PASSED; + } - x += 24; - } - - return passes; - } - - - /** - * Calculates the value of the message checksum as a long - */ - public static long getLongChecksum( BinaryMessage message, - int crcStart, int crcLength ) - { - return message.getLong( crcStart, crcStart + crcLength - 1 ); + return CRC.FAILED_CRC; } - /** - * Calculates the value of the message checksum as an integer - */ - public static int getIntChecksum( BinaryMessage message, - int crcStart, int crcLength ) + + /** + * Performs Galois 24/12/7 error detection and correction against the 12 + * encoded 24-bit message segments following the 64-bit NID in the message + * + * @return - true if all 12 segments of the message can be checked/corrected + */ + public static boolean correctGalois24(CorrectedBinaryMessage tdulc) { - return message.getInt( crcStart, crcStart + crcLength - 1 ); + boolean passes = true; + + int x = 64; + + while(x < tdulc.size() && passes) + { + + int errors = Golay24.checkAndCorrect(tdulc, x); + + passes = errors < 2; + + x += 24; + } + + return passes; + } + + + /** + * Calculates the value of the message checksum as a long + */ + public static long getLongChecksum(BinaryMessage message, + int crcStart, int crcLength) + { + return message.getLong(crcStart, crcStart + crcLength - 1); + } + + /** + * Calculates the value of the message checksum as an integer + */ + public static int getIntChecksum(BinaryMessage message, + int crcStart, int crcLength) + { + return message.getInt(crcStart, crcStart + crcLength - 1); } /** * Identifies any single bit error position that matches the checksum error. */ - public static int getBitError( long checksumError, long[] checksums ) + public static int getBitError(long checksumError, long[] checksums) { - for( int x = 0; x < checksums.length; x++ ) - { - if( checksums[ x ] == checksumError ) - { - return x; - } - } + for(int x = 0; x < checksums.length; x++) + { + if(checksums[x] == checksumError) + { + return x; + } + } - return -1; + return -1; } - + /** * Identifies any single bit error position that matches the checksum error. */ - public static int getBitError( int checksumError, int[] checksums ) + public static int getBitError(int checksumError, int[] checksums) { - for( int x = 0; x < checksums.length; x++ ) - { - if( checksums[ x ] == checksumError ) - { - return x; - } - } + for(int x = 0; x < checksums.length; x++) + { + if(checksums[x] == checksumError) + { + return x; + } + } - return -1; + return -1; } - - public static void main( String[] args ) + + public static void main(String[] args) { - String raw = "000000001000001100000001010001111011000100001010010001111100000000000101000000000000000001000000000000110000000000000001101010101010101010101010"; - - BinaryMessage message = BinaryMessage.load( raw ); - - mLog.debug( "MSG:" + message.toString() ); + String raw = "000000001000001100000001010001111011000100001010010001111100000000000101000000000000000001000000000000110000000000000001101010101010101010101010"; - CRC results = checkCRC9( message, 0 ); + BinaryMessage message = BinaryMessage.load(raw); - mLog.debug( "COR:" + message.toString() ); + mLog.debug("MSG:" + message.toString()); - mLog.debug( "Results: " + results.getDisplayText() ); + CRC results = checkCRC9(message, 0); + + mLog.debug("COR:" + message.toString()); + + mLog.debug("Results: " + results.getDisplayText()); } } diff --git a/src/main/java/io/github/dsheirer/edac/CRCPassport.java b/src/main/java/io/github/dsheirer/edac/CRCPassport.java index 5591cb31..4ebb56da 100644 --- a/src/main/java/io/github/dsheirer/edac/CRCPassport.java +++ b/src/main/java/io/github/dsheirer/edac/CRCPassport.java @@ -1,215 +1,224 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.edac; import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import java.util.BitSet; /** * Passport CRC checksum utility - * + * * Passport message blocks are 68 bits in length as follows: - * 0 - 8: Sync - * 9 - 59: message bits - * 60 - 67: CRC-7 check bits plus 1 parity - * + * 0 - 8: Sync + * 9 - 59: message bits + * 60 - 67: CRC-7 check bits plus 1 parity + * * Passport uses a CRC-7 (0x44) with an initial fill of 0x44 plus parity set. - * + * * CRC-7 Generating Polynomial: x6 + x2 + 1 (0x44) */ public class CRCPassport { - private static final byte sFILL_00 = (byte)0x00; - - private static byte[] sCHECKSUMS = new byte[] - { - (byte)0x6E, //DCC 1 - (byte)0xBF, //DCC 0 - (byte)0xD6, //LCN 10 - (byte)0xE3, //LCN 9 - (byte)0xF8, //LCN 8 - (byte)0x7C, //LCN 7 - (byte)0x3E, //LCN 6 - (byte)0x97, //LCN 5 - (byte)0xC2, //LCN 4 - (byte)0xE9, //LCN 3 - (byte)0x75, //LCN 2 - (byte)0x3B, //LCN 1 - (byte)0x94, //LCN 0 - (byte)0x4A, //SITE 6 - (byte)0xAD, //SITE 5 - (byte)0x57, //SITE 4 - (byte)0xA2, //SITE 3 - (byte)0xD9, //SITE 2 - (byte)0x6D, //SITE 1 - (byte)0x37, //SITE 0 - (byte)0x92, //GROUP 15 - (byte)0xC1, //GROUP 14 - (byte)0x61, //GROUP 13 - (byte)0x31, //GROUP 12 - (byte)0x19, //GROUP 11 - (byte)0x0D, //GROUP 10 - (byte)0x07, //GROUP 9 - (byte)0x8A, //GROUP 8 - (byte)0xCD, //GROUP 7 - (byte)0x67, //GROUP 6 - (byte)0xBA, //GROUP 5 - (byte)0xD5, //GROUP 4 - (byte)0x6B, //GROUP 3 - (byte)0xBC, //GROUP 2 - (byte)0x5E, //GROUP 1 - (byte)0xA7, //GROUP 0 - (byte)0xDA, //TYPE 3 - (byte)0xE5, //TYPE 2 - (byte)0x73, //TYPE 1 - (byte)0xB0, //TYPE 0 - (byte)0x58, //FREE 10 - (byte)0x2C, //FREE 9 - (byte)0x16, //FREE 8 - (byte)0x83, //FREE 7 - (byte)0xC8, //FREE 6 - (byte)0x64, //FREE 5 - (byte)0x32, //FREE 4 - (byte)0x91, //FREE 3 - (byte)0x49, //FREE 2 - (byte)0x25, //FREE 1 - (byte)0x13 //FREE 0 - }; + private static final byte sFILL_00 = (byte)0x00; - /** - * Determines if message bits 9 - 59 pass the CRC checksum - * contained in bits 60 - 68, using a lookup table of CRC checksum values - * derived from the CRC-7 value and the final parity bit - */ - public static CRC check( BitSet msg ) - { - CRC crc = CRC.UNKNOWN; - - byte calculated = 0x0; //Starting value for an OSW + private static byte[] sCHECKSUMS = new byte[] + { + (byte)0x6E, //DCC 1 + (byte)0xBF, //DCC 0 + (byte)0xD6, //LCN 10 + (byte)0xE3, //LCN 9 + (byte)0xF8, //LCN 8 + (byte)0x7C, //LCN 7 + (byte)0x3E, //LCN 6 + (byte)0x97, //LCN 5 + (byte)0xC2, //LCN 4 + (byte)0xE9, //LCN 3 + (byte)0x75, //LCN 2 + (byte)0x3B, //LCN 1 + (byte)0x94, //LCN 0 + (byte)0x4A, //SITE 6 + (byte)0xAD, //SITE 5 + (byte)0x57, //SITE 4 + (byte)0xA2, //SITE 3 + (byte)0xD9, //SITE 2 + (byte)0x6D, //SITE 1 + (byte)0x37, //SITE 0 + (byte)0x92, //GROUP 15 + (byte)0xC1, //GROUP 14 + (byte)0x61, //GROUP 13 + (byte)0x31, //GROUP 12 + (byte)0x19, //GROUP 11 + (byte)0x0D, //GROUP 10 + (byte)0x07, //GROUP 9 + (byte)0x8A, //GROUP 8 + (byte)0xCD, //GROUP 7 + (byte)0x67, //GROUP 6 + (byte)0xBA, //GROUP 5 + (byte)0xD5, //GROUP 4 + (byte)0x6B, //GROUP 3 + (byte)0xBC, //GROUP 2 + (byte)0x5E, //GROUP 1 + (byte)0xA7, //GROUP 0 + (byte)0xDA, //TYPE 3 + (byte)0xE5, //TYPE 2 + (byte)0x73, //TYPE 1 + (byte)0xB0, //TYPE 0 + (byte)0x58, //FREE 10 + (byte)0x2C, //FREE 9 + (byte)0x16, //FREE 8 + (byte)0x83, //FREE 7 + (byte)0xC8, //FREE 6 + (byte)0x64, //FREE 5 + (byte)0x32, //FREE 4 + (byte)0x91, //FREE 3 + (byte)0x49, //FREE 2 + (byte)0x25, //FREE 1 + (byte)0x13 //FREE 0 + }; + + /** + * Determines if message bits 9 - 59 pass the CRC checksum + * contained in bits 60 - 68, using a lookup table of CRC checksum values + * derived from the CRC-7 value and the final parity bit + */ + public static CRC check(BitSet msg) + { + CRC crc = CRC.UNKNOWN; + + byte calculated = 0x0; //Starting value for an OSW //Iterate bits that are set and XOR running checksum with lookup value - for (int i = msg.nextSetBit( 9 ); i >= 9 && i <= 59; i = msg.nextSetBit( i+1 ) ) - { - calculated ^= sCHECKSUMS[ i - 9 ]; - } + for(int i = msg.nextSetBit(9); i >= 9 && i <= 59; i = msg.nextSetBit(i + 1)) + { + calculated ^= sCHECKSUMS[i - 9]; + } - //Apply the message checksum to derive the residual - calculated ^= getChecksum( msg ); - - switch( (byte)calculated ) - { - case sFILL_00: - crc = CRC.PASSED; - break; - default: - crc = CRC.FAILED_CRC; - } - - return crc; - } - - public static byte getResidual( BitSet msg ) + //Apply the message checksum to derive the residual + calculated ^= getChecksum(msg); + + switch((byte)calculated) + { + case sFILL_00: + crc = CRC.PASSED; + break; + default: + crc = CRC.FAILED_CRC; + } + + return crc; + } + + public static byte getResidual(BitSet msg) { byte calculated = 0x0; //Initial fill of zero //Iterate bits that are set and XOR running checksum with lookup value - for (int i = msg.nextSetBit( 9 ); i >= 9 && i <= 59; i = msg.nextSetBit( i+1 ) ) + for(int i = msg.nextSetBit(9); i >= 9 && i <= 59; i = msg.nextSetBit(i + 1)) { - calculated ^= sCHECKSUMS[ i - 9 ]; + calculated ^= sCHECKSUMS[i - 9]; } - - calculated ^= getChecksum( msg ); - + + calculated ^= getChecksum(msg); + return calculated; } - - public static byte[] getChecks() - { - return sCHECKSUMS; - } - /** - * Returns the integer value of the 7 bit crc checksum - */ - public static byte getChecksum( BitSet msg ) + public static byte[] getChecks() + { + return sCHECKSUMS; + } + + /** + * Returns the integer value of the 7 bit crc checksum + */ + public static byte getChecksum(BitSet msg) { byte retVal = 0x0; - - for( int x = 0; x < 8; x++ ) - { - if( msg.get( x + 60 ) ) - { - retVal ^= (byte)( 1<<( 7 - x ) ); - } - } - - return retVal; + + for(int x = 0; x < 8; x++) + { + if(msg.get(x + 60)) + { + retVal ^= (byte)(1 << (7 - x)); + } + } + + return retVal; } /** * Initial take on error correction. The majority of the errors detected * are in the final 2 bits, owing to the forward looking soft bit detection - * used in the decoder. For right now, correct any messages where the + * used in the decoder. For right now, correct any messages where the * residual indicates that the final 1 or 2 bits are bad. */ - public static BinaryMessage correct( BinaryMessage msg ) + public static CorrectedBinaryMessage correct(CorrectedBinaryMessage msg) { - int residual = (int)( 0xFF & getResidual( msg ) ); - - switch( residual ) - { - case 1: - case 136: - msg.flip( 67 ); - break; - case 3: - case 138: - msg.flip( 66 ); - msg.flip( 67 ); - break; - case 2: - case 139: - msg.flip( 66 ); - break; - case 140: - msg.flip( 65 ); - msg.flip( 67 ); - break; - case 153: - msg.flip( 67 ); - break; - case 142: - msg.flip( 65 ); - msg.flip( 66 ); - msg.flip( 67 ); - break; - } + int residual = (int)(0xFF & getResidual(msg)); - return msg; + switch(residual) + { + case 1: + case 136: + msg.flip(67); + msg.incrementCorrectedBitCount(1); + break; + case 3: + case 138: + msg.flip(66); + msg.flip(67); + msg.incrementCorrectedBitCount(2); + break; + case 2: + case 139: + msg.flip(66); + msg.incrementCorrectedBitCount(1); + break; + case 140: + msg.flip(65); + msg.flip(67); + msg.incrementCorrectedBitCount(2); + break; + case 153: + msg.flip(67); + msg.incrementCorrectedBitCount(1); + break; + case 142: + msg.flip(65); + msg.flip(66); + msg.flip(67); + msg.incrementCorrectedBitCount(3); + break; + } + + return msg; } - + /** * Performs an even parity check on the bitset. If the number of bits that * are set is an even number, then it passes the even parity check */ - public static boolean isEvenParity( BinaryMessage bits ) + public static boolean isEvenParity(BinaryMessage bits) { - return ( bits.cardinality() % 2 == 0 ); + return (bits.cardinality() % 2 == 0); } } diff --git a/src/main/java/io/github/dsheirer/edac/CRCUtil.java b/src/main/java/io/github/dsheirer/edac/CRCUtil.java index be5b31bd..8f5faa60 100644 --- a/src/main/java/io/github/dsheirer/edac/CRCUtil.java +++ b/src/main/java/io/github/dsheirer/edac/CRCUtil.java @@ -161,8 +161,8 @@ public class CRCUtil // decode( message, 0, 135, polynomial, 9 ); // // mLog.debug( message.toString() ); - - long[] table = generate( 135, 9, 0x259l, 0, false, Parity.NONE ); + + long[] table = CRCUtil.generate( 135, 9, 0x259l, 0x1FF, false, Parity.NONE ); mLog.debug( toCodeArray( table ) ); mLog.debug( "Finished" ); } diff --git a/src/main/java/io/github/dsheirer/edac/Golay18.java b/src/main/java/io/github/dsheirer/edac/Golay18.java index 17a91c25..943366cf 100644 --- a/src/main/java/io/github/dsheirer/edac/Golay18.java +++ b/src/main/java/io/github/dsheirer/edac/Golay18.java @@ -1,23 +1,23 @@ package io.github.dsheirer.edac; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /******************************************************************************* * 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 * ----------------------------------------------------------------------- @@ -32,22 +32,25 @@ import org.slf4j.LoggerFactory; */ public class Golay18 { - private final static Logger mLog = LoggerFactory.getLogger( Golay18.class ); + private final static Logger mLog = LoggerFactory.getLogger(Golay18.class); - /** - * Performs error detection and correction. - */ - public static void checkAndCorrect( BinaryMessage message, int startIndex ) - { - int value = message.getInt( startIndex, startIndex + 17 ); + /** + * Performs error detection and correction. + */ + public static void checkAndCorrect(CorrectedBinaryMessage message, int startIndex) + { + int value = message.getInt(startIndex, startIndex + 17); - BinaryMessage temp = new BinaryMessage( 24 ); - temp.load( 6, 18, value ); + CorrectedBinaryMessage temp = new CorrectedBinaryMessage(24); + temp.load(6, 18, value); - BinaryMessage corrected = Golay24.checkAndCorrect( temp, 0 ); + int errorsCorrected = Golay24.checkAndCorrect(temp, 0); - int correctedValue = corrected.getInt( 6, 23 ); - - message.load( startIndex, 18, correctedValue ); - } + int correctedValue = temp.getInt(6, 23); + + message.load(startIndex, 18, correctedValue); + + int correctedBitCount = Integer.bitCount(value ^ correctedValue); + message.incrementCorrectedBitCount(correctedBitCount); + } } diff --git a/src/main/java/io/github/dsheirer/edac/Golay24.java b/src/main/java/io/github/dsheirer/edac/Golay24.java index 6d1da2bb..44ad8a50 100644 --- a/src/main/java/io/github/dsheirer/edac/Golay24.java +++ b/src/main/java/io/github/dsheirer/edac/Golay24.java @@ -1,23 +1,24 @@ package io.github.dsheirer.edac; import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /******************************************************************************* * 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 * ----------------------------------------------------------------------- @@ -30,138 +31,133 @@ import org.slf4j.LoggerFactory; */ public class Golay24 { - private final static Logger mLog = LoggerFactory.getLogger( Golay24.class ); + private final static Logger mLog = LoggerFactory.getLogger(Golay24.class); - /** - * Galois 24/12 checksums generated by: - * - * CRCUtil.generate( 12, 11, 0xC75, 0x0, true ); - */ - public static final int[] CHECKSUMS = new int[] - { - 0x63A, 0x31D, 0x7B4, 0x3DA, 0x1ED, 0x6CC, 0x366, 0x1B3, - 0x6E3, 0x54B, 0x49F, 0x475, 0x400, 0x200, 0x100, 0x080, - 0x040, 0x020, 0x010, 0x008, 0x004, 0x002, 0x001 - }; + /** + * Galois 24/12 checksums generated by: + * + * CRCUtil.generate( 12, 11, 0xC75, 0x0, true ); + */ + public static final int[] CHECKSUMS = new int[] + { + 0x63A, 0x31D, 0x7B4, 0x3DA, 0x1ED, 0x6CC, 0x366, 0x1B3, + 0x6E3, 0x54B, 0x49F, 0x475, 0x400, 0x200, 0x100, 0x080, + 0x040, 0x020, 0x010, 0x008, 0x004, 0x002, 0x001 + }; - private static int calculateChecksum( BinaryMessage message, int startIndex ) - { - int calculated = 0; //Starting value + private static int calculateChecksum(BinaryMessage message, int startIndex) + { + int calculated = 0; //Starting value - /* Iterate the set bits and XOR running checksum with lookup value */ - for (int i = message.nextSetBit( startIndex ); - i >= startIndex && i < startIndex + 12; - i = message.nextSetBit( i+1 ) ) - { - calculated ^= CHECKSUMS[ i - startIndex ]; - } - - return calculated; - } - - /** - * Performs error detection and returns a corrected copy of the 24-bit - * message that starts at the start index. - * @param message - source message containing startIndex + 24 bits length - * @param startIndex - start of the 24-bit galois 24 protected bit set - * @return - corrected 24-bit galois value - */ - public static BinaryMessage checkAndCorrect( BinaryMessage message, int startIndex ) - { - boolean parityError = message.cardinality() % 2 != 0; - - int syndrome = getSyndrome( message, startIndex ); - - /* No errors */ - if( syndrome == 0 ) - { - if( parityError ) - { - message.flip( startIndex + 23 ); - } + /* Iterate the set bits and XOR running checksum with lookup value */ + for(int i = message.nextSetBit(startIndex); + i >= startIndex && i < startIndex + 12; + i = message.nextSetBit(i + 1)) + { + calculated ^= CHECKSUMS[i - startIndex]; + } - message.setCRC( CRC.PASSED ); - - return message; - } + return calculated; + } - /* Get original message value */ - int original = message.getInt( 0, 22 ); - - int index = -1; - int syndromeWeight = 3; - int errors = 0; - - while( index < 23 ) - { - if( index != -1 ) - { - /* restore the previous flipped bit */ - if( index > 0 ) - { - message.flip( index - 1 ); - } - - message.flip( index ); - - syndromeWeight = 2; - } - - syndrome = getSyndrome( message, startIndex ); - - if( syndrome > 0 ) - { - for( int i = 0; i < 23; i++ ) - { - - errors = Integer.bitCount( syndrome ); - - if( errors <= syndromeWeight ) - { - message.xor( 12, 11, syndrome ); - - message.rotateRight( i, startIndex, startIndex + 22 ); + /** + * Performs error detection and returns a corrected copy of the 24-bit + * message that starts at the start index. + * + * @param message - source message containing startIndex + 24 bits length + * @param startIndex - start of the 24-bit galois 24 protected bit set + * @return - corrected 24-bit galois value + */ + public static int checkAndCorrect(CorrectedBinaryMessage message, int startIndex) + { + boolean parityError = message.cardinality() % 2 != 0; - if( index >= 0 ) - { - errors ++; - } + int syndrome = getSyndrome(message, startIndex); - int corrected = message.getInt( 0, 22 ); - - if( Integer.bitCount( original ^ corrected ) > 3 ) - { - message.setCRC( CRC.FAILED_CRC ); - - return message; - } - - message.setCRC( CRC.PASSED ); - - return message; - } - else - { - message.rotateLeft( startIndex, startIndex + 22 ); - syndrome = getSyndrome( message, startIndex ); - } - } - - index++; - } - } + /* No errors */ + if(syndrome == 0) + { + if(parityError) + { + message.flip(startIndex + 23); + message.incrementCorrectedBitCount(1); + return 1; + } - message.setCRC( CRC.FAILED_CRC ); - - return message; - } + return 0; + } - private static int getSyndrome( BinaryMessage message, int startIndex ) - { - int calculated = calculateChecksum( message, startIndex ); - - int checksum = message.getInt( startIndex + 12, startIndex + 22 ); + /* Get original message value */ + int original = message.getInt(0, 22); - return ( checksum ^ calculated ); - } + int index = -1; + int syndromeWeight = 3; + int errors = 0; + + while(index < 23) + { + if(index != -1) + { + /* restore the previous flipped bit */ + if(index > 0) + { + message.flip(index - 1); + } + + message.flip(index); + + syndromeWeight = 2; + } + + syndrome = getSyndrome(message, startIndex); + + if(syndrome > 0) + { + for(int i = 0; i < 23; i++) + { + + errors = Integer.bitCount(syndrome); + + if(errors <= syndromeWeight) + { + message.xor(12, 11, syndrome); + + message.rotateRight(i, startIndex, startIndex + 22); + + if(index >= 0) + { + errors++; + } + + int corrected = message.getInt(0, 22); + + if(Integer.bitCount(original ^ corrected) > 3) + { + return 2; + } + + return 1; + } + else + { + message.rotateLeft(startIndex, startIndex + 22); + syndrome = getSyndrome(message, startIndex); + } + } + + index++; + } + } + + return 2; + } + + private static int getSyndrome(BinaryMessage message, int startIndex) + { + int calculated = calculateChecksum(message, startIndex); + + int checksum = message.getInt(startIndex + 12, startIndex + 22); + + return (checksum ^ calculated); + } } diff --git a/src/main/java/io/github/dsheirer/edac/Hamming10.java b/src/main/java/io/github/dsheirer/edac/Hamming10.java index 979e10dd..9862f3b2 100644 --- a/src/main/java/io/github/dsheirer/edac/Hamming10.java +++ b/src/main/java/io/github/dsheirer/edac/Hamming10.java @@ -1,115 +1,124 @@ package io.github.dsheirer.edac; import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Hamming (10,6,3) error detection and correction utility. This is a truncated version of the Hamming15 class. + */ public class Hamming10 { - private final static Logger mLog = LoggerFactory.getLogger( Hamming10.class ); - - private static int[] CHECKSUMS = new int[] { 0xE,0xD,0xB,0X7,0x3,0xC }; + private final static Logger mLog = LoggerFactory.getLogger(Hamming10.class); - /** - * Performs error detection and correction of any single-bit errors. This - * is a truncated version of the Hamming15 class. - * - * @param frame - binary frame containing a Hamming(10,6,3) protected field - * - * @param startIndex - offset to the first bit of the field - * - * @return - 0 = no errors - * 1 = a single-bit error was corrected - */ - public static int checkAndCorrect( BinaryMessage frame, int startIndex ) - { - int syndrome = getSyndrome( frame, startIndex ); + private static int[] CHECKSUMS = new int[]{0xE, 0xD, 0xB, 0X7, 0x3, 0xC}; - switch( syndrome ) - { - case 0: - return 0; - case 1: - frame.flip( startIndex + 9 ); //Parity 1 - return 1; - case 2: - frame.flip( startIndex + 8 ); //Parity 2 - return 1; - case 3: - frame.flip( startIndex + 4 ); //Data 2 - return 1; - case 4: - frame.flip( startIndex + 7 ); //Parity 4 - return 1; - case 5: - return 2; - case 6: - return 2; - case 7: - frame.flip( startIndex + 3 ); //Data 3 - return 1; - case 8: - frame.flip( startIndex + 6 ); //Parity 8 - return 1; - case 9: - return 2; - case 10: - return 2; - case 11: - frame.flip( startIndex + 2 ); //Data 4 - return 1; - case 12: - frame.flip( startIndex + 5 ); //Data 1 - return 1; - case 13: - frame.flip( startIndex + 1 ); //Data 5 - return 1; - case 14: - frame.flip( startIndex + 0 ); //Data 6 - return 1; - case 15: - return 2; - } + /** + * Performs error detection and correction of any single-bit errors. + * + * @param frame - binary frame containing a Hamming(10,6,3) protected field + * @param startIndex - offset to the first bit of the field + * @return - 0 = no errors + * 1 = a single-bit error was corrected + * 2 = more than 1 bit error detected - unable to correct word + */ + public static int checkAndCorrect(CorrectedBinaryMessage frame, int startIndex) + { + int syndrome = getSyndrome(frame, startIndex); - /* We'll never get to here */ - return 2; - } + switch(syndrome) + { + case 0: + return 0; + case 1: + frame.flip(startIndex + 9); //Parity 1 + frame.incrementCorrectedBitCount(1); + return 1; + case 2: + frame.flip(startIndex + 8); //Parity 2 + frame.incrementCorrectedBitCount(1); + return 1; + case 3: + frame.flip(startIndex + 4); //Data 2 + frame.incrementCorrectedBitCount(1); + return 1; + case 4: + frame.flip(startIndex + 7); //Parity 4 + frame.incrementCorrectedBitCount(1); + return 1; + case 5: + return 2; + case 6: + return 2; + case 7: + frame.flip(startIndex + 3); //Data 3 + frame.incrementCorrectedBitCount(1); + return 1; + case 8: + frame.flip(startIndex + 6); //Parity 8 + frame.incrementCorrectedBitCount(1); + return 1; + case 9: + return 2; + case 10: + return 2; + case 11: + frame.flip(startIndex + 2); //Data 4 + frame.incrementCorrectedBitCount(1); + return 1; + case 12: + frame.flip(startIndex + 5); //Data 1 + frame.incrementCorrectedBitCount(1); + return 1; + case 13: + frame.flip(startIndex + 1); //Data 5 + frame.incrementCorrectedBitCount(1); + return 1; + case 14: + frame.flip(startIndex + 0); //Data 6 + frame.incrementCorrectedBitCount(1); + return 1; + case 15: + return 2; + } - /** - * Calculates the checksum (Parity 8,4,2,1) for data (6 <> 1 ) bits. - * @param frame - frame containing hamming(10) protected word - * @param startIndex - start bit index of the hamming protected word - * @return parity value, 0 - 15 - */ - private static int calculateChecksum( BinaryMessage frame, int startIndex ) - { - int calculated = 0; //Starting value + /* We'll never get to here */ + return 2; + } - /* Iterate the set bits and XOR running checksum with lookup value */ - for (int i = frame.nextSetBit( startIndex ); - i >= startIndex && i < startIndex + 6; - i = frame.nextSetBit( i+1 ) ) - { - calculated ^= CHECKSUMS[ i - startIndex ]; - } - - return calculated; - } + /** + * Calculates the checksum (Parity 8,4,2,1) for data (6 <> 1 ) bits. + * + * @param frame - frame containing hamming(10) protected word + * @param startIndex - start bit index of the hamming protected word + * @return parity value, 0 - 15 + */ + private static int calculateChecksum(BinaryMessage frame, int startIndex) + { + int calculated = 0; //Starting value - /** - * Calculates the syndrome - xor of the calculated checksum and the actual - * checksum. - * - * @param frame - binary frame containing a hamming(15,11,4) protected word - * @param startIndex - of bit 0 of the hamming protected word - * @return - 0 (no errors) or 1 (single bit error corrected) - */ - private static int getSyndrome( BinaryMessage frame, int startIndex ) - { - int calculated = calculateChecksum( frame, startIndex ); - - int checksum = frame.getInt( startIndex + 6, startIndex + 9 ); + /* Iterate the set bits and XOR running checksum with lookup value */ + for(int i = frame.nextSetBit(startIndex); i >= startIndex && i < startIndex + 6; i = frame.nextSetBit(i + 1)) + { + calculated ^= CHECKSUMS[i - startIndex]; + } - return ( checksum ^ calculated ); - } + return calculated; + } + + /** + * Calculates the syndrome - xor of the calculated checksum and the actual + * checksum. + * + * @param frame - binary frame containing a hamming(15,11,4) protected word + * @param startIndex - of bit 0 of the hamming protected word + * @return - 0 (no errors) or 1 (single bit error corrected) + */ + private static int getSyndrome(BinaryMessage frame, int startIndex) + { + int calculated = calculateChecksum(frame, startIndex); + int checksum = frame.getInt(startIndex + 6, startIndex + 9); + return (checksum ^ calculated); + } } diff --git a/src/main/java/io/github/dsheirer/edac/ReedSolomon_63_47_17.java b/src/main/java/io/github/dsheirer/edac/ReedSolomon_63_47_17.java index 83bb510e..ad2189a8 100644 --- a/src/main/java/io/github/dsheirer/edac/ReedSolomon_63_47_17.java +++ b/src/main/java/io/github/dsheirer/edac/ReedSolomon_63_47_17.java @@ -1,38 +1,40 @@ -package io.github.dsheirer.edac; +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 + * ***************************************************************************** + */ -/******************************************************************************* - * 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 io.github.dsheirer.edac; public class ReedSolomon_63_47_17 extends BerlekempMassey_63 { - /** - * Reed-Solomon RS(63,47,17) decoder. This can also be used for error detection - * and correction of the following RS codes: - * - * RS(36,20,17) - max 8 errors - * RS(24,16,9) - max 4 errors - * RS(24,12,13) - max 6 errors - * - * The maximum correctable errors is determined by (n-k)/2, or hamming - * distance divided by 2. - */ - public ReedSolomon_63_47_17( int maximumCorrectableErrors ) + /** + * Reed-Solomon RS(63,47,17) decoder. This can also be used for error detection + * and correction of the following RS codes: + * + * RS(36,20,17) - max 8 errors + * RS(24,16,9) - max 4 errors + * RS(24,12,13) - max 6 errors + * + * The maximum correctable errors is determined by (n-k)/2, or hamming + * distance divided by 2. + */ + public ReedSolomon_63_47_17(int maximumCorrectableErrors) { - super( maximumCorrectableErrors ); + super(maximumCorrectableErrors); } } diff --git a/src/main/java/io/github/dsheirer/edac/trellis/DMRNode.java b/src/main/java/io/github/dsheirer/edac/trellis/DMR_3_4_Node.java similarity index 96% rename from src/main/java/io/github/dsheirer/edac/trellis/DMRNode.java rename to src/main/java/io/github/dsheirer/edac/trellis/DMR_3_4_Node.java index bd4255e2..b04f90c8 100644 --- a/src/main/java/io/github/dsheirer/edac/trellis/DMRNode.java +++ b/src/main/java/io/github/dsheirer/edac/trellis/DMR_3_4_Node.java @@ -18,7 +18,7 @@ ******************************************************************************/ package io.github.dsheirer.edac.trellis; -public class DMRNode extends Node +public class DMR_3_4_Node extends Node { //Hamming error values indicating the number of bits set in each indexed value, 0-15 public static final int[] HAMMING_ERROR_COUNT = new int[]{0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4}; @@ -47,7 +47,7 @@ public class DMRNode extends Node * @param inputValue for the trellis time instant * @param transmittedOutputValue the actual four-bit output value that was transmitted. */ - public DMRNode(int inputValue, int transmittedOutputValue) + public DMR_3_4_Node(int inputValue, int transmittedOutputValue) { super(inputValue, transmittedOutputValue); } diff --git a/src/main/java/io/github/dsheirer/edac/trellis/P25_1_2_Node.java b/src/main/java/io/github/dsheirer/edac/trellis/P25_1_2_Node.java new file mode 100644 index 00000000..59e25145 --- /dev/null +++ b/src/main/java/io/github/dsheirer/edac/trellis/P25_1_2_Node.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * 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 io.github.dsheirer.edac.trellis; + +public class P25_1_2_Node extends Node +{ + /** + * Hamming error values indicating the number of bits set in each indexed value, 0-15. XOR the + * transmitted 4-bit value against the reference 4-bit value to obtain the lookup index to this array. + */ + public static final int[] HAMMING_ERROR_COUNT = new int[]{0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4}; + + /** + * P25 Finite State Machine 1/2 Rate transition matrix. + * + * Note: these transmitted bit values are translated from TIA-102 BAAA Table 7-2 encoder state + * table constellation values. + */ + public static final int[][] TRANSITION_MATRIX = new int[][] + { + {2,12,1,15},//0,15,12,3 + {14,0,13,3},//4,11,8,7 + {9,7,10,4}, //13,2,1,14 + {5,11,6,8} //9,6,5,10 + }; + + /** + * P25 1/2-rate Trellis Coded Modulation (TCM) node + * + * @param inputValue for the trellis time instant + * @param transmittedOutputValue the actual four-bit output value that was transmitted. + */ + public P25_1_2_Node(int inputValue, int transmittedOutputValue) + { + super(inputValue, transmittedOutputValue); + } + + /** + * Calculates the error between the preceeding node and this node as the Hamming distance between the expected + * four-bit output value versus the actual transmitted output value. + * + * @param precedingNode that precedes this node + * @return error value for this node relative to the preceeding node argument + */ + @Override + public int getError(Node precedingNode) + { + if(precedingNode != null) + { + int expectedOutputValue = getOutputValue(precedingNode); + int errorMask = expectedOutputValue ^ getTransmittedOutputValue(); + return HAMMING_ERROR_COUNT[errorMask]; + } + + return 0; + } + + /** + * Expected output four-bit value when this node is preceeded by the argument node. + * + * @param precedingNode that precedes this node + * @return expected output value + */ + @Override + public int getOutputValue(Node precedingNode) + { + return TRANSITION_MATRIX[precedingNode.getInputValue()][getInputValue()]; + } +} diff --git a/src/main/java/io/github/dsheirer/edac/trellis/P25_3_4_Node.java b/src/main/java/io/github/dsheirer/edac/trellis/P25_3_4_Node.java new file mode 100644 index 00000000..0654961b --- /dev/null +++ b/src/main/java/io/github/dsheirer/edac/trellis/P25_3_4_Node.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * 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 io.github.dsheirer.edac.trellis; + +public class P25_3_4_Node extends Node +{ + //Hamming error values indicating the number of bits set in each indexed value, 0-15 + public static final int[] HAMMING_ERROR_COUNT = new int[]{0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4}; + + /** + * Trellis encoder state transition table. Note: this table is converted from the ICD state + * transition table where the table symbol is converted to the transmitted bit value. + */ + public static final int[][] TRANSITION_MATRIX = new int[][] + { + {2,13,14,1,7,8,11,4}, + {14,1,7,8,11,4,2,13}, + {10,5,6,9,15,0,3,12}, + {6,9,15,0,3,12,10,5}, + {15,0,3,12,10,5,6,9}, + {3,12,10,5,6,9,15,0}, + {7,8,11,4,2,13,14,1}, + {11,4,2,13,14,1,7,8} + }; + + /** + * Trellis Coded Modulation (TCM) 3/4-rate trellis node for decoding P25 + * + * @param inputValue for the trellis time instant + * @param transmittedOutputValue the actual four-bit output value that was transmitted. + */ + public P25_3_4_Node(int inputValue, int transmittedOutputValue) + { + super(inputValue, transmittedOutputValue); + } + + /** + * Calculates the error between the preceeding node and this node as the Hamming distance between the expected + * four-bit output value versus the actual transmitted output value. + * + * @param precedingNode that precedes this node + * @return error value for this node relative to the preceeding node argument + */ + @Override + public int getError(Node precedingNode) + { + if(precedingNode != null) + { + int expectedOutputValue = getOutputValue(precedingNode); + int errorMask = expectedOutputValue ^ getTransmittedOutputValue(); + return HAMMING_ERROR_COUNT[errorMask]; + } + + return 0; + } + + /** + * Expected output four-bit value when this node is preceeded by the argument node. + * + * @param precedingNode that precedes this node + * @return expected output value + */ + @Override + public int getOutputValue(Node precedingNode) + { + return TRANSITION_MATRIX[precedingNode.getInputValue()][getInputValue()]; + } +} diff --git a/src/main/java/io/github/dsheirer/edac/trellis/ViterbiDecoder_1_2_P25.java b/src/main/java/io/github/dsheirer/edac/trellis/ViterbiDecoder_1_2_P25.java new file mode 100644 index 00000000..56a9e8dc --- /dev/null +++ b/src/main/java/io/github/dsheirer/edac/trellis/ViterbiDecoder_1_2_P25.java @@ -0,0 +1,154 @@ +/******************************************************************************* + * 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 io.github.dsheirer.edac.trellis; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; + +import java.util.List; + +/** + * Viterbi decoder for APCO-25 1/2 rate Trellis Coded Modulation (TCM) encoded messages. + */ +public class ViterbiDecoder_1_2_P25 extends ViterbiDecoder +{ + public ViterbiDecoder_1_2_P25() + { + super(2,4); + } + + /** + * Decodes a 1/2 rate trellis coded modulation (TCM) encoded P25 binary message containing 196 bits that have + * already been deinterleaved. + * + * @param encodedMessage to decode that has already been deinterleaved. + * @return decoded message + */ + public CorrectedBinaryMessage decode(BinaryMessage encodedMessage) + { + int[] symbols = getSymbols(encodedMessage); + + Path mostLikelyPath = decode(symbols); + + return getMessage(mostLikelyPath); + } + + /** + * Extracts the decoded/corrected message from the surviving path argument. + * + * @param path to extract a message from + * @return corrected binary message. + */ + private CorrectedBinaryMessage getMessage(Path path) + { + List mNodes = path.getNodes(); + + //Each node contains an input value excluding the starting(0) and final flushing(0) nodes. + CorrectedBinaryMessage message = new CorrectedBinaryMessage((mNodes.size() - 2) * getInputBitLength()); + + if(mNodes.size() > 2) + { + for(int x = 1; x < mNodes.size() - 1; x++) + { + int inputValue = mNodes.get(x).getInputValue(); + + int messageOffset = getInputBitLength() * (x - 1); + + if((inputValue & 2) == 2) + { + message.set(messageOffset); + } + + if((inputValue & 1) == 1) + { + message.set(messageOffset + 1); + } + } + } + + //Transfer the corrected error count to the message + message.setCorrectedBitCount(path.getError()); + + return message; + } + + /** + * Extracts (49) four-bit symbols (196 bits / 4) from the deinterleaved P25 binary message + * + * @param encodedMessage that deinterleaved and contains 196-bits of 1/2 TCM encoding + * @return symbols + */ + public int[] getSymbols(BinaryMessage encodedMessage) + { + //Ensure we have an integral number of transmitted symbols (nibbles) in the message + if(encodedMessage.size() % getOutputBitLength() != 0) + { + throw new IllegalArgumentException("Encoded message must contain an integral number of 4-bit symbols -" + + " message size: " + encodedMessage.size()); + } + + int[] symbols = new int[encodedMessage.size() / getOutputBitLength()]; + + int index = 0; + int pointer = 0; + + while(index < encodedMessage.size()) + { + int symbol = encodedMessage.getInt(index, index + (getOutputBitLength() - 1)); + symbols[pointer++] = symbol; + index += getOutputBitLength(); + } + + return symbols; + } + + /** + * Creates a P25 node + * @param inputValue for the specific trellis node + * @param transmittedOutputValue the actual transmitted output value + * @return created node + */ + @Override + protected Node createNode(int inputValue, int transmittedOutputValue) + { + return new P25_1_2_Node(inputValue, transmittedOutputValue); + } + + /** + * Creates a P25 flushing node. P25 uses an input value of zero to flush the final output value out of the + * encoder. + * + * @param transmittedOutputValue the actual transmitted output value + * @return + */ + @Override + protected Node createFlushingNode(int transmittedOutputValue) + { + return new P25_1_2_Node(0, transmittedOutputValue); + } + + /** + * Creates a P25 starting node. P25 uses a starting input value of zero for the initial node. + */ + @Override + protected Node createStartingNode() + { + return new P25_1_2_Node(0,0); + } +} diff --git a/src/main/java/io/github/dsheirer/edac/trellis/ViterbiDecoder_3_4_DMR.java b/src/main/java/io/github/dsheirer/edac/trellis/ViterbiDecoder_3_4_DMR.java index e4586cfb..28c87cfd 100644 --- a/src/main/java/io/github/dsheirer/edac/trellis/ViterbiDecoder_3_4_DMR.java +++ b/src/main/java/io/github/dsheirer/edac/trellis/ViterbiDecoder_3_4_DMR.java @@ -40,7 +40,7 @@ public class ViterbiDecoder_3_4_DMR extends ViterbiDecoder * @param encodedMessage to decode that has already been deinterleaved. * @return decoded message */ - public BinaryMessage decode(BinaryMessage encodedMessage) + public CorrectedBinaryMessage decode(BinaryMessage encodedMessage) { int[] symbols = getSymbols(encodedMessage); @@ -132,7 +132,7 @@ public class ViterbiDecoder_3_4_DMR extends ViterbiDecoder @Override protected Node createNode(int inputValue, int transmittedOutputValue) { - return new DMRNode(inputValue, transmittedOutputValue); + return new DMR_3_4_Node(inputValue, transmittedOutputValue); } /** @@ -145,7 +145,7 @@ public class ViterbiDecoder_3_4_DMR extends ViterbiDecoder @Override protected Node createFlushingNode(int transmittedOutputValue) { - return new DMRNode(0, transmittedOutputValue); + return new DMR_3_4_Node(0, transmittedOutputValue); } /** @@ -154,6 +154,6 @@ public class ViterbiDecoder_3_4_DMR extends ViterbiDecoder @Override protected Node createStartingNode() { - return new DMRNode(0,0); + return new DMR_3_4_Node(0,0); } } diff --git a/src/main/java/io/github/dsheirer/edac/trellis/ViterbiDecoder_3_4_P25.java b/src/main/java/io/github/dsheirer/edac/trellis/ViterbiDecoder_3_4_P25.java new file mode 100644 index 00000000..97a2a44d --- /dev/null +++ b/src/main/java/io/github/dsheirer/edac/trellis/ViterbiDecoder_3_4_P25.java @@ -0,0 +1,160 @@ +/******************************************************************************* + * 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 io.github.dsheirer.edac.trellis; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; + +import java.util.List; + +public class ViterbiDecoder_3_4_P25 extends ViterbiDecoder +{ + /** + * Viterbi decoder for P25 3/4 rate Trellis Coded Modulation (TCM) encoded messages. + */ + public ViterbiDecoder_3_4_P25() + { + super(3, 4); + } + + /** + * Decodes a 3/4 rate trellis coded modulation (TCM) encoded P25 binary message containing 196 bits that have + * already been deinterleaved. + * + * @param encodedMessage to decode that has already been deinterleaved. + * @return decoded message + */ + public CorrectedBinaryMessage decode(BinaryMessage encodedMessage) + { + int[] symbols = getSymbols(encodedMessage); + + Path mostLikelyPath = decode(symbols); + + return getMessage(mostLikelyPath); + } + + /** + * Extracts the decoded/corrected message from the surviving path argument. + * + * @param path to extract a message from + * @return corrected binary message. + */ + private static CorrectedBinaryMessage getMessage(Path path) + { + List mNodes = path.getNodes(); + + //Each node contains an input value excluding the starting(0) and final flushing(0) nodes. + CorrectedBinaryMessage message = new CorrectedBinaryMessage((mNodes.size() - 2) * 3); + + if(mNodes.size() > 2) + { + for(int x = 1; x < mNodes.size() - 1; x++) + { + int inputValue = mNodes.get(x).getInputValue(); + + int messageOffset = 3 * (x - 1); + + if((inputValue & 4) == 4) + { + message.set(messageOffset); + } + + if((inputValue & 2) == 2) + { + message.set(messageOffset + 1); + } + + if((inputValue & 1) == 1) + { + message.set(messageOffset + 2); + } + } + } + + //Transfer the corrected error count to the message + message.setCorrectedBitCount(path.getError()); + + return message; + } + + /** + * Extracts (49) four-bit symbols (196 bits / 4) from the deinterleaved P25 binary message + * + * @param encodedMessage that deinterleaved and contains 196-bits of 3/4 TCM encoding + * @return symbols + */ + public int[] getSymbols(BinaryMessage encodedMessage) + { + //Ensure we have an integral number of transmitted symbols (nibbles) in the message + if(encodedMessage.size() % getOutputBitLength() != 0) + { + throw new IllegalArgumentException("Encoded message must contain an integral number of 4-bit symbols -" + + " message size: " + encodedMessage.size()); + } + + int[] symbols = new int[encodedMessage.size() / getOutputBitLength()]; + + int index = 0; + int pointer = 0; + + while(index < encodedMessage.size()) + { + int symbol = encodedMessage.getInt(index, index + (getOutputBitLength() - 1)); + symbols[pointer++] = symbol; + index += getOutputBitLength(); + } + + return symbols; + } + + /** + * Creates a P25 node + * + * @param inputValue for the specific trellis node + * @param transmittedOutputValue the actual transmitted output value + * @return created node + */ + @Override + protected Node createNode(int inputValue, int transmittedOutputValue) + { + return new P25_3_4_Node(inputValue, transmittedOutputValue); + } + + /** + * Creates a P25 flushing node. P25 uses an input value of zero to flush the final output value out of the + * encoder. + * + * @param transmittedOutputValue the actual transmitted output value + * @return + */ + @Override + protected Node createFlushingNode(int transmittedOutputValue) + { + return new P25_3_4_Node(0, transmittedOutputValue); + } + + /** + * Creates a P25 starting node. P25 uses a starting input value of zero for the initial node. + */ + @Override + protected Node createStartingNode() + { + return new P25_3_4_Node(0, 0); + } +} diff --git a/src/main/java/io/github/dsheirer/eventbus/MyEventBus.java b/src/main/java/io/github/dsheirer/eventbus/MyEventBus.java new file mode 100644 index 00000000..ea34d7f7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/eventbus/MyEventBus.java @@ -0,0 +1,36 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.eventbus; + +import com.google.common.eventbus.EventBus; + +/** + * System wide event bus for dispatching/broadcasting system wide events or objects + */ +public class MyEventBus +{ + private static final EventBus EVENT_BUS = new EventBus(); + + public static EventBus getEventBus() + { + return EVENT_BUS; + } +} diff --git a/src/main/java/io/github/dsheirer/filter/FilterEditorPanel.java b/src/main/java/io/github/dsheirer/filter/FilterEditorPanel.java index 49ebdc38..009d1db7 100644 --- a/src/main/java/io/github/dsheirer/filter/FilterEditorPanel.java +++ b/src/main/java/io/github/dsheirer/filter/FilterEditorPanel.java @@ -2,14 +2,20 @@ package io.github.dsheirer.filter; import net.miginfocom.swing.MigLayout; -import javax.swing.*; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JTree; +import javax.swing.SwingUtilities; import javax.swing.event.CellEditorListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeCellEditor; import javax.swing.tree.TreeSelectionModel; -import java.awt.*; +import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; @@ -25,376 +31,391 @@ import java.util.List; public class FilterEditorPanel extends JPanel { private static final long serialVersionUID = 1L; - + private JTree mTree; private DefaultTreeModel mModel; - private FilterSet mFilterSet; - - public FilterEditorPanel( FilterSet filterSet ) - { - setLayout( new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]") ); - - mFilterSet = filterSet; - - init(); - } - - private void init() - { - DefaultMutableTreeNode root = new DefaultMutableTreeNode( mFilterSet ); - - mModel = new DefaultTreeModel( root ); - - addFilterSet( mFilterSet, root ); - - mTree = new JTree( mModel ); - mTree.setShowsRootHandles( true ); - - mTree.addMouseListener( new MouseHandler() ); + private FilterSet mFilterSet; - mTree.getSelectionModel().setSelectionMode( - TreeSelectionModel.SINGLE_TREE_SELECTION ); + public FilterEditorPanel(FilterSet filterSet) + { + setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]")); - mTree.setCellRenderer( new EditorTreeCellRenderer() ); + mFilterSet = filterSet; - mTree.setCellEditor( new FilterTreeNodeEditor() ); - - mTree.setEditable( true ); - - add( mTree ); - } - - private void addFilterSet( FilterSet filterSet, DefaultMutableTreeNode parent ) - { - List> filters = filterSet.getFilters(); + init(); + } - /* sort the filters in alphabetical order by name */ - Collections.sort( filters, new Comparator>() - { - @Override - public int compare( IFilter first, IFilter second ) - { - return first.getName().compareTo( second.getName() ); - } - }); - - for( IFilter filter: filters ) - { - DefaultMutableTreeNode child = new DefaultMutableTreeNode( filter ); + private void init() + { + DefaultMutableTreeNode root = new DefaultMutableTreeNode(mFilterSet); - mModel.insertNodeInto( child, parent, parent.getChildCount() ); - - if( filter instanceof FilterSet ) - { - addFilterSet( (FilterSet)filter, child ); - } - else if( filter instanceof Filter ) - { - addFilter( (Filter)filter, child ); - } - } - } - - private void addFilter( Filter filter, DefaultMutableTreeNode parent ) - { - List> elements = filter.getFilterElements(); - - Collections.sort( elements, new Comparator>() - { - @Override - public int compare( FilterElement o1, FilterElement o2 ) - { - return o1.toString().compareTo( o2.toString() ); - } - } ); - - for( FilterElement element: elements ) - { - DefaultMutableTreeNode child = new DefaultMutableTreeNode( element ); + mModel = new DefaultTreeModel(root); - mModel.insertNodeInto( child, parent, parent.getChildCount() ); - } - } - - public class MouseHandler implements MouseListener - { - public MouseHandler() - { - } + addFilterSet(mFilterSet, root); - @Override - public void mouseClicked( MouseEvent event ) + mTree = new JTree(mModel); + mTree.setShowsRootHandles(true); + + mTree.addMouseListener(new MouseHandler()); + + mTree.getSelectionModel().setSelectionMode( + TreeSelectionModel.SINGLE_TREE_SELECTION); + + mTree.setCellRenderer(new EditorTreeCellRenderer()); + + mTree.setCellEditor(new FilterTreeNodeEditor()); + + mTree.setEditable(true); + + add(mTree); + } + + private void addFilterSet(FilterSet filterSet, DefaultMutableTreeNode parent) + { + List> filters = filterSet.getFilters(); + + /* sort the filters in alphabetical order by name */ + Collections.sort(filters, new Comparator>() { - if( SwingUtilities.isRightMouseButton( event ) ) - { - int row = mTree.getRowForLocation( event.getX(), - event.getY() ); - - if( row != -1 ) - { - mTree.setSelectionRow( row ); - - Object selectedNode = - mTree.getLastSelectedPathComponent(); - - if( selectedNode instanceof DefaultMutableTreeNode ) - { - final DefaultMutableTreeNode node = - (DefaultMutableTreeNode)selectedNode; - - if( node.getChildCount() > 0 ) - { - JPopupMenu selectionMenu = new JPopupMenu(); + @Override + public int compare(IFilter first, IFilter second) + { + return first.getName().compareTo(second.getName()); + } + }); - JMenuItem selectAll = new JMenuItem( "Select All" ); - selectAll.addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent arg0 ) - { - setFilterEnabled( mModel, node, true ); - } - }); - - selectionMenu.add( selectAll ); - - JMenuItem deselectAll = new JMenuItem( "Deselect All" ); - deselectAll.addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent arg0 ) - { - setFilterEnabled( mModel, node, false ); - } - }); + for(IFilter filter : filters) + { + DefaultMutableTreeNode child = new DefaultMutableTreeNode(filter); - selectionMenu.add( deselectAll ); - - selectionMenu.show( mTree, - event.getX(), - event.getY() ); - } - } - } - } + mModel.insertNodeInto(child, parent, parent.getChildCount()); + + if(filter instanceof FilterSet) + { + addFilterSet((FilterSet) filter, child); + } + else if(filter instanceof Filter) + { + addFilter((Filter) filter, child); + } + } + } + + private void addFilter(Filter filter, DefaultMutableTreeNode parent) + { + List> elements = filter.getFilterElements(); + + Collections.sort(elements, new Comparator>() + { + @Override + public int compare(FilterElement o1, FilterElement o2) + { + return o1.toString().compareTo(o2.toString()); + } + }); + + for(FilterElement element : elements) + { + DefaultMutableTreeNode child = new DefaultMutableTreeNode(element); + + mModel.insertNodeInto(child, parent, parent.getChildCount()); + } + } + + public class MouseHandler implements MouseListener + { + public MouseHandler() + { } - @Override - public void mousePressed( MouseEvent e ){} - @Override - public void mouseReleased( MouseEvent e ){} - @Override - public void mouseEntered( MouseEvent e ){} - @Override - public void mouseExited( MouseEvent e ){} - } - - /** - * Recursively sets the enabled state on all filter node children below the - * specified parent node. - * - * @param model - model containing the tree nodes - * @param node - parent node - * @param enabled - true or false to enable or disable the filters - */ - private void setFilterEnabled( DefaultTreeModel model, - DefaultMutableTreeNode node, - boolean enabled ) - { - Object obj = node.getUserObject(); - - if( obj instanceof FilterSet ) - { - ((FilterSet)obj).setEnabled( enabled ); - } - else if( obj instanceof Filter ) - { - ((Filter)obj).setEnabled( enabled ); - } - else if( obj instanceof FilterElement ) - { - ((FilterElement)obj).setEnabled( enabled ); - } - - model.nodeChanged( node ); - - /* Recursively set the children of this node */ - Enumeration children = node.children(); - - while( children.hasMoreElements() ) - { - Object child = children.nextElement(); - - if( child instanceof DefaultMutableTreeNode ) - { - setFilterEnabled( model, (DefaultMutableTreeNode)child, enabled ); - } - } - } - - public class EditorTreeCellRenderer extends DefaultTreeCellRenderer - { - private static final long serialVersionUID = 1L; - - public Component getTreeCellRendererComponent( JTree tree, - Object treeNode, - boolean selected, - boolean expanded, - boolean leaf, - int row, - boolean hasFocus ) - { - if( treeNode instanceof DefaultMutableTreeNode ) - { - Object userObject = ((DefaultMutableTreeNode)treeNode).getUserObject(); - - JCheckBox checkBox = null; - - if( userObject instanceof IFilter ) - { - IFilter filter = (IFilter)userObject; - - checkBox = new JCheckBox( filter.getName() ); - checkBox.setSelected( filter.isEnabled() ); - } - else if( userObject instanceof FilterElement ) - { - FilterElement element = (FilterElement)userObject; - - checkBox = new JCheckBox( element.getName() ); - checkBox.setSelected( element.isEnabled() ); - } - - if( checkBox != null ) - { - if( selected ) - { - checkBox.setForeground( getTextSelectionColor() ); - checkBox.setBackground( getBackgroundSelectionColor() ); - } - else - { - checkBox.setForeground( getTextNonSelectionColor() ); - checkBox.setBackground( getBackgroundNonSelectionColor() ); - } - - return checkBox; - } - } - - return super.getTreeCellRendererComponent( tree, treeNode, selected, - expanded, leaf, row, hasFocus ); - } - } - - public class FilterCheckBox extends JCheckBox - { - private static final long serialVersionUID = 1L; - - private IFilter mFilter; - - public FilterCheckBox( IFilter filter ) - { - super( filter.getName() ); - - mFilter = filter; - - setSelected( mFilter.isEnabled() ); - - addItemListener( new ItemListener() - { - @Override - public void itemStateChanged( ItemEvent e ) - { - mFilter.setEnabled( FilterCheckBox.this.isSelected() ); - } - } ); - } - } - - public class FilterElementCheckBox extends JCheckBox - { - private static final long serialVersionUID = 1L; - - private FilterElement mFilter; - - public FilterElementCheckBox( FilterElement filter ) - { - super( filter.getName() ); - - mFilter = filter; - - setSelected( mFilter.isEnabled() ); - - addItemListener( new ItemListener() - { - @Override - public void itemStateChanged( ItemEvent arg0 ) - { - mFilter.setEnabled( FilterElementCheckBox.this.isSelected() ); - } - } ); - } - } - - public class FilterTreeNodeEditor implements TreeCellEditor - { @Override - @SuppressWarnings( { "rawtypes", "unchecked" } ) - public Component getTreeCellEditorComponent( JTree tree, Object node, - boolean isSelected, boolean expanded, boolean leaf, int row ) + public void mouseClicked(MouseEvent event) { - if( node instanceof DefaultMutableTreeNode ) - { - Object userObject = ((DefaultMutableTreeNode)node).getUserObject(); - - if( userObject instanceof IFilter ) - { - return new FilterCheckBox( (IFilter)userObject ); - } - else if( userObject instanceof FilterElement ) - { - return new FilterElementCheckBox( (FilterElement)userObject ); - } - } - - return new JLabel( node.toString() ); + if(SwingUtilities.isRightMouseButton(event)) + { + int row = mTree.getRowForLocation(event.getX(), + event.getY()); + + if(row != -1) + { + mTree.setSelectionRow(row); + + Object selectedNode = + mTree.getLastSelectedPathComponent(); + + if(selectedNode instanceof DefaultMutableTreeNode) + { + final DefaultMutableTreeNode node = + (DefaultMutableTreeNode) selectedNode; + + if(node.getChildCount() > 0) + { + JPopupMenu selectionMenu = new JPopupMenu(); + + JMenuItem selectAll = new JMenuItem("Select All"); + selectAll.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent arg0) + { + setFilterEnabled(mModel, node, true); + } + }); + + selectionMenu.add(selectAll); + + JMenuItem deselectAll = new JMenuItem("Deselect All"); + deselectAll.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent arg0) + { + setFilterEnabled(mModel, node, false); + } + }); + + selectionMenu.add(deselectAll); + + selectionMenu.show(mTree, + event.getX(), + event.getY()); + } + } + } + } } - @Override - public void addCellEditorListener( CellEditorListener l ) {} + @Override + public void mousePressed(MouseEvent e) + { + } - @Override + @Override + public void mouseReleased(MouseEvent e) + { + } + + @Override + public void mouseEntered(MouseEvent e) + { + } + + @Override + public void mouseExited(MouseEvent e) + { + } + } + + /** + * Recursively sets the enabled state on all filter node children below the + * specified parent node. + * + * @param model - model containing the tree nodes + * @param node - parent node + * @param enabled - true or false to enable or disable the filters + */ + private void setFilterEnabled(DefaultTreeModel model, + DefaultMutableTreeNode node, + boolean enabled) + { + Object obj = node.getUserObject(); + + if(obj instanceof FilterSet) + { + ((FilterSet) obj).setEnabled(enabled); + } + else if(obj instanceof Filter) + { + ((Filter) obj).setEnabled(enabled); + } + else if(obj instanceof FilterElement) + { + ((FilterElement) obj).setEnabled(enabled); + } + + model.nodeChanged(node); + + /* Recursively set the children of this node */ + Enumeration children = node.children(); + + while(children.hasMoreElements()) + { + Object child = children.nextElement(); + + if(child instanceof DefaultMutableTreeNode) + { + setFilterEnabled(model, (DefaultMutableTreeNode) child, enabled); + } + } + } + + public class EditorTreeCellRenderer extends DefaultTreeCellRenderer + { + private static final long serialVersionUID = 1L; + + public Component getTreeCellRendererComponent(JTree tree, + Object treeNode, + boolean selected, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus) + { + if(treeNode instanceof DefaultMutableTreeNode) + { + Object userObject = ((DefaultMutableTreeNode) treeNode).getUserObject(); + + JCheckBox checkBox = null; + + if(userObject instanceof IFilter) + { + IFilter filter = (IFilter) userObject; + + checkBox = new JCheckBox(filter.getName()); + checkBox.setSelected(filter.isEnabled()); + } + else if(userObject instanceof FilterElement) + { + FilterElement element = (FilterElement) userObject; + + checkBox = new JCheckBox(element.getName()); + checkBox.setSelected(element.isEnabled()); + } + + if(checkBox != null) + { + if(selected) + { + checkBox.setForeground(getTextSelectionColor()); + checkBox.setBackground(getBackgroundSelectionColor()); + } + else + { + checkBox.setForeground(getTextNonSelectionColor()); + checkBox.setBackground(getBackgroundNonSelectionColor()); + } + + return checkBox; + } + } + + return super.getTreeCellRendererComponent(tree, treeNode, selected, + expanded, leaf, row, hasFocus); + } + } + + public class FilterCheckBox extends JCheckBox + { + private static final long serialVersionUID = 1L; + + private IFilter mFilter; + + public FilterCheckBox(IFilter filter) + { + super(filter.getName()); + + mFilter = filter; + + setSelected(mFilter.isEnabled()); + + addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent e) + { + mFilter.setEnabled(FilterCheckBox.this.isSelected()); + } + }); + } + } + + public class FilterElementCheckBox extends JCheckBox + { + private static final long serialVersionUID = 1L; + + private FilterElement mFilter; + + public FilterElementCheckBox(FilterElement filter) + { + super(filter.getName()); + + mFilter = filter; + + setSelected(mFilter.isEnabled()); + + addItemListener(new ItemListener() + { + @Override + public void itemStateChanged(ItemEvent arg0) + { + mFilter.setEnabled(FilterElementCheckBox.this.isSelected()); + } + }); + } + } + + public class FilterTreeNodeEditor implements TreeCellEditor + { + @Override + @SuppressWarnings({"rawtypes", "unchecked"}) + public Component getTreeCellEditorComponent(JTree tree, Object node, + boolean isSelected, boolean expanded, boolean leaf, int row) + { + if(node instanceof DefaultMutableTreeNode) + { + Object userObject = ((DefaultMutableTreeNode) node).getUserObject(); + + if(userObject instanceof IFilter) + { + return new FilterCheckBox((IFilter) userObject); + } + else if(userObject instanceof FilterElement) + { + return new FilterElementCheckBox((FilterElement) userObject); + } + } + + return new JLabel(node.toString()); + } + + @Override + public void addCellEditorListener(CellEditorListener l) + { + } + + @Override public void cancelCellEditing() { - + } - @Override + @Override public Object getCellEditorValue() { - return null; + return null; } - @Override - public boolean isCellEditable( EventObject anEvent ) + @Override + public boolean isCellEditable(EventObject anEvent) { - return true; + return true; } - @Override - public void removeCellEditorListener( CellEditorListener l ) {} - - @Override - public boolean shouldSelectCell( EventObject anEvent ) + @Override + public void removeCellEditorListener(CellEditorListener l) { - return false; } - @Override + @Override + public boolean shouldSelectCell(EventObject anEvent) + { + return false; + } + + @Override public boolean stopCellEditing() { - return false; + return false; } - } + } } diff --git a/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java b/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java new file mode 100644 index 00000000..e655c8d2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.gui; + +import com.google.common.eventbus.Subscribe; +import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.gui.preference.PreferenceEditorViewRequest; +import io.github.dsheirer.gui.preference.PreferencesEditor; +import io.github.dsheirer.preference.UserPreferences; +import javafx.application.Platform; +import javafx.embed.swing.JFXPanel; +import javafx.stage.Stage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Java FX window manager. Handles all secondary Java FX windows that are used within this primarily + * Swing application. + */ +public class JavaFxWindowManager +{ + private final static Logger mLog = LoggerFactory.getLogger(JavaFxWindowManager.class); + + private UserPreferences mUserPreferences; + private PreferencesEditor mPreferencesEditor; + + public JavaFxWindowManager(UserPreferences userPreferences) + { + mUserPreferences = userPreferences; + + //Register this class to receive events via each method annotated with @Subscribe + MyEventBus.getEventBus().register(this); + } + + /** + * Closes all JavaFX windows and shutsdown the FX thread + */ + public void shutdown() + { + Platform.exit(); + } + + @Subscribe + public void process(final PreferenceEditorViewRequest request) + { + if(mPreferencesEditor == null) + { + new JFXPanel(); + Platform.setImplicitExit(false); + Platform.runLater(() -> { + try + { + mPreferencesEditor = new PreferencesEditor(mUserPreferences); + + Stage stage = new Stage(); + stage.setOnHidden(event -> mPreferencesEditor = null); + mPreferencesEditor.start(stage); + mPreferencesEditor.showEditor(request); + } + catch(Throwable e) + { + mLog.error("Error launching user preferences window", e); + } + }); + } + else + { + Platform.runLater(() -> { + try + { + Stage stage = mPreferencesEditor.getStage(); + stage.show(); + mPreferencesEditor.showEditor(request); + } + catch(Throwable t) + { + mLog.error("Error showing existing preferences editor window", t); + } + }); + } + } +} diff --git a/src/main/java/io/github/dsheirer/gui/SDRTrunk.java b/src/main/java/io/github/dsheirer/gui/SDRTrunk.java index b996796a..2b732325 100644 --- a/src/main/java/io/github/dsheirer/gui/SDRTrunk.java +++ b/src/main/java/io/github/dsheirer/gui/SDRTrunk.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,16 +15,16 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.gui; import com.jidesoft.swing.JideSplitPane; import io.github.dsheirer.alias.AliasModel; -import io.github.dsheirer.alias.action.AliasActionManager; -import io.github.dsheirer.audio.AudioManager; +import io.github.dsheirer.audio.AudioPacketManager; import io.github.dsheirer.audio.broadcast.BroadcastModel; import io.github.dsheirer.audio.broadcast.BroadcastStatusPanel; +import io.github.dsheirer.audio.playback.AudioPlaybackManager; import io.github.dsheirer.controller.ControllerPanel; import io.github.dsheirer.controller.channel.Channel; import io.github.dsheirer.controller.channel.ChannelAutoStartFrame; @@ -31,10 +32,14 @@ import io.github.dsheirer.controller.channel.ChannelModel; import io.github.dsheirer.controller.channel.ChannelProcessingManager; import io.github.dsheirer.controller.channel.ChannelSelectionManager; import io.github.dsheirer.controller.channel.map.ChannelMapModel; +import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.gui.preference.PreferenceEditorType; +import io.github.dsheirer.gui.preference.PreferenceEditorViewRequest; import io.github.dsheirer.icon.IconManager; import io.github.dsheirer.map.MapService; import io.github.dsheirer.module.log.EventLogManager; import io.github.dsheirer.playlist.PlaylistManager; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.properties.SystemProperties; import io.github.dsheirer.record.RecorderManager; import io.github.dsheirer.sample.Listener; @@ -87,6 +92,7 @@ public class SDRTrunk implements Listener private static final String PROPERTY_BROADCAST_STATUS_VISIBLE = "main.broadcast.status.visible"; private boolean mBroadcastStatusVisible; + private AudioPacketManager mAudioPacketManager; private IconManager mIconManager; private BroadcastStatusPanel mBroadcastStatusPanel; private BroadcastModel mBroadcastModel; @@ -98,6 +104,8 @@ public class SDRTrunk implements Listener private SpectralDisplayPanel mSpectralPanel; private JFrame mMainGui = new JFrame(); private JideSplitPane mSplitPane; + private JavaFxWindowManager mJavaFxWindowManager; + private UserPreferences mUserPreferences = new UserPreferences(); private String mTitle; @@ -108,10 +116,10 @@ public class SDRTrunk implements Listener mLog.info("**** website: https://github.com/dsheirer/sdrtrunk ***"); mLog.info("*******************************************************************"); mLog.info("Memory Logging Format: [Used/Allocated PercentUsed%]"); - mLog.info("Host CPU Cores: " + Runtime.getRuntime().availableProcessors()); mLog.info("Host OS Name: " + System.getProperty("os.name")); mLog.info("Host OS Arch: " + System.getProperty("os.arch")); mLog.info("Host OS Version: " + System.getProperty("os.version")); + mLog.info("Host CPU Cores: " + Runtime.getRuntime().availableProcessors()); mLog.info("Host Max Java Memory: " + FileUtils.byteCountToDisplaySize(Runtime.getRuntime().maxMemory())); //Setup the application home directory @@ -145,36 +153,40 @@ public class SDRTrunk implements Listener EventLogManager eventLogManager = new EventLogManager(); - RecorderManager recorderManager = new RecorderManager(); + RecorderManager recorderManager = new RecorderManager(aliasModel); + + mJavaFxWindowManager = new JavaFxWindowManager(mUserPreferences); mSourceManager = new SourceManager(tunerModel, mSettingsManager); - mChannelProcessingManager = new ChannelProcessingManager(mChannelModel, channelMapModel, aliasModel, - eventLogManager, recorderManager, mSourceManager); - mChannelProcessingManager.addAudioPacketListener(recorderManager); + mChannelProcessingManager = new ChannelProcessingManager(channelMapModel, eventLogManager, recorderManager, + mSourceManager, aliasModel); mChannelModel.addListener(mChannelProcessingManager); + mChannelProcessingManager.addChannelEventListener(mChannelModel); - ChannelSelectionManager channelSelectionManager = - new ChannelSelectionManager(mChannelModel); + ChannelSelectionManager channelSelectionManager = new ChannelSelectionManager(mChannelModel); mChannelModel.addListener(channelSelectionManager); - AliasActionManager aliasActionManager = new AliasActionManager(); - mChannelProcessingManager.addMessageListener(aliasActionManager); + AudioPlaybackManager audioPlaybackManager = new AudioPlaybackManager(mSourceManager.getMixerManager()); - AudioManager audioManager = new AudioManager(mSourceManager.getMixerManager()); - mChannelProcessingManager.addAudioPacketListener(audioManager); + mBroadcastModel = new BroadcastModel(aliasModel, mIconManager); - mBroadcastModel = new BroadcastModel(mIconManager); - - mChannelProcessingManager.addAudioPacketListener(mBroadcastModel); + //Audio packets are routed through the audio packet manager for metadata enrichment and then + //distributed to the audio packet processors (ie playback, recording, streaming, etc.) + mAudioPacketManager = new AudioPacketManager(aliasModel); + mAudioPacketManager.addListener(recorderManager); + mAudioPacketManager.addListener(audioPlaybackManager); + mAudioPacketManager.addListener(mBroadcastModel); + mAudioPacketManager.start(); + mChannelProcessingManager.addAudioPacketListener(mAudioPacketManager); MapService mapService = new MapService(mIconManager); - mChannelProcessingManager.addMessageListener(mapService); + mChannelProcessingManager.addDecodeEventListener(mapService); - mControllerPanel = new ControllerPanel(audioManager, aliasModel, mBroadcastModel, + mControllerPanel = new ControllerPanel(audioPlaybackManager, aliasModel, mBroadcastModel, mChannelModel, channelMapModel, mChannelProcessingManager, mIconManager, - mapService, mSettingsManager, mSourceManager, tunerModel); + mapService, mSettingsManager, mSourceManager, tunerModel, mUserPreferences); mSpectralPanel = new SpectralDisplayPanel(mChannelModel, mChannelProcessingManager, mSettingsManager, tunerModel); @@ -185,7 +197,7 @@ public class SDRTrunk implements Listener tunerModel.addListener(this); PlaylistManager playlistManager = new PlaylistManager(aliasModel, mBroadcastModel, mChannelModel, - channelMapModel); + channelMapModel, mUserPreferences); playlistManager.init(); @@ -338,6 +350,12 @@ public class SDRTrunk implements Listener viewMenu.add(new JSeparator()); viewMenu.add(new ClearTunerMenuItem(mSpectralPanel)); + viewMenu.add(new JSeparator()); + JMenuItem preferencesItem = new JMenuItem("Preferences"); + preferencesItem.addActionListener(e -> MyEventBus.getEventBus() + .post(new PreferenceEditorViewRequest(PreferenceEditorType.CHANNEL_EVENT))); + viewMenu.add(preferencesItem); + menuBar.add(viewMenu); JMenuItem screenCaptureItem = new JMenuItem("Screen Capture"); @@ -414,14 +432,16 @@ public class SDRTrunk implements Listener */ private void processShutdown() { - mLog.debug("Application shutdown started ..."); - mLog.debug("Stopping channels ..."); + mLog.info("Application shutdown started ..."); + mJavaFxWindowManager.shutdown(); + mLog.info("Stopping channels ..."); mChannelProcessingManager.shutdown(); - mLog.debug("Stopping spectral display ..."); + mAudioPacketManager.stop(); + mLog.info("Stopping spectral display ..."); mSpectralPanel.clearTuner(); - mLog.debug("Releasing tuners ..."); + mLog.info("Releasing tuners ..."); mSourceManager.shutdown(); - mLog.debug("Shutdown complete."); + mLog.info("Shutdown complete."); } /** diff --git a/src/main/java/io/github/dsheirer/gui/instrument/chart/IInstrumentedAFSK1200Decoder.java b/src/main/java/io/github/dsheirer/gui/instrument/chart/IInstrumentedAFSK1200Decoder.java index 446c6c64..5308cd81 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/chart/IInstrumentedAFSK1200Decoder.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/chart/IInstrumentedAFSK1200Decoder.java @@ -16,15 +16,15 @@ package io.github.dsheirer.gui.instrument.chart; import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.sample.Listener; import javafx.beans.property.SimpleIntegerProperty; public interface IInstrumentedAFSK1200Decoder { - public SimpleIntegerProperty getBufferCountProperty(); + SimpleIntegerProperty getBufferCountProperty(); - public AFSK1200DecoderInstrumented getAFSK1200Decoder(); + AFSK1200DecoderInstrumented getAFSK1200Decoder(); - public void setMessageListener(Listener listener); + void setMessageListener(Listener listener); } diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractAFSK1200Pane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractAFSK1200Pane.java index e4e48d1f..546b807d 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractAFSK1200Pane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/AbstractAFSK1200Pane.java @@ -20,7 +20,7 @@ import io.github.dsheirer.gui.instrument.chart.AFSK1200ZeroCrossingErrorDetector import io.github.dsheirer.gui.instrument.chart.DecodedSymbolChart; import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder; import io.github.dsheirer.gui.instrument.chart.RealSampleLineChart; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; @@ -52,7 +52,7 @@ public abstract class AbstractAFSK1200Pane extends RealDecoderPane private void init() { //This is force-cast without type checking .... - addListener((Listener)getDecoder()); + addListener((Listener) getDecoder()); addListener(getSampleLineChart()); @@ -69,12 +69,12 @@ public abstract class AbstractAFSK1200Pane extends RealDecoderPane getDecoder().getAFSK1200Decoder().addListener(getDecodedSymbolChart()); - getDecoder().setMessageListener(new Listener() + getDecoder().setMessageListener(new Listener() { @Override - public void receive(Message message) + public void receive(IMessage message) { - mLog.debug((message.isValid() ? "PASS " : "FAIL ") + message.getMessage()); + mLog.debug((message.isValid() ? "PASS " : "FAIL ") + message.toString()); } }); } @@ -144,7 +144,7 @@ public abstract class AbstractAFSK1200Pane extends RealDecoderPane if(mZeroCrossingErrorDetectorChart == null) { mZeroCrossingErrorDetectorChart = new AFSK1200ZeroCrossingErrorDetectorChart(getDecoder(), - getDecoder().getAFSK1200Decoder().getErrorDetector().getBuffer().length); + getDecoder().getAFSK1200Decoder().getErrorDetector().getBuffer().length); } return mZeroCrossingErrorDetectorChart; diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/LJ1200Pane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/LJ1200Pane.java index 41f6341c..84c1d94e 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/LJ1200Pane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/LJ1200Pane.java @@ -40,7 +40,7 @@ public class LJ1200Pane extends AbstractAFSK1200Pane mDecoder.getTowerMessageFramer().setSyncDetectListener(new ISyncDetectListener() { @Override - public void syncDetected() + public void syncDetected(int bitErrors) { mLog.debug("Tower Sync Detected!"); } @@ -54,7 +54,7 @@ public class LJ1200Pane extends AbstractAFSK1200Pane mDecoder.getTransponderMessageFramer().setSyncDetectListener(new ISyncDetectListener() { @Override - public void syncDetected() + public void syncDetected(int bitErrors) { mLog.debug("Transponder Sync Detected!"); } diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/LTRNetPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/LTRNetPane.java index bf09bb33..ddefac8f 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/LTRNetPane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/LTRNetPane.java @@ -18,7 +18,7 @@ package io.github.dsheirer.gui.instrument.decoder; import io.github.dsheirer.gui.instrument.chart.LTRNetSampleBufferChart; import io.github.dsheirer.gui.instrument.chart.RealSampleLineChart; import io.github.dsheirer.gui.instrument.chart.ZeroCrossingErrorDetectorChart; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.MessageDirection; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.ltrnet.DecodeConfigLTRNet; @@ -72,13 +72,13 @@ public class LTRNetPane extends RealDecoderPane { DecodeConfigLTRNet config = new DecodeConfigLTRNet(); config.setMessageDirection(MessageDirection.OSW); - mDecoder = new LTRNetDecoderInstrumented(config, null); - mDecoder.setMessageListener(new Listener() + mDecoder = new LTRNetDecoderInstrumented(config); + mDecoder.setMessageListener(new Listener() { @Override - public void receive(Message message) + public void receive(IMessage message) { - mLog.debug(message.getMessage()); + mLog.debug(message.toString()); } }); } @@ -143,7 +143,7 @@ public class LTRNetPane extends RealDecoderPane if(mZeroCrossingErrorDetectorChart == null) { mZeroCrossingErrorDetectorChart = new ZeroCrossingErrorDetectorChart(getDecoder(), - getDecoder().getLTRDecoder().getErrorDetector().getBuffer().length); + getDecoder().getLTRDecoder().getErrorDetector().getBuffer().length); } return mZeroCrossingErrorDetectorChart; diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/MDC1200Pane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/MDC1200Pane.java index 431a86c0..81e63de2 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/MDC1200Pane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/MDC1200Pane.java @@ -40,7 +40,7 @@ public class MDC1200Pane extends AbstractAFSK1200Pane mDecoder.getMessageFramer().setSyncDetectListener(new ISyncDetectListener() { @Override - public void syncDetected() + public void syncDetected(int bitErrors) { mLog.debug("Sync Detected!"); } diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/MPT1327Pane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/MPT1327Pane.java index 5ddbf912..d0c910d9 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/MPT1327Pane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/MPT1327Pane.java @@ -40,7 +40,7 @@ public class MPT1327Pane extends AbstractAFSK1200Pane mDecoder.getControlMessageFramer().setSyncDetectListener(new ISyncDetectListener() { @Override - public void syncDetected() + public void syncDetected(int bitErrors) { // mLog.debug("Control Sync Detected!"); } @@ -54,7 +54,7 @@ public class MPT1327Pane extends AbstractAFSK1200Pane mDecoder.getTrafficMessageFramer().setSyncDetectListener(new ISyncDetectListener() { @Override - public void syncDetected() + public void syncDetected(int bitErrors) { // mLog.debug("Traffic Sync Detected!"); } diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1C4FMPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1C4FMPane.java index 34755d18..d691c853 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1C4FMPane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1C4FMPane.java @@ -20,7 +20,7 @@ import io.github.dsheirer.gui.instrument.chart.DoubleLineChart; import io.github.dsheirer.gui.instrument.chart.EyeDiagramChart; import io.github.dsheirer.gui.instrument.chart.PhaseLineChart; import io.github.dsheirer.gui.instrument.chart.SymbolChart; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.p25.P25DecoderC4FMInstrumented; import io.github.dsheirer.sample.Listener; @@ -43,7 +43,7 @@ public class P25Phase1C4FMPane extends ComplexDecoderPane private DoubleLineChart mPLLFrequencyLineChart; private DoubleLineChart mSamplesPerSymbolLineChart; private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster(); - private P25DecoderC4FMInstrumented mDecoder = new P25DecoderC4FMInstrumented(null); + private P25DecoderC4FMInstrumented mDecoder = new P25DecoderC4FMInstrumented(); public P25Phase1C4FMPane() { @@ -67,12 +67,12 @@ public class P25Phase1C4FMPane extends ComplexDecoderPane HBox.setHgrow(getDecoderChartBox(), Priority.ALWAYS); getChildren().addAll(getSampleChartBox(), getDecoderChartBox()); - mDecoder.setMessageListener(new Listener() + mDecoder.setMessageListener(new Listener() { @Override - public void receive(Message message) + public void receive(IMessage message) { - mLog.debug(message.getMessage()); + mLog.debug(message.toString()); } }); @@ -86,7 +86,7 @@ public class P25Phase1C4FMPane extends ComplexDecoderPane mDecoder.setSampleRate(sampleRate); double samplesPerSymbol = sampleRate / 4800.0; - getSampleLineChart().setSamplesPerSymbol((int)samplesPerSymbol); + getSampleLineChart().setSamplesPerSymbol((int) samplesPerSymbol); } private P25DecoderC4FMInstrumented getDecoder() @@ -117,7 +117,7 @@ public class P25Phase1C4FMPane extends ComplexDecoderPane HBox.setHgrow(getPLLPhaseErrorLineChart(), Priority.ALWAYS); HBox.setHgrow(getPLLFrequencyLineChart(), Priority.ALWAYS); mDecoderChartBox.getChildren().addAll(getSymbolChart(), getPLLPhaseErrorLineChart(), - getPLLFrequencyLineChart()); + getPLLFrequencyLineChart()); } return mDecoderChartBox; @@ -136,7 +136,7 @@ public class P25Phase1C4FMPane extends ComplexDecoderPane HBox.setHgrow(getEyeDiagramChart(), Priority.ALWAYS); HBox.setHgrow(getSamplesPerSymbolLineChart(), Priority.ALWAYS); mSampleChartBox.getChildren().addAll(getSampleLineChart(), getEyeDiagramChart(), - getSamplesPerSymbolLineChart()); + getSamplesPerSymbolLineChart()); } return mSampleChartBox; @@ -176,7 +176,7 @@ public class P25Phase1C4FMPane extends ComplexDecoderPane { if(mPLLFrequencyLineChart == null) { - mPLLFrequencyLineChart = new DoubleLineChart( "PLL Frequency", -500, 500, 50, 40); + mPLLFrequencyLineChart = new DoubleLineChart("PLL Frequency", -500, 500, 50, 40); } return mPLLFrequencyLineChart; @@ -186,7 +186,7 @@ public class P25Phase1C4FMPane extends ComplexDecoderPane { if(mSamplesPerSymbolLineChart == null) { - mSamplesPerSymbolLineChart = new DoubleLineChart( "Sample Point", 9.5, 11.5, 0.1, 40); + mSamplesPerSymbolLineChart = new DoubleLineChart("Sample Point", 9.5, 11.5, 0.1, 40); } return mSamplesPerSymbolLineChart; diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1LSMPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1LSMPane.java index d80bf4e7..3272889f 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1LSMPane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/P25Phase1LSMPane.java @@ -21,7 +21,7 @@ import io.github.dsheirer.gui.instrument.chart.EyeDiagramChart; import io.github.dsheirer.gui.instrument.chart.PhaseLineChart; import io.github.dsheirer.gui.instrument.chart.SamplesPerSymbolChart; import io.github.dsheirer.gui.instrument.chart.SymbolChart; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.p25.P25DecoderLSMInstrumented; import io.github.dsheirer.sample.Listener; @@ -44,7 +44,7 @@ public class P25Phase1LSMPane extends ComplexDecoderPane private DoubleLineChart mPLLFrequencyLineChart; private SamplesPerSymbolChart mSamplesPerSymbolLineChart; private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster(); - private P25DecoderLSMInstrumented mDecoder = new P25DecoderLSMInstrumented(null); + private P25DecoderLSMInstrumented mDecoder = new P25DecoderLSMInstrumented(); public P25Phase1LSMPane() { @@ -68,12 +68,12 @@ public class P25Phase1LSMPane extends ComplexDecoderPane HBox.setHgrow(getBottomChartBox(), Priority.ALWAYS); getChildren().addAll(getTopChartBox(), getBottomChartBox()); - mDecoder.setMessageListener(new Listener() + mDecoder.setMessageListener(new Listener() { @Override - public void receive(Message message) + public void receive(IMessage message) { - mLog.debug(message.getMessage()); + mLog.debug(message.toString()); } }); @@ -87,7 +87,7 @@ public class P25Phase1LSMPane extends ComplexDecoderPane mDecoder.setSampleRate(sampleRate); double samplesPerSymbol = sampleRate / 4800.0; - getDifferentialDemodulatedSamplesChartBox().setSamplesPerSymbol((int)samplesPerSymbol); + getDifferentialDemodulatedSamplesChartBox().setSamplesPerSymbol((int) samplesPerSymbol); getSamplesPerSymbolLineChart().setSamplesPerSymbol(samplesPerSymbol); } @@ -119,7 +119,7 @@ public class P25Phase1LSMPane extends ComplexDecoderPane HBox.setHgrow(getPLLPhaseErrorLineChart(), Priority.ALWAYS); HBox.setHgrow(getPLLFrequencyLineChart(), Priority.ALWAYS); mBottomChartBox.getChildren().addAll(getSymbolConstellationChart(), getPLLPhaseErrorLineChart(), - getPLLFrequencyLineChart()); + getPLLFrequencyLineChart()); } return mBottomChartBox; @@ -138,7 +138,7 @@ public class P25Phase1LSMPane extends ComplexDecoderPane HBox.setHgrow(getEyeDiagramChart(), Priority.ALWAYS); HBox.setHgrow(getSamplesPerSymbolLineChart(), Priority.ALWAYS); mTopChartBox.getChildren().addAll(getDifferentialDemodulatedSamplesChartBox(), getEyeDiagramChart(), - getSamplesPerSymbolLineChart()); + getSamplesPerSymbolLineChart()); } return mTopChartBox; @@ -178,7 +178,7 @@ public class P25Phase1LSMPane extends ComplexDecoderPane { if(mPLLFrequencyLineChart == null) { - mPLLFrequencyLineChart = new DoubleLineChart( "PLL Frequency", -2500, 2500, 200, 40); + mPLLFrequencyLineChart = new DoubleLineChart("PLL Frequency", -2500, 2500, 200, 40); } return mPLLFrequencyLineChart; diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/Tait1200Pane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/Tait1200Pane.java index feee0a58..c3b23aaa 100644 --- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/Tait1200Pane.java +++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/Tait1200Pane.java @@ -40,7 +40,7 @@ public class Tait1200Pane extends AbstractAFSK1200Pane mDecoder.getANIMessageFramer().setSyncDetectListener(new ISyncDetectListener() { @Override - public void syncDetected() + public void syncDetected(int bitErrors) { mLog.debug("ANI Sync Detected!"); } @@ -54,7 +54,7 @@ public class Tait1200Pane extends AbstractAFSK1200Pane mDecoder.getGPSMessageFramer().setSyncDetectListener(new ISyncDetectListener() { @Override - public void syncDetected() + public void syncDetected(int bitErrors) { mLog.debug("GPS Sync Detected!"); } diff --git a/src/main/java/io/github/dsheirer/gui/preference/DecodeEventViewPreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/DecodeEventViewPreferenceEditor.java new file mode 100644 index 00000000..f5e34074 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/preference/DecodeEventViewPreferenceEditor.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.gui.preference; + +import io.github.dsheirer.preference.TimestampFormat; +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.preference.event.DecodeEventPreference; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.Label; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; + +import java.util.Date; + + +/** + * Preference settings for channel event view + */ +public class DecodeEventViewPreferenceEditor extends HBox +{ + private DecodeEventPreference mDecodeEventPreference; + private GridPane mEditorPane; + private ChoiceBox mTimestampFormatChoiceBox; + private Label mTimestampFormatLabel; + + public DecodeEventViewPreferenceEditor(UserPreferences userPreferences) + { + mDecodeEventPreference = userPreferences.getDecodeEventPreference(); + getChildren().add(getEditorPane()); + } + + private GridPane getEditorPane() + { + if(mEditorPane == null) + { + mEditorPane = new GridPane(); + mEditorPane.setPadding(new Insets(10, 10, 10, 10)); + GridPane.setMargin(getTimestampFormatLabel(), new Insets(0, 10, 0, 0)); + GridPane.setHalignment(getTimestampFormatLabel(), HPos.LEFT); + mEditorPane.add(getTimestampFormatLabel(), 0, 0); + mEditorPane.add(getTimestampFormatChoiceBox(), 1, 0); + } + + return mEditorPane; + } + + private Label getTimestampFormatLabel() + { + if(mTimestampFormatLabel == null) + { + mTimestampFormatLabel = new Label("Timestamp Format"); + } + + return mTimestampFormatLabel; + } + + private ChoiceBox getTimestampFormatChoiceBox() + { + if(mTimestampFormatChoiceBox == null) + { + mTimestampFormatChoiceBox = new ChoiceBox<>(); + + for(TimestampFormat format: TimestampFormat.values()) + { + mTimestampFormatChoiceBox.getItems().add(new DisplayableTimestamp(format)); + } + + TimestampFormat current = mDecodeEventPreference.getTimestampFormat(); + + for(DisplayableTimestamp displayableTimestamp: mTimestampFormatChoiceBox.getItems()) + { + if(displayableTimestamp.getTimestampFormat() == current) + { + mTimestampFormatChoiceBox.getSelectionModel().select(displayableTimestamp); + continue; + } + } + + mTimestampFormatChoiceBox.setOnAction(event -> { + DisplayableTimestamp selected = mTimestampFormatChoiceBox.getSelectionModel().getSelectedItem(); + mDecodeEventPreference.setTimestampFormat(selected.getTimestampFormat()); + }); + } + + return mTimestampFormatChoiceBox; + } + + public class DisplayableTimestamp + { + private TimestampFormat mTimestampFormat; + + public DisplayableTimestamp(TimestampFormat timestampFormat) + { + mTimestampFormat = timestampFormat; + } + + public TimestampFormat getTimestampFormat() + { + return mTimestampFormat; + } + + public String toString() + { + return mTimestampFormat.getFormatter().format(new Date(System.currentTimeMillis())); + } + } +} diff --git a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java new file mode 100644 index 00000000..831cbc28 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.gui.preference; + +import io.github.dsheirer.preference.UserPreferences; +import javafx.scene.Node; + +/** + * Creates an editor for the specified preference editor type + */ +public class PreferenceEditorFactory +{ + public static Node getEditor(PreferenceEditorType preferenceEditorType, UserPreferences userPreferences) + { + switch(preferenceEditorType) + { + case TALKGROUP_FORMAT: + return new TalkgroupFormatPreferenceEditor(userPreferences); + case CHANNEL_EVENT: + return new DecodeEventViewPreferenceEditor(userPreferences); + } + + return null; + } +} diff --git a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java new file mode 100644 index 00000000..c12c8796 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.gui.preference; + +/** + * Preference editor tree node enumeration. + */ +public enum PreferenceEditorType +{ + CHANNEL_EVENT("Channel Events"), + TALKGROUP_FORMAT("Talkgroups"); + + private String mLabel; + + PreferenceEditorType(String label) + { + mLabel = label; + } + + public String toString() + { + return mLabel; + } +} diff --git a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorViewRequest.java b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorViewRequest.java new file mode 100644 index 00000000..2edee61a --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorViewRequest.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.gui.preference; + +/** + * Request to launch the preferences editor and scroll to a specific editor view + */ +public class PreferenceEditorViewRequest +{ + private PreferenceEditorType mPreferenceType; + + public PreferenceEditorViewRequest(PreferenceEditorType preferenceEditorType) + { + mPreferenceType = preferenceEditorType; + } + + public PreferenceEditorType getPreferenceType() + { + return mPreferenceType; + } +} diff --git a/src/main/java/io/github/dsheirer/gui/preference/PreferencesEditor.java b/src/main/java/io/github/dsheirer/gui/preference/PreferencesEditor.java new file mode 100644 index 00000000..617a4031 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/preference/PreferencesEditor.java @@ -0,0 +1,291 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.gui.preference; + +import io.github.dsheirer.preference.UserPreferences; +import javafx.application.Application; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.TreeItem; +import javafx.scene.control.TreeView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import javafx.stage.Window; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.ListIterator; +import java.util.Map; + +/** + * Preferences editor dialog + */ +public class PreferencesEditor extends Application +{ + private final static Logger mLog = LoggerFactory.getLogger(PreferencesEditor.class); + + private UserPreferences mUserPreferences; + private BorderPane mBorderPane; + private TreeView mEditorSelectionTreeView; + private DecodeEventViewPreferenceEditor mDecodeEventViewPreferenceEditor; + private VBox mDefaultEditor; + private HBox mControlBox; + + /** + * Constructs a preferences editor instance + */ + public PreferencesEditor() + { + } + + public Stage getStage() + { + try + { + Window window = getBorderPane().getScene().getWindow(); + + if(window instanceof Stage) + { + return (Stage)window; + } + } + catch(Throwable t) + { + mLog.debug("Error", t); + } + + return null; + } + + public PreferencesEditor(UserPreferences userPreferences) + { + mUserPreferences = userPreferences; + } + + private UserPreferences getUserPreferences() + { + if(mUserPreferences == null) + { + mUserPreferences = new UserPreferences(); + } + + return mUserPreferences; + } + + @Override + public void start(Stage stage) throws Exception + { + stage.setTitle("Preferences"); + Scene scene = new Scene(getBorderPane(), 800, 500); + stage.setScene(scene); + stage.show(); + } + + /** + * Shows the editor specified in the request by scrolling the editor view tree to the selected item. + */ + public void showEditor(PreferenceEditorViewRequest request) + { + TreeItem toSelect = recursivelyFindEditorType(getEditorSelectionTreeView().getRoot(), request.getPreferenceType()); + + if(toSelect != null) + { + getEditorSelectionTreeView().getSelectionModel().select(toSelect); + } + } + + private TreeItem recursivelyFindEditorType(TreeItem parent, PreferenceEditorType type) + { + ListIterator li = parent.getChildren().listIterator(); + + while(li.hasNext()) + { + TreeItem treeItem = li.next(); + + if(treeItem.getValue() instanceof PreferenceEditorType && + ((PreferenceEditorType)treeItem.getValue()).equals(type)) + { + return treeItem; + } + else + { + TreeItem item = recursivelyFindEditorType(treeItem, type); + + if(item != null) + { + return item; + } + } + } + + return null; + } + + + /** + * Primary layout for the editor window + */ + private BorderPane getBorderPane() + { + if(mBorderPane == null) + { + mBorderPane = new BorderPane(); + mBorderPane.setLeft(getEditorSelectionTreeView()); + mBorderPane.setCenter(getDefaultEditor()); + mBorderPane.setBottom(getControlBox()); + } + + return mBorderPane; + } + + private VBox getDefaultEditor() + { + if(mDefaultEditor == null) + { + mDefaultEditor = new VBox(); + mDefaultEditor.setPadding(new Insets(10, 10, 10, 10)); + Label label = new Label("Please select a preference ..."); + + mDefaultEditor.getChildren().add(label); + } + + return mDefaultEditor; + } + + /** + * Preference type selection list + */ + private TreeView getEditorSelectionTreeView() + { + if(mEditorSelectionTreeView == null) + { + TreeItem treeRoot = new TreeItem<>("Root node"); + + TreeItem displayItem = new TreeItem<>("Display"); + displayItem.getChildren().add(new TreeItem(PreferenceEditorType.CHANNEL_EVENT)); + displayItem.getChildren().add(new TreeItem(PreferenceEditorType.TALKGROUP_FORMAT)); + treeRoot.getChildren().add(displayItem); + displayItem.setExpanded(true); + + mEditorSelectionTreeView = new TreeView(); + mEditorSelectionTreeView.setShowRoot(false); + mEditorSelectionTreeView.setRoot(treeRoot); + treeRoot.setExpanded(true); + mEditorSelectionTreeView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); + mEditorSelectionTreeView.getSelectionModel().selectedItemProperty().addListener(new EditorTreeSelectionListener()); + } + + return mEditorSelectionTreeView; + } + + /** + * Decode event view preferences editor + */ + private DecodeEventViewPreferenceEditor getDecodeEventViewPreferenceEditor() + { + if(mDecodeEventViewPreferenceEditor == null) + { + mDecodeEventViewPreferenceEditor = new DecodeEventViewPreferenceEditor(getUserPreferences()); + } + + return mDecodeEventViewPreferenceEditor; + } + + /** + * Control box with OK button. + */ + private HBox getControlBox() + { + if(mControlBox == null) + { + mControlBox = new HBox(); + Button okButton = new Button("Ok"); + okButton.setOnAction(event -> { + Stage stage = (Stage)getBorderPane().getScene().getWindow(); + stage.close(); + }); + HBox.setMargin(okButton, new Insets(5, 5, 5, 5)); + mControlBox.setAlignment(Pos.CENTER_RIGHT); + mControlBox.getChildren().add(okButton); + } + + return mControlBox; + } + + /** + * Listens for editor tree selection events and creates a preference editor instance for each type as needed. + * + * Constructed editors are cached for reuse. + */ + public class EditorTreeSelectionListener implements ChangeListener + { + private Map mEditors = new HashMap<>(); + + @Override + public void changed(ObservableValue observable, Object oldValue, Object newValue) + { + Node editor = getEditor(newValue); + BorderPane.setAlignment(editor, Pos.CENTER_LEFT); + getBorderPane().setCenter(editor); + } + + private Node getEditor(Object treeNodeItem) + { + if(treeNodeItem instanceof TreeItem) + { + Object value = ((TreeItem)treeNodeItem).getValue(); + + if(value instanceof PreferenceEditorType) + { + PreferenceEditorType type = (PreferenceEditorType)value; + + Node editor = mEditors.get(type); + + if(editor != null) + { + return editor; + } + + editor = PreferenceEditorFactory.getEditor(type, getUserPreferences()); + + if(editor != null) + { + mEditors.put(type, editor); + return editor; + } + } + } + + return getDefaultEditor(); + } + } + + public static void main(String[] args) + { + launch(args); + } +} diff --git a/src/main/java/io/github/dsheirer/gui/preference/TalkgroupFormatPreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/TalkgroupFormatPreferenceEditor.java new file mode 100644 index 00000000..12893b56 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/preference/TalkgroupFormatPreferenceEditor.java @@ -0,0 +1,120 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.gui.preference; + +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.preference.identifier.IntegerFormat; +import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference; +import io.github.dsheirer.protocol.Protocol; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.Label; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; + + +/** + * Preference settings for channel event view + */ +public class TalkgroupFormatPreferenceEditor extends HBox +{ + private TalkgroupFormatPreference mTalkgroupFormatPreference; + private GridPane mEditorPane; + + public TalkgroupFormatPreferenceEditor(UserPreferences userPreferences) + { + mTalkgroupFormatPreference = userPreferences.getTalkgroupFormatPreference(); + getChildren().add(getEditorPane()); + } + + private GridPane getEditorPane() + { + if(mEditorPane == null) + { + mEditorPane = new GridPane(); + mEditorPane.setPadding(new Insets(10, 10, 10, 10)); + + int row = 0; + + mEditorPane.add(new Label("Protocol"), 0, row); + mEditorPane.add(new Label("Display Format"), 1, row++); + + for(Protocol protocol : Protocol.TALKGROUP_PROTOCOLS) + { + Label label = new Label(protocol.toString()); + GridPane.setMargin(label, new Insets(0, 10, 0, 0)); + GridPane.setHalignment(label, HPos.LEFT); + mEditorPane.add(label, 0, row); + IntegerFormatEditor editor = new IntegerFormatEditor(mTalkgroupFormatPreference, protocol); + GridPane.setMargin(editor, new Insets(5,5,5,5)); + mEditorPane.add(editor, 1, row); + FixedWidthEditor fixedWidthEditor = new FixedWidthEditor(mTalkgroupFormatPreference, protocol); + GridPane.setMargin(fixedWidthEditor, new Insets(0,0,0,10)); + mEditorPane.add(fixedWidthEditor, 2, row); + row++; + } + } + + return mEditorPane; + } + + /** + * Choice box control for tracking talkgroup format preference per protocol + */ + public class IntegerFormatEditor extends ChoiceBox + { + private TalkgroupFormatPreference mTalkgroupFormatPreference; + private Protocol mProtocol; + + public IntegerFormatEditor(TalkgroupFormatPreference preference, Protocol protocol) + { + mTalkgroupFormatPreference = preference; + mProtocol = protocol; + getItems().addAll(IntegerFormat.values()); + getSelectionModel().select(mTalkgroupFormatPreference.getTalkgroupFormat(mProtocol)); + + setOnAction(event -> { + IntegerFormat selected = getSelectionModel().getSelectedItem(); + mTalkgroupFormatPreference.setTalkgroupFormat(mProtocol, selected); + }); + } + } + + /** + * Check box control for tracking fixed width preference per protocol + */ + public class FixedWidthEditor extends CheckBox + { + private TalkgroupFormatPreference mTalkgroupFormatPreference; + private Protocol mProtocol; + + public FixedWidthEditor(TalkgroupFormatPreference preference, Protocol protocol) + { + super("Fixed Width"); + mTalkgroupFormatPreference = preference; + mProtocol = protocol; + setSelected(mTalkgroupFormatPreference.isTalkgroupFixedWidth(mProtocol)); + setOnAction(event -> mTalkgroupFormatPreference.setTalkgroupFixedWidth(mProtocol, isSelected())); + } + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/Form.java b/src/main/java/io/github/dsheirer/identifier/Form.java new file mode 100644 index 00000000..09f7ddf6 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/Form.java @@ -0,0 +1,54 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier; + +/** + * Identifier form. Indicates the type of identifier. + */ +public enum Form +{ + ALIAS_LIST, + CHANNEL, + CHANNEL_DESCRIPTOR, + CHANNEL_NAME, + CHANNEL_FREQUENCY, + DECODER_TYPE, + ENCRYPTION_KEY, + ESN, + IPV4_ADDRESS, + LOCATION_REGISTRATION_AREA, + LOJACK, + NEIGHBOR_SITE, + NETWORK_ACCESS_CODE, + PATCH_GROUP, + RF_SUBSYSTEM, + SHORT_DATA_MESSAGE, + SITE, + STATE, + SYSTEM, + TALKGROUP, + TELEPHONE_NUMBER, + UNIT_IDENTIFIER, + UNIT_STATUS, + USER_STATUS, + UNIQUE_ID, + WACN, + ANY; +} diff --git a/src/main/java/io/github/dsheirer/identifier/Identifier.java b/src/main/java/io/github/dsheirer/identifier/Identifier.java new file mode 100644 index 00000000..928bb349 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/Identifier.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier; + +import io.github.dsheirer.protocol.Protocol; + +import java.util.Objects; + +/** + * Identifier base class. + * + * Note: Identifiers are intended to be immutable. + */ +public abstract class Identifier +{ + private T mValue; + private IdentifierClass mIdentifierClass; + private Form mForm; + private Role mRole; + + public Identifier(T value, IdentifierClass identifierClass, Form form, Role role) + { + mValue = value; + mIdentifierClass = identifierClass; + mForm = form; + mRole = role; + } + + public String debug() + { + StringBuilder sb = new StringBuilder(); + sb.append("CLASS:").append(getIdentifierClass()); + sb.append(" FORM:").append(getForm()); + sb.append(" VALUE:").append(getValue()); + sb.append(" VALUE CLASS:").append(getValue().getClass()); + sb.append(" CLASS:").append(getClass()); + return sb.toString(); + } + + /** + * Validity test for the identifier. This method always returns true unless a subclass + * implementation overrides the value for a specific validity test implementation. + */ + public boolean isValid() + { + return true; + } + + /** + * Value of the identifier + */ + public T getValue() + { + return mValue; + } + + /** + * Class/type of identifier + */ + public IdentifierClass getIdentifierClass() + { + return mIdentifierClass; + } + + public Form getForm() + { + return mForm; + } + + /** + * Role of the identifier in the communications + */ + public Role getRole() + { + return mRole; + } + + /** + * Protocol that produces this identifier + */ + public abstract Protocol getProtocol(); + + @Override + public String toString() + { + return getValue().toString(); + } + + @Override + public boolean equals(Object o) + { + if(this == o) + { + return true; + } + if(o == null || getClass() != o.getClass()) + { + return false; + } + Identifier that = (Identifier)o; + return Objects.equals(getValue(), that.getValue()) && + getIdentifierClass() == that.getIdentifierClass() && + getForm() == that.getForm() && + getRole() == that.getRole(); + } + + @Override + public int hashCode() + { + return Objects.hash(getValue(), getIdentifierClass(), getForm(), getRole()); + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/IdentifierClass.java b/src/main/java/io/github/dsheirer/identifier/IdentifierClass.java new file mode 100644 index 00000000..c0ff2bc2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/IdentifierClass.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier; + +/** + * Identifier form. Indicates the type of identifier. + */ +public enum IdentifierClass +{ + //Decoder configuration detail + CONFIGURATION, + + //Decoder state or information + DECODER, + + //Communication network identifier + NETWORK, + + //Communication user identifier + USER; +} diff --git a/src/main/java/io/github/dsheirer/identifier/IdentifierCollection.java b/src/main/java/io/github/dsheirer/identifier/IdentifierCollection.java new file mode 100644 index 00000000..a2309804 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/IdentifierCollection.java @@ -0,0 +1,296 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier; + +import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * (Immutable) Collection of identifiers with convenient accessor methods + * + * @see MutableIdentifierCollection for the mutable version of this class + */ +public class IdentifierCollection +{ + private final static Logger mLog = LoggerFactory.getLogger(IdentifierCollection.class); + protected List mIdentifiers = new ArrayList<>(); + protected AliasListConfigurationIdentifier mAliasListConfigurationIdentifier; + private boolean mUpdated = false; + + /** + * Constructs an empty identifier collection + */ + public IdentifierCollection() + { + } + + public IdentifierCollection(Collection identifiers) + { + for(Identifier identifier: identifiers) + { + if(identifier == null) + { + throw new IllegalArgumentException("Identifier cannot be null"); + } + + mIdentifiers.add(identifier); + + if(identifier instanceof AliasListConfigurationIdentifier) + { + mAliasListConfigurationIdentifier = (AliasListConfigurationIdentifier)identifier; + } + } + } + + public IdentifierCollection(Identifier identifier) + { + if(identifier == null) + { + throw new IllegalArgumentException("Identifier cannot be null"); + } + + mIdentifiers.add(identifier); + } + + /** + * Indicates if this identifier collection contains an updated list of identifiers. + */ + public boolean isUpdated() + { + return mUpdated; + } + + protected void setUpdated(boolean updated) + { + mUpdated = updated; + } + + /** + * Alias List configuration identifier containing the name of the alias list for this collection. + * @return alias list or null + */ + public AliasListConfigurationIdentifier getAliasListConfiguration() + { + return mAliasListConfigurationIdentifier; + } + + /** + * Indicates if this collection has an alias list specified. + */ + public boolean hasAliasListConfiguration() + { + return mAliasListConfigurationIdentifier != null; + } + + /** + * Immutable list of identifiers contained in this collection + */ + public List getIdentifiers() + { + return Collections.unmodifiableList(mIdentifiers); + } + + /** + * Get a list of identifiers by identifier class from this collection. + * + * @param identifierClass to match + * @return list of zero or more identifiers + */ + public List getIdentifiers(IdentifierClass identifierClass) + { + List identifiers = new ArrayList<>(); + + for(Identifier identifier : mIdentifiers) + { + if(identifier.getIdentifierClass() == identifierClass) + { + identifiers.add(identifier); + } + } + + return identifiers; + } + + /** + * Get a list of identifiers by form from this collection. + * + * @param form to match + * @return list of zero or more identifiers + */ + public List getIdentifiers(Form form) + { + List identifiers = new ArrayList<>(); + + for(Identifier identifier : mIdentifiers) + { + if(identifier.getForm() == form) + { + identifiers.add(identifier); + } + } + + return identifiers; + } + + /** + * Get a list of identifiers by role from this collection. + * + * @param role to match + * @return list of zero or more identifiers + */ + public List getIdentifiers(Role role) + { + List identifiers = new ArrayList<>(); + + for(Identifier identifier : mIdentifiers) + { + try + { + if(identifier.getRole() == role) + { + identifiers.add(identifier); + } + } + catch(NullPointerException npe) + { + StringBuilder sb = new StringBuilder(); + for(Identifier i : mIdentifiers) + { + if(i == null) + { + sb.append("Identifier: (null)").append("\n"); + } + else + { + sb.append("Identifier: ").append(i).append(" Class:").append(i.getClass()).append("\n"); + } + } + mLog.warn("An identifier in a collection is somehow null ...\n" + sb.toString()); + } + } + + return identifiers; + } + + /** + * Get a list of identifiers by identifier class and role from this collection. + * + * @param identifierClass to match + * @param role to match + * @return list of zero or more identifiers + */ + public List getIdentifiers(IdentifierClass identifierClass, Role role) + { + List identifiers = new ArrayList<>(); + + for(Identifier identifier : mIdentifiers) + { + if(identifier.getIdentifierClass() == identifierClass && identifier.getRole() == role) + { + identifiers.add(identifier); + } + } + + return identifiers; + } + + /** + * Get a list of identifiers by identifier class and form from this collection. + * + * @param identifierClass to match + * @param form to match + * @return list of zero or more identifiers + */ + public List getIdentifiers(IdentifierClass identifierClass, Form form) + { + List identifiers = new ArrayList<>(); + + for(Identifier identifier : mIdentifiers) + { + if(identifier.getIdentifierClass() == identifierClass && identifier.getForm() == form) + { + identifiers.add(identifier); + } + } + + return identifiers; + } + + /** + * Get the single identifier by identifier class, form and role from this collection. + * + * @param identifierClass to match + * @param form to match + * @param role to match + * @return matching identifier or null + */ + public Identifier getIdentifier(IdentifierClass identifierClass, Form form, Role role) + { + for(Identifier identifier : mIdentifiers) + { + if(identifier.getIdentifierClass() == identifierClass && + identifier.getForm() == form && + identifier.getRole() == role) + { + return identifier; + } + } + + return null; + } + + /** + * Returns the first identifier in this collection that is assigned a FROM role + */ + public Identifier getFromIdentifier() + { + List fromIdentifiers = getIdentifiers(Role.FROM); + + if(!fromIdentifiers.isEmpty()) + { + return fromIdentifiers.get(0); + } + + return null; + } + + /** + * Returns the first identifier in this collection that is assigned a FROM role + */ + public Identifier getToIdentifier() + { + List toIdentifiers = getIdentifiers(Role.TO); + + if(!toIdentifiers.isEmpty()) + { + return toIdentifiers.get(0); + } + + return null; + } + +} diff --git a/src/main/java/io/github/dsheirer/identifier/IdentifierUpdateListener.java b/src/main/java/io/github/dsheirer/identifier/IdentifierUpdateListener.java new file mode 100644 index 00000000..6c261b6d --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/IdentifierUpdateListener.java @@ -0,0 +1,30 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier; + +import io.github.dsheirer.sample.Listener; + +/** + * Interface for receiving identifier update notifications + */ +public interface IdentifierUpdateListener +{ + Listener getIdentifierUpdateListener(); +} diff --git a/src/main/java/io/github/dsheirer/identifier/IdentifierUpdateNotification.java b/src/main/java/io/github/dsheirer/identifier/IdentifierUpdateNotification.java new file mode 100644 index 00000000..a948bfb2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/IdentifierUpdateNotification.java @@ -0,0 +1,87 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier; + +/** + * Request to add or update an identifier + */ +public class IdentifierUpdateNotification +{ + private Identifier mIdentifier; + private Operation mOperation; + + /** + * Constructs an identifier update notification + * @param identifier that has been updated + */ + public IdentifierUpdateNotification(Identifier identifier, Operation operation) + { + mIdentifier = identifier; + mOperation = operation; + } + + public Identifier getIdentifier() + { + return mIdentifier; + } + + public Operation getOperation() + { + return mOperation; + } + + public boolean isAdded() + { + return mOperation == Operation.ADD; + } + + public boolean isRemoved() + { + return mOperation == Operation.REMOVE; + } + + /** + * Indicates that this update should not be rebroadcast by any of the consumers. + */ + public boolean isSilentAdd() + { + return mOperation == Operation.SILENT_ADD; + } + + /** + * Indicates that this update should not be rebroadcast by any of the consumers. + */ + public boolean isSilentRemove() + { + return mOperation == Operation.SILENT_REMOVE; + } + + @Override + public String toString() + { + return mOperation.name() + " - " + mIdentifier; + } + + public enum Operation + { + ADD, REMOVE, SILENT_ADD, SILENT_REMOVE; + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/IdentifierUpdateProvider.java b/src/main/java/io/github/dsheirer/identifier/IdentifierUpdateProvider.java new file mode 100644 index 00000000..ce6a7201 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/IdentifierUpdateProvider.java @@ -0,0 +1,29 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier; + +import io.github.dsheirer.sample.Listener; + +public interface IdentifierUpdateProvider +{ + void setIdentifierUpdateListener(Listener listener); + + void removeIdentifierUpdateListener(); +} diff --git a/src/main/java/io/github/dsheirer/identifier/MutableIdentifierCollection.java b/src/main/java/io/github/dsheirer/identifier/MutableIdentifierCollection.java new file mode 100644 index 00000000..c18e6cb7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/MutableIdentifierCollection.java @@ -0,0 +1,399 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier; + +import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier; +import io.github.dsheirer.sample.Listener; + +import java.util.Collection; +import java.util.Iterator; + +/** + * Identifier collection with methods for changing or updating managed identifiers + */ +public class MutableIdentifierCollection extends IdentifierCollection implements IdentifierUpdateProvider, + Listener +{ + private Listener mListener; + + public MutableIdentifierCollection() + { + } + + public MutableIdentifierCollection(Collection identifiers) + { + for(Identifier identifier: identifiers) + { + update(identifier); + } + } + + /** + * Broadcasts all of the currently held identifiers to the registered listener + */ + public void broadcastIdentifiers() + { + for(Identifier identifier : getIdentifiers()) + { + notifyAdd(identifier); + } + } + + /** + * Registers a listener to be notified when identifiers are added to or removed from this collection + */ + @Override + public void setIdentifierUpdateListener(Listener listener) + { + mListener = listener; + } + + /** + * Unregisters the listener from receiving identifier update notifications + */ + @Override + public void removeIdentifierUpdateListener() + { + mListener = null; + } + + /** + * Notifies a registered listener that the identifier has been added to this collection + */ + private void notifyAdd(Identifier identifier) + { + setUpdated(true); + if(mListener != null) + { + mListener.receive(new IdentifierUpdateNotification(identifier, IdentifierUpdateNotification.Operation.ADD)); + } + } + + /** + * Notifies a registered listener that the identifier has been removed from this collection + */ + private void notifyRemove(Identifier identifier) + { + setUpdated(true); + if(mListener != null) + { + mListener.receive(new IdentifierUpdateNotification(identifier, + IdentifierUpdateNotification.Operation.REMOVE)); + } + } + + /** + * Adds the identifier to this collection if not already contained in this collection. + * + * @param identifier to add + */ + private void add(Identifier identifier) + { + if(identifier.isValid() && !mIdentifiers.contains(identifier)) + { + mIdentifiers.add(identifier); + notifyAdd(identifier); + } + + //Retain a reference to the alias list identifier separately so that it can be accessed quickly. + if(identifier instanceof AliasListConfigurationIdentifier) + { + mAliasListConfigurationIdentifier = (AliasListConfigurationIdentifier)identifier; + } + } + + /** + * Adds the identifier to this collection if not already contained in this collection. Does NOT broadcast an + * update notification. + * + * @param identifier to add + */ + private void silentAdd(Identifier identifier) + { + if(identifier.isValid() && !mIdentifiers.contains(identifier)) + { + mIdentifiers.add(identifier); + } + + //Retain a reference to the alias list identifier separately so that it can be accessed quickly. + if(identifier instanceof AliasListConfigurationIdentifier) + { + mAliasListConfigurationIdentifier = (AliasListConfigurationIdentifier)identifier; + } + } + + /** + * Removes the identifier from this collection + */ + public void remove(Identifier identifier) + { + if(mIdentifiers.remove(identifier)) + { + notifyRemove(identifier); + } + + //Remove the reference to the alias list identifier. + if(identifier instanceof AliasListConfigurationIdentifier) + { + mAliasListConfigurationIdentifier = null; + } + } + + /** + * Removes the identifier from this collection and does NOT broadcast an update notification + */ + public void silentRemove(Identifier identifier) + { + mIdentifiers.remove(identifier); + + //Remove the reference to the alias list identifier. + if(identifier instanceof AliasListConfigurationIdentifier) + { + mAliasListConfigurationIdentifier = null; + } + } + + /** + * Adds the identifier only if the value is different from the existing identifiers(s). If + * there are multiple matching identifiers, then they are all removed and replaced by this + * single identifier. + * + * @param identifier + */ + public void update(Identifier identifier) + { + if(identifier != null) + { + Identifier existing = getIdentifier(identifier.getIdentifierClass(), + identifier.getForm(), identifier.getRole()); + + if(existing != null) + { + if(!existing.equals(identifier)) + { + remove(existing); + add(identifier); + } + } + else + { + add(identifier); + } + } + } + + /** + * Adds the identifier only if the value is different from the existing identifiers(s). If + * there are multiple matching identifiers, then they are all removed and replaced by this + * single identifier. + * + * @param identifier + */ + public void silentUpdate(Identifier identifier) + { + if(identifier != null) + { + Identifier existing = getIdentifier(identifier.getIdentifierClass(), identifier.getForm(), identifier.getRole()); + + if(existing != null) + { + if(!existing.equals(identifier)) + { + silentRemove(existing); + silentAdd(identifier); + } + } + else + { + silentAdd(identifier); + } + } + } + + /** + * Updates all identifiers + */ + public void update(Collection identifiers) + { + for(Identifier identifier : identifiers) + { + update(identifier); + } + } + + /** + * Removes/clears all identifiers from the collection + */ + public void clear() + { + Iterator it = mIdentifiers.iterator(); + + while(it.hasNext()) + { + notifyRemove(it.next()); + it.remove(); + } + } + + /** + * Removes all identifiers of the specified identifier class + */ + public void remove(IdentifierClass identifierClass) + { + Iterator it = mIdentifiers.iterator(); + + Identifier next = null; + + while(it.hasNext()) + { + next = it.next(); + + if(next.getIdentifierClass() == identifierClass) + { + it.remove(); + notifyRemove(next); + } + } + } + + /** + * Removes all identifiers of the specified form + */ + public void remove(Form form) + { + Iterator it = mIdentifiers.iterator(); + + Identifier next = null; + + while(it.hasNext()) + { + next = it.next(); + + if(next.getForm() == form) + { + it.remove(); + notifyRemove(next); + } + } + } + + /** + * Removes all identifiers of the specified role + */ + public void remove(Role role) + { + Iterator it = mIdentifiers.iterator(); + + Identifier next = null; + + while(it.hasNext()) + { + next = it.next(); + + if(next.getRole() == role) + { + it.remove(); + notifyRemove(next); + } + } + } + + /** + * Removes all identifiers of the specified identifier class, form and role + */ + public void remove(IdentifierClass identifierClass, Form form, Role role) + { + Iterator it = mIdentifiers.iterator(); + + Identifier next = null; + + while(it.hasNext()) + { + next = it.next(); + + if(next.getIdentifierClass() == identifierClass && next.getForm() == form && next.getRole() == role) + { + it.remove(); + notifyRemove(next); + } + } + } + + /** + * Removes all identifiers of the specified identifier class and role + */ + public void remove(IdentifierClass identifierClass, Role role) + { + Iterator it = mIdentifiers.iterator(); + + Identifier next = null; + + while(it.hasNext()) + { + next = it.next(); + + if(next.getIdentifierClass() == identifierClass && next.getRole() == role) + { + it.remove(); + notifyRemove(next); + } + } + } + + /** + * Implements the listener interface to receive notifications of identifier updates. This allows an + * identifier collection to maintain a synchronized state with a remote identifier collection. + * + * @param identifierUpdateNotification to add or remove an identifier + */ + @Override + public void receive(IdentifierUpdateNotification identifierUpdateNotification) + { + if(identifierUpdateNotification.isAdded()) + { + update(identifierUpdateNotification.getIdentifier()); + } + else if(identifierUpdateNotification.isRemoved()) + { + remove(identifierUpdateNotification.getIdentifier()); + } + else if(identifierUpdateNotification.isSilentAdd()) + { + silentUpdate(identifierUpdateNotification.getIdentifier()); + } + else if(identifierUpdateNotification.isSilentRemove()) + { + silentRemove(identifierUpdateNotification.getIdentifier()); + } + } + + /** + * Creates a copy of this collection and resets the updated flag so that future invocations of this + * method will return a copy with the updated flag set to false. + */ + public IdentifierCollection copyOf() + { + IdentifierCollection copy = new IdentifierCollection(getIdentifiers()); + copy.setUpdated(isUpdated()); + setUpdated(false); + return copy; + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/Role.java b/src/main/java/io/github/dsheirer/identifier/Role.java new file mode 100644 index 00000000..ce05669d --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/Role.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier; + +/** + * Identifier role. Indicates the role of the entity in the communication. + */ +public enum Role +{ + BROADCAST, + FROM, + IDENTIFIER, + STATUS, + TO, + ANY; +} diff --git a/src/main/java/io/github/dsheirer/identifier/configuration/AliasListConfigurationIdentifier.java b/src/main/java/io/github/dsheirer/identifier/configuration/AliasListConfigurationIdentifier.java new file mode 100644 index 00000000..22e77878 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/configuration/AliasListConfigurationIdentifier.java @@ -0,0 +1,45 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.configuration; + +import io.github.dsheirer.identifier.Form; + +/** + * User specified alias list name for a decode configuration. + */ +public class AliasListConfigurationIdentifier extends ConfigurationStringIdentifier +{ + public AliasListConfigurationIdentifier(String value) + { + super(value, Form.ALIAS_LIST); + } + + @Override + public boolean isValid() + { + return getValue() != null && !getValue().isEmpty(); + } + + public static AliasListConfigurationIdentifier create(String value) + { + return new AliasListConfigurationIdentifier(value); + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/configuration/ChannelDescriptorConfigurationIdentifier.java b/src/main/java/io/github/dsheirer/identifier/configuration/ChannelDescriptorConfigurationIdentifier.java new file mode 100644 index 00000000..e9aae49c --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/configuration/ChannelDescriptorConfigurationIdentifier.java @@ -0,0 +1,57 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.configuration; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.protocol.Protocol; + +/** + * Configuration identifier that identifies a channel descriptor. This is primarily used to broadcast the channel + * descriptor for a traffic channel so that it is aware of what channel number and frequency it is decoding. + */ +public class ChannelDescriptorConfigurationIdentifier extends Identifier +{ + /** + * Constructs a channel descriptor configuration identifier + */ + public ChannelDescriptorConfigurationIdentifier(IChannelDescriptor value) + { + super(value, IdentifierClass.CONFIGURATION, Form.CHANNEL_DESCRIPTOR, Role.ANY); + } + + /** + * Protocol for the channel descriptor and the enclosing identifier instance. + */ + @Override + public Protocol getProtocol() + { + if(getValue() != null) + { + return getValue().getProtocol(); + } + + return Protocol.UNKNOWN; + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/configuration/ChannelNameConfigurationIdentifier.java b/src/main/java/io/github/dsheirer/identifier/configuration/ChannelNameConfigurationIdentifier.java new file mode 100644 index 00000000..d23f5a96 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/configuration/ChannelNameConfigurationIdentifier.java @@ -0,0 +1,50 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.configuration; + +import io.github.dsheirer.identifier.Form; + +/** + * User specified channel name for a decode configuration. + */ +public class ChannelNameConfigurationIdentifier extends ConfigurationStringIdentifier +{ + public ChannelNameConfigurationIdentifier() + { + this(null); + } + + public ChannelNameConfigurationIdentifier(String value) + { + super(value, Form.CHANNEL); + } + + @Override + public boolean isValid() + { + return getValue() != null && !getValue().isEmpty(); + } + + public static ChannelNameConfigurationIdentifier create(String value) + { + return new ChannelNameConfigurationIdentifier(value); + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/configuration/ConfigurationLongIdentifier.java b/src/main/java/io/github/dsheirer/identifier/configuration/ConfigurationLongIdentifier.java new file mode 100644 index 00000000..0434a827 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/configuration/ConfigurationLongIdentifier.java @@ -0,0 +1,44 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.configuration; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.longnumber.LongIdentifier; +import io.github.dsheirer.protocol.Protocol; + +/** + * Decoder channel configuration identifier. + */ +public class ConfigurationLongIdentifier extends LongIdentifier +{ + public ConfigurationLongIdentifier(Long value, Form form) + { + super(value, IdentifierClass.CONFIGURATION, form, Role.ANY); + } + + @Override + public Protocol getProtocol() + { + return Protocol.UNKNOWN; + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/configuration/ConfigurationStringIdentifier.java b/src/main/java/io/github/dsheirer/identifier/configuration/ConfigurationStringIdentifier.java new file mode 100644 index 00000000..378c569c --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/configuration/ConfigurationStringIdentifier.java @@ -0,0 +1,44 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.configuration; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.string.StringIdentifier; +import io.github.dsheirer.protocol.Protocol; + +/** + * Decoder channel configuration identifier. + */ +public class ConfigurationStringIdentifier extends StringIdentifier +{ + public ConfigurationStringIdentifier(String value, Form form) + { + super(value, IdentifierClass.CONFIGURATION, form, Role.ANY); + } + + @Override + public Protocol getProtocol() + { + return Protocol.UNKNOWN; + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/configuration/DecoderTypeConfigurationIdentifier.java b/src/main/java/io/github/dsheirer/identifier/configuration/DecoderTypeConfigurationIdentifier.java new file mode 100644 index 00000000..ccba2a44 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/configuration/DecoderTypeConfigurationIdentifier.java @@ -0,0 +1,56 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.configuration; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.module.decode.DecoderType; +import io.github.dsheirer.protocol.Protocol; + +public class DecoderTypeConfigurationIdentifier extends Identifier +{ + public DecoderTypeConfigurationIdentifier(DecoderType value) + { + super(value, IdentifierClass.CONFIGURATION, Form.DECODER_TYPE, Role.ANY); + } + + @Override + public Protocol getProtocol() + { + return Protocol.UNKNOWN; + } + + /** + * Creates a decoder state identifier from the decoder state value + */ + public static DecoderTypeConfigurationIdentifier create(DecoderType decoderType) + { + return new DecoderTypeConfigurationIdentifier(decoderType); + } + + @Override + public String toString() + { + return getValue().getShortDisplayString(); + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/configuration/FrequencyConfigurationIdentifier.java b/src/main/java/io/github/dsheirer/identifier/configuration/FrequencyConfigurationIdentifier.java new file mode 100644 index 00000000..0631e5df --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/configuration/FrequencyConfigurationIdentifier.java @@ -0,0 +1,39 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.configuration; + +import io.github.dsheirer.identifier.Form; + +/** + * Channel tuned frequency configuration identifier + */ +public class FrequencyConfigurationIdentifier extends ConfigurationLongIdentifier +{ + public FrequencyConfigurationIdentifier(Long value) + { + super(value, Form.CHANNEL_FREQUENCY); + } + + public static FrequencyConfigurationIdentifier create(Long value) + { + return new FrequencyConfigurationIdentifier(value); + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/configuration/SiteConfigurationIdentifier.java b/src/main/java/io/github/dsheirer/identifier/configuration/SiteConfigurationIdentifier.java new file mode 100644 index 00000000..6a0afbd1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/configuration/SiteConfigurationIdentifier.java @@ -0,0 +1,50 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.configuration; + +import io.github.dsheirer.identifier.Form; + +/** + * User specified site name for a decode configuration. + */ +public class SiteConfigurationIdentifier extends ConfigurationStringIdentifier +{ + public SiteConfigurationIdentifier() + { + this(null); + } + + public SiteConfigurationIdentifier(String value) + { + super(value, Form.SITE); + } + + @Override + public boolean isValid() + { + return getValue() != null && !getValue().isEmpty(); + } + + public static SiteConfigurationIdentifier create(String value) + { + return new SiteConfigurationIdentifier(value); + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/configuration/SystemConfigurationIdentifier.java b/src/main/java/io/github/dsheirer/identifier/configuration/SystemConfigurationIdentifier.java new file mode 100644 index 00000000..e4dca465 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/configuration/SystemConfigurationIdentifier.java @@ -0,0 +1,50 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.configuration; + +import io.github.dsheirer.identifier.Form; + +/** + * User specified system name for a decode configuration. + */ +public class SystemConfigurationIdentifier extends ConfigurationStringIdentifier +{ + public SystemConfigurationIdentifier() + { + this(null); + } + + public SystemConfigurationIdentifier(String value) + { + super(value, Form.SYSTEM); + } + + @Override + public boolean isValid() + { + return getValue() != null && !getValue().isEmpty(); + } + + public static SystemConfigurationIdentifier create(String value) + { + return new SystemConfigurationIdentifier(value); + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/decoder/DecoderLogicalChannelNameIdentifier.java b/src/main/java/io/github/dsheirer/identifier/decoder/DecoderLogicalChannelNameIdentifier.java new file mode 100644 index 00000000..8d2842f5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/decoder/DecoderLogicalChannelNameIdentifier.java @@ -0,0 +1,52 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.decoder; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.string.StringIdentifier; +import io.github.dsheirer.protocol.Protocol; + +public class DecoderLogicalChannelNameIdentifier extends StringIdentifier +{ + private Protocol mProtocol; + + public DecoderLogicalChannelNameIdentifier(String name, Protocol protocol) + { + super(name, IdentifierClass.DECODER, Form.CHANNEL_NAME, Role.BROADCAST); + mProtocol = protocol; + } + + @Override + public Protocol getProtocol() + { + return mProtocol; + } + + /** + * Creates a decoder state identifier from the decoder state value + */ + public static DecoderLogicalChannelNameIdentifier create(String name, Protocol protocol) + { + return new DecoderLogicalChannelNameIdentifier(name, protocol); + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/decoder/DecoderStateIdentifier.java b/src/main/java/io/github/dsheirer/identifier/decoder/DecoderStateIdentifier.java new file mode 100644 index 00000000..41ccf413 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/decoder/DecoderStateIdentifier.java @@ -0,0 +1,66 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.decoder; + +import io.github.dsheirer.channel.state.State; +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.protocol.Protocol; + +public class DecoderStateIdentifier extends Identifier +{ + public static final DecoderStateIdentifier ACTIVE = DecoderStateIdentifier.create(State.ACTIVE); + public static final DecoderStateIdentifier CALL = DecoderStateIdentifier.create(State.CALL); + public static final DecoderStateIdentifier CONTROL = DecoderStateIdentifier.create(State.CONTROL); + public static final DecoderStateIdentifier DATA = DecoderStateIdentifier.create(State.DATA); + public static final DecoderStateIdentifier ENCRYPTED = DecoderStateIdentifier.create(State.ENCRYPTED); + public static final DecoderStateIdentifier FADE = DecoderStateIdentifier.create(State.FADE); + public static final DecoderStateIdentifier IDLE = DecoderStateIdentifier.create(State.IDLE); + public static final DecoderStateIdentifier TEARDOWN = DecoderStateIdentifier.create(State.TEARDOWN); + public static final DecoderStateIdentifier RESET = DecoderStateIdentifier.create(State.RESET); + + public DecoderStateIdentifier(State value) + { + super(value, IdentifierClass.DECODER, Form.STATE, Role.ANY); + } + + @Override + public Protocol getProtocol() + { + return Protocol.UNKNOWN; + } + + /** + * Creates a decoder state identifier from the decoder state value + */ + public static DecoderStateIdentifier create(State state) + { + return new DecoderStateIdentifier(state); + } + + @Override + public String toString() + { + return getValue().getDisplayValue(); + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/encryption/EncryptionKey.java b/src/main/java/io/github/dsheirer/identifier/encryption/EncryptionKey.java new file mode 100644 index 00000000..67f8e152 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/encryption/EncryptionKey.java @@ -0,0 +1,93 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.encryption; + +import java.util.Objects; + +public class EncryptionKey implements Comparable +{ + private int mAlgorithm; + private int mKey; + + public EncryptionKey(int algorithm, int key) + { + mAlgorithm = algorithm; + mKey = key; + } + + public boolean isEncrypted() + { + return mAlgorithm != 0x80; + } + + public int getAlgorithm() + { + return mAlgorithm; + } + + public int getKey() + { + return mKey; + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("ALGORITHM:").append(getAlgorithm()); + sb.append(" KEY:").append(getKey()); + return sb.toString(); + } + + @Override + public int compareTo(EncryptionKey other) + { + if(getAlgorithm() == other.getKey()) + { + return Integer.compare(getKey(), other.getKey()); + } + else + { + return Integer.compare(getAlgorithm(), other.getAlgorithm()); + } + } + + @Override + public boolean equals(Object o) + { + if(this == o) + { + return true; + } + if(o == null || getClass() != o.getClass()) + { + return false; + } + EncryptionKey that = (EncryptionKey)o; + return getAlgorithm() == that.getAlgorithm() && + getKey() == that.getKey(); + } + + @Override + public int hashCode() + { + return Objects.hash(getAlgorithm(), getKey()); + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/encryption/EncryptionKeyIdentifier.java b/src/main/java/io/github/dsheirer/identifier/encryption/EncryptionKeyIdentifier.java new file mode 100644 index 00000000..51176a2e --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/encryption/EncryptionKeyIdentifier.java @@ -0,0 +1,36 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.encryption; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; + +public abstract class EncryptionKeyIdentifier extends Identifier +{ + public EncryptionKeyIdentifier(EncryptionKey value, IdentifierClass identifierClass, Form form, Role role) + { + super(value, identifierClass, form, role); + } + + +} diff --git a/src/main/java/io/github/dsheirer/identifier/esn/ESNIdentifier.java b/src/main/java/io/github/dsheirer/identifier/esn/ESNIdentifier.java new file mode 100644 index 00000000..e83cda6d --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/esn/ESNIdentifier.java @@ -0,0 +1,66 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.esn; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.string.StringIdentifier; +import io.github.dsheirer.protocol.Protocol; + +public class ESNIdentifier extends StringIdentifier implements Comparable +{ + private Protocol mProtocol; + + public ESNIdentifier(String esn, Protocol protocol, Role role) + { + super(esn, IdentifierClass.USER, Form.ESN, role); + mProtocol = protocol; + } + + @Override + public Protocol getProtocol() + { + return mProtocol; + } + + public static ESNIdentifier create(String esn, Protocol protocol, Role role) + { + return new ESNIdentifier(esn, protocol, role); + } + + @Override + public int compareTo(ESNIdentifier o) + { + if(getValue() == null) + { + return 1; + } + else if(o.getValue() == null) + { + return -1; + } + else + { + return getValue().compareTo(o.getValue()); + } + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/integer/IntegerIdentifier.java b/src/main/java/io/github/dsheirer/identifier/integer/IntegerIdentifier.java new file mode 100644 index 00000000..8351de0e --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/integer/IntegerIdentifier.java @@ -0,0 +1,66 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.integer; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; + +import java.util.Objects; + +/** + * Integer identifier base class. + */ +public abstract class IntegerIdentifier extends Identifier +{ + public IntegerIdentifier(int value, IdentifierClass identifierClass, Form form, Role role) + { + super(value, identifierClass, form, role); + } + + + @Override + public boolean equals(Object o) + { + if(this == o) + { + return true; + } + if(o == null || getClass() != o.getClass()) + { + return false; + } + + IntegerIdentifier that = (IntegerIdentifier)o; + + return (getValue().intValue() == that.getValue().intValue()) && + getIdentifierClass() == that.getIdentifierClass() && + getForm() == that.getForm() && + getRole() == that.getRole(); + } + + @Override + public int hashCode() + { + return Objects.hash(getValue(), getIdentifierClass(), getForm(), getRole()); + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/ipv4/IPV4Address.java b/src/main/java/io/github/dsheirer/identifier/ipv4/IPV4Address.java new file mode 100644 index 00000000..f71474c3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/ipv4/IPV4Address.java @@ -0,0 +1,93 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.ipv4; + +import java.util.Objects; + +/** + * IPV4 Address + */ +public class IPV4Address +{ + private int mIPAddress; + + public IPV4Address(int ipAddress) + { + mIPAddress = ipAddress; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOctet(0)); + sb.append("."); + sb.append(getOctet(1)); + sb.append("."); + sb.append(getOctet(2)); + sb.append("."); + sb.append(getOctet(3)); + return sb.toString(); + } + + /** + * Extracts the octet value from the IP address + * @param index of the octet where 0 is the MSB and 3 is the LSB + * @return octet value + */ + private int getOctet(int index) + { + switch(index) + { + case 0: + return ((mIPAddress & 0xFF000000) >> 24) & 0xFF; + case 1: + return ((mIPAddress & 0xFF0000) >> 16); + case 2: + return ((mIPAddress & 0xFF00) >> 8); + case 3: + return ((mIPAddress & 0xFF)); + default: + throw new IllegalArgumentException("Invalid Octet index: " + index); + } + } + + @Override + public boolean equals(Object o) + { + if(this == o) + { + return true; + } + if(o == null || getClass() != o.getClass()) + { + return false; + } + IPV4Address that = (IPV4Address)o; + return mIPAddress == that.mIPAddress; + } + + @Override + public int hashCode() + { + return Objects.hash(mIPAddress); + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/ipv4/IPV4Identifier.java b/src/main/java/io/github/dsheirer/identifier/ipv4/IPV4Identifier.java new file mode 100644 index 00000000..94adcbd8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/ipv4/IPV4Identifier.java @@ -0,0 +1,34 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.ipv4; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; + +public abstract class IPV4Identifier extends Identifier +{ + public IPV4Identifier(IPV4Address value, Role role) + { + super(value, IdentifierClass.USER, Form.IPV4_ADDRESS, role); + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/longnumber/LongIdentifier.java b/src/main/java/io/github/dsheirer/identifier/longnumber/LongIdentifier.java new file mode 100644 index 00000000..9b01ecf6 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/longnumber/LongIdentifier.java @@ -0,0 +1,66 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.longnumber; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; + +import java.util.Objects; + +/** + * Long identifier base class. + */ +public abstract class LongIdentifier extends Identifier +{ + public LongIdentifier(Long value, IdentifierClass identifierClass, Form form, Role role) + { + super(value, identifierClass, form, role); + } + + + @Override + public boolean equals(Object o) + { + if(this == o) + { + return true; + } + if(o == null || getClass() != o.getClass()) + { + return false; + } + + LongIdentifier that = (LongIdentifier)o; + + return (getValue().longValue() == that.getValue().longValue()) && + getIdentifierClass() == that.getIdentifierClass() && + getForm() == that.getForm() && + getRole() == that.getRole(); + } + + @Override + public int hashCode() + { + return Objects.hash(getValue(), getIdentifierClass(), getForm(), getRole()); + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/patch/PatchGroup.java b/src/main/java/io/github/dsheirer/identifier/patch/PatchGroup.java new file mode 100644 index 00000000..0ee60788 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/patch/PatchGroup.java @@ -0,0 +1,101 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.patch; + +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class PatchGroup +{ +// private final static Logger mLog = LoggerFactory.getLogger(PatchGroup.class); + + private TalkgroupIdentifier mPatchGroupIdentifier; + private List mPatchedGroupIdentifiers = new ArrayList<>(); + + public PatchGroup(TalkgroupIdentifier patchGroupIdentifier) + { + mPatchGroupIdentifier = patchGroupIdentifier; + } + + public TalkgroupIdentifier getPatchGroup() + { + return mPatchGroupIdentifier; + } + + public void addPatchedGroup(TalkgroupIdentifier patchedGroupIdentifier) + { + mPatchedGroupIdentifiers.add(patchedGroupIdentifier); + } + + public void addPatchedGroups(List patchedGroupIdentifiers) + { + for(TalkgroupIdentifier identifier : patchedGroupIdentifiers) + { + if(!mPatchedGroupIdentifiers.contains(identifier)) + { + mPatchedGroupIdentifiers.add(identifier); + } + } + } + + public List getPatchedGroupIdentifiers() + { + return mPatchedGroupIdentifiers; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("P:").append(getPatchGroup()); + + if(!getPatchedGroupIdentifiers().isEmpty()) + { + sb.append(" ").append(getPatchedGroupIdentifiers()); + } + + return sb.toString(); + } + + @Override + public boolean equals(Object o) + { + if(this == o) + { + return true; + } + if(o == null || getClass() != o.getClass()) + { + return false; + } + PatchGroup that = (PatchGroup)o; + return Objects.equals(mPatchGroupIdentifier, that.mPatchGroupIdentifier); + } + + @Override + public int hashCode() + { + return Objects.hash(mPatchGroupIdentifier); + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/patch/PatchGroupIdentifier.java b/src/main/java/io/github/dsheirer/identifier/patch/PatchGroupIdentifier.java new file mode 100644 index 00000000..299af56c --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/patch/PatchGroupIdentifier.java @@ -0,0 +1,34 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.patch; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; + +public abstract class PatchGroupIdentifier extends Identifier +{ + public PatchGroupIdentifier(PatchGroup value) + { + super(value, IdentifierClass.USER, Form.PATCH_GROUP, Role.TO); + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/patch/PatchGroupManager.java b/src/main/java/io/github/dsheirer/identifier/patch/PatchGroupManager.java new file mode 100644 index 00000000..4c9ff34a --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/patch/PatchGroupManager.java @@ -0,0 +1,184 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.patch; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Manager for (temporary) patch groups and related identifiers. + */ +public class PatchGroupManager +{ + // private final static Logger mLog = LoggerFactory.getLogger(PatchGroupManager.class); + private Map mPatchGroupLastUpdatedMap = new HashMap<>(); + private Map mPatchGroupMap = new HashMap<>(); + + public PatchGroupManager() + { + } + + /** + * Adds the patch group identifier to the set of managed patch groups. If the patch group is already being + * managed, the new identifier is checked for additional patched talkgroups and they are added to the existing + * managed patch group. + * + * @param patchGroupIdentifier to add or update + */ + public synchronized void addPatchGroup(PatchGroupIdentifier patchGroupIdentifier) + { + PatchGroup update = patchGroupIdentifier.getValue(); + + if(update.getPatchGroup().getValue() > 0) + { + if(mPatchGroupMap.containsKey(update.getPatchGroup().getValue())) + { + PatchGroup current = mPatchGroupMap.get(update.getPatchGroup().getValue()).getValue(); + current.addPatchedGroups(update.getPatchedGroupIdentifiers()); + } + else + { + mPatchGroupMap.put(update.getPatchGroup().getValue(), patchGroupIdentifier); + } + } + } + + /** + * Adds any patch group identifiers contained in the list. + */ + public synchronized void addPatchGroups(List identifiers) + { + for(Identifier identifier : identifiers) + { + if(identifier instanceof PatchGroupIdentifier) + { + addPatchGroup((PatchGroupIdentifier)identifier); + } + } + } + + /** + * Removes the patch group from this manager if it is currently being managed. + * + * @param patchGroupIdentifier to remove + */ + public synchronized void removePatchGroup(PatchGroupIdentifier patchGroupIdentifier) + { + int id = patchGroupIdentifier.getValue().getPatchGroup().getValue(); + mPatchGroupMap.remove(id); + mPatchGroupLastUpdatedMap.remove(id); + } + + /** + * Removes any patch group identifiers contained in the list. + */ + public synchronized void removePatchGroups(List identifiers) + { + for(Identifier identifier : identifiers) + { + if(identifier instanceof PatchGroupIdentifier) + { + removePatchGroup((PatchGroupIdentifier)identifier); + } + } + } + + /** + * Updates the list of identifiers by replacing any talkgroups or patch groups with the current + * version of the patch group and a complete listing of the patched talkgroups. + * @param identifiers to update + * @return list of identifiers + */ + public List update(List identifiers) + { + List updated = new ArrayList<>(); + + for(Identifier identifier: identifiers) + { + updated.add(update(identifier)); + } + + return updated; + } + + /** + * Checks the TALKGROUP or PATCH GROUP identifier and replaces the identifier with the current patch group + * if the identifier matches a currently managed patch group. + * + * @param identifier for a talkgroup or a patch group. + * @return current patch group or the original identifier + */ + public Identifier update(Identifier identifier) + { + if(identifier == null) + { + return identifier; + } + + if(identifier.getIdentifierClass() == IdentifierClass.USER && identifier.getRole() == Role.TO) + { + switch(identifier.getForm()) + { + case TALKGROUP: + if(identifier instanceof TalkgroupIdentifier) + { + TalkgroupIdentifier tgi = (TalkgroupIdentifier)identifier; + + if(tgi.isGroup()) + { + int talkgroupId = ((TalkgroupIdentifier)identifier).getValue(); + + if(mPatchGroupMap.containsKey(talkgroupId)) + { + return mPatchGroupMap.get(talkgroupId); + } + } + } + break; + case PATCH_GROUP: + if(identifier instanceof PatchGroupIdentifier) + { + PatchGroupIdentifier patchGroupIdentifier = (PatchGroupIdentifier)identifier; + int patchGroupId = patchGroupIdentifier.getValue().getPatchGroup().getValue(); + + if(mPatchGroupMap.containsKey(patchGroupId)) + { + return mPatchGroupMap.get(patchGroupId); + } + else + { + addPatchGroup(patchGroupIdentifier); + } + } + break; + } + } + + return identifier; + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/string/StringIdentifier.java b/src/main/java/io/github/dsheirer/identifier/string/StringIdentifier.java new file mode 100644 index 00000000..b79b93ef --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/string/StringIdentifier.java @@ -0,0 +1,65 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.string; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; + +import java.util.Objects; + +/** + * String identifier base class + */ +public abstract class StringIdentifier extends Identifier +{ + public StringIdentifier(String value, IdentifierClass identifierClass, Form form, Role role) + { + super(value, identifierClass, form, role); + } + + @Override + public boolean equals(Object o) + { + if(this == o) + { + return true; + } + if(o == null || getClass() != o.getClass()) + { + return false; + } + + StringIdentifier that = (StringIdentifier)o; + + return (getValue().equals(that.getValue())) && + getIdentifierClass() == that.getIdentifierClass() && + getForm() == that.getForm() && + getRole() == that.getRole(); + } + + @Override + public int hashCode() + { + return Objects.hash(getValue(), getIdentifierClass(), getForm(), getRole()); + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/talkgroup/LTRTalkgroup.java b/src/main/java/io/github/dsheirer/identifier/talkgroup/LTRTalkgroup.java new file mode 100644 index 00000000..7eee4f5a --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/talkgroup/LTRTalkgroup.java @@ -0,0 +1,79 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.talkgroup; + +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.protocol.Protocol; + +/** + * LTR talkgroup identifier + */ +public class LTRTalkgroup extends TalkgroupIdentifier implements Comparable +{ + public LTRTalkgroup(Integer talkgroup, Role role) + { + super(talkgroup, role, true); + } + + @Override + public Protocol getProtocol() + { + return Protocol.LTR_NET; + } + + /** + * This identifier formatted as HH:GGG where H=home channel and G=group + */ + public String formatted() + { + return String.format("%02d-%03d", getHomeChannel(), getTalkgroup()); + } + + /** + * Home channel for the identifier + */ + public int getHomeChannel() + { + return (getValue() >> 8) & 0x1F; + } + + /** + * Talkgroup + */ + public int getTalkgroup() + { + return getValue() & 0xFF; + } + + /** + * Creates an LTR-Net identifier from the integer value that contains both the fleet and the ident with a TO role + */ + public static LTRTalkgroup create(int talkgroup) + { + return new LTRTalkgroup(talkgroup, Role.TO); + } + + @Override + public int compareTo(LTRTalkgroup o) + { + return getValue().compareTo(o.getValue()); + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/talkgroup/TalkgroupIdentifier.java b/src/main/java/io/github/dsheirer/identifier/talkgroup/TalkgroupIdentifier.java new file mode 100644 index 00000000..a4102998 --- /dev/null +++ b/src/main/java/io/github/dsheirer/identifier/talkgroup/TalkgroupIdentifier.java @@ -0,0 +1,51 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.identifier.talkgroup; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.integer.IntegerIdentifier; + +public abstract class TalkgroupIdentifier extends IntegerIdentifier +{ + private boolean mIsGroup; + + public TalkgroupIdentifier(Integer value, Role role, boolean isGroup) + { + super(value, IdentifierClass.USER, Form.TALKGROUP, role); + mIsGroup = isGroup; + } + + @Override + public boolean isValid() + { + return getValue() > 0; + } + + /** + * Indicates if this is a group or individual identifier + */ + public boolean isGroup() + { + return mIsGroup; + } +} diff --git a/src/main/java/io/github/dsheirer/map/IPlottableUpdateListener.java b/src/main/java/io/github/dsheirer/map/IPlottableUpdateListener.java new file mode 100644 index 00000000..02d81012 --- /dev/null +++ b/src/main/java/io/github/dsheirer/map/IPlottableUpdateListener.java @@ -0,0 +1,27 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.map; + +public interface IPlottableUpdateListener +{ + void entitiesUpdated(); + void addPlottableEntity(PlottableEntityHistory entity); + void removePlottableEntity(PlottableEntityHistory entity); +} diff --git a/src/main/java/io/github/dsheirer/map/MapPanel.java b/src/main/java/io/github/dsheirer/map/MapPanel.java index 17fcf983..a6c7abbf 100644 --- a/src/main/java/io/github/dsheirer/map/MapPanel.java +++ b/src/main/java/io/github/dsheirer/map/MapPanel.java @@ -18,6 +18,7 @@ ******************************************************************************/ package io.github.dsheirer.map; +import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.icon.IconManager; import io.github.dsheirer.settings.MapViewSetting; import io.github.dsheirer.settings.SettingsManager; @@ -30,10 +31,10 @@ import org.jdesktop.swingx.mapviewer.DefaultTileFactory; import org.jdesktop.swingx.mapviewer.GeoPosition; import org.jdesktop.swingx.mapviewer.TileFactoryInfo; -import javax.swing.*; -import java.awt.*; +import javax.swing.JPanel; +import java.awt.EventQueue; -public class MapPanel extends JPanel implements PlottableUpdateListener +public class MapPanel extends JPanel implements IPlottableUpdateListener { private static final long serialVersionUID = 1L; @@ -42,11 +43,11 @@ public class MapPanel extends JPanel implements PlottableUpdateListener private JXMapViewer mMapViewer = new JXMapViewer(); private PlottableEntityPainter mMapPainter; - public MapPanel(MapService mapService, IconManager iconManager, SettingsManager settingsManager) + public MapPanel(MapService mapService, AliasModel aliasModel, IconManager iconManager, SettingsManager settingsManager) { mSettingsManager = settingsManager; mMapService = mapService; - mMapPainter = new PlottableEntityPainter(iconManager); + mMapPainter = new PlottableEntityPainter(aliasModel, iconManager); init(); } @@ -115,29 +116,20 @@ public class MapPanel extends JPanel implements PlottableUpdateListener @Override public void entitiesUpdated() { - EventQueue.invokeLater(new Runnable() - { - @Override - public void run() - { - mMapViewer.repaint(); - } - }); + EventQueue.invokeLater(() -> mMapViewer.repaint()); } @Override - public void addPlottableEntity(PlottableEntity entity) + public void addPlottableEntity(PlottableEntityHistory entity) { mMapPainter.addEntity(entity); - entitiesUpdated(); } @Override - public void removePlottableEntity(PlottableEntity entity) + public void removePlottableEntity(PlottableEntityHistory entity) { mMapPainter.removeEntity(entity); - entitiesUpdated(); } } diff --git a/src/main/java/io/github/dsheirer/map/MapService.java b/src/main/java/io/github/dsheirer/map/MapService.java index 80d53f77..0f695db8 100644 --- a/src/main/java/io/github/dsheirer/map/MapService.java +++ b/src/main/java/io/github/dsheirer/map/MapService.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,155 +15,78 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.map; -import io.github.dsheirer.alias.Alias; import io.github.dsheirer.icon.IconManager; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.event.IDecodeEvent; +import io.github.dsheirer.module.decode.event.PlottableDecodeEvent; import io.github.dsheirer.sample.Listener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.awt.*; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.Set; +import java.util.Map; -public class MapService implements Listener +public class MapService implements Listener { + private final static Logger mLog = LoggerFactory.getLogger(MapService.class); + private int mMaxHistory = 2; - private int mCullThresholdMinutes = 60; - - private static final Color sDEFAULT_COLOR = Color.BLACK; - - private List mListeners = new ArrayList(); - - private HashMap mEntities = - new HashMap(); - + private List mListeners = new ArrayList(); + private Map mEntityHistories = new HashMap<>(); private IconManager mIconManager; public MapService(IconManager resourceManager) { mIconManager = resourceManager; - -// scheduler.scheduleAtFixedRate( new CullThread(), 5, 5, TimeUnit.MINUTES ); - } - - /** - * Returns the max plottables (locations) history setting that is applied - * to all plottable entities - */ - public int getMaxHistory() - { - return mMaxHistory; - } - - /** - * Sets the max history trail for entities. Value is applied to all newly - * created PlottableEntity and all existing entities. - */ - public void setMaxHistory(int maxHistory) - { - mMaxHistory = maxHistory; - - /** - * Update any existing entities with the new max history value - */ - for(PlottableEntity entity : mEntities.values()) - { - entity.setMaxHistory(mMaxHistory); - } } @Override - public void receive(Message message) + public void receive(IDecodeEvent decodeEvent) { - Plottable plottable = message.getPlottable(); - - if(plottable != null) + if(decodeEvent instanceof PlottableDecodeEvent) { - PlottableEntity entity = mEntities.get(plottable.getID()); + PlottableDecodeEvent plottableDecodeEvent = (PlottableDecodeEvent)decodeEvent; + Identifier from = plottableDecodeEvent.getIdentifierCollection().getFromIdentifier(); - if(entity == null) + if(from != null) { - Alias alias = plottable.getAlias(); + PlottableEntityHistory entityHistory = mEntityHistories.get(from); - Color color = null; - - if(alias != null) + if(entityHistory == null) { - color = alias.getDisplayColor(); + entityHistory = new PlottableEntityHistory(from, plottableDecodeEvent); + mEntityHistories.put(from, entityHistory); + } + else + { + entityHistory.add(plottableDecodeEvent); } - if(color == null) + for(IPlottableUpdateListener listener : mListeners) { - color = sDEFAULT_COLOR; - } - - entity = new PlottableEntity(plottable, color); - - entity.setMaxHistory(mMaxHistory); - - mEntities.put(plottable.getID(), entity); - - for(PlottableUpdateListener listener : mListeners) - { - listener.addPlottableEntity(entity); + listener.addPlottableEntity(entityHistory); } } else { - entity.addPlottable(plottable); - - for(PlottableUpdateListener listener : mListeners) - { - listener.entitiesUpdated(); - ; - } + mLog.warn("Received plottable decode event that does not contain a FROM identifier - cannot plot"); } } } - public void addListener(PlottableUpdateListener listener) + public void addListener(IPlottableUpdateListener listener) { mListeners.add(listener); } - public void removeListener(PlottableUpdateListener listener) + public void removeListener(IPlottableUpdateListener listener) { mListeners.remove(listener); } - - public class CullThread implements Runnable - { - @Override - public void run() - { - long cutoffTime = System.currentTimeMillis() - - (mCullThresholdMinutes * 1000); - - Set ids = Collections.unmodifiableSet(mEntities.keySet()); - - for(String id : ids) - { - PlottableEntity entity = mEntities.get(id); - - if(entity.getCurrentPlottable().getTimestamp() < cutoffTime) - { - mEntities.remove(id); - - /** - * Let all listeners know we've removed (culled) an entity - */ - for(PlottableUpdateListener listener : mListeners) - { - listener.removePlottableEntity(entity); - } - } - } - } - } } diff --git a/src/main/java/io/github/dsheirer/map/Plottable.java b/src/main/java/io/github/dsheirer/map/Plottable.java deleted file mode 100644 index 3c8a0113..00000000 --- a/src/main/java/io/github/dsheirer/map/Plottable.java +++ /dev/null @@ -1,138 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.map; - -import io.github.dsheirer.alias.Alias; -import org.jdesktop.swingx.mapviewer.GeoPosition; - -import java.util.HashMap; - -public class Plottable implements Comparable -{ - private long mTimestamp; - private GeoPosition mGeoPosition; - private String mID; - private Alias mAlias; - - HashMap mAttributes = new HashMap(); - - public Plottable() - { - } - - public Plottable( long timestamp, - GeoPosition position, - String id, - Alias alias, - HashMap attributes ) - { - this( timestamp, position, id, alias ); - mAttributes = attributes; - } - - public Plottable( long timestamp, - GeoPosition position, - String id, - Alias alias ) - { - mTimestamp = timestamp; - mGeoPosition = position; - mID = id; - mAlias = alias; - } - - public long getTimestamp() - { - return mTimestamp; - } - - public void setTimestamp( long timestamp ) - { - mTimestamp = timestamp; - } - - public GeoPosition getGeoPosition() - { - return mGeoPosition; - } - - public void setGeoPosition( GeoPosition position ) - { - mGeoPosition = position; - } - - public String getID() - { - return mID; - } - - public void setID( String id ) - { - mID = id; - } - - public Alias getAlias() - { - return mAlias; - } - - public void setAlias( Alias alias ) - { - mAlias = alias; - } - - public HashMap getAttributes() - { - return mAttributes; - } - - public void addAttribute( String key, String value ) - { - mAttributes.put( key, value ); - } - - public void removeAttribute( String key ) - { - mAttributes.remove( key ); - } - - public void clearAttributes() - { - mAttributes.clear(); - } - - /** - * Comparisons for sorting of plottables is based on timestamps - */ - @Override - public int compareTo( Plottable o ) - { - if( mTimestamp == o.mTimestamp ) - { - return 0; - } - else if( mTimestamp < o.mTimestamp ) - { - return -1; - } - else - { - return 1; - } - } -} diff --git a/src/main/java/io/github/dsheirer/map/PlottableEntity.java b/src/main/java/io/github/dsheirer/map/PlottableEntity.java deleted file mode 100644 index 62bfb474..00000000 --- a/src/main/java/io/github/dsheirer/map/PlottableEntity.java +++ /dev/null @@ -1,170 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.map; - -import io.github.dsheirer.alias.Alias; -import org.jdesktop.swingx.mapviewer.GeoPosition; - -import java.awt.*; -import java.util.Collections; -import java.util.Set; -import java.util.TreeSet; - -public class PlottableEntity implements Comparable -{ - private TreeSet mPlottables = new TreeSet(); - - private Alias mAlias; - private Color mColor; - private String mID; - - private int mMaxHistory = 2; - - /** - * Constructs the entity using the group and alias from the plottable and - * adds the plottable as the first plottable entry in the history - */ - public PlottableEntity( Plottable plottable, Color color ) - { - addPlottable( plottable ); - mColor = color; - } - - public int getMaxHistory() - { - return mMaxHistory; - } - - public void setMaxHistory( int maxHistory ) - { - if( maxHistory >= 1 ) - { - mMaxHistory = maxHistory; - } - } - - public Plottable getCurrentPlottable() - { - return mPlottables.last(); - } - - public GeoPosition getCurrentGeoPosition() - { - return getCurrentPlottable().getGeoPosition(); - } - - /** - * Adds the plottable to this entity - */ - public void addPlottable( Plottable plottable ) - { - /** - * Grab the entity settings from the latest plot - */ - mAlias = plottable.getAlias(); - mID = plottable.getID(); - - /** - * Add the plottable to the list - */ - mPlottables.add( plottable ); - - while( mPlottables.size() > mMaxHistory ) - { - mPlottables.remove( mPlottables.first() ); - } - } - - public String getLabel() - { - if( mAlias == null ) - { - return mID; - } - else - { - return mAlias.getName(); - } - } - - public String getMapIconName() - { - if( mAlias != null ) - { - return mAlias.getIconName(); - } - else - { - return null; - } - } - - /** - * Color for rendering this entity (label, route, etc.) - */ - public Color getColor() - { - return mColor; - } - - public void setColor( Color color ) - { - mColor = color; - } - - /** - * Default id to use in the label - */ - public String getID() - { - return mID; - } - - /** - * Gets the alias for this entity - */ - public Alias getAlias() - { - return mAlias; - } - - /** - * Returns an unmodifiable sorted (time order) list of plottables - */ - public Set getPlottables() - { - return Collections.unmodifiableSet( mPlottables ); - } - - /** - * Clears all plottables from this entity - */ - public void clear() - { - mPlottables.clear(); - } - - /** - * Compares by alias name - */ - @Override - public int compareTo( PlottableEntity o ) - { - return this.getAlias().getName().compareTo( o.getAlias().getName() ); - } -} diff --git a/src/main/java/io/github/dsheirer/map/PlottableEntityHistory.java b/src/main/java/io/github/dsheirer/map/PlottableEntityHistory.java new file mode 100644 index 00000000..f99c9a9f --- /dev/null +++ b/src/main/java/io/github/dsheirer/map/PlottableEntityHistory.java @@ -0,0 +1,82 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.map; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.module.decode.event.PlottableDecodeEvent; +import org.jdesktop.swingx.mapviewer.GeoPosition; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Plottable entity history with location history. + */ +public class PlottableEntityHistory +{ + private List mLocationHistory = new ArrayList<>(); + private PlottableDecodeEvent mCurrentEvent; + private Identifier mIdentifier; + + /** + * Constructs a plottable entity history + */ + public PlottableEntityHistory(Identifier identifier, PlottableDecodeEvent event) + { + mIdentifier = identifier; + add(event); + } + + /** + * Location history for this entity + */ + public List getLocationHistory() + { + return Collections.unmodifiableList(mLocationHistory); + } + + /** + * Identifier for this plottable + */ + public Identifier getIdentifier() + { + return mIdentifier; + } + + /** + * Identifier collection from the latest event for this plottable + */ + public IdentifierCollection getIdentifierCollection() + { + return mCurrentEvent.getIdentifierCollection(); + } + + /** + * Updates the entity history with a location from the latest decode event + */ + public void add(PlottableDecodeEvent event) + { + mCurrentEvent = event; + mLocationHistory.add(event.getLocation()); + } +} diff --git a/src/main/java/io/github/dsheirer/map/PlottableEntityPainter.java b/src/main/java/io/github/dsheirer/map/PlottableEntityPainter.java index bc7960aa..8a3042a4 100644 --- a/src/main/java/io/github/dsheirer/map/PlottableEntityPainter.java +++ b/src/main/java/io/github/dsheirer/map/PlottableEntityPainter.java @@ -1,7 +1,8 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -15,15 +16,17 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.map; +import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.icon.IconManager; import org.jdesktop.swingx.JXMapViewer; import org.jdesktop.swingx.painter.AbstractPainter; -import java.awt.*; +import java.awt.Graphics2D; +import java.awt.Rectangle; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -31,21 +34,21 @@ import java.util.Set; public class PlottableEntityPainter extends AbstractPainter { private PlottableEntityRenderer mRenderer; - private Set mEntities = new HashSet(); + private Set mEntities = new HashSet<>(); - public PlottableEntityPainter(IconManager iconManager) + public PlottableEntityPainter(AliasModel aliasModel, IconManager iconManager) { - mRenderer = new PlottableEntityRenderer(iconManager); + mRenderer = new PlottableEntityRenderer(aliasModel, iconManager); setAntialiasing(true); setCacheable(false); } - public void addEntity(PlottableEntity entity) + public void addEntity(PlottableEntityHistory entity) { mEntities.add(entity); } - public void removeEntity(PlottableEntity entity) + public void removeEntity(PlottableEntityHistory entity) { mEntities.remove(entity); } @@ -55,7 +58,7 @@ public class PlottableEntityPainter extends AbstractPainter mEntities.clear(); } - private Set getEntities() + private Set getEntities() { return Collections.unmodifiableSet(mEntities); } @@ -67,9 +70,9 @@ public class PlottableEntityPainter extends AbstractPainter g.translate(-viewportBounds.getX(), -viewportBounds.getY()); - Set entities = getEntities(); + Set entities = getEntities(); - for(PlottableEntity entity : entities) + for(PlottableEntityHistory entity : entities) { mRenderer.paintPlottableEntity(g, map, entity, true); } diff --git a/src/main/java/io/github/dsheirer/map/PlottableEntityRenderer.java b/src/main/java/io/github/dsheirer/map/PlottableEntityRenderer.java index d7136de9..a5309aa5 100644 --- a/src/main/java/io/github/dsheirer/map/PlottableEntityRenderer.java +++ b/src/main/java/io/github/dsheirer/map/PlottableEntityRenderer.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,96 +15,117 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.map; +import io.github.dsheirer.alias.Alias; +import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.icon.IconManager; import org.jdesktop.swingx.JXMapViewer; +import org.jdesktop.swingx.mapviewer.GeoPosition; -import javax.swing.*; -import java.awt.*; +import javax.swing.ImageIcon; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.RenderingHints; import java.awt.geom.Point2D; -import java.util.Set; +import java.util.List; public class PlottableEntityRenderer { + private AliasModel mAliasModel; private IconManager mIconManager; - public PlottableEntityRenderer(IconManager iconManager) + public PlottableEntityRenderer(AliasModel aliasModel, IconManager iconManager) { + mAliasModel = aliasModel; mIconManager = iconManager; } - public void paintPlottableEntity(Graphics2D g, JXMapViewer viewer, PlottableEntity entity, boolean antiAliasing) + public void paintPlottableEntity(Graphics2D g, JXMapViewer viewer, PlottableEntityHistory entity, boolean antiAliasing) { - Graphics2D graphics = (Graphics2D) g.create(); + List locationHistory = entity.getLocationHistory(); - if(antiAliasing) + if(!locationHistory.isEmpty() && locationHistory.get(locationHistory.size() - 1).isValid()) { - graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); + Graphics2D graphics = (Graphics2D)g.create(); + + if(antiAliasing) + { + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } + + Alias alias = getAlias(entity); + /** + * Use the entity's preferred color for lines and labels + */ + Color color = (alias != null ? alias.getDisplayColor() : Color.YELLOW); + graphics.setColor(color); + + /** + * Paint the route first, so the icon and label overlay it + */ + paintRoute(graphics, viewer, entity, color); + + + /** + * Convert the lat/long geoposition to an x/y point on the viewer + */ + + Point2D point = viewer.getTileFactory().geoToPixel(locationHistory.get(locationHistory.size() - 1), viewer.getZoom()); + + /** + * Paint the icon at the current location + */ + ImageIcon icon = getIcon(alias); + paintIcon(graphics, point, icon); + + /** + * Paint the label offset to the right of the icon + */ + String label = (alias != null ? alias.getName() : entity.getIdentifier().toString()); + paintLabel(graphics, point, label, (int)(icon.getIconWidth() / 2), 0); + + /** + * Cleanup + */ + graphics.dispose(); + } + } + + /** + * Optional alias that matches the identifier for the entity history + */ + private Alias getAlias(PlottableEntityHistory entityHistory) + { + AliasList aliasList = mAliasModel.getAliasList(entityHistory.getIdentifierCollection()); + + if(aliasList != null) + { + return aliasList.getAlias(entityHistory.getIdentifier()); } - /** - * Use the entity's preferred color for lines and labels - */ - graphics.setColor(entity.getColor()); - - /** - * Convert the lat/long geoposition to an x/y point on the viewer - */ - Point2D point = viewer.getTileFactory().geoToPixel( - entity.getCurrentGeoPosition(), viewer.getZoom()); - - /** - * Paint the route first, so the icon and label overlay it - */ - paintRoute(graphics, viewer, entity); - - /** - * Paint the icon at the current location - */ - ImageIcon icon = getIcon(entity); - - paintIcon(graphics, point, icon); - - /** - * Paint the label offset to the right of the icon - */ - paintLabel(graphics, point, entity, (int) (icon.getIconWidth() / 2), 0); - - /** - * Cleanup - */ - graphics.dispose(); + return null; } - private ImageIcon getIcon(PlottableEntity entity) + private ImageIcon getIcon(Alias alias) { - return mIconManager.getIcon(entity.getMapIconName(), IconManager.DEFAULT_ICON_SIZE); + String iconName = (alias != null ? alias.getIconName() : null); + return mIconManager.getIcon(iconName, IconManager.DEFAULT_ICON_SIZE); } - private void paintIcon(Graphics2D graphics, - Point2D point, - ImageIcon icon) + private void paintIcon(Graphics2D graphics, Point2D point, ImageIcon icon) { - graphics.drawImage(icon.getImage(), - (int) point.getX() - (icon.getIconWidth() / 2), - (int) point.getY() - (icon.getIconHeight() / 2), - null); - + graphics.drawImage(icon.getImage(), (int)point.getX() - (icon.getIconWidth() / 2), + (int)point.getY() - (icon.getIconHeight() / 2), null); } - private void paintLabel(Graphics2D graphics, - Point2D point, - PlottableEntity entity, - int xOffset, - int yOffset) + private void paintLabel(Graphics2D graphics, Point2D point, String label, int xOffset, int yOffset) { - graphics.drawString(entity.getLabel(), - (int) point.getX() + xOffset, - (int) point.getY() + yOffset); + graphics.drawString(label, (int)point.getX() + xOffset, (int)point.getY() + yOffset); } /** @@ -111,47 +133,41 @@ public class PlottableEntityRenderer * using black as a wider background route, and the entity's preferred color * as a narrower foreground route. */ - private void paintRoute(Graphics2D graphics, - JXMapViewer viewer, - PlottableEntity entity) + private void paintRoute(Graphics2D graphics, JXMapViewer viewer, PlottableEntityHistory entity, Color color) { - Set plottables = entity.getPlottables(); + List locations = entity.getLocationHistory(); - if(plottables.size() > 1) + if(!locations.isEmpty()) { // Draw the route with a black background line graphics.setColor(Color.BLACK); graphics.setStroke(new BasicStroke(3)); - drawRoute(plottables, graphics, viewer); + drawRoute(locations, graphics, viewer); // Draw the route again, in the entity's preferred color - graphics.setColor(entity.getColor()); + graphics.setColor(color); graphics.setStroke(new BasicStroke(1)); - drawRoute(plottables, graphics, viewer); + drawRoute(locations, graphics, viewer); } } /** * Draws a route from a list of plottables */ - private void drawRoute(Set plottables, - Graphics2D g, - JXMapViewer viewer) + private void drawRoute(List locations, Graphics2D g, JXMapViewer viewer) { Point2D lastPoint = null; - for(Plottable plottable : plottables) + for(GeoPosition location : locations) { // convert geo-coordinate to world bitmap pixel - Point2D currentPoint = viewer.getTileFactory() - .geoToPixel(plottable.getGeoPosition(), viewer.getZoom()); + Point2D currentPoint = viewer.getTileFactory().geoToPixel(location, viewer.getZoom()); if(lastPoint != null) { - g.drawLine((int) lastPoint.getX(), (int) lastPoint.getY(), - (int) currentPoint.getX(), (int) currentPoint.getY()); + g.drawLine((int)lastPoint.getX(), (int)lastPoint.getY(), (int)currentPoint.getX(), (int)currentPoint.getY()); } lastPoint = currentPoint; diff --git a/src/main/java/io/github/dsheirer/map/PlottableUpdateListener.java b/src/main/java/io/github/dsheirer/map/PlottableUpdateListener.java deleted file mode 100644 index cd24aca0..00000000 --- a/src/main/java/io/github/dsheirer/map/PlottableUpdateListener.java +++ /dev/null @@ -1,27 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.map; - -public interface PlottableUpdateListener -{ - public void entitiesUpdated(); - - public void addPlottableEntity( PlottableEntity entity ); - - public void removePlottableEntity( PlottableEntity entity ); -} diff --git a/src/main/java/io/github/dsheirer/message/IBitErrorProvider.java b/src/main/java/io/github/dsheirer/message/IBitErrorProvider.java new file mode 100644 index 00000000..89ee0d15 --- /dev/null +++ b/src/main/java/io/github/dsheirer/message/IBitErrorProvider.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.message; + +/** + * Interface for elements to provide a number of bits processed and the number of errors that were + * corrected. + */ +public interface IBitErrorProvider +{ + /** + * Indicates the number of bits that have been processed. + */ + int getBitsProcessedCount(); + + /** + * Indicates the number of errors contained in the bits that have been processed. + */ + int getBitErrorsCount(); +} diff --git a/src/main/java/io/github/dsheirer/message/IMessage.java b/src/main/java/io/github/dsheirer/message/IMessage.java new file mode 100644 index 00000000..6af72d71 --- /dev/null +++ b/src/main/java/io/github/dsheirer/message/IMessage.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.message; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.protocol.Protocol; + +import java.util.List; + +public interface IMessage +{ + /** + * Timestamp for when the message was transmitted or processed + * @return milliseconds since epoch timestamp + */ + long getTimestamp(); + + /** + * Indicates if the message is valid and has passed all error detection and correction. + */ + boolean isValid(); + + /** + * Protocol name associated with the message + */ + Protocol getProtocol(); + + /** + * List of identifiers present in the message + * @return list of identifiers or an empty list + */ + List getIdentifiers(); +} diff --git a/src/main/java/io/github/dsheirer/message/IMessageListener.java b/src/main/java/io/github/dsheirer/message/IMessageListener.java index b8d26c9b..42164a2c 100644 --- a/src/main/java/io/github/dsheirer/message/IMessageListener.java +++ b/src/main/java/io/github/dsheirer/message/IMessageListener.java @@ -4,5 +4,5 @@ import io.github.dsheirer.sample.Listener; public interface IMessageListener { - public Listener getMessageListener(); + Listener getMessageListener(); } diff --git a/src/main/java/io/github/dsheirer/message/IMessageProvider.java b/src/main/java/io/github/dsheirer/message/IMessageProvider.java index 7f3fbfd4..1f800d3b 100644 --- a/src/main/java/io/github/dsheirer/message/IMessageProvider.java +++ b/src/main/java/io/github/dsheirer/message/IMessageProvider.java @@ -4,6 +4,6 @@ import io.github.dsheirer.sample.Listener; public interface IMessageProvider { - public void setMessageListener( Listener listener ); - public void removeMessageListener(); + void setMessageListener(Listener listener); + void removeMessageListener(); } diff --git a/src/main/java/io/github/dsheirer/message/Message.java b/src/main/java/io/github/dsheirer/message/Message.java index 1ff480e2..57ead0df 100644 --- a/src/main/java/io/github/dsheirer/message/Message.java +++ b/src/main/java/io/github/dsheirer/message/Message.java @@ -1,115 +1,68 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.message; -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.map.Plottable; +import io.github.dsheirer.protocol.Protocol; -import java.util.Collections; -import java.util.List; - -public abstract class Message +/** + * IMessage implementation. + */ +public abstract class Message implements IMessage { - protected long mTimeReceived; - - public Message() - { - mTimeReceived = System.currentTimeMillis(); - } + private long mTimestamp; - public long getTimeReceived() - { - return mTimeReceived; - } + /** + * Constructs a new message using current system time as the timestamp. + */ + public Message() + { + mTimestamp = System.currentTimeMillis(); + } - /** - * Map plottable - */ - public Plottable getPlottable() - { -//TODO: move this to an IPlottable interface that only gets implemented as needed - return null; - } + /** + * Constructs a new message using the argument timestamp. + * + * @param timestamp for the message + */ + public Message(long timestamp) + { + mTimestamp = timestamp; + } - /** - * Decoded textual representation of the message - */ - public abstract String toString(); - - /** - * Indicates if the message is valid and has passed crc/integrity checks - */ - public abstract boolean isValid(); + /** + * Timestamp when the message was received or processed. This timestamp should be as close to + * accurate as possible. + */ + public long getTimestamp() + { + return mTimestamp; + } - /** - * Status of the CRC check of the message - */ - public abstract String getErrorStatus(); + /** + * Decoded textual representation of the message + */ + public abstract String toString(); - /** - * Parsed Message - * @return - */ - public abstract String getMessage(); + /** + * Indicates if the message is valid and has passed crc/integrity checks + */ + public abstract boolean isValid(); - /** - * Raw ( 0 & 1 ) message bits - */ - public abstract String getBinaryMessage(); - - - /** - * Decoded protocol - */ - public abstract String getProtocol(); - - /** - * Event - call, data, idle, etc. - */ - public abstract String getEventType(); - - /** - * Formatted from identifier - */ - public abstract String getFromID(); - - /** - * From identifier alias (from AliasManager) - */ - public abstract Alias getFromIDAlias(); - - /** - * Formatted to identifier - */ - public abstract String getToID(); - - /** - * To identifier alias (from AliasManager) - * @return - */ - public abstract Alias getToIDAlias(); - - - /** - * Provides a listing of aliases contained in the message. - */ - public List getAliases() - { - return Collections.EMPTY_LIST; - } + /** + * Decoded protocol + */ + public abstract Protocol getProtocol(); } \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/message/SyncLossMessage.java b/src/main/java/io/github/dsheirer/message/SyncLossMessage.java new file mode 100644 index 00000000..9740b980 --- /dev/null +++ b/src/main/java/io/github/dsheirer/message/SyncLossMessage.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.message; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.protocol.Protocol; + +import java.util.Collections; +import java.util.List; + +/** + * Empty message to convey that a number of bits were processed with no sync or valid message detected. + * + * This message supports periodic updates to external processor(s) that may be tracking bit error rates. + */ +public class SyncLossMessage extends Message +{ + private int mBitsProcessed; + private Protocol mProtocol; + + /** + * Constructs a sync loss message. + * + * @param timestamp of the message + * @param bitsProcessed without a sync pattern detection of message decode + */ + public SyncLossMessage(long timestamp, int bitsProcessed, Protocol protocol) + { + super(timestamp); + mBitsProcessed = bitsProcessed; + mProtocol = protocol; + } + + /** + * Indicates the number of bits that were processed without a sync dectection or message decode + */ + public int getBitsProcessed() + { + return mBitsProcessed; + } + + @Override + public String toString() + { + return "<-> SYNC LOSS - BITS PROCESSED [" + getBitsProcessed() + "]"; + } + + @Override + public boolean isValid() + { + return true; + } + + @Override + public Protocol getProtocol() + { + return mProtocol; + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/ProcessingChain.java b/src/main/java/io/github/dsheirer/module/ProcessingChain.java index 11255615..661cbea7 100644 --- a/src/main/java/io/github/dsheirer/module/ProcessingChain.java +++ b/src/main/java/io/github/dsheirer/module/ProcessingChain.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,34 +15,35 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.module; +import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.audio.IAudioPacketListener; import io.github.dsheirer.audio.IAudioPacketProvider; import io.github.dsheirer.audio.squelch.ISquelchStateListener; import io.github.dsheirer.audio.squelch.ISquelchStateProvider; import io.github.dsheirer.audio.squelch.SquelchState; -import io.github.dsheirer.channel.metadata.AttributeChangeRequest; -import io.github.dsheirer.channel.metadata.IAttributeChangeRequestListener; -import io.github.dsheirer.channel.metadata.IAttributeChangeRequestProvider; import io.github.dsheirer.channel.state.ChannelState; import io.github.dsheirer.channel.state.DecoderState; import io.github.dsheirer.channel.state.DecoderStateEvent; import io.github.dsheirer.channel.state.IDecoderStateEventListener; import io.github.dsheirer.channel.state.IDecoderStateEventProvider; -import io.github.dsheirer.controller.channel.Channel.ChannelType; +import io.github.dsheirer.controller.channel.Channel; import io.github.dsheirer.controller.channel.ChannelEvent; import io.github.dsheirer.controller.channel.IChannelEventListener; import io.github.dsheirer.controller.channel.IChannelEventProvider; +import io.github.dsheirer.identifier.IdentifierUpdateListener; +import io.github.dsheirer.identifier.IdentifierUpdateNotification; +import io.github.dsheirer.identifier.IdentifierUpdateProvider; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.IMessageListener; import io.github.dsheirer.message.IMessageProvider; -import io.github.dsheirer.message.Message; -import io.github.dsheirer.module.decode.event.CallEvent; -import io.github.dsheirer.module.decode.event.CallEventModel; -import io.github.dsheirer.module.decode.event.ICallEventListener; -import io.github.dsheirer.module.decode.event.ICallEventProvider; +import io.github.dsheirer.module.decode.event.DecodeEventModel; +import io.github.dsheirer.module.decode.event.IDecodeEvent; +import io.github.dsheirer.module.decode.event.IDecodeEventListener; +import io.github.dsheirer.module.decode.event.IDecodeEventProvider; import io.github.dsheirer.module.decode.event.MessageActivityModel; import io.github.dsheirer.module.log.EventLogger; import io.github.dsheirer.record.wave.ComplexBufferWaveRecorder; @@ -91,7 +93,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * Optional: if you want to reuse the processing chain with a new sample source, * invoke the following method sequence: stop(), setSource(), start() */ -public class ProcessingChain implements IChannelEventListener +public class ProcessingChain implements Listener { private final static Logger mLog = LoggerFactory.getLogger(ProcessingChain.class); @@ -99,40 +101,40 @@ public class ProcessingChain implements IChannelEventListener private ReusableBufferBroadcaster mDemodulatedAudioBufferBroadcaster = new ReusableBufferBroadcaster(); private ReusableBufferBroadcaster mBasebandComplexBufferBroadcaster = new ReusableBufferBroadcaster(); private ReusableBufferBroadcaster mDemodulatedBitstreamBufferBroadcaster = new ReusableBufferBroadcaster(); - private Broadcaster mAttributeChangeRequestBroadcaster = new Broadcaster<>(); - private Broadcaster mCallEventBroadcaster = new Broadcaster<>(); + private Broadcaster mDecodeEventBroadcaster = new Broadcaster<>(); private Broadcaster mChannelEventBroadcaster = new Broadcaster<>(); private Broadcaster mDecoderStateEventBroadcaster = new Broadcaster<>(); private Broadcaster mHeartbeatBroadcaster = new Broadcaster<>(); + private Broadcaster mIdentifierUpdateNotificationBroadcaster = new Broadcaster<>(); private Broadcaster mSourceEventBroadcaster = new Broadcaster<>(); - private Broadcaster mMessageBroadcaster = new Broadcaster<>(); + private Broadcaster mMessageBroadcaster = new Broadcaster<>(); private Broadcaster mSquelchStateBroadcaster = new Broadcaster<>(); private AtomicBoolean mRunning = new AtomicBoolean(); protected Source mSource; private List mModules = new ArrayList<>(); - private CallEventModel mCallEventModel; + private DecodeEventModel mDecodeEventModel; private ChannelState mChannelState; private MessageActivityModel mMessageActivityModel; /** * Creates a processing chain for managing a set of modules * - * @param channelType + * @param channel */ - public ProcessingChain(ChannelType channelType) + public ProcessingChain(Channel channel, AliasModel aliasModel) { - mChannelState = new ChannelState(channelType); + mChannelState = new ChannelState(channel, aliasModel); addModule(mChannelState); - mCallEventModel = new CallEventModel(); - addCallEventListener(mCallEventModel); + mDecodeEventModel = new DecodeEventModel(); + addDecodeEventListener(mDecodeEventModel); } - public CallEventModel getCallEventModel() + public DecodeEventModel getDecodeEventModel() { - return mCallEventModel; + return mDecodeEventModel; } public ChannelState getChannelState() @@ -164,7 +166,7 @@ public class ProcessingChain implements IChannelEventListener mModules.clear(); mAudioPacketBroadcaster.dispose(); - mCallEventBroadcaster.dispose(); + mDecodeEventBroadcaster.dispose(); mChannelEventBroadcaster.dispose(); mBasebandComplexBufferBroadcaster.dispose(); mDemodulatedBitstreamBufferBroadcaster.dispose(); @@ -230,7 +232,7 @@ public class ProcessingChain implements IChannelEventListener { if(module instanceof DecoderState) { - decoderStates.add((DecoderState) module); + decoderStates.add((DecoderState)module); } } @@ -288,29 +290,29 @@ public class ProcessingChain implements IChannelEventListener */ private void registerListeners(Module module) { - if(module instanceof IAttributeChangeRequestListener) + if(module instanceof IdentifierUpdateListener) { - mAttributeChangeRequestBroadcaster.addListener(((IAttributeChangeRequestListener)module).getAttributeChangeRequestListener()); + mIdentifierUpdateNotificationBroadcaster.addListener(((IdentifierUpdateListener)module).getIdentifierUpdateListener()); } if(module instanceof IAudioPacketListener) { - mAudioPacketBroadcaster.addListener(((IAudioPacketListener) module).getAudioPacketListener()); + mAudioPacketBroadcaster.addListener(((IAudioPacketListener)module).getAudioPacketListener()); } - if(module instanceof ICallEventListener) + if(module instanceof IDecodeEventListener) { - mCallEventBroadcaster.addListener(((ICallEventListener) module).getCallEventListener()); + mDecodeEventBroadcaster.addListener(((IDecodeEventListener)module).getDecodeEventListener()); } if(module instanceof IChannelEventListener) { - mChannelEventBroadcaster.addListener(((IChannelEventListener) module).getChannelEventListener()); + mChannelEventBroadcaster.addListener(((IChannelEventListener)module).getChannelEventListener()); } if(module instanceof IDecoderStateEventListener) { - mDecoderStateEventBroadcaster.addListener(((IDecoderStateEventListener) module).getDecoderStateListener()); + mDecoderStateEventBroadcaster.addListener(((IDecoderStateEventListener)module).getDecoderStateListener()); } if(module instanceof IHeartbeatListener) @@ -320,7 +322,7 @@ public class ProcessingChain implements IChannelEventListener if(module instanceof IMessageListener) { - mMessageBroadcaster.addListener(((IMessageListener) module).getMessageListener()); + mMessageBroadcaster.addListener(((IMessageListener)module).getMessageListener()); } if(module instanceof IReusableBufferListener) @@ -350,7 +352,7 @@ public class ProcessingChain implements IChannelEventListener if(module instanceof ISquelchStateListener) { - mSquelchStateBroadcaster.addListener(((ISquelchStateListener) module).getSquelchStateListener()); + mSquelchStateBroadcaster.addListener(((ISquelchStateListener)module).getSquelchStateListener()); } } @@ -360,29 +362,29 @@ public class ProcessingChain implements IChannelEventListener */ private void unregisterListeners(Module module) { - if(module instanceof IAttributeChangeRequestListener) + if(module instanceof IdentifierUpdateListener) { - mAttributeChangeRequestBroadcaster.removeListener(((IAttributeChangeRequestListener) module).getAttributeChangeRequestListener()); + mIdentifierUpdateNotificationBroadcaster.removeListener(((IdentifierUpdateListener)module).getIdentifierUpdateListener()); } if(module instanceof IAudioPacketListener) { - mAudioPacketBroadcaster.removeListener(((IAudioPacketListener) module).getAudioPacketListener()); + mAudioPacketBroadcaster.removeListener(((IAudioPacketListener)module).getAudioPacketListener()); } - if(module instanceof ICallEventListener) + if(module instanceof IDecodeEventListener) { - mCallEventBroadcaster.removeListener(((ICallEventListener) module).getCallEventListener()); + mDecodeEventBroadcaster.removeListener(((IDecodeEventListener)module).getDecodeEventListener()); } if(module instanceof IChannelEventListener) { - mChannelEventBroadcaster.removeListener(((IChannelEventListener) module).getChannelEventListener()); + mChannelEventBroadcaster.removeListener(((IChannelEventListener)module).getChannelEventListener()); } if(module instanceof IDecoderStateEventListener) { - mDecoderStateEventBroadcaster.removeListener(((IDecoderStateEventListener) module).getDecoderStateListener()); + mDecoderStateEventBroadcaster.removeListener(((IDecoderStateEventListener)module).getDecoderStateListener()); } if(module instanceof IHeartbeatListener) @@ -397,27 +399,27 @@ public class ProcessingChain implements IChannelEventListener if(module instanceof IReusableByteBufferListener) { - mDemodulatedBitstreamBufferBroadcaster.removeListener(((IReusableByteBufferListener) module).getReusableByteBufferListener()); + mDemodulatedBitstreamBufferBroadcaster.removeListener(((IReusableByteBufferListener)module).getReusableByteBufferListener()); } if(module instanceof IReusableComplexBufferListener) { - mBasebandComplexBufferBroadcaster.removeListener(((IReusableComplexBufferListener) module).getReusableComplexBufferListener()); + mBasebandComplexBufferBroadcaster.removeListener(((IReusableComplexBufferListener)module).getReusableComplexBufferListener()); } if(module instanceof ISourceEventListener) { - mSourceEventBroadcaster.removeListener(((ISourceEventListener) module).getSourceEventListener()); + mSourceEventBroadcaster.removeListener(((ISourceEventListener)module).getSourceEventListener()); } if(module instanceof IMessageListener) { - mMessageBroadcaster.removeListener(((IMessageListener) module).getMessageListener()); + mMessageBroadcaster.removeListener(((IMessageListener)module).getMessageListener()); } if(module instanceof ISquelchStateListener) { - mSquelchStateBroadcaster.removeListener(((ISquelchStateListener) module).getSquelchStateListener()); + mSquelchStateBroadcaster.removeListener(((ISquelchStateListener)module).getSquelchStateListener()); } } @@ -427,29 +429,29 @@ public class ProcessingChain implements IChannelEventListener */ private void registerProviders(Module module) { - if(module instanceof IAttributeChangeRequestProvider) + if(module instanceof IdentifierUpdateProvider) { - ((IAttributeChangeRequestProvider)module).setAttributeChangeRequestListener(mAttributeChangeRequestBroadcaster); + ((IdentifierUpdateProvider)module).setIdentifierUpdateListener(mIdentifierUpdateNotificationBroadcaster); } if(module instanceof IAudioPacketProvider) { - ((IAudioPacketProvider) module).setAudioPacketListener(mAudioPacketBroadcaster); + ((IAudioPacketProvider)module).setAudioPacketListener(mAudioPacketBroadcaster); } - if(module instanceof ICallEventProvider) + if(module instanceof IDecodeEventProvider) { - ((ICallEventProvider) module).addCallEventListener(mCallEventBroadcaster); + ((IDecodeEventProvider)module).addDecodeEventListener(mDecodeEventBroadcaster); } if(module instanceof IChannelEventProvider) { - ((IChannelEventProvider) module).setChannelEventListener(mChannelEventBroadcaster); + ((IChannelEventProvider)module).setChannelEventListener(mChannelEventBroadcaster); } if(module instanceof IDecoderStateEventProvider) { - ((IDecoderStateEventProvider) module).setDecoderStateListener(mDecoderStateEventBroadcaster); + ((IDecoderStateEventProvider)module).setDecoderStateListener(mDecoderStateEventBroadcaster); } if(module instanceof IHeartbeatProvider) @@ -459,12 +461,12 @@ public class ProcessingChain implements IChannelEventListener if(module instanceof IMessageProvider) { - ((IMessageProvider) module).setMessageListener(mMessageBroadcaster); + ((IMessageProvider)module).setMessageListener(mMessageBroadcaster); } if(module instanceof IMessageProvider) { - ((IMessageProvider) module).setMessageListener(mMessageBroadcaster); + ((IMessageProvider)module).setMessageListener(mMessageBroadcaster); } if(module instanceof IReusableByteBufferProvider) @@ -479,12 +481,12 @@ public class ProcessingChain implements IChannelEventListener if(module instanceof ISourceEventProvider) { - ((ISourceEventProvider) module).setSourceEventListener(mSourceEventBroadcaster); + ((ISourceEventProvider)module).setSourceEventListener(mSourceEventBroadcaster); } if(module instanceof ISquelchStateProvider) { - ((ISquelchStateProvider) module).setSquelchStateListener(mSquelchStateBroadcaster); + ((ISquelchStateProvider)module).setSquelchStateListener(mSquelchStateBroadcaster); } } @@ -494,14 +496,14 @@ public class ProcessingChain implements IChannelEventListener */ private void unregisterProviders(Module module) { - if(module instanceof IAttributeChangeRequestProvider) + if(module instanceof IdentifierUpdateProvider) { - ((IAttributeChangeRequestProvider)module).removeAttributeChangeRequestListener(mAttributeChangeRequestBroadcaster); + ((IdentifierUpdateProvider)module).removeIdentifierUpdateListener(); } if(module instanceof IAudioPacketProvider) { - ((IAudioPacketProvider) module).setAudioPacketListener(null); + ((IAudioPacketProvider)module).setAudioPacketListener(null); } if(module instanceof IReusableByteBufferProvider) @@ -509,19 +511,19 @@ public class ProcessingChain implements IChannelEventListener ((IReusableByteBufferProvider)module).removeBufferListener(mDemodulatedBitstreamBufferBroadcaster); } - if(module instanceof ICallEventProvider) + if(module instanceof IDecodeEventProvider) { - ((ICallEventProvider) module).removeCallEventListener(mCallEventBroadcaster); + ((IDecodeEventProvider)module).removeDecodeEventListener(mDecodeEventBroadcaster); } if(module instanceof IChannelEventProvider) { - ((IChannelEventProvider) module).setChannelEventListener(null); + ((IChannelEventProvider)module).removeChannelEventListener(); } if(module instanceof IDecoderStateEventProvider) { - ((IDecoderStateEventProvider) module).setDecoderStateListener(null); + ((IDecoderStateEventProvider)module).setDecoderStateListener(null); } if(module instanceof IHeartbeatProvider) @@ -531,12 +533,12 @@ public class ProcessingChain implements IChannelEventListener if(module instanceof IMessageProvider) { - ((IMessageProvider) module).setMessageListener(null); + ((IMessageProvider)module).setMessageListener(null); } if(module instanceof IMessageProvider) { - ((IMessageProvider) module).setMessageListener(null); + ((IMessageProvider)module).setMessageListener(null); } if(module instanceof IReusableBufferProvider) @@ -546,12 +548,12 @@ public class ProcessingChain implements IChannelEventListener if(module instanceof ISourceEventProvider) { - ((ISourceEventProvider) module).setSourceEventListener(null); + ((ISourceEventProvider)module).setSourceEventListener(null); } if(module instanceof ISquelchStateProvider) { - ((ISquelchStateProvider) module).setSquelchStateListener(null); + ((ISquelchStateProvider)module).setSquelchStateListener(null); } } @@ -574,23 +576,23 @@ public class ProcessingChain implements IChannelEventListener //Setup the channel state to monitor source overflow conditions mSource.setOverflowListener(mChannelState); - /* Register with the source to receive sample data. Setup a - * timer task to process the buffer queues 50 times a second - * (every 20 ms) */ + /* Register with the source to receive sample data. Setup a + * timer task to process the buffer queues 50 times a second + * (every 20 ms) */ switch(mSource.getSampleType()) { case COMPLEX: - ((ComplexSource) mSource).setListener(mBasebandComplexBufferBroadcaster); + ((ComplexSource)mSource).setListener(mBasebandComplexBufferBroadcaster); break; case REAL: - ((RealSource) mSource).setListener(mDemodulatedAudioBufferBroadcaster); + ((RealSource)mSource).setListener(mDemodulatedAudioBufferBroadcaster); break; default: throw new IllegalArgumentException("Unrecognized source " + "sample type - cannot start processing chain"); } - - /* Start each of the modules */ + + /* Start each of the modules */ for(Module module : mModules) { try @@ -628,10 +630,10 @@ public class ProcessingChain implements IChannelEventListener switch(mSource.getSampleType()) { case COMPLEX: - ((ComplexSource) mSource).removeListener(mBasebandComplexBufferBroadcaster); + ((ComplexSource)mSource).removeListener(mBasebandComplexBufferBroadcaster); break; case REAL: - ((RealSource) mSource).removeListener(mDemodulatedAudioBufferBroadcaster); + ((RealSource)mSource).removeListener(mDemodulatedAudioBufferBroadcaster); break; default: throw new IllegalArgumentException("Unrecognized source sample type - cannot start processing " + @@ -641,7 +643,7 @@ public class ProcessingChain implements IChannelEventListener mSource = null; } - /* Stop each of the remaining modules */ + /* Stop each of the remaining modules */ for(Module module : mModules) { module.stop(); @@ -711,16 +713,16 @@ public class ProcessingChain implements IChannelEventListener } /** - * Adds the listener to receive call events from all modules. + * Adds the listener to receive decode events from all modules. */ - public void addCallEventListener(Listener listener) + public void addDecodeEventListener(Listener listener) { - mCallEventBroadcaster.addListener(listener); + mDecodeEventBroadcaster.addListener(listener); } - public void removeCallEventListener(Listener listener) + public void removeDecodeEventListener(Listener listener) { - mCallEventBroadcaster.removeListener(listener); + mDecodeEventBroadcaster.removeListener(listener); } /** @@ -757,7 +759,7 @@ public class ProcessingChain implements IChannelEventListener /** * Adds the listener to receive decoded messages from all decoders. */ - public void addMessageListener(Listener listener) + public void addMessageListener(Listener listener) { mMessageBroadcaster.addListener(listener); } @@ -765,9 +767,9 @@ public class ProcessingChain implements IChannelEventListener /** * Adds the list of listeners to receive decoded messages from all decoders. */ - public void addMessageListeners(List> listeners) + public void addMessageListeners(List> listeners) { - for(Listener listener : listeners) + for(Listener listener : listeners) { mMessageBroadcaster.addListener(listener); } @@ -776,13 +778,14 @@ public class ProcessingChain implements IChannelEventListener /** * Removes the listener from receiving decoded messages from all decoders. */ - public void removeMessageListener(Listener listener) + public void removeMessageListener(Listener listener) { mMessageBroadcaster.removeListener(listener); } /** * Adds the listener to receive frequency change events from the processing chain + * * @param listener to receive events */ public void addFrequencyChangeListener(Listener listener) @@ -792,6 +795,7 @@ public class ProcessingChain implements IChannelEventListener /** * Removes the listener from receiving frequency change events + * * @param listener to remove */ public void removeFrequencyChangeListener(Listener listener) @@ -828,9 +832,22 @@ public class ProcessingChain implements IChannelEventListener mDemodulatedAudioBufferBroadcaster.removeListener(listener); } + /** + * Primary method for the channel processing manager to broadcast channel events, namely traffic channel processing + * stop events so that traffic channel manager(s) can maintain a correct state of traffic channels. + */ @Override - public Listener getChannelEventListener() + public void receive(ChannelEvent channelEvent) { - return mChannelEventBroadcaster; + mChannelEventBroadcaster.broadcast(channelEvent); + } + + /** + * Broadcasts an identifier update notification to all modules + * @param updateNotification to broadcast + */ + public void receive(IdentifierUpdateNotification updateNotification) + { + mIdentifierUpdateNotificationBroadcaster.broadcast(updateNotification); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/Decoder.java b/src/main/java/io/github/dsheirer/module/decode/Decoder.java index 00859c78..9cedf003 100644 --- a/src/main/java/io/github/dsheirer/module/decode/Decoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/Decoder.java @@ -17,6 +17,7 @@ ******************************************************************************/ package io.github.dsheirer.module.decode; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.IMessageProvider; import io.github.dsheirer.message.Message; import io.github.dsheirer.module.Module; @@ -25,8 +26,8 @@ import io.github.dsheirer.sample.Listener; public abstract class Decoder extends Module implements IMessageProvider { /* This has to be a broadcaster in order for references to persist */ - private Listener mMessageDistributor = new MessageDistributor(); - protected Listener mMessageListener; + private Listener mMessageDistributor = new MessageDistributor(); + protected Listener mMessageListener; /** * Decoder - parent class for all decoders, demodulators and components. @@ -66,7 +67,7 @@ public abstract class Decoder extends Module implements IMessageProvider * Adds a listener for receiving decoded messages from this decoder */ @Override - public void setMessageListener(Listener listener) + public void setMessageListener(Listener listener) { mMessageListener = listener; } @@ -84,7 +85,7 @@ public abstract class Decoder extends Module implements IMessageProvider /** * Message listener for receiving the output from this decoder */ - protected Listener getMessageListener() + protected Listener getMessageListener() { return mMessageDistributor; } @@ -92,10 +93,10 @@ public abstract class Decoder extends Module implements IMessageProvider /** * Distributes/forwards messages from sub-class decoder implementations to the registered message listener. */ - public class MessageDistributor implements Listener + public class MessageDistributor implements Listener { @Override - public void receive(Message message) + public void receive(IMessage message) { if(mMessageListener != null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java index 9e06f168..a5694007 100644 --- a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,20 +15,17 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.module.decode; import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.alias.AliasModel; +import io.github.dsheirer.alias.action.AliasActionManager; import io.github.dsheirer.audio.AudioModule; -import io.github.dsheirer.channel.metadata.Metadata; import io.github.dsheirer.channel.state.AlwaysUnsquelchedDecoderState; -import io.github.dsheirer.channel.traffic.TrafficChannelManager; import io.github.dsheirer.controller.channel.Channel; import io.github.dsheirer.controller.channel.Channel.ChannelType; -import io.github.dsheirer.controller.channel.ChannelModel; -import io.github.dsheirer.controller.channel.ChannelProcessingManager; import io.github.dsheirer.controller.channel.map.ChannelMap; import io.github.dsheirer.controller.channel.map.ChannelMapModel; import io.github.dsheirer.filter.AllPassFilter; @@ -35,7 +33,7 @@ import io.github.dsheirer.filter.FilterSet; import io.github.dsheirer.filter.IFilter; import io.github.dsheirer.gui.editor.EmptyValidatingEditor; import io.github.dsheirer.gui.editor.ValidatingEditor; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.MessageDirection; import io.github.dsheirer.module.Module; import io.github.dsheirer.module.decode.am.AMDecoder; @@ -67,16 +65,17 @@ import io.github.dsheirer.module.decode.mpt1327.MPT1327Decoder; import io.github.dsheirer.module.decode.mpt1327.MPT1327DecoderEditor; import io.github.dsheirer.module.decode.mpt1327.MPT1327DecoderState; import io.github.dsheirer.module.decode.mpt1327.MPT1327MessageFilter; +import io.github.dsheirer.module.decode.mpt1327.MPT1327TrafficChannelManager; import io.github.dsheirer.module.decode.mpt1327.Sync; import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM; import io.github.dsheirer.module.decode.nbfm.NBFMDecoder; import io.github.dsheirer.module.decode.nbfm.NBFMDecoderEditor; import io.github.dsheirer.module.decode.p25.DecodeConfigP25Phase1; -import io.github.dsheirer.module.decode.p25.P25Decoder.Modulation; import io.github.dsheirer.module.decode.p25.P25DecoderC4FM; import io.github.dsheirer.module.decode.p25.P25DecoderEditor; import io.github.dsheirer.module.decode.p25.P25DecoderLSM; import io.github.dsheirer.module.decode.p25.P25DecoderState; +import io.github.dsheirer.module.decode.p25.P25TrafficChannelManager; import io.github.dsheirer.module.decode.p25.audio.P25AudioModule; import io.github.dsheirer.module.decode.p25.message.filter.P25MessageFilterSet; import io.github.dsheirer.module.decode.passport.DecodeConfigPassport; @@ -110,37 +109,31 @@ public class DecoderFactory * * @return list of configured decoders */ - public static List getModules(ChannelModel channelModel, - ChannelMapModel channelMapModel, - ChannelProcessingManager channelProcessingManager, - AliasModel aliasModel, - Channel channel, - Metadata metadata) + public static List getModules(ChannelMapModel channelMapModel, Channel channel, AliasModel aliasModel) { - - /* Get the optional alias list for the decode modules to use */ - AliasList aliasList = aliasModel.getAliasList(channel.getAliasListName()); - - List modules = getPrimaryModules(channelModel, channelMapModel, channelProcessingManager, aliasList, - channel, metadata); - - modules.addAll(getAuxiliaryDecoders(channel.getAuxDecodeConfiguration(), aliasList)); - + List modules = getPrimaryModules(channelMapModel, channel, aliasModel); + modules.addAll(getAuxiliaryDecoders(channel.getAuxDecodeConfiguration())); return modules; } /** * Constructs a primary decoder as specified in the decode configuration */ - public static List getPrimaryModules(ChannelModel channelModel, - ChannelMapModel channelMapModel, - ChannelProcessingManager channelProcessingManager, - AliasList aliasList, - Channel channel, - Metadata metadata) + public static List getPrimaryModules(ChannelMapModel channelMapModel, Channel channel, AliasModel aliasModel) { List modules = new ArrayList(); + //Adds an alias action manager when there are alias actions specified + if(channel.getAliasListName() != null && !channel.getAliasListName().isEmpty()) + { + AliasList aliasList = aliasModel.getAliasList(channel.getAliasListName()); + + if(aliasList != null && aliasList.hasAliasActions()) + { + modules.add(new AliasActionManager(aliasList)); + } + } + ChannelType channelType = channel.getChannelType(); /* Baseband low-pass filter pass and stop frequencies */ @@ -151,7 +144,7 @@ public class DecoderFactory case AM: modules.add(new AMDecoder(decodeConfig)); modules.add(new AlwaysUnsquelchedDecoderState(DecoderType.AM, channel.getName())); - modules.add(new AudioModule(metadata)); + modules.add(new AudioModule()); if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER) { modules.add(new AMDemodulatorModule(AM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE)); @@ -160,87 +153,92 @@ public class DecoderFactory case NBFM: modules.add(new NBFMDecoder(decodeConfig)); modules.add(new AlwaysUnsquelchedDecoderState(DecoderType.NBFM, channel.getName())); - modules.add(new AudioModule(metadata)); + modules.add(new AudioModule()); if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER) { modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE)); } break; case LTR_STANDARD: - MessageDirection direction = ((DecodeConfigLTRStandard) decodeConfig).getMessageDirection(); - modules.add(new LTRStandardDecoder(aliasList, direction)); - modules.add(new LTRStandardDecoderState(aliasList)); - modules.add(new AudioModule(metadata)); + MessageDirection direction = ((DecodeConfigLTRStandard)decodeConfig).getMessageDirection(); + modules.add(new LTRStandardDecoder(null, direction)); + modules.add(new LTRStandardDecoderState()); + modules.add(new AudioModule()); if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER) { modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE)); } break; case LTR_NET: - modules.add(new LTRNetDecoder((DecodeConfigLTRNet) decodeConfig, aliasList)); - modules.add(new LTRNetDecoderState(aliasList)); - modules.add(new AudioModule(metadata)); + modules.add(new LTRNetDecoder((DecodeConfigLTRNet)decodeConfig)); + modules.add(new LTRNetDecoderState()); + modules.add(new AudioModule()); if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER) { modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE)); } break; case MPT1327: - DecodeConfigMPT1327 mptConfig = (DecodeConfigMPT1327) decodeConfig; + DecodeConfigMPT1327 mptConfig = (DecodeConfigMPT1327)decodeConfig; ChannelMap channelMap = channelMapModel.getChannelMap(mptConfig.getChannelMapName()); Sync sync = mptConfig.getSync(); - modules.add(new MPT1327Decoder(aliasList, sync)); - modules.add(new MPT1327DecoderState(aliasList, channelMap, channelType, mptConfig.getCallTimeout() * 1000)); - modules.add(new AudioModule(metadata)); + modules.add(new MPT1327Decoder(sync)); + modules.add(new AudioModule()); if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER) { modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE)); } + long timeout = mptConfig.getCallTimeout() * 1000; + if(channelType == ChannelType.STANDARD) { - modules.add(new TrafficChannelManager(channelModel, decodeConfig, - channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(), - (aliasList != null ? aliasList.getName() : null), mptConfig.getTrafficChannelPoolSize())); + MPT1327TrafficChannelManager trafficChannelManager = new MPT1327TrafficChannelManager(channel, channelMap); + modules.add(trafficChannelManager); + modules.add(new MPT1327DecoderState(trafficChannelManager, channelType, timeout)); + } + else + { + modules.add(new MPT1327DecoderState(channelType, timeout)); } break; case PASSPORT: - modules.add(new PassportDecoder(decodeConfig, aliasList)); - modules.add(new PassportDecoderState(aliasList)); - modules.add(new AudioModule(metadata)); + modules.add(new PassportDecoder(decodeConfig)); + modules.add(new PassportDecoderState()); + modules.add(new AudioModule()); if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER) { modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE)); } break; case P25_PHASE1: - DecodeConfigP25Phase1 p25Config = (DecodeConfigP25Phase1) decodeConfig; + DecodeConfigP25Phase1 p25Config = (DecodeConfigP25Phase1)decodeConfig; - Modulation modulation = p25Config.getModulation(); - - switch(modulation) + switch(p25Config.getModulation()) { case C4FM: - modules.add(new P25DecoderC4FM(aliasList)); - modules.add(new P25DecoderState(aliasList, channelType, Modulation.C4FM, - p25Config.getIgnoreDataCalls())); + modules.add(new P25DecoderC4FM()); break; case CQPSK: - modules.add(new P25DecoderLSM(aliasList)); - modules.add(new P25DecoderState(aliasList, channelType, Modulation.CQPSK, p25Config.getIgnoreDataCalls())); + modules.add(new P25DecoderLSM()); break; default: - throw new IllegalArgumentException("Unrecognized P25 Phase 1 Modulation [" + modulation + "]"); + throw new IllegalArgumentException("Unrecognized P25 Phase 1 Modulation [" + + p25Config.getModulation() + "]"); } if(channelType == ChannelType.STANDARD) { - modules.add(new TrafficChannelManager(channelModel, decodeConfig, - channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(), - (aliasList != null ? aliasList.getName() : null), p25Config.getTrafficChannelPoolSize())); + P25TrafficChannelManager trafficChannelManager = new P25TrafficChannelManager(channel); + modules.add(trafficChannelManager); + modules.add(new P25DecoderState(channel, trafficChannelManager)); + } + else + { + modules.add(new P25DecoderState(channel)); } - modules.add(new P25AudioModule(metadata)); + modules.add(new P25AudioModule()); break; default: throw new IllegalArgumentException("Unknown decoder type [" + decodeConfig.getDecoderType().toString() + "]"); @@ -253,11 +251,9 @@ public class DecoderFactory * Constructs a list of auxiliary decoders, as specified in the configuration * * @param config - auxiliary configuration - * @param aliasList - optional alias list * @return - list of auxiliary decoders */ - public static List getAuxiliaryDecoders(AuxDecodeConfiguration config, - AliasList aliasList) + public static List getAuxiliaryDecoders(AuxDecodeConfiguration config) { List modules = new ArrayList<>(); @@ -268,24 +264,23 @@ public class DecoderFactory switch(auxDecoder) { case FLEETSYNC2: - modules.add(new Fleetsync2Decoder(aliasList)); - modules.add(new Fleetsync2DecoderState(aliasList)); + modules.add(new Fleetsync2Decoder()); + modules.add(new Fleetsync2DecoderState()); break; case MDC1200: - modules.add(new MDCDecoder(aliasList)); - modules.add(new MDCDecoderState(aliasList)); + modules.add(new MDCDecoder()); + modules.add(new MDCDecoderState()); break; case LJ_1200: - modules.add(new LJ1200Decoder(aliasList)); - modules.add(new LJ1200DecoderState(aliasList)); + modules.add(new LJ1200Decoder()); + modules.add(new LJ1200DecoderState()); break; case TAIT_1200: - modules.add(new Tait1200Decoder(aliasList)); - modules.add(new Tait1200DecoderState(aliasList)); + modules.add(new Tait1200Decoder()); + modules.add(new Tait1200DecoderState()); break; default: - throw new IllegalArgumentException("Unrecognized auxiliary " - + "decoder type [" + auxDecoder + "]"); + throw new IllegalArgumentException("Unrecognized auxiliary decoder type [" + auxDecoder + "]"); } } } @@ -297,9 +292,9 @@ public class DecoderFactory * Assembles a filter set containing filters for the primary channel * decoder and each of the auxiliary decoders */ - public static FilterSet getMessageFilters(List modules) + public static FilterSet getMessageFilters(List modules) { - FilterSet filterSet = new FilterSet<>(); + FilterSet filterSet = new FilterSet<>(); for(Module module : modules) { @@ -312,7 +307,7 @@ public class DecoderFactory /* If we don't have any filters, add an ALL-PASS filter */ if(filterSet.getFilters().isEmpty()) { - filterSet.addFilter(new AllPassFilter()); + filterSet.addFilter(new AllPassFilter<>()); } return filterSet; @@ -322,9 +317,9 @@ public class DecoderFactory * Returns a set of IMessageFilter objects (FilterSets or Filters) that * can process all of the messages produced by the specified decoder type. */ - public static List> getMessageFilter(DecoderType decoder) + public static List> getMessageFilter(DecoderType decoder) { - ArrayList> filters = new ArrayList>(); + ArrayList> filters = new ArrayList<>(); switch(decoder) { @@ -424,21 +419,19 @@ public class DecoderFactory switch(config.getDecoderType()) { case AM: - DecodeConfigAM originalAM = (DecodeConfigAM) config; - DecodeConfigAM copyAM = new DecodeConfigAM(); - return copyAM; + return new DecodeConfigAM(); case LTR_NET: - DecodeConfigLTRNet originalLTRNet = (DecodeConfigLTRNet) config; + DecodeConfigLTRNet originalLTRNet = (DecodeConfigLTRNet)config; DecodeConfigLTRNet copyLTRNet = new DecodeConfigLTRNet(); copyLTRNet.setMessageDirection(originalLTRNet.getMessageDirection()); return copyLTRNet; case LTR_STANDARD: - DecodeConfigLTRStandard originalLTRStandard = (DecodeConfigLTRStandard) config; + DecodeConfigLTRStandard originalLTRStandard = (DecodeConfigLTRStandard)config; DecodeConfigLTRStandard copyLTRStandard = new DecodeConfigLTRStandard(); copyLTRStandard.setMessageDirection(originalLTRStandard.getMessageDirection()); return copyLTRStandard; case MPT1327: - DecodeConfigMPT1327 originalMPT = (DecodeConfigMPT1327) config; + DecodeConfigMPT1327 originalMPT = (DecodeConfigMPT1327)config; DecodeConfigMPT1327 copyMPT = new DecodeConfigMPT1327(); copyMPT.setCallTimeout(originalMPT.getCallTimeout()); copyMPT.setChannelMapName(originalMPT.getChannelMapName()); @@ -446,20 +439,16 @@ public class DecoderFactory copyMPT.setTrafficChannelPoolSize(originalMPT.getTrafficChannelPoolSize()); return copyMPT; case NBFM: - DecodeConfigNBFM originalNBFM = (DecodeConfigNBFM) config; - DecodeConfigNBFM copyNBFM = new DecodeConfigNBFM(); - return copyNBFM; + return new DecodeConfigNBFM(); case P25_PHASE1: - DecodeConfigP25Phase1 originalP25 = (DecodeConfigP25Phase1) config; + DecodeConfigP25Phase1 originalP25 = (DecodeConfigP25Phase1)config; DecodeConfigP25Phase1 copyP25 = new DecodeConfigP25Phase1(); copyP25.setIgnoreDataCalls(originalP25.getIgnoreDataCalls()); copyP25.setModulation(originalP25.getModulation()); copyP25.setTrafficChannelPoolSize(originalP25.getTrafficChannelPoolSize()); return copyP25; case PASSPORT: - DecodeConfigPassport originalPass = (DecodeConfigPassport) config; - DecodeConfigPassport copyPass = new DecodeConfigPassport(); - return copyPass; + return new DecodeConfigPassport(); default: throw new IllegalArgumentException("Unrecognized decoder configuration type:" + config.getDecoderType()); } @@ -469,12 +458,12 @@ public class DecoderFactory } /** - * Decoder(s) that support bitstreams. + * Decoder(s) that support bitstreams indicating that they produce ReusableByteBuffers of decoded bits. * * @return set of decoders that support bitstreams. */ public static EnumSet getBitstreamDecoders() { - return EnumSet.of(DecoderType.P25_PHASE1); + return EnumSet.of(DecoderType.P25_PHASE1, DecoderType.MPT1327); } } \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/DecoderType.java b/src/main/java/io/github/dsheirer/module/decode/DecoderType.java index 261428f2..1c7733d1 100644 --- a/src/main/java/io/github/dsheirer/module/decode/DecoderType.java +++ b/src/main/java/io/github/dsheirer/module/decode/DecoderType.java @@ -1,20 +1,26 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.module.decode; +import io.github.dsheirer.protocol.Protocol; + import java.util.ArrayList; import java.util.EnumSet; @@ -24,27 +30,29 @@ import java.util.EnumSet; public enum DecoderType { //Primary Decoders - AM("AM", "AM"), - LTR_STANDARD("LTR-Standard", "LTR"), - LTR_NET("LTR-Net", "LTR-Net"), - MPT1327("MPT1327", "MPT1327"), - NBFM("NBFM", "NBFM"), - PASSPORT("Passport", "Passport"), - P25_PHASE1("P25 Phase I", "P25-1"), + AM("AM", "AM", Protocol.UNKNOWN), + LTR_STANDARD("LTR-Standard", "LTR", Protocol.LTR_STANDARD), + LTR_NET("LTR-Net", "LTR-Net", Protocol.LTR_NET), + MPT1327("MPT1327", "MPT1327", Protocol.MPT1327), + NBFM("NBFM", "NBFM", Protocol.UNKNOWN), + PASSPORT("Passport", "Passport", Protocol.PASSPORT), + P25_PHASE1("P25 Phase I", "P25-1", Protocol.APCO25), //Auxiliary Decoders - FLEETSYNC2("Fleetsync II", "Fleetsync2"), - LJ_1200("LJ1200 173.075", "LJ1200"), - MDC1200("MDC1200", "MDC1200"), - TAIT_1200("Tait 1200", "Tait 1200"); + FLEETSYNC2("Fleetsync II", "Fleetsync2", Protocol.FLEETSYNC), + LJ_1200("LJ1200 173.075", "LJ1200", Protocol.LOJACK), + MDC1200("MDC1200", "MDC1200", Protocol.MDC1200), + TAIT_1200("Tait 1200", "Tait 1200", Protocol.TAIT1200); private String mDisplayString; private String mShortDisplayString; + private Protocol mProtocol; - DecoderType(String displayString, String shortDisplayString) + DecoderType(String displayString, String shortDisplayString, Protocol protocol) { mDisplayString = displayString; mShortDisplayString = shortDisplayString; + mProtocol = protocol; } /** @@ -76,6 +84,11 @@ public enum DecoderType return decoders; } + public Protocol getProtocol() + { + return mProtocol; + } + public String getDisplayString() { return mDisplayString; diff --git a/src/main/java/io/github/dsheirer/module/decode/event/ActivitySummaryProvider.java b/src/main/java/io/github/dsheirer/module/decode/event/ActivitySummaryProvider.java index a94e7ea9..c3a97b43 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/ActivitySummaryProvider.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/ActivitySummaryProvider.java @@ -17,10 +17,11 @@ ******************************************************************************/ package io.github.dsheirer.module.decode.event; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.Message; import io.github.dsheirer.sample.Listener; -public interface ActivitySummaryProvider extends Listener +public interface ActivitySummaryProvider extends Listener { public String getActivitySummary(); } diff --git a/src/main/java/io/github/dsheirer/module/decode/event/CallEvent.java b/src/main/java/io/github/dsheirer/module/decode/event/CallEvent.java index a0126273..73ee4a5a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/CallEvent.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/CallEvent.java @@ -20,9 +20,12 @@ package io.github.dsheirer.module.decode.event; import io.github.dsheirer.alias.Alias; import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.DecoderType; import java.text.SimpleDateFormat; +import java.util.Collection; import java.util.Date; public abstract class CallEvent implements Comparable @@ -40,8 +43,31 @@ public abstract class CallEvent implements Comparable protected String mToID; protected String mDetails; + //New call event format member variables + protected IChannelDescriptor mChannelDescriptor; + protected Collection mIdentifiers; + private boolean mValid = true; + /** + * Constructs a call event + * @param decoder type + * @param callEventType description + * @param channelDescriptor that identifies the channel and frequency value(s) + * @param identifiers for to, from, comments, etc. + */ + public CallEvent(DecoderType decoder, CallEventType callEventType, IChannelDescriptor channelDescriptor, Collection identifiers) + { + mDecoderType = decoder; + mCallEventType = callEventType; + mChannelDescriptor = channelDescriptor; + mIdentifiers = identifiers; + } + + /** + * Legacy call event constructor + */ + @Deprecated //Remove once all decoder states have been updated to new constructor public CallEvent(DecoderType decoder, CallEventType callEventType, AliasList aliasList, String fromID, String toID, String details) { @@ -53,6 +79,14 @@ public abstract class CallEvent implements Comparable mDetails = details; } + /** + * Identifier collection for this call event + */ + public Collection getIdentifiers() + { + return mIdentifiers; + } + /** * Indicates if this event is valid. Use this method to mark a previously * dispatched event as invalid and resend it so that any downstream @@ -168,7 +202,7 @@ public abstract class CallEvent implements Comparable public static String getCSVHeader() { - return "START_DATE,START_TIME,END_DATE,END_TIME,DECODER,EVENT,FROM,FROM_ALIAS,TO,TO_ALIAS,CHANNEL," + + return "START_DATE,START_TIME,END_DATE,END_TIME,DECODER,EVENT,FROM,FROM_ALIAS,TO,TO_ALIAS,CHANNEL_NUMBER," + "FREQUENCY,DETAILS"; } @@ -252,6 +286,7 @@ public abstract class CallEvent implements Comparable public enum CallEventType { ANNOUNCEMENT("Announcement"), + AUTOMATIC_REGISTRATION_SERVICE("Motorola ARS"), CALL("Call"), CALL_ALERT("Call Alert"), CALL_DETECT("Call Detect"), @@ -262,6 +297,7 @@ public abstract class CallEvent implements Comparable CALL_TIMEOUT("Call Timeout"), COMMAND("Command"), DATA_CALL("Data Call"), + DATA_PACKET("Data Packet"), DEREGISTER("Deregister"), EMERGENCY("EMERGENCY"), ENCRYPTED_CALL("Encrypted Call"), @@ -270,6 +306,7 @@ public abstract class CallEvent implements Comparable GROUP_CALL("Group Call"), ID_ANI("ANI"), ID_UNIQUE("Unique ID"), + IP_PACKET("IP Packet"), NOTIFICATION("Notification"), PAGE("Page"), PATCH_GROUP_ADD("Patch Group Add"), @@ -279,10 +316,14 @@ public abstract class CallEvent implements Comparable REGISTER("Register"), REGISTER_ESN("ESN"), RESPONSE("Response"), + RESPONSE_PACKET("Response Packet"), SDM("Short Data Message"), + SNDCP_PACKET("SNDCP Packet"), STATION_ID("Station ID"), STATUS("Status"), + TCP_PACKET("TCP/IP Packet"), TELEPHONE_INTERCONNECT("Telephone Interconnect"), + UDP_PACKET("UDP/IP Packet"), UNIT_TO_UNIT_CALL("Unit To Unit Call"), UNKNOWN("Unknown"), INVALID("Invalid"); diff --git a/src/main/java/io/github/dsheirer/module/decode/event/CallEventAliasCellRenderer.java b/src/main/java/io/github/dsheirer/module/decode/event/CallEventAliasCellRenderer.java deleted file mode 100644 index 302d08fe..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/event/CallEventAliasCellRenderer.java +++ /dev/null @@ -1,70 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.event; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.icon.IconManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.swing.*; -import javax.swing.table.DefaultTableCellRenderer; -import java.awt.*; - -/** - * Custom renderer for JTable cells that contain Alias objects. Renders the - * alias name, and if there is an icon name, attempts to get that icon from - * the settings manager and render it. - */ -public class CallEventAliasCellRenderer extends DefaultTableCellRenderer -{ - private static final long serialVersionUID = 1L; - private final static Logger mLog = LoggerFactory.getLogger(CallEventAliasCellRenderer.class); - - private IconManager mIconManager; - - public CallEventAliasCellRenderer(IconManager iconManager) - { - super(); - mIconManager = iconManager; - } - - @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) - { - JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - - if(value instanceof Alias) - { - Alias alias = (Alias)value; - - label.setText(alias.getName()); - label.setForeground(alias.getDisplayColor()); - label.setIcon(mIconManager.getIcon(alias.getIconName(), IconManager.DEFAULT_ICON_SIZE)); - } - else - { - label.setText(""); - label.setForeground(table.getForeground()); - label.setIcon(null); - } - - return label; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/CallEventModel.java b/src/main/java/io/github/dsheirer/module/decode/event/CallEventModel.java deleted file mode 100644 index 93ed0fcb..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/event/CallEventModel.java +++ /dev/null @@ -1,220 +0,0 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ -package io.github.dsheirer.module.decode.event; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.sample.Listener; - -import javax.swing.table.AbstractTableModel; -import java.text.DecimalFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.List; - -public class CallEventModel extends AbstractTableModel implements Listener -{ - private static final long serialVersionUID = 1L; - - private static DecimalFormat mFrequencyFormatter = new DecimalFormat("0.000000"); - - public static final int TIME = 0; - public static final int EVENT = 1; - public static final int FROM_ID = 2; - public static final int FROM_ALIAS = 3; - public static final int TO_ID = 4; - public static final int TO_ALIAS = 5; - public static final int CHANNEL = 6; - public static final int FREQUENCY = 7; - public static final int DETAILS = 8; - - protected int mMaxMessages = 500; - - protected List mEvents = new ArrayList(); - - protected String[] mHeaders = new String[]{"Time", - "Event", - "From", - "Alias", - "To", - "Alias", - "Channel", - "Frequency", - "Details"}; - - private SimpleDateFormat mSDFTime = new SimpleDateFormat("HH:mm:ss"); - - public CallEventModel() - { - } - - public void dispose() - { - mEvents.clear(); - } - - public void reset() - { - mEvents.clear(); - - fireTableDataChanged(); - } - - public int getMaxMessageCount() - { - return mMaxMessages; - } - - public void setMaxMessageCount(int count) - { - mMaxMessages = count; - } - - /** - * Adds, updates or deletes the event from the model. Producers can send - * the same call event multiple times to indicate that information in the - * event is updated. Producers can also mark the event as invalid and the - * event will be removed from the model. - */ - public void receive(final CallEvent event) - { - if(event.isValid()) - { - if(!mEvents.contains(event)) - { - mEvents.add(0, event); - - fireTableRowsInserted(0, 0); - - prune(); - } - else - { - int row = mEvents.indexOf(event); - - fireTableRowsUpdated(row, row); - } - } - else - { - if(mEvents.contains(event)) - { - int row = mEvents.indexOf(event); - - mEvents.remove(event); - - fireTableRowsDeleted(row, row); - } - } - } - - private void prune() - { - while(mEvents.size() > mMaxMessages) - { - int index = mEvents.size() - 1; - mEvents.remove(index); - - fireTableRowsDeleted(index, index); - } - } - - @Override - public int getRowCount() - { - return mEvents.size(); - } - - @Override - public int getColumnCount() - { - return mHeaders.length; - } - - public String getColumnName(int column) - { - return mHeaders[column]; - } - - @Override - public Object getValueAt(int rowIndex, int columnIndex) - { - synchronized(mEvents) - { - switch(columnIndex) - { - case TIME: - StringBuilder sb = new StringBuilder(); - - sb.append(mSDFTime.format( - mEvents.get(rowIndex).getEventStartTime())); - - if(mEvents.get(rowIndex).getEventEndTime() != 0) - { - sb.append(" - "); - sb.append(mSDFTime.format( - mEvents.get(rowIndex).getEventEndTime())); - } - else if(mEvents.get(rowIndex) - .getCallEventType() == CallEvent.CallEventType.CALL) - { - sb.append(" - In Progress"); - } - - return sb.toString(); - case EVENT: - return mEvents.get(rowIndex).getCallEventType(); - case FROM_ID: - return mEvents.get(rowIndex).getFromID(); - case FROM_ALIAS: - return mEvents.get(rowIndex).getFromIDAlias(); - case TO_ID: - return mEvents.get(rowIndex).getToID(); - case TO_ALIAS: - return mEvents.get(rowIndex).getToIDAlias(); - case CHANNEL: - return mEvents.get(rowIndex).getChannel(); - case FREQUENCY: - long frequency = mEvents.get(rowIndex).getFrequency(); - - if(frequency != 0) - { - return mFrequencyFormatter.format((double) frequency / 1E6d); - } - else - { - return null; - } - case DETAILS: - return mEvents.get(rowIndex).getDetails(); - } - } - - return null; - } - - @Override - public Class getColumnClass(int columnIndex) - { - if(columnIndex == FROM_ALIAS || columnIndex == TO_ALIAS) - { - return Alias.class; - } - - return super.getColumnClass(columnIndex); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/CallEventPanel.java b/src/main/java/io/github/dsheirer/module/decode/event/CallEventPanel.java deleted file mode 100644 index 0223e56b..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/event/CallEventPanel.java +++ /dev/null @@ -1,75 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.event; - -import io.github.dsheirer.icon.IconManager; -import io.github.dsheirer.module.ProcessingChain; -import io.github.dsheirer.sample.Listener; -import net.miginfocom.swing.MigLayout; - -import javax.swing.*; -import java.awt.*; - -public class CallEventPanel extends JPanel implements Listener -{ - private static final long serialVersionUID = 1L; - private JTable mTable; - private CallEventModel mEmptyCallEventModel = new CallEventModel(); - private JScrollPane mEmptyScroller; - private CallEventAliasCellRenderer mRenderer; - - /** - * View for call event table - * @param iconManager to display alias icons in table rows - */ - public CallEventPanel(IconManager iconManager) - { - setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]")); - - mTable = new JTable(mEmptyCallEventModel); - mTable.setAutoCreateRowSorter(true); - mTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); - - mRenderer = new CallEventAliasCellRenderer(iconManager); - - - mEmptyScroller = new JScrollPane(mTable); - - add(mEmptyScroller); - } - - @Override - public void receive(ProcessingChain processingChain) - { - EventQueue.invokeLater(new Runnable() - { - @Override - public void run() - { - mTable.setModel(processingChain != null ? processingChain.getCallEventModel() : mEmptyCallEventModel); - - if(processingChain != null) - { - mTable.getColumnModel().getColumn(CallEventModel.FROM_ALIAS).setCellRenderer(mRenderer); - mTable.getColumnModel().getColumn(CallEventModel.TO_ALIAS).setCellRenderer(mRenderer); - } - } - }); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEvent.java b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEvent.java new file mode 100644 index 00000000..cfd6e079 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEvent.java @@ -0,0 +1,297 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.event; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.protocol.Protocol; + +/** + * Decode event implementation. A decode event is a discrete event such as a call or text message + * or radio registration. + */ +public class DecodeEvent implements IDecodeEvent +{ + private long mTimeStart; + private long mDuration; + private String mEventDescription; + private IdentifierCollection mIdentifierCollection; + private IChannelDescriptor mChannelDescriptor; + private String mDetails; + private Protocol mProtocol; + + public DecodeEvent(long start) + { + mTimeStart = start; + } + + /** + * Creates a new decode event builder with the specified start timestamp. + * @param timeStart for the event + * @return builder + */ + public static DecodeEventBuilder builder(long timeStart) + { + return new DecodeEventBuilder(timeStart); + } + + /** + * Updates the duration value using the end - start timestamps. Note: this method can be + * invoked multiple times and the duration will be set to the value of the final invocation. + * @param timestamp for end of the event. + */ + public void end(long timestamp) + { + if(timestamp > mTimeStart) + { + mDuration = timestamp - mTimeStart; + } + } + + /** + * Updates the current in-progress call with the latest timestamp + */ + public void update(long timestamp) + { + end(timestamp); + } + + /** + * Event start in milliseconds + */ + @Override + public long getTimeStart() + { + return mTimeStart; + } + + /** + * Event duration in milliseconds + */ + @Override + public long getDuration() + { + return mDuration; + } + + /** + * Sets the event duration in milliseconds + */ + public void setDuration(long duration) + { + mDuration = duration; + } + + /** + * Event description + */ + @Override + public String getEventDescription() + { + return mEventDescription; + } + + /** + * Sets the event description text + */ + public void setEventDescription(String description) + { + mEventDescription = description; + } + + /** + * Identifier collection for this event. + */ + @Override + public IdentifierCollection getIdentifierCollection() + { + return mIdentifierCollection; + } + + /** + * Sets the identifier collection for this event. Note: identifier collection should contain + * a FrequencyConfigurationIdentifier and optionally an AliasListConfigurationIdentifier so that + * display elements can be properly configured to display the event. + */ + public void setIdentifierCollection(IdentifierCollection identifierCollection) + { + mIdentifierCollection = identifierCollection; + } + + /** + * Channel descriptor for the channel + */ + @Override + public IChannelDescriptor getChannelDescriptor() + { + return mChannelDescriptor; + } + + /** + * Sets the channel descriptor for the event + */ + public void setChannelDescriptor(IChannelDescriptor channelDescriptor) + { + mChannelDescriptor = channelDescriptor; + } + + /** + * Event details text + */ + @Override + public String getDetails() + { + return mDetails; + } + + /** + * Sets the event details text + */ + public void setDetails(String details) + { + mDetails = details; + } + + /** + * Protocol (ie decoder type) for the event + */ + @Override + public Protocol getProtocol() + { + return mProtocol; + } + + /** + * Sets the protocol for the event + */ + public void setProtocol(Protocol protocol) + { + mProtocol = protocol; + } + + /** + * Builder pattern for constructing decode events. + */ + public static class DecodeEventBuilder + { + protected long mTimeStart; + protected long mDuration; + protected String mEventDescription; + protected IdentifierCollection mIdentifierCollection; + protected IChannelDescriptor mChannelDescriptor; + protected String mDetails; + protected Protocol mProtocol = Protocol.UNKNOWN; + + /** + * Constructs a builder instance with the specified start time in milliseconds + */ + public DecodeEventBuilder(long timeStart) + { + mTimeStart = timeStart; + } + + /** + * Sets the duration value + * @param duration in milliseconds + */ + public DecodeEventBuilder duration(long duration) + { + mDuration = duration; + return this; + } + + /** + * Sets the duration value using the end - start timestamps + * @param timestamp for end of event + */ + public DecodeEventBuilder end(long timestamp) + { + mDuration = timestamp - mTimeStart; + return this; + } + + /** + * Sets the channel descriptor for this event + * @param channelDescriptor + */ + public DecodeEventBuilder channel(IChannelDescriptor channelDescriptor) + { + mChannelDescriptor = channelDescriptor; + return this; + } + + /** + * Sets the event description text + * @param description of the event + */ + public DecodeEventBuilder eventDescription(String description) + { + mEventDescription = description; + return this; + } + + /** + * Sets the identifier collection. + * @param identifierCollection containing optional identifiers like TO, FROM, frequency and + * alias list configuration name. + */ + public DecodeEventBuilder identifiers(IdentifierCollection identifierCollection) + { + mIdentifierCollection = identifierCollection; + return this; + } + + /** + * Sets the details for the event + * @param details + */ + public DecodeEventBuilder details(String details) + { + mDetails = details; + return this; + } + + /** + * Sets the protocol for this event + * @param protocol + */ + public DecodeEventBuilder protocol(Protocol protocol) + { + mProtocol = protocol; + return this; + } + + /** + * Builds the decode event + */ + public DecodeEvent build() + { + DecodeEvent decodeEvent = new DecodeEvent(mTimeStart); + decodeEvent.setChannelDescriptor(mChannelDescriptor); + decodeEvent.setDetails(mDetails); + decodeEvent.setDuration(mDuration); + decodeEvent.setEventDescription(mEventDescription); + decodeEvent.setIdentifierCollection(mIdentifierCollection); + decodeEvent.setProtocol(mProtocol); + return decodeEvent; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventModel.java b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventModel.java new file mode 100644 index 00000000..917e98be --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventModel.java @@ -0,0 +1,222 @@ +/******************************************************************************* + * 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 + ******************************************************************************/ +package io.github.dsheirer.module.decode.event; + +import com.google.common.eventbus.Subscribe; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.preference.PreferenceType; +import io.github.dsheirer.sample.Listener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.table.AbstractTableModel; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class DecodeEventModel extends AbstractTableModel implements Listener +{ + private static final long serialVersionUID = 1L; + private final static Logger mLog = LoggerFactory.getLogger(DecodeEventModel.class); + + public static final int COLUMN_TIME = 0; + public static final int COLUMN_DURATION = 1; + public static final int COLUMN_EVENT = 2; + public static final int COLUMN_FROM_ID = 3; + public static final int COLUMN_FROM_ALIAS = 4; + public static final int COLUMN_TO_ID = 5; + public static final int COLUMN_TO_ALIAS = 6; + public static final int COLUMN_CHANNEL = 7; + public static final int COLUMN_FREQUENCY = 8; + public static final int COLUMN_DETAILS = 9; + + protected int mMaxMessages = 500; + + protected List mEvents = new ArrayList<>(); + + protected String[] mHeaders = new String[]{"Time", "Duration", "Event", "From", "Alias", "To", "Alias", "Channel", "Frequency", "Details"}; + + public DecodeEventModel() + { + MyEventBus.getEventBus().register(this); + } + + /** + * Receives preference update notifications via the event bus + * @param preferenceType that was updated + */ + @Subscribe + public void preferenceUpdated(PreferenceType preferenceType) + { + if(preferenceType == PreferenceType.DECODE_EVENT) + { + for(int row = 0; row < mEvents.size(); row++) + { + fireTableCellUpdated(row, COLUMN_TIME); + } + } + else if(preferenceType == PreferenceType.IDENTIFIER) + { + for(int row = 0; row < mEvents.size(); row++) + { + fireTableCellUpdated(row, COLUMN_FROM_ID); + fireTableCellUpdated(row, COLUMN_TO_ID); + } + } + } + + + public void dispose() + { + MyEventBus.getEventBus().unregister(this); + Iterator it = mEvents.iterator(); + + while(it.hasNext()) + { + it.remove(); + } + } + + public void reset() + { + dispose(); + fireTableDataChanged(); + } + + public int getMaxMessageCount() + { + return mMaxMessages; + } + + public void setMaxMessageCount(int count) + { + mMaxMessages = count; + } + + /** + * Adds, updates or deletes the event from the model. Producers can send + * the same call event multiple times to indicate that information in the + * event is updated. Producers can also mark the event as invalid and the + * event will be removed from the model. + */ + public void receive(final IDecodeEvent event) + { + if(!mEvents.contains(event)) + { + mEvents.add(0, event); + fireTableRowsInserted(0, 0); + prune(); + } + else + { + int row = mEvents.indexOf(event); + fireTableRowsUpdated(row, row); + } + } + + private void prune() + { + while(mEvents.size() > mMaxMessages) + { + int index = mEvents.size() - 1; + mEvents.remove(index); + fireTableRowsDeleted(index, index); + } + } + + @Override + public int getRowCount() + { + return mEvents.size(); + } + + @Override + public int getColumnCount() + { + return mHeaders.length; + } + + public String getColumnName(int column) + { + return mHeaders[column]; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) + { + synchronized(mEvents) + { + IDecodeEvent event = mEvents.get(rowIndex); + + if(event != null) + { + switch(columnIndex) + { + case COLUMN_TIME: + return event.getTimeStart(); + case COLUMN_DURATION: + return event.getDuration(); + case COLUMN_EVENT: + return event.getEventDescription(); + case COLUMN_FROM_ID: + return event.getIdentifierCollection(); + case COLUMN_FROM_ALIAS: + return event.getIdentifierCollection(); + case COLUMN_TO_ID: + return event.getIdentifierCollection(); + case COLUMN_TO_ALIAS: + return event.getIdentifierCollection(); + case COLUMN_CHANNEL: + return event.getChannelDescriptor(); + case COLUMN_FREQUENCY: + return event.getChannelDescriptor(); + case COLUMN_DETAILS: + return event.getDetails(); + } + } + } + + return null; + } + + @Override + public Class getColumnClass(int columnIndex) + { + switch(columnIndex) + { + case COLUMN_DURATION: + case COLUMN_TIME: + return Long.class; + case COLUMN_DETAILS: + case COLUMN_EVENT: + return String.class; + case COLUMN_FREQUENCY: + case COLUMN_FROM_ALIAS: + case COLUMN_FROM_ID: + case COLUMN_TO_ALIAS: + case COLUMN_TO_ID: + return IdentifierCollection.class; + case COLUMN_CHANNEL: + return IChannelDescriptor.class; + } + + return super.getColumnClass(columnIndex); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventPanel.java b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventPanel.java new file mode 100644 index 00000000..057b7374 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventPanel.java @@ -0,0 +1,411 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.event; + +import com.google.common.eventbus.Subscribe; +import io.github.dsheirer.alias.Alias; +import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.alias.AliasModel; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.icon.IconManager; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.module.ProcessingChain; +import io.github.dsheirer.preference.PreferenceType; +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.sample.Listener; +import net.miginfocom.swing.MigLayout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.table.DefaultTableCellRenderer; +import java.awt.Color; +import java.awt.Component; +import java.awt.EventQueue; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +public class DecodeEventPanel extends JPanel implements Listener +{ + private final static Logger mLog = LoggerFactory.getLogger(DecodeEventPanel.class); + + private static final long serialVersionUID = 1L; + private JTable mTable; + private DecodeEventModel mEmptyDecodeEventModel; + private JScrollPane mEmptyScroller; + private IconManager mIconManager; + private AliasModel mAliasModel; + private UserPreferences mUserPreferences; + private TimestampCellRenderer mTimestampCellRenderer; + + /** + * View for call event table + * @param iconManager to display alias icons in table rows + */ + public DecodeEventPanel(IconManager iconManager, UserPreferences userPreferences, AliasModel aliasModel) + { + MyEventBus.getEventBus().register(this); + + setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]")); + mIconManager = iconManager; + mAliasModel = aliasModel; + mUserPreferences = userPreferences; + mTimestampCellRenderer = new TimestampCellRenderer(); + mEmptyDecodeEventModel = new DecodeEventModel(); + mTable = new JTable(mEmptyDecodeEventModel); + mTable.setAutoCreateRowSorter(true); + mTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); + updateCellRenderers(); + + mEmptyScroller = new JScrollPane(mTable); + add(mEmptyScroller); + } + + /** + * Receives preference update notifications via the event bus + * @param preferenceType that was updated + */ + @Subscribe + public void preferenceUpdated(PreferenceType preferenceType) + { + if(preferenceType == PreferenceType.DECODE_EVENT || preferenceType == PreferenceType.IDENTIFIER) + { + EventQueue.invokeLater(() -> mTimestampCellRenderer.updatePreferences()); + } + } + + + private void updateCellRenderers() + { + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TIME) + .setCellRenderer(mTimestampCellRenderer); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_DURATION) + .setCellRenderer(new DurationCellRenderer()); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FROM_ID) + .setCellRenderer(new IdentifierCellRenderer(Role.FROM)); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FROM_ALIAS) + .setCellRenderer(new AliasedIdentifierCellRenderer(Role.FROM)); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TO_ID) + .setCellRenderer(new IdentifierCellRenderer(Role.TO)); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TO_ALIAS) + .setCellRenderer(new AliasedIdentifierCellRenderer(Role.TO)); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_CHANNEL) + .setCellRenderer(new ChannelDescriptorCellRenderer()); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FREQUENCY) + .setCellRenderer(new FrequencyCellRenderer()); + } + + @Override + public void receive(ProcessingChain processingChain) + { + EventQueue.invokeLater(() -> { + mTable.setModel(processingChain != null ? processingChain.getDecodeEventModel() : mEmptyDecodeEventModel); + + if(processingChain != null) + { + updateCellRenderers(); + } + }); + } + + /** + * Custom cell renderer for displaying identifiers from an identifier collection + */ + public class IdentifierCellRenderer extends DefaultTableCellRenderer + { + protected Role mRole; + + /** + * Constructs an instance of the cell renderer. + * + * @param role of the identifier + */ + public IdentifierCellRenderer(Role role) + { + mRole = role; + setHorizontalAlignment(JLabel.CENTER); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + if(value instanceof IdentifierCollection) + { + List identifiers = ((IdentifierCollection)value).getIdentifiers(mRole); + +// if(identifiers.size() > 1) +// { +// StringBuilder sb = new StringBuilder(); +// sb.append("Multiple Identifiers:\n"); +// for(Identifier identifier: identifiers) +// { +// sb.append("ID:").append(identifier.debug()).append("\n"); +// } +// mLog.debug(sb.toString()); +// } + label.setText(format(identifiers)); + } + else + { + label.setText(null); + } + + return label; + } + + /** + * Formats a list of identifiers as a comma separated list of values + * @param identifiers to format + * @return formatted list or null + */ + protected String format(List identifiers) + { + if(identifiers == null || identifiers.isEmpty()) + { + return null; + } + + StringBuilder sb = new StringBuilder(); + + for(Identifier identifier: identifiers) + { + if(sb.length() > 0) + { + sb.append(","); + } + + sb.append(mUserPreferences.getTalkgroupFormatPreference().format(identifier)); + } + + return sb.toString(); + } + } + + /** + * Cell renderer for identifier aliases + */ + public class AliasedIdentifierCellRenderer extends DefaultTableCellRenderer + { + private Role mRole; + + /** + * Constructs an instance of the cell renderer. + * + * @param role of the identifier + */ + public AliasedIdentifierCellRenderer(Role role) + { + mRole = role; + setHorizontalAlignment(JLabel.CENTER); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + Color color = mTable.getForeground(); + ImageIcon icon = null; + String text = null; + + if(value instanceof IdentifierCollection) + { + IdentifierCollection identifierCollection = (IdentifierCollection)value; + List identifiers = identifierCollection.getIdentifiers(mRole); + + if(identifiers != null && !identifiers.isEmpty()) + { + AliasList aliasList = mAliasModel.getAliasList(identifierCollection); + + if(aliasList != null) + { + StringBuilder sb = new StringBuilder(); + + for(Identifier identifier: identifiers) + { + Alias alias = aliasList.getAlias(identifier); + + if(alias != null) + { + if(sb.length() > 0) + { + sb.append(","); + } + sb.append(alias.getName()); + + color = alias.getDisplayColor(); + icon = mIconManager.getIcon(alias.getIconName(), IconManager.DEFAULT_ICON_SIZE); + } + } + + text = sb.toString(); + } + } + } + + label.setText(text); + label.setForeground(color); + label.setIcon(icon); + + return label; + } + } + + public class TimestampCellRenderer extends DefaultTableCellRenderer + { + private SimpleDateFormat mTimestampFormatter; + + public TimestampCellRenderer() + { + setHorizontalAlignment(JLabel.CENTER); + updatePreferences(); + } + + public void updatePreferences() + { + mTimestampFormatter = mUserPreferences.getDecodeEventPreference().getTimestampFormat().getFormatter(); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + if(value instanceof Long) + { + label.setText(mTimestampFormatter.format(new Date((long)value))); + } + else + { + label.setText(null); + } + + return label; + } + } + + public class DurationCellRenderer extends DefaultTableCellRenderer + { + private DecimalFormat mDecimalFormat = new DecimalFormat("0.0"); + + public DurationCellRenderer() + { + setHorizontalAlignment(JLabel.CENTER); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + String formatted = null; + + if(value instanceof Long) + { + long duration = (long)value; + + if(duration > 0) + { + formatted = mDecimalFormat.format((double)duration / 1e3d); + } + } + + label.setText(formatted); + + return label; + } + } + + /** + * Frequency value cell renderer + */ + public class FrequencyCellRenderer extends DefaultTableCellRenderer + { + private DecimalFormat mFrequencyFormatter = new DecimalFormat("0.00000 MHz"); + + public FrequencyCellRenderer() + { + setHorizontalAlignment(JLabel.CENTER); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + String formatted = null; + + if(value instanceof IChannelDescriptor) + { + IChannelDescriptor channelDescriptor = (IChannelDescriptor)value; + + long frequency = channelDescriptor.getDownlinkFrequency(); + + if(frequency > 0) + { + formatted = mFrequencyFormatter.format(frequency / 1e6d); + } + } + + label.setText(formatted); + + return label; + } + } + + /** + * Channel descriptor value cell renderer + */ + public class ChannelDescriptorCellRenderer extends DefaultTableCellRenderer + { + public ChannelDescriptorCellRenderer() + { + setHorizontalAlignment(JLabel.CENTER); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + if(value instanceof IChannelDescriptor) + { + label.setText(value.toString()); + } + else + { + label.setText(null); + } + + return label; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java new file mode 100644 index 00000000..e2cfabc7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java @@ -0,0 +1,85 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.event; + +public enum DecodeEventType +{ + ANNOUNCEMENT("Announcement"), + AUTOMATIC_REGISTRATION_SERVICE("Motorola ARS"), + CALL("Call"), + CALL_ENCRYPTED("Encrypted Call"), + CALL_GROUP("Group Call"), + CALL_GROUP_ENCRYPTED("Encrypted Group Call"), + CALL_PATCH_GROUP("Patch Call"), + CALL_PATCH_GROUP_ENCRYPTED("Encrypted Patch Call"), + CALL_ALERT("Call Alert"), + CALL_DETECT("Call Detect"), + CALL_DO_NOT_MONITOR("Call-Do Not Monitor"), + CALL_END("Call End"), + CALL_INTERCONNECT("Telephone Call"), + CALL_INTERCONNECT_ENCRYPTED("Encrypted Telephone Call"), + CALL_UNIQUE_ID("UID Call"), + CALL_UNIT_TO_UNIT("Unit To Unit Call"), + CALL_UNIT_TO_UNIT_ENCRYPTED("Encrypted Unit To Unit Call"), + CALL_NO_TUNER("Call - No Tuner"), + CALL_TIMEOUT("Call Timeout"), + COMMAND("Command"), + DATA_CALL("Data Call"), + DATA_CALL_ENCRYPTED("Encrypted Data Call"), + DATA_PACKET("Data Packet"), + DEREGISTER("Deregister"), + EMERGENCY("EMERGENCY"), + FUNCTION("Function"), + GPS("GPS"), + ID_ANI("ANI"), + ID_UNIQUE("Unique ID"), + IP_PACKET("IP Packet"), + NOTIFICATION("Notification"), + PAGE("Page"), + QUERY("Query"), + REGISTER("Register"), + REGISTER_ESN("ESN"), + REQUEST("Request"), + RESPONSE("Response"), + RESPONSE_PACKET("Response Packet"), + SDM("Short Data Message"), + STATION_ID("Station ID"), + STATUS("Status"), + UDP_PACKET("UDP/IP Packet"), + UNKNOWN("Unknown"); + + private String mLabel; + + DecodeEventType(String label) + { + mLabel = label; + } + + public String getLabel() + { + return mLabel; + } + + public String toString() + { + return mLabel; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/ICallEventListener.java b/src/main/java/io/github/dsheirer/module/decode/event/ICallEventListener.java deleted file mode 100644 index 36d52790..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/event/ICallEventListener.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.github.dsheirer.module.decode.event; - -import io.github.dsheirer.sample.Listener; - -public interface ICallEventListener -{ - public Listener getCallEventListener(); -} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/ICallEventProvider.java b/src/main/java/io/github/dsheirer/module/decode/event/ICallEventProvider.java deleted file mode 100644 index bc8fc786..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/event/ICallEventProvider.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.github.dsheirer.module.decode.event; - -import io.github.dsheirer.sample.Listener; - -public interface ICallEventProvider -{ - public void addCallEventListener( Listener listener ); - public void removeCallEventListener( Listener listener ); -} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/IDecodeEvent.java b/src/main/java/io/github/dsheirer/module/decode/event/IDecodeEvent.java new file mode 100644 index 00000000..722c72da --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/IDecodeEvent.java @@ -0,0 +1,74 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.event; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.protocol.Protocol; + +/** + * Decode event interface + */ +public interface IDecodeEvent +{ + /** + * Event start time + * + * @return timestamp in milliseconds + */ + long getTimeStart(); + + /** + * Event duration. + * + * @return event duration in milliseconds or 0 if there is no duration + */ + long getDuration(); + + /** + * A description of the event or event type + */ + String getEventDescription(); + + /** + * Collection of identifiers associated with the event. This collection should contain a + * Role.FROM and a Role.TO identifier, a Decoder Type identifier, and (optionally) an Alias List + * Configuration identifier. + */ + IdentifierCollection getIdentifierCollection(); + + /** + * Channel descriptor for the channel where the event occurred + * + * @return descriptor or null + */ + IChannelDescriptor getChannelDescriptor(); + + /** + * Optional Details about the event + */ + String getDetails(); + + /** + * Protocol for the decoder that produced the event + */ + Protocol getProtocol(); +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/IDecodeEventListener.java b/src/main/java/io/github/dsheirer/module/decode/event/IDecodeEventListener.java new file mode 100644 index 00000000..9c53fca2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/IDecodeEventListener.java @@ -0,0 +1,28 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.event; + +import io.github.dsheirer.sample.Listener; + +public interface IDecodeEventListener +{ + Listener getDecodeEventListener(); +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/IDecodeEventProvider.java b/src/main/java/io/github/dsheirer/module/decode/event/IDecodeEventProvider.java new file mode 100644 index 00000000..8c10f5c7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/IDecodeEventProvider.java @@ -0,0 +1,9 @@ +package io.github.dsheirer.module.decode.event; + +import io.github.dsheirer.sample.Listener; + +public interface IDecodeEventProvider +{ + void addDecodeEventListener(Listener listener ); + void removeDecodeEventListener(Listener listener ); +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/MessageActivityModel.java b/src/main/java/io/github/dsheirer/module/decode/event/MessageActivityModel.java index d4ff0809..25a10e08 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/MessageActivityModel.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/MessageActivityModel.java @@ -1,193 +1,180 @@ /******************************************************************************* * SDR Trunk * Copyright (C) 2014-2016 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 io.github.dsheirer.module.decode.event; import io.github.dsheirer.filter.FilterSet; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.sample.Listener; import javax.swing.table.AbstractTableModel; -import java.awt.*; +import java.awt.EventQueue; import java.text.SimpleDateFormat; -import java.util.Date; import java.util.LinkedList; -public class MessageActivityModel extends AbstractTableModel - implements Listener +public class MessageActivityModel extends AbstractTableModel implements Listener { private static final long serialVersionUID = 1L; private static final int TIME = 0; private static final int PROTOCOL = 1; - private static final int ERROR_STATUS = 2; - private static final int MESSAGE = 3; - private static final int MESSAGE_BITS = 4; + private static final int MESSAGE = 2; - protected int mMaxMessages = 500; - protected LinkedList mMessages = new LinkedList(); + protected int mMaxMessages = 500; + protected LinkedList mMessageItems = new LinkedList<>(); + protected int[] mColumnWidths = {20, 20, 500}; + protected String[] mHeaders = new String[]{"Time", "Protocol", "Message"}; - protected int[] mColumnWidths = { 110, 110, 110, -1, -1 }; + private SimpleDateFormat mSDFTime = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss"); - protected String[] mHeaders = new String[] { "Time", - "Protocol", - "Error Check", - "Message", - "Binary" }; + private FilterSet mMessageFilter; - private SimpleDateFormat mSDFTime = new SimpleDateFormat( "HH:mm:ss" ); - - private boolean mNewMessagesFirst = true; - - private FilterSet mMessageFilter; - - public MessageActivityModel( FilterSet messageFilter ) - { - mMessageFilter = messageFilter; - } - - public MessageActivityModel() - { - } - - /** - * Clears all messages from history - */ - public void clear() - { - EventQueue.invokeLater( new Runnable() - { - @Override + public MessageActivityModel(FilterSet messageFilter) + { + mMessageFilter = messageFilter; + } + + public MessageActivityModel() + { + } + + /** + * Clears all messages from history + */ + public void clear() + { + EventQueue.invokeLater(new Runnable() + { + @Override public void run() { - int messageCount = mMessages.size(); - - mMessages.clear(); + int messageCount = mMessageItems.size(); - fireTableRowsDeleted( 0, messageCount - 1 ); + mMessageItems.clear(); + + fireTableRowsDeleted(0, messageCount - 1); } - }); - } - - public FilterSet getMessageFilter() - { - return mMessageFilter; - } - - public void dispose() - { - mMessages.clear(); - } - - public int[] getColumnWidths() - { - return mColumnWidths; - } - - public void setColumnWidths( int[] widths ) - { - if( widths.length != 5 ) - { - throw new IllegalArgumentException( "MessageActivityModel - " - + "column widths array should have 5 elements" ); - } - else - { - mColumnWidths = widths; - } - } - - public int getMaxMessageCount() - { - return mMaxMessages; - } + }); + } - public void setMaxMessageCount( int count ) - { - mMaxMessages = count; - } - - public void receive( final Message message ) - { - if( message.isValid() && mMessageFilter.passes( message ) ) - { - EventQueue.invokeLater( new Runnable() - { - @Override - public void run() - { - mMessages.addFirst( message ); + public FilterSet getMessageFilter() + { + return mMessageFilter; + } - MessageActivityModel.this.fireTableRowsInserted( 0, 0 ); + public void dispose() + { + mMessageItems.clear(); + } - prune(); - } - } ); - } - } - - private void prune() - { - while( mMessages.size() > mMaxMessages ) - { - mMessages.removeLast(); - - super.fireTableRowsDeleted( mMessages.size() - 1, mMessages.size() - 1 ); - } - } + public int[] getColumnWidths() + { + return mColumnWidths; + } - @Override + public void setColumnWidths(int[] widths) + { + if(widths.length != 3) + { + throw new IllegalArgumentException("MessageActivityModel - column widths array should have 3 elements"); + } + else + { + mColumnWidths = widths; + } + } + + public int getMaxMessageCount() + { + return mMaxMessages; + } + + public void setMaxMessageCount(int count) + { + mMaxMessages = count; + } + + public void receive(final IMessage message) + { + if(mMessageFilter.passes(message)) + { + final MessageItem messageItem = new MessageItem(message); + + EventQueue.invokeLater(new Runnable() + { + @Override + public void run() + { + mMessageItems.addFirst(messageItem); + + MessageActivityModel.this.fireTableRowsInserted(0, 0); + + prune(); + } + }); + } + } + + private void prune() + { + while(mMessageItems.size() > mMaxMessages) + { + MessageItem removed = mMessageItems.removeLast(); + removed.dispose(); + super.fireTableRowsDeleted(mMessageItems.size() - 1, mMessageItems.size() - 1); + } + } + + @Override public int getRowCount() { - return mMessages.size(); + return mMessageItems.size(); } - @Override + @Override public int getColumnCount() { - return mHeaders.length; + return mHeaders.length; } - public String getColumnName( int column ) - { - return mHeaders[ column ]; - } - - @Override - public Object getValueAt( int rowIndex, int columnIndex ) + public String getColumnName(int column) { - if( 0 <= rowIndex && rowIndex < mMessages.size() ) - { - Message message = mMessages.get( rowIndex ); - - switch( columnIndex ) - { - case TIME: - return mSDFTime.format( new Date( message.getTimeReceived() ) ); - case PROTOCOL: - return message.getProtocol(); - case ERROR_STATUS: - return message.getErrorStatus(); - case MESSAGE: - return message.getMessage(); - case MESSAGE_BITS: - return message.getBinaryMessage(); - } - } - - return null; + return mHeaders[column]; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) + { + if(0 <= rowIndex && rowIndex < mMessageItems.size()) + { + MessageItem messageItem = mMessageItems.get(rowIndex); + + switch(columnIndex) + { + case TIME: + return messageItem.getTimestamp(mSDFTime); + case PROTOCOL: + return messageItem.getProtocol(); + case MESSAGE: + return messageItem.getText(); + default: + break; + } + } + + return null; } } \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/event/MessageActivityPanel.java b/src/main/java/io/github/dsheirer/module/decode/event/MessageActivityPanel.java index fd65789a..42c89762 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/MessageActivityPanel.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/MessageActivityPanel.java @@ -23,7 +23,7 @@ import com.jidesoft.swing.JideSplitButton; import io.github.dsheirer.controller.channel.ChannelProcessingManager; import io.github.dsheirer.filter.FilterEditorPanel; import io.github.dsheirer.filter.FilterSet; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.ProcessingChain; import io.github.dsheirer.sample.Listener; import net.miginfocom.swing.MigLayout; @@ -81,6 +81,10 @@ public class MessageActivityPanel extends JPanel implements Listener filter = (FilterSet) ((MessageActivityModel)mTable.getModel()).getMessageFilter(); + FilterSet filter = (FilterSet) ((MessageActivityModel) mTable.getModel()) + .getMessageFilter(); - FilterEditorPanel panel = new FilterEditorPanel(filter); + FilterEditorPanel panel = new FilterEditorPanel<>(filter); JScrollPane scroller = new JScrollPane(panel); scroller.setViewportView(panel); diff --git a/src/main/java/io/github/dsheirer/module/decode/event/MessageItem.java b/src/main/java/io/github/dsheirer/module/decode/event/MessageItem.java new file mode 100644 index 00000000..df669c9b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/MessageItem.java @@ -0,0 +1,97 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.event; + +import io.github.dsheirer.message.IMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Simple message carrier class that persists activity model column values so that they do not need + * to be recalculated with each row update. + */ +public class MessageItem +{ + private final static Logger mLog = LoggerFactory.getLogger(MessageItem.class); + private IMessage mMessage; + private String mTimestamp; + private String mProtocol; + private String mText; + + public MessageItem(IMessage message) + { + mMessage = message; + } + + public IMessage getMessage() + { + return mMessage; + } + + public void dispose() + { + mMessage = null; + mTimestamp = null; + mProtocol = null; + mText = null; + } + + public String getTimestamp(SimpleDateFormat simpleDateFormat) + { + if(mTimestamp == null) + { + mTimestamp = simpleDateFormat.format(new Date(getMessage().getTimestamp())); + } + + return mTimestamp; + } + + public String getText() + { + if(mText == null) + { + try + { + mText = getMessage().toString(); + } + catch(Throwable t) + { + mLog.error("Error accessing message toString() method - protocol [" + getProtocol() + "]", t); + mText = "MESSAGE ITEM ENCOUNTERED PARSING ERROR"; + } + } + + return mText; + } + + public String getProtocol() + { + if(mProtocol == null) + { + mProtocol = getMessage().getProtocol().toString(); + } + + return mProtocol; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/PlottableDecodeEvent.java b/src/main/java/io/github/dsheirer/module/decode/event/PlottableDecodeEvent.java new file mode 100644 index 00000000..87fca123 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/PlottableDecodeEvent.java @@ -0,0 +1,239 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.event; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.protocol.Protocol; +import org.jdesktop.swingx.mapviewer.GeoPosition; + +public class PlottableDecodeEvent extends DecodeEvent +{ + private GeoPosition mGeoPosition; + private double mHeading; + private double mSpeed; + + public PlottableDecodeEvent(long start) + { + super(start); + } + + /** + * Creates a new decode event builder with the specified start timestamp. + * @param timeStart for the event + * @return builder + */ + public static PlottableDecodeEventBuilder plottableBuilder(long timeStart) + { + return new PlottableDecodeEventBuilder(timeStart); + } + + /** + * Sets the location for the event + */ + public void setLocation(GeoPosition geoPosition) + { + mGeoPosition = geoPosition; + } + + /** + * Location of the event + */ + public GeoPosition getLocation() + { + return mGeoPosition; + } + + /** + * Sets the heading for the mobile plottable event + * @param heading + */ + public void setHeading(double heading) + { + mHeading = heading; + } + + /** + * Heading of the mobile plottable event + */ + public double getHeading() + { + return mHeading; + } + + /** + * Sets the speed of the mobile plottable event in kph + */ + public void setSpeed(double speed) + { + mSpeed = speed; + } + + /** + * Speed of the mobile plottable event in kph + */ + public double getSpeed() + { + return mSpeed; + } + + /** + * Builder pattern for constructing decode events. + */ + public static class PlottableDecodeEventBuilder + { + private long mTimeStart; + private long mDuration; + private String mEventDescription; + private IdentifierCollection mIdentifierCollection; + private IChannelDescriptor mChannelDescriptor; + private String mDetails; + private Protocol mProtocol = Protocol.UNKNOWN; + private GeoPosition mGeoPosition; + private double mHeading; + private double mSpeed; + + /** + * Constructs a builder instance with the specified start time in milliseconds + */ + public PlottableDecodeEventBuilder(long timeStart) + { + mTimeStart = timeStart; + } + + /** + * Sets the duration value + * @param duration in milliseconds + */ + public PlottableDecodeEventBuilder duration(long duration) + { + mDuration = duration; + return this; + } + + /** + * Sets the duration value using the end - start timestamps + * @param timestamp for end of event + */ + public PlottableDecodeEventBuilder end(long timestamp) + { + mDuration = timestamp - mTimeStart; + return this; + } + + /** + * Sets the channel descriptor for this event + * @param channelDescriptor + */ + public PlottableDecodeEventBuilder channel(IChannelDescriptor channelDescriptor) + { + mChannelDescriptor = channelDescriptor; + return this; + } + + /** + * Sets the event description text + * @param description of the event + */ + public PlottableDecodeEventBuilder eventDescription(String description) + { + mEventDescription = description; + return this; + } + + /** + * Sets the identifier collection. + * @param identifierCollection containing optional identifiers like TO, FROM, frequency and + * alias list configuration name. + */ + public PlottableDecodeEventBuilder identifiers(IdentifierCollection identifierCollection) + { + mIdentifierCollection = identifierCollection; + return this; + } + + /** + * Sets the details for the event + * @param details + */ + public PlottableDecodeEventBuilder details(String details) + { + mDetails = details; + return this; + } + + /** + * Sets the protocol for this event + * @param protocol + */ + public PlottableDecodeEventBuilder protocol(Protocol protocol) + { + mProtocol = protocol; + return this; + } + + /** + * Sets the location for the plottable event + */ + public PlottableDecodeEventBuilder location(GeoPosition geoPosition) + { + mGeoPosition = geoPosition; + return this; + } + + /** + * Sets the speed for the plottable event in kph + */ + public PlottableDecodeEventBuilder speed(double speed) + { + mSpeed = speed; + return this; + } + + /** + * Sets the heading for the plottable event in degrees relative to true North + */ + public PlottableDecodeEventBuilder heading(double heading) + { + mHeading = heading; + return this; + } + + /** + * Builds the decode event + */ + public PlottableDecodeEvent build() + { + PlottableDecodeEvent decodeEvent = new PlottableDecodeEvent(mTimeStart); + decodeEvent.setChannelDescriptor(mChannelDescriptor); + decodeEvent.setDetails(mDetails); + decodeEvent.setDuration(mDuration); + decodeEvent.setEventDescription(mEventDescription); + decodeEvent.setIdentifierCollection(mIdentifierCollection); + decodeEvent.setProtocol(mProtocol); + decodeEvent.setLocation(mGeoPosition); + decodeEvent.setHeading(mHeading); + decodeEvent.setSpeed(mSpeed); + return decodeEvent; + } + } + +} diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync1/Fleetsync1Message.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync1/Fleetsync1Message.java deleted file mode 100644 index 14bccd61..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/fleetsync1/Fleetsync1Message.java +++ /dev/null @@ -1,201 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.fleetsync1; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.map.Plottable; -import io.github.dsheirer.message.Message; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -public class Fleetsync1Message extends Message -{ - private static SimpleDateFormat mSDF = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); - - private BinaryMessage mMessage; - - public Fleetsync1Message( BinaryMessage message ) - { - mMessage = message; - } - - public boolean isValid() - { - return true; - } - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(); - - sb.append( mSDF.format( new Date( System.currentTimeMillis() ) ) ); - sb.append( " " ); - sb.append( "FS1 [" + mMessage.toString() + "]" ); - - return sb.toString(); - } - - /** - * Pads spaces onto the end of the value to make it 'places' long - */ - public String pad( String value, int places, String padCharacter ) - { - StringBuilder sb = new StringBuilder(); - - sb.append( value ); - - while( sb.length() < places ) - { - sb.append( padCharacter ); - } - - return sb.toString(); - } - - /** - * Pads an integer value with additional zeroes to make it decimalPlaces long - */ - public String format( int number, int decimalPlaces ) - { - StringBuilder sb = new StringBuilder(); - - int paddingRequired = decimalPlaces - ( String.valueOf( number ).length() ); - - for( int x = 0; x < paddingRequired; x++) - { - sb.append( "0" ); - } - - sb.append( number ); - - return sb.toString(); - } - - @SuppressWarnings( "unused" ) - private int getInt( int[] bits ) - { - int retVal = 0; - - for( int x = 0; x < bits.length; x++ ) - { - if( mMessage.get( bits[ x ] ) ) - { - retVal += 1< getAliases() - { - List aliases = new ArrayList(); - - Alias from = getFromIDAlias(); - - if( from != null ) - { - aliases.add( from ); - } - - Alias to = getToIDAlias(); - - if( to != null ) - { - aliases.add( to ); - } - - return aliases; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync1/Fleetsync1MessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync1/Fleetsync1MessageProcessor.java deleted file mode 100644 index 5d85f070..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/fleetsync1/Fleetsync1MessageProcessor.java +++ /dev/null @@ -1,51 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.fleetsync1; - -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.message.Message; -import io.github.dsheirer.sample.Broadcaster; -import io.github.dsheirer.sample.Listener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class Fleetsync1MessageProcessor implements Listener -{ - private final static Logger mLog = - LoggerFactory.getLogger( Fleetsync1MessageProcessor.class ); - - private Broadcaster mBroadcaster = new Broadcaster(); - - @Override - public void receive( BinaryMessage buffer ) - { - Fleetsync1Message message = new Fleetsync1Message( buffer ); - - mBroadcaster.receive( message ); - } - - public void addMessageListener( Listener listener ) - { - mBroadcaster.addListener( listener ); - } - - public void removeMessageListener( Listener listener ) - { - mBroadcaster.removeListener( listener ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2Decoder.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2Decoder.java index 5d5676d7..6554739f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2Decoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2Decoder.java @@ -1,23 +1,24 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,2015 Dennis Sheirer +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 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. + * 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 - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.module.decode.fleetsync2; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.bits.MessageFramer; import io.github.dsheirer.bits.SyncPattern; import io.github.dsheirer.dsp.afsk.AFSK1200Decoder; @@ -36,34 +37,32 @@ public class Fleetsync2Decoder extends AbstractAFSKDecoder /** * Constructs a decoder for Fleetsync II protocol + * * @param afsk1200Decoder to use for decoding the symbols - * @param aliasList to assign aliases to decoded values */ - protected Fleetsync2Decoder(AFSK1200Decoder afsk1200Decoder, AliasList aliasList) + protected Fleetsync2Decoder(AFSK1200Decoder afsk1200Decoder) { super(afsk1200Decoder); - init(aliasList); + init(); } /** * Constructs a decoder for Fleetsync II protocol - * @param aliasList to assign aliases to decoded values */ - public Fleetsync2Decoder(AliasList aliasList) + public Fleetsync2Decoder() { super(AFSK1200Decoder.Output.NORMAL); - init(aliasList); + init(); } /** * Initializes the decoding chain. - * @param aliasList to assign to the message processor */ - private void init(AliasList aliasList) + private void init() { mMessageFramer = new MessageFramer(SyncPattern.FLEETSYNC2.getPattern(), MESSAGE_LENGTH); getDecoder().setSymbolProcessor(mMessageFramer); - mMessageProcessor = new Fleetsync2MessageProcessor(aliasList); + mMessageProcessor = new Fleetsync2MessageProcessor(); mMessageFramer.addMessageListener(mMessageProcessor); mMessageProcessor.setMessageListener(getMessageListener()); } diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderInstrumented.java index 894d22c7..f05dea5c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderInstrumented.java @@ -29,7 +29,7 @@ public class Fleetsync2DecoderInstrumented extends Fleetsync2Decoder implements public Fleetsync2DecoderInstrumented() { - super(new AFSK1200DecoderInstrumented(false), null); + super(new AFSK1200DecoderInstrumented(false)); } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderState.java index e65848d3..a822230b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderState.java @@ -18,42 +18,30 @@ ******************************************************************************/ package io.github.dsheirer.module.decode.fleetsync2; -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.alias.id.AliasIDType; -import io.github.dsheirer.channel.metadata.AliasedStringAttributeMonitor; -import io.github.dsheirer.channel.metadata.Attribute; -import io.github.dsheirer.channel.metadata.AttributeChangeRequest; import io.github.dsheirer.channel.state.DecoderState; import io.github.dsheirer.channel.state.DecoderStateEvent; -import io.github.dsheirer.channel.state.DecoderStateEvent.Event; import io.github.dsheirer.channel.state.State; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.util.StringUtils; +import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; +import io.github.dsheirer.module.decode.event.PlottableDecodeEvent; +import io.github.dsheirer.module.decode.fleetsync2.identifier.FleetsyncIdentifier; +import io.github.dsheirer.module.decode.fleetsync2.message.Fleetsync2Message; +import io.github.dsheirer.protocol.Protocol; import java.util.Iterator; +import java.util.Set; import java.util.TreeSet; public class Fleetsync2DecoderState extends DecoderState { - private TreeSet mIdents = new TreeSet(); - private TreeSet mEmergencyIdents = new TreeSet(); + private Set mIdents = new TreeSet<>(); - private AliasedStringAttributeMonitor mFromAttribute; - private AliasedStringAttributeMonitor mToAttribute; - private String mMessage; - private String mMessageType; - private long mFrequency; - - public Fleetsync2DecoderState(AliasList aliasList) + public Fleetsync2DecoderState() { - super(aliasList); - - mFromAttribute = new AliasedStringAttributeMonitor(Attribute.SECONDARY_ADDRESS_FROM, - getAttributeChangeRequestListener(), getAliasList(), AliasIDType.FLEETSYNC); - mToAttribute = new AliasedStringAttributeMonitor(Attribute.SECONDARY_ADDRESS_TO, - getAttributeChangeRequestListener(), getAliasList(), AliasIDType.FLEETSYNC); + super(); } @Override @@ -62,12 +50,6 @@ public class Fleetsync2DecoderState extends DecoderState return DecoderType.FLEETSYNC2; } - @Override - public void dispose() - { - super.dispose(); - } - /** * Resets the overall decoder state and clears any accumulated event details */ @@ -75,21 +57,16 @@ public class Fleetsync2DecoderState extends DecoderState public void reset() { mIdents.clear(); - mEmergencyIdents.clear(); - - resetState(); } @Override public void start() { - } @Override public void stop() { - } @Override @@ -98,110 +75,60 @@ public class Fleetsync2DecoderState extends DecoderState } - /** - * Resets this decoder state - */ - public void resetState() - { - mFromAttribute.reset(); - mToAttribute.reset(); - mMessage = null; - mMessageType = null; - } - @Override - public void receive(Message message) + public void receive(IMessage message) { - if(message instanceof FleetsyncMessage) + if(message.isValid() && message instanceof Fleetsync2Message) { - FleetsyncMessage fleetsync = (FleetsyncMessage) message; + Fleetsync2Message fleetsync = (Fleetsync2Message)message; - if(fleetsync.isValid()) + getIdentifierCollection().update(fleetsync.getIdentifiers()); + + mIdents.add(fleetsync.getFromIdentifier()); + mIdents.add(fleetsync.getToIdentifier()); + + switch(fleetsync.getMessageType()) { - State state = State.CALL; + case ANI: + case EMERGENCY: + case LONE_WORKER_EMERGENCY: + DecodeEvent aniEvent = DecodeEvent.builder(fleetsync.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(fleetsync.getMessageType().toString()) + .details(fleetsync.toString()) + .identifiers(getIdentifierCollection().copyOf()) + .build(); - mFromAttribute.process(fleetsync.getFromID()); - mIdents.add(fleetsync.getFromID()); - - FleetsyncMessageType type = fleetsync.getMessageType(); - - if(type != FleetsyncMessageType.ANI) - { - mToAttribute.process(fleetsync.getToID()); - mIdents.add(fleetsync.getToID()); - } - - setMessageType(type.getLabel()); - - switch(type) - { - case GPS: - setMessage(fleetsync.getGPSLocation()); - state = State.DATA; - break; - case STATUS: - StringBuilder sb = new StringBuilder(); - sb.append(fleetsync.getStatus()); - - Alias status = fleetsync.getStatusAlias(); - - if(status != null) - { - sb.append("/"); - sb.append(status.getName()); - } - - setMessage(sb.toString()); - state = State.DATA; - break; - case EMERGENCY: - case LONE_WORKER_EMERGENCY: - mEmergencyIdents.add(fleetsync.getFromID()); - state = State.DATA; - break; - default: - break; - } - - FleetsyncCallEvent fsCallEvent = - FleetsyncCallEvent.getFleetsync2Event(fleetsync, mFrequency); - - fsCallEvent.setAliasList(getAliasList()); - - broadcast(fsCallEvent); - - /* Broadcast decode event so that the channel state will - * kick in and reset everything after a short delay */ - broadcast(new DecoderStateEvent(this, Event.DECODE, state)); + broadcast(aniEvent); + broadcast(new DecoderStateEvent(this, DecoderStateEvent.Event.DECODE, State.CALL)); + break; + case ACKNOWLEDGE: + case PAGING: + case STATUS: + case UNKNOWN: + DecodeEvent statusEvent = DecodeEvent.builder(fleetsync.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(fleetsync.getMessageType().toString()) + .details(fleetsync.toString()) + .identifiers(getIdentifierCollection().copyOf()) + .build(); + broadcast(statusEvent); + broadcast(new DecoderStateEvent(this, DecoderStateEvent.Event.DECODE, State.DATA)); + break; + case GPS: + PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(fleetsync.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.GPS.toString()) + .details(fleetsync.toString()) + .identifiers(getIdentifierCollection().copyOf()) + .protocol(Protocol.FLEETSYNC) + .build(); + broadcast(plottableDecodeEvent); + broadcast(new DecoderStateEvent(this, DecoderStateEvent.Event.DECODE, State.DATA)); + break; } - } - } - public String getMessage() - { - return mMessage; - } - - public void setMessage(String message) - { - if(!StringUtils.isEqual(mMessage, message)) - { - mMessage = message; - broadcast(new AttributeChangeRequest(Attribute.MESSAGE, mMessage)); - } - } - - public String getMessageType() - { - return "Fleetsync " + mMessageType; - } - - public void setMessageType(String type) - { - if(!StringUtils.isEqual(mMessageType, type)) - { - mMessageType = type; - broadcast(new AttributeChangeRequest(Attribute.MESSAGE_TYPE, getMessageType())); + getIdentifierCollection().remove(IdentifierClass.USER); } } @@ -214,10 +141,7 @@ public class Fleetsync2DecoderState extends DecoderState switch(event.getEvent()) { case RESET: - resetState(); - break; - case SOURCE_FREQUENCY: - mFrequency = event.getFrequency(); + reset(); break; default: break; @@ -239,80 +163,14 @@ public class Fleetsync2DecoderState extends DecoderState } else { - Iterator it = mIdents.iterator(); + Iterator it = mIdents.iterator(); while(it.hasNext()) { - String ident = it.next(); - - sb.append(" "); - sb.append(formatIdent(ident)); - - if(hasAliasList()) - { - Alias alias = getAliasList().getFleetsyncAlias(ident); - - if(alias != null) - { - sb.append(" "); - sb.append(alias.getName()); - } - } - - sb.append("\n"); - } - } - - sb.append("\nFleetsync Emergency Activations\n"); - - if(mEmergencyIdents.isEmpty()) - { - sb.append(" None\n"); - } - else - { - Iterator it = mEmergencyIdents.iterator(); - - while(it.hasNext()) - { - String ident = it.next(); - - sb.append(" "); - sb.append(formatIdent(ident)); - - if(hasAliasList()) - { - Alias alias = getAliasList().getFleetsyncAlias(ident); - - if(alias != null) - { - sb.append(" "); - sb.append(alias.getName()); - } - - sb.append("\n"); - } + sb.append(" ").append(it.next().formatted()).append("\n"); } } return sb.toString(); } - - public static String formatIdent(String ident) - { - StringBuilder sb = new StringBuilder(); - - if(ident.length() == 7) - { - sb.append(ident.substring(0, 3)); - sb.append("-"); - sb.append(ident.substring(3, 7)); - - return sb.toString(); - } - else - { - return ident; - } - } } diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2MessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2MessageProcessor.java index c958b785..3cad7928 100644 --- a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2MessageProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2MessageProcessor.java @@ -1,62 +1,97 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.fleetsync2; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.module.decode.fleetsync2.message.AutomaticNumberIdentification; +import io.github.dsheirer.module.decode.fleetsync2.message.Fleetsync2Message; +import io.github.dsheirer.module.decode.fleetsync2.message.LocationReport; +import io.github.dsheirer.module.decode.fleetsync2.message.Status; import io.github.dsheirer.sample.Listener; -public class Fleetsync2MessageProcessor implements Listener +/** + * Fleetsync message processor converts binary messages into IMessage implementations. + */ +public class Fleetsync2MessageProcessor implements Listener { - private Listener mMessageListener; - - private AliasList mAliasList; - - public Fleetsync2MessageProcessor( AliasList list ) - { - mAliasList = list; - } - - public void dispose() - { - mMessageListener = null; - mAliasList = null; - } - - @Override - public void receive( BinaryMessage buffer ) - { - FleetsyncMessage message = new FleetsyncMessage( buffer, mAliasList ); + private Listener mMessageListener; - if( mMessageListener != null ) - { - mMessageListener.receive( message ); - } - } - - public void setMessageListener( Listener listener ) + /** + * Constructs a message processor + */ + public Fleetsync2MessageProcessor() { - mMessageListener = listener; } + public void dispose() + { + mMessageListener = null; + } + + /** + * Primary receive interface for demodulated binary messages + */ + @Override + public void receive(CorrectedBinaryMessage message) + { + FleetsyncMessageType messageType = Fleetsync2Message.getMessageType(message); + + switch(messageType) + { + case GPS: + broadcast(new LocationReport(message, System.currentTimeMillis())); + break; + case STATUS: + broadcast(new Status(message, System.currentTimeMillis())); + break; + default: + broadcast(new AutomaticNumberIdentification(message, System.currentTimeMillis())); + break; + } + } + + /** + * Broadcasts the message to an optional registered listener + */ + private void broadcast(IMessage message) + { + if(mMessageListener != null) + { + mMessageListener.receive(message); + } + } + + /** + * Registers the listener to receive fleetsync messages + */ + public void setMessageListener(Listener listener) + { + mMessageListener = listener; + } + + /** + * Unregisters the current listener from receiving fleetsync messages + */ public void removeMessageListener() { - mMessageListener = null; + mMessageListener = null; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/FleetsyncCallEvent.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/FleetsyncCallEvent.java deleted file mode 100644 index 70fb96e7..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/FleetsyncCallEvent.java +++ /dev/null @@ -1,206 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.fleetsync2; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.module.decode.event.CallEvent; - -public class FleetsyncCallEvent extends CallEvent -{ - private long mFrequency = 0; - - public FleetsyncCallEvent( CallEventType callEventType, - AliasList aliasList, - String fromID, - String toID, - String details, - long frequency ) - { - super( DecoderType.FLEETSYNC2, callEventType, - aliasList, fromID, toID, details ); - - mFrequency = frequency; - } - - private Alias getAlias( String ident ) - { - if( hasAliasList() ) - { - return getAliasList().getFleetsyncAlias( ident ); - } - - return null; - } - - @Override - public Alias getFromIDAlias() - { - return getAlias( getFromID() ); - } - - @Override - public Alias getToIDAlias() - { - return getAlias( getToID() ); - } - - @Override - public String getChannel() - { - return null; - } - - @Override - public long getFrequency() - { - return mFrequency; - } - - public static class Builder - { - /* Required parameters */ - private CallEventType mCallEventType; - - /* Optional parameters */ - private AliasList mAliasList; - private String mFromID; - private String mToID; - private String mDetails; - private long mFrequency; - - public Builder( CallEventType callEventType ) - { - mCallEventType = callEventType; - } - - public Builder aliasList( AliasList aliasList ) - { - mAliasList = aliasList; - return this; - } - - public Builder frequency( long frequency ) - { - mFrequency = frequency; - return this; - } - - public Builder from( String val ) - { - mFromID = val; - return this; - } - - public Builder details( String details ) - { - mDetails = details; - return this; - } - - public Builder to( String toID ) - { - mToID = toID; - return this; - } - - public FleetsyncCallEvent build() - { - return new FleetsyncCallEvent( this ); - } - } - - /** - * Private constructor for the builder - */ - private FleetsyncCallEvent( Builder builder ) - { - this( builder.mCallEventType, - builder.mAliasList, - builder.mFromID, - builder.mToID, - builder.mDetails, - builder.mFrequency ); - } - - public static FleetsyncCallEvent getFleetsync2Event( - FleetsyncMessage message, long frequency ) - { - CallEventType type = CallEventType.UNKNOWN; - StringBuilder sbDetails = new StringBuilder(); - sbDetails.append( "Fleetsync " ); - - switch( message.getMessageType() ) - { - case ACKNOWLEDGE: - type = CallEventType.RESPONSE; - sbDetails.append( "ACK" ); - break; - case ANI: - type = CallEventType.ID_ANI; - break; - case EMERGENCY: - type = CallEventType.EMERGENCY; - break; - case GPS: - type = CallEventType.GPS; - sbDetails.append( message.getGPSLocation() ); - break; - case LONE_WORKER_EMERGENCY: - type = CallEventType.EMERGENCY; - sbDetails.append( "LONE WORKER EMERGENCY ALERT" ); - break; - case PAGING: - type = CallEventType.PAGE; - break; - case STATUS: - type = CallEventType.STATUS; - sbDetails.append( "Status: " ); - sbDetails.append( message.getStatus() ); - - if( message.getStatusAlias() != null ) - { - sbDetails.append( "/" ); - sbDetails.append( message.getStatusAlias().getName() ); - } - break; - default: - break; - } - - if( type == CallEventType.ID_ANI || - type == CallEventType.EMERGENCY ) - { - return new FleetsyncCallEvent.Builder( type ) - .details( sbDetails.toString() ) - .frequency( frequency ) - .from( message.getFromID() ) - .build(); - } - else - { - return new FleetsyncCallEvent.Builder( type ) - .details( sbDetails.toString() ) - .frequency( frequency ) - .from( message.getFromID() ) - .to( message.getToID() ) - .build(); - } - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/FleetsyncMessage.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/FleetsyncMessage.java deleted file mode 100644 index 206aa3fd..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/FleetsyncMessage.java +++ /dev/null @@ -1,894 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.fleetsync2; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRC; -import io.github.dsheirer.edac.CRCFleetsync; -import io.github.dsheirer.map.Plottable; -import io.github.dsheirer.message.Message; -import org.jdesktop.swingx.mapviewer.GeoPosition; - -import java.text.DecimalFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.List; - -public class FleetsyncMessage extends Message -{ - private static DecimalFormat mDecimalFormatter = new DecimalFormat("#.#####"); - private static SimpleDateFormat mSDF = new SimpleDateFormat( "yyyyMMdd HHmmss" ); - - //Calendar to use in calculating time hacks - Calendar mCalendar = new GregorianCalendar(); - - //Message parts are identified in big-endian order for correct translation - - //Message Header - private static int[] sRevs = { 4,3,2,1,0 }; - private static int[] sSync = { 20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5 }; - - //Message Block 1 - private static int[] sStatusMessage = { 27,26,25,24,23,22,21 }; - private static int[] sMessageType = { 33,32,31,30,29 }; - - private static int sEmergencyFlag = 22; - private static int sLoneWorkerFlag = 24; - private static int sPagingFlag = 26; - private static int sEndOfTransmissionFlag = 27; - private static int sManualFlag = 28; - private static int sANIFlag = 29; - private static int sStatusFlag = 30; - private static int sAcknowledgeFlag = 31; - //32 - unknown - always 0 - //33 - unknown - always 0 - //34 - unknown - set for ACKNOWLEDGE - private static int sGPSExtensionFlag = 35; - private static int sFleetExtensionFlag = 36; - private static int[] sFleetFrom = { 44,43,42,41,40,39,38,37 }; - private static int[] sIdentFrom = { 56,55,54,53,52,51,50,49,48,47,46,45 }; - private static int[] sIdentTo = { 68,67,66,65,64,63,62,61,60,59,58,57 }; - private static int[] sCRC1 = { 84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69 }; - - //Message Block 2 - private static int[] sFleetTo = { 92,91,90,89,88,87,86,85 }; - private static int[] sCRC2 = { 148,147,146,145,144,143,142,141,140,139,138,137,136,135,134,132 }; - - //Message Block 3 - private static int[] sGPSHours = { 176,175,174,173,172 }; - private static int[] sGPSMinutes = { 182,181,180,179,178,177 }; - private static int[] sGPSSeconds = { 188,187,186,185,184,183 }; - private static int[] sCRC3 = { 212,211,210,209,208,207,206,205,204,203,202,201,200,199,198,197 }; - - //Message Block 4 - private static int[] sGPSChecksum = { 220,219,218,217,216,215,214,213 }; - private static int[] sLatitudeDegreesMinutes = { 236,235,234,233,232,231,230,229,228,227,226,225,224,223,222,221 }; - private static int[] sLatitudeDecimalMinutes = { 251,250,249,248,247,246,245,244,243,242,241,240,239,238 }; - private static int[] sSpeed = { 276,275,274,273,272,271,270,269,268,267,266,265,264,263,262,261,260,259,258,257,256,255,254,253,252 }; - private static int[] sCRC4 = { 276,275,274,273,272,271,270,269,268,267,266,265,264,263,262,261 }; - - //Message Block 5 - private static int[] sGPSCentury = { 284,283,282,281,280,279,278,277 }; - private static int[] sGPSYear = { 291,290,289,288,287,286,285 }; - private static int[] sGPSMonth = { 295,294,293,292 }; - private static int[] sGPSDay = { 300,299,298,297,296 }; - private static int[] sLongitudeDegreesMinutes = { 316,315,314,313,312,311,310,309,308,307,306,305,304,303,302,301 }; - private static int[] sLongitudeDecimalMinutes = { 331,330,329,328,327,326,325,324,323,322,321,320,319,318 }; - private static int[] sCRC5 = { 340,339,338,337,336,335,334,333,332,331,330,329,328,327,326,325 }; - - //Message Block 6 - private static int[] sGPSUnknown1 = { 352,351,350,349 }; - private static int[] sGPSHeading = { 365,363,362,361,360,359,358,357,356,355,354,353 }; - private static int[] sCRC6 = { 404,403,402,401,400,399,398,397,396,395,394,393,392,391,390,389 }; - - //Message Block 7 - private static int[] sBLK7WhatIsIt = { 444,443,442,441,440,439,438,437,436,435,434,433,432,431,430,429 }; - private static int[] sCRC7 = { 468,467,466,465,464,463,462,461,460,459,458,457,456,455,454,453 }; - - //Message Block 8 - private static int[] sGPSSpeed = { 491,490,489,488,487,486,485,484 }; - private static int[] sGPSSpeedFractional = { 499,498,497,496,495,494,493,492 }; - private static int[] sCRC8 = { 532,531,530,529,528,527,526,525,524,523,522,521,520,519,518,517 }; - - private BinaryMessage mMessage; - private CRC[] mCRC = new CRC[ 8 ]; - private AliasList mAliasList; - - public FleetsyncMessage( BinaryMessage message, AliasList list ) - { - mMessage = message; - mAliasList = list; - checkParity(); - } - - private void checkParity() - { - //Check message block 1 - mCRC[ 0 ] = detectAndCorrect( 21, 85 ); - - //Only check subsequent blocks if we know block 1 is correct - if( mCRC[ 0 ] == CRC.PASSED || mCRC[ 0 ] == CRC.CORRECTED ) - { - if( hasFleetExtensionFlag() ) - { - //Check message block 2 - mCRC[ 1 ] = detectAndCorrect( 85, 149 ); - } - - if( hasGPSFlag() ) - { - //Check message block 3 - mCRC[ 2 ] = detectAndCorrect( 149, 213 ); - //Check message block 4 - mCRC[ 3 ] = detectAndCorrect( 213, 277 ); - //Check message block 5 - mCRC[ 4 ] = detectAndCorrect( 277, 341 ); - //Check message block 6 - mCRC[ 5 ] = detectAndCorrect( 341, 405 ); - //Check message block 7 - mCRC[ 6 ] = detectAndCorrect( 405, 469 ); - //Check message block 8 - mCRC[ 7 ] = detectAndCorrect( 469, 533 ); - } - } - } - - private CRC detectAndCorrect( int start, int end ) - { - BitSet original = mMessage.get( start, end ); - - CRC retVal = CRCFleetsync.check( original ); - - //Attempt to correct single-bit errors - if( retVal == CRC.FAILED_PARITY ) - { - int[] errorBitPositions = CRCFleetsync.findBitErrors( original ); - - if( errorBitPositions != null ) - { - for( int errorBitPosition: errorBitPositions ) - { - mMessage.flip( start + errorBitPosition ); - } - - retVal = CRC.CORRECTED; - } - } - - return retVal; - } - - public boolean isValid() - { - boolean valid = true; - - for( int x = 0; x < mCRC.length; x++ ) - { - CRC crc = mCRC[ x ]; - - if( crc != null ) - { - if( crc == CRC.FAILED_CRC || - crc == CRC.FAILED_PARITY || - crc == CRC.UNKNOWN ) - { - valid = false; - } - } - } - - return valid; - } - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(); - - sb.append( mSDF.format( new Date( System.currentTimeMillis() ) ) ); - - sb.append( " FSync2 " + getParity() ); - - sb.append( getMessage() ); - - sb.append( " [" + mMessage.toString() + "]" ); - - return sb.toString(); - } - - /** - * Pads spaces onto the end of the value to make it 'places' long - */ - public String pad( String value, int places, String padCharacter ) - { - StringBuilder sb = new StringBuilder(); - - sb.append( value ); - - while( sb.length() < places ) - { - sb.append( padCharacter ); - } - - return sb.toString(); - } - - /** - * Pads an integer value with additional zeroes to make it decimalPlaces long - */ - public String format( int number, int decimalPlaces ) - { - StringBuilder sb = new StringBuilder(); - - int paddingRequired = decimalPlaces - ( String.valueOf( number ).length() ); - - for( int x = 0; x < paddingRequired; x++) - { - sb.append( "0" ); - } - - sb.append( number ); - - return sb.toString(); - } - - public FleetsyncMessageType getMessageType() - { - if( hasStatusFlag() ) - { - if( hasAcknowledgeFlag() ) - { - return FleetsyncMessageType.ACKNOWLEDGE; - } - - if( hasGPSFlag() ) - { - return FleetsyncMessageType.GPS; - } - else - { - return FleetsyncMessageType.STATUS; - } - } - else - { - if( hasAcknowledgeFlag() ) - { - return FleetsyncMessageType.ACKNOWLEDGE; - } - - if( hasANIFlag() ) - { - return FleetsyncMessageType.ANI; - } - - if( hasGPSFlag() ) - { - return FleetsyncMessageType.GPS; - } - - if( hasPagingFlag() ) - { - return FleetsyncMessageType.PAGING; - } - - if( hasEmergencyFlag() ) - { - if( hasLoneWorkerFlag() ) - { - return FleetsyncMessageType.LONE_WORKER_EMERGENCY; - } - else - return FleetsyncMessageType.EMERGENCY; - } - } - - return FleetsyncMessageType.UNKNOWN; - } - - public int getStatus() - { - return getStatusNumber() + 9; - } - - /** - * Returns the RAW status number. The actual status number should be - * accessed via the getStatus() method. - * @return - */ - public int getStatusNumber() - { - return getInt( sStatusMessage ); - } - - /** - * @return - */ - public Alias getStatusAlias() - { - if( mAliasList != null ) - { - return mAliasList.getStatus( getStatus() ); - } - - return null; - } - - public int getFleetFrom() - { - return getInt( sFleetFrom ) + 99; - } - - public int getIdentifierFrom() - { - return getInt( sIdentFrom ) + 999; - } - - /** - * Inverted Flags - 0 = flag is true - * @return - */ - public boolean hasEndOfTransmissionFlag() - { - return !mMessage.get( sEndOfTransmissionFlag ); - } - - public boolean hasEmergencyFlag() - { - return !mMessage.get( sEmergencyFlag ); - } - - public boolean hasLoneWorkerFlag() - { - return !mMessage.get( sLoneWorkerFlag ); - } - - public boolean hasPagingFlag() - { - return !mMessage.get( sPagingFlag ); - } - - - /** - * Normal Flags - 1 = flag is true - * @return - */ - public boolean hasANIFlag() - { - return mMessage.get( sANIFlag ); - } - - public boolean hasAcknowledgeFlag() - { - return mMessage.get( sAcknowledgeFlag ); - } - - public boolean hasFleetExtensionFlag() - { - return mMessage.get( sFleetExtensionFlag ); - } - - public boolean hasGPSFlag() - { - return mMessage.get( sGPSExtensionFlag ); - } - - public boolean hasStatusFlag() - { - return mMessage.get( sStatusFlag ); - } - - - public int getFleetTo() - { - if( hasFleetExtensionFlag() ) - { - return getInt( sFleetTo ) + 99; - } - else - { - return getInt( sFleetFrom ) + 99; - } - } - - public int getIdentifierTo() - { - return getInt( sIdentTo ) + 999; - } - - /** - * GPS Heading - * @return - */ - public double getHeading() - { - double retVal = 0.0; - - int heading = getInt( sGPSHeading ); - - if( heading != 4095 ) - { - retVal = (double)(heading / 10.0 ); - } - - return retVal; - } - - public int getBlock7WhatIsIt() - { - int value = getInt( sBLK7WhatIsIt ); - - if( value == 65535 ) - { - value = -1; - } - - return value; - } - - /** - * Speed in Kph - whole numbers and three decimal digits of precision - * @return - */ - public double getSpeed() - { - double retVal = 0.0; - - int temp = getInt( sSpeed ); - - if( temp != 0 ) - { - retVal = (double)temp / 1000.0D; - } - return retVal; - } - - public double getLatitude() - { - //TODO: determine the correct hemisphere indicator and replace this - //hardcoded "0" with the correct hemisphere value - - return convertDDMToDD( 0, - getInt( sLatitudeDegreesMinutes ), - getInt( sLatitudeDecimalMinutes ) ); - } - - public double getLongitude() - { - //TODO: determine the correct hemisphere indicator and replace this - //hardcoded "1" with the correct hemisphere value - - return convertDDMToDD( 1, - getInt( sLongitudeDegreesMinutes ), - getInt( sLongitudeDecimalMinutes ) ); - } - - /** - * Converts Degrees Decimal Minutes to Decimal Degrees - * - * Latitude and Longitude values are represented by: - * - * @param hemisphere - 0=North & East, 1=South & West - * @param degreesMinutes - an integer value with the first 2-3 digits representing - * the degrees and the last two digits representing the minutes - * - * @param decimalDegrees - an integer value representing the fractional - * minutes - * - * @return - decimal degrees formatted value - */ - public double convertDDMToDD( int hemisphere, int degreesMinutes, int decimalDegrees ) - { - double retVal = 0.0; - - if( degreesMinutes != 0 ) - { - //Degrees - divide value by 100 and retain the whole number value (ie degrees) - retVal += (double)( degreesMinutes / 100 ); - - //Minutes - modulus by 100 to get the whole minutes value - int wholeMinutes = degreesMinutes % 100; - - if( wholeMinutes != 0 ) - { - retVal += (double)( wholeMinutes / 60.0D ); - } - } - - if( decimalDegrees != 0 ) - { - //Fractional Minutes - divide by 10,000 to get the decimal place correct - //then divide by 60 (minutes) to get the decimal value - //10,000 * 60 = 600,000 - retVal += (double)( decimalDegrees / 600000.0D ); - } - - //Adjust the value +/- for the hemisphere - if( hemisphere == 1 ) //South and West values - { - retVal = -retVal; - } - - return retVal; - } - - public String getGPSTime() - { - StringBuilder sb = new StringBuilder(); - sb.append( format( getInt( sGPSHours ), 2 ) ); - sb.append( ":" ); - sb.append( format( getInt( sGPSMinutes ), 2 ) ); - sb.append( ":" ); - sb.append( format( getInt( sGPSSeconds ), 2 ) ); - sb.append( "z" ); - - return sb.toString(); - } - - - /** - * 7 bit checksum that is part of the GPGGA message, I think - * @return - */ - public int getGPSChecksum() - { - return getInt( sGPSChecksum ); - } - - public String getGPSChecksumString() - { - String sum = Integer.toHexString( getGPSChecksum() ); - - if( sum.length() == 1 ) - { - return "*0" + sum; - } - else - { - return "*" + sum; - } - } - - /** - * Returns 1-based calendar day of month - * 1 = 1st day of month - */ - public int getGPSDay() - { - return getInt( sGPSDay ) + 1; - } - - /** - * Returns 0-based GPS Month - * 0 = January - * - * Note: actual day of month value is 1-based, so we subtract 1 to get - * the actual day of month value - */ - public int getGPSMonth() - { - return getInt( sGPSMonth ); - } - - /** - * Returns year, which is a combination of the century bit field + 1, times - * 100, plus the year bit field - */ - public int getGPSYear() - { -// return ( ( getInt( sGPSCentury ) + 1 ) * 100 ) + getInt( sGPSYear ); - return ( 2000 ) + getInt( sGPSYear ); - } - - public String getGPSLocation() - { - StringBuilder sb = new StringBuilder(); - sb.append( pad( String.valueOf( mDecimalFormatter.format( getLatitude() ) ), 8, "0" ) + " " ); - sb.append( pad( String.valueOf( mDecimalFormatter.format( getLongitude() ) ), 10, "0" ) ); - - return sb.toString(); - } - - - public String getGPSDate() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getGPSYear() + "-" ); - - int month = getGPSMonth(); - - if( month < 10 ) - { - sb.append( "0" ); - } - - sb.append( month + "-" ); - - int day = getGPSDay(); - - if( day < 10 ) - { - sb.append( "0" ); - } - - sb.append( day ); - - return sb.toString(); - } - - private int getInt( int[] bits ) - { - int retVal = 0; - - for( int x = 0; x < bits.length; x++ ) - { - if( mMessage.get( bits[ x ] ) ) - { - retVal += 1< getAliases() - { - List aliases = new ArrayList(); - - Alias from = getFromIDAlias(); - - if( from != null ) - { - aliases.add( from ); - } - - Alias to = getToIDAlias(); - - if( to != null ) - { - aliases.add( to ); - } - - Alias status = getStatusAlias(); - - if( status != null ) - { - aliases.add( status ); - } - - return aliases; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/FleetsyncMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/FleetsyncMessageFilter.java index fdd2f2a6..c40039bd 100644 --- a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/FleetsyncMessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/FleetsyncMessageFilter.java @@ -1,56 +1,77 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.fleetsync2; import io.github.dsheirer.filter.Filter; import io.github.dsheirer.filter.FilterElement; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.module.decode.fleetsync2.message.Fleetsync2Message; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -public class FleetsyncMessageFilter extends Filter +public class FleetsyncMessageFilter extends Filter { - private HashMap> mElements = - new HashMap>(); - - public FleetsyncMessageFilter() - { - super( "Fleetsync Message Filter" ); + private HashMap> mElements = + new HashMap>(); - for( FleetsyncMessageType type: FleetsyncMessageType.values() ) - { - if( type != FleetsyncMessageType.UNKNOWN ) - { - mElements.put( type, new FilterElement( type ) ); - } - } - } - - @Override - public boolean passes( Message message ) + public FleetsyncMessageFilter() { - if( mEnabled && canProcess( message ) ) - { - FleetsyncMessage fleet = (FleetsyncMessage)message; - - if( mElements.containsKey( fleet.getMessageType() ) ) - { - return mElements.get( fleet.getMessageType() ).isEnabled(); - } - } - - return false; + super("Fleetsync Message Filter"); + + for(FleetsyncMessageType type : FleetsyncMessageType.values()) + { + if(type != FleetsyncMessageType.UNKNOWN) + { + mElements.put(type, new FilterElement(type)); + } + } } - @Override - public boolean canProcess( Message message ) + @Override + public boolean passes(IMessage message) { - return message instanceof FleetsyncMessage; + if(mEnabled && canProcess(message)) + { + Fleetsync2Message fleet = (Fleetsync2Message)message; + + if(mElements.containsKey(fleet.getMessageType())) + { + return mElements.get(fleet.getMessageType()).isEnabled(); + } + } + + return false; } - @Override + @Override + public boolean canProcess(IMessage message) + { + return message instanceof Fleetsync2Message; + } + + @Override public List> getFilterElements() { - return new ArrayList>( mElements.values() ); + return new ArrayList>(mElements.values()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/identifier/FleetsyncIdentifier.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/identifier/FleetsyncIdentifier.java new file mode 100644 index 00000000..49dedc14 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/identifier/FleetsyncIdentifier.java @@ -0,0 +1,88 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.fleetsync2.identifier; + +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.protocol.Protocol; + +/** + * Fleetsync talkgroup identifier + */ +public class FleetsyncIdentifier extends TalkgroupIdentifier implements Comparable +{ + public FleetsyncIdentifier(Integer talkgroup, Role role) + { + super(talkgroup, role, false); + } + + @Override + public Protocol getProtocol() + { + return Protocol.FLEETSYNC; + } + + /** + * This identifier formatted as FFF:IIII where F=fleet and I=ident + */ + public String formatted() + { + return String.format("%03d:%04d", getFleet(), getIdent()); + } + + /** + * Fleet prefix for the identifier + */ + public int getFleet() + { + return (getValue() >> 12) & 0xFF; + } + + /** + * Unit identifier + */ + public int getIdent() + { + return getValue() & 0xFFF; + } + + /** + * Creates a fleetsync identifier from the integer value that contains both the fleet and the ident with a FROM role + */ + public static FleetsyncIdentifier createFromUser(int talkgroup) + { + return new FleetsyncIdentifier(talkgroup, Role.FROM); + } + + /** + * Creates a fleetsync identifier from the integer value that contains both the fleet and the ident with a TO role + */ + public static FleetsyncIdentifier createToUser(int talkgroup) + { + return new FleetsyncIdentifier(talkgroup, Role.TO); + } + + @Override + public int compareTo(FleetsyncIdentifier o) + { + return getValue().compareTo(o.getValue()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/identifier/FleetsyncStatus.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/identifier/FleetsyncStatus.java new file mode 100644 index 00000000..775d9351 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/identifier/FleetsyncStatus.java @@ -0,0 +1,59 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.fleetsync2.identifier; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.integer.IntegerIdentifier; +import io.github.dsheirer.protocol.Protocol; + +/** + * Fleetsync User status + */ +public class FleetsyncStatus extends IntegerIdentifier +{ + private Role mRole; + + /** + * Constructs an APCO-25 status + * @param status value + * @param role of the user/unit that the status applies to + */ + public FleetsyncStatus(int status, IdentifierClass identifierClass, Form form, Role role) + { + super(status, identifierClass, form, role); + mRole = role; + } + + @Override + public Protocol getProtocol() + { + return Protocol.FLEETSYNC; + } + + /** + * Creates a User status + */ + public static FleetsyncStatus createUserStatus(int status) + { + return new FleetsyncStatus(status, IdentifierClass.USER, Form.USER_STATUS, Role.FROM); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/message/AutomaticNumberIdentification.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/message/AutomaticNumberIdentification.java new file mode 100644 index 00000000..6d349a34 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/message/AutomaticNumberIdentification.java @@ -0,0 +1,93 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.fleetsync2.message; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; + +import java.util.ArrayList; +import java.util.List; + +/** + * Automatic Number Identification (ANI). + */ +public class AutomaticNumberIdentification extends Fleetsync2Message +{ + private List mIdentifers; + + public AutomaticNumberIdentification(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + protected int getBlockCount() + { + return hasFleetExtensionFlag(getMessage()) ? 2 : 1; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append("FROM:").append(getFromIdentifier().formatted()); + + if(hasEmergencyFlag(getMessage())) + { + sb.append(" EMERGENCY"); + } + + if(hasLoneWorkerFlag(getMessage())) + { + sb.append(" LONE WORKER"); + } + + if(hasPagingFlag(getMessage())) + { + sb.append(" PAGING"); + } + + if(hasAcknowledgeFlag(getMessage())) + { + sb.append(" ACKNOWLEDGE"); + } + + if(hasANIFlag(getMessage())) + { + sb.append(" ANI"); + } + + return sb.toString().trim(); + } + + @Override + public List getIdentifiers() + { + if(mIdentifers == null) + { + mIdentifers = new ArrayList<>(); + mIdentifers.add(getFromIdentifier()); + } + + return mIdentifers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/message/Fleetsync2Message.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/message/Fleetsync2Message.java new file mode 100644 index 00000000..2aebf9d3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/message/Fleetsync2Message.java @@ -0,0 +1,304 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.fleetsync2.message; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.CRC; +import io.github.dsheirer.edac.CRCFleetsync; +import io.github.dsheirer.message.Message; +import io.github.dsheirer.module.decode.fleetsync2.FleetsyncMessageType; +import io.github.dsheirer.module.decode.fleetsync2.identifier.FleetsyncIdentifier; +import io.github.dsheirer.protocol.Protocol; + +import java.util.BitSet; + +public abstract class Fleetsync2Message extends Message +{ + //Message Header + private static int[] BIT_REVERSALS = {0, 1, 2, 3, 4}; + private static int[] SYNC_PATTERN = {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}; + + //Message Block 1 + private static int[] STATUS = {21, 22, 23, 24, 25, 26, 27}; + private static int[] MESSAGE_TYPE = {29, 30, 31, 32, 33}; + + //Message Type Flags + private static int FLAG_UNKNOWN_1 = 21; + private static int FLAG_EMERGENCY_WORKER = 22; + private static int FLAG_UNKNOWN_2 = 23; + private static int FLAG_LONE_WORKER = 24; + private static int FLAG_UNKNOWN_3 = 25; + private static int FLAG_PAGING = 26; + private static int FLAG_END_OF_TRANSMISSION = 27; + private static int FLAG_MANUAL = 28; + private static int FLAG_AUTOMATIC_NUMBER_IDENTIFIER = 29; + private static int FLAG_STATUS = 30; + private static int FLAG_ACKNOWLEDGE = 31; + private static int FLAG_UNKNOWN_4 = 32; + private static int FLAG_UNKNOWN_5 = 33; + private static int FLAG_UNKNOWN_6 = 34; //Always set for Acknowledge + private static int FLAG_GPS_EXTENSION = 35; + private static int FLAG_FLEET_EXTENSION = 36; + + //From and To talkgroups share a common fleet value for single-block messages. + private static int[] FLEET = {37, 38, 39, 40, 41, 42, 43, 44}; + private static int[] FROM_IDENT = {45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56}; + private static int[] TO_IDENT = {57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68}; + + private static int[] CRC_BLOCK_1 = {69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84}; + + //Message Block 2 + private static int[] FLEET_TO = {85, 86, 87, 88, 89, 90, 91, 92}; + private static int[] CRC_BLOCK_2 = {132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148}; + + private CorrectedBinaryMessage mMessage; + private CRC[] mCRC = new CRC[2]; + private FleetsyncIdentifier mFromIdentifier; + private FleetsyncIdentifier mToIdentifier; + + public Fleetsync2Message(CorrectedBinaryMessage message, long timestamp) + { + super(timestamp); + mMessage = message; + checkParity(); + } + + protected CorrectedBinaryMessage getMessage() + { + return mMessage; + } + + @Override + public Protocol getProtocol() + { + return Protocol.FLEETSYNC; + } + + /** + * Indicates the number of 64-bit blocks that makeup this message. + */ + protected abstract int getBlockCount(); + + /** + * Fleetsync identifier for the from or transmitting radio. + */ + public FleetsyncIdentifier getFromIdentifier() + { + if(mFromIdentifier == null) + { + mFromIdentifier = FleetsyncIdentifier.createFromUser(getCommonFleet() + getFromIdent()); + } + + return mFromIdentifier; + } + + /** + * Fleetsync identifier for the to or receiving radio + */ + public FleetsyncIdentifier getToIdentifier() + { + if(mToIdentifier == null) + { + int value = (hasFleetExtensionFlag(getMessage()) ? getExtendedFleet() : getCommonFleet()) + getToIdent(); + mToIdentifier = FleetsyncIdentifier.createToUser(value); + } + + return mToIdentifier; + } + + /** + * Shared or common fleet value. Note: this value is added to 99 and then left shifted by 12 to obtain fleets in + * the range of 1 - 127. + */ + private int getCommonFleet() + { + return (getMessage().getInt(FLEET) + 99) << 12; + } + + /** + * Extended fleet for the TO identifier. This value is only valid when the extended fleet flag is set. + * Note: this value is added to 99 and then left shifted by 12 to obtain fleets in the range of 1 - 127. + */ + private int getExtendedFleet() + { + return (getMessage().getInt(FLEET_TO) + 99) << 12; + } + + /** + * From ident value. Transmitted value is added to 999 to obtain ident values in range of 1000 - 4996 + */ + private int getFromIdent() + { + return getMessage().getInt(FROM_IDENT) + 999; + } + + /** + * To ident value. Transmitted value is added to 999 to obtain ident values in range of 1000 - 4996 + */ + private int getToIdent() + { + return getMessage().getInt(TO_IDENT) + 999; + } + + private void checkParity() + { + //Check message block 1 + CRC block1Crc = detectAndCorrect(getMessage(), 21, 85); + + mCRC = new CRC[getBlockCount()]; + mCRC[0] = block1Crc; + + for(int x = 1; x < getBlockCount(); x++) + { + int blockStart = 21 + (x * 64); + mCRC[x] = detectAndCorrect(getMessage(), blockStart, blockStart + 64); + } + } + + public static CRC detectAndCorrect(CorrectedBinaryMessage message, int start, int end) + { + BitSet original = message.get(start, end); + + CRC retVal = CRCFleetsync.check(original); + + //Attempt to correct single-bit errors + if(retVal == CRC.FAILED_PARITY) + { + int[] errorBitPositions = CRCFleetsync.findBitErrors(original); + + if(errorBitPositions != null) + { + for(int errorBitPosition : errorBitPositions) + { + message.flip(start + errorBitPosition); + message.incrementCorrectedBitCount(1); + } + + retVal = CRC.CORRECTED; + } + } + + return retVal; + } + + public boolean isValid() + { + return mCRC[0].passes(); + } + + /** + * Type of message + */ + public FleetsyncMessageType getMessageType() + { + return getMessageType(getMessage()); + } + + public static FleetsyncMessageType getMessageType(BinaryMessage message) + { + if(hasAcknowledgeFlag(message)) + { + return FleetsyncMessageType.ACKNOWLEDGE; + } + + if(hasGPSFlag(message)) + { + return FleetsyncMessageType.GPS; + } + + if(hasStatusFlag(message)) + { + return FleetsyncMessageType.STATUS; + } + + if(hasANIFlag(message)) + { + return FleetsyncMessageType.ANI; + } + + if(hasPagingFlag(message)) + { + return FleetsyncMessageType.PAGING; + } + + if(hasLoneWorkerFlag(message) && hasEmergencyFlag(message)) + { + return FleetsyncMessageType.LONE_WORKER_EMERGENCY; + } + + return FleetsyncMessageType.UNKNOWN; + } + + /** + * Message Type Indicator Flags. The flags have inverted values where 0 = flag is true + */ + public static boolean hasEndOfTransmissionFlag(BinaryMessage message) + { + return !message.get(FLAG_END_OF_TRANSMISSION); + } + + public static boolean hasEmergencyFlag(BinaryMessage message) + { + return !message.get(FLAG_EMERGENCY_WORKER); + } + + public static boolean hasLoneWorkerFlag(BinaryMessage message) + { + return !message.get(FLAG_LONE_WORKER); + } + + public static boolean hasPagingFlag(BinaryMessage message) + { + return !message.get(FLAG_PAGING); + } + + + /** + * Message Type Indicator Flags. The flags have normal values where 1 = true + * + * @return + */ + public static boolean hasANIFlag(BinaryMessage message) + { + return message.get(FLAG_AUTOMATIC_NUMBER_IDENTIFIER); + } + + public static boolean hasAcknowledgeFlag(BinaryMessage message) + { + return message.get(FLAG_ACKNOWLEDGE); + } + + public static boolean hasFleetExtensionFlag(BinaryMessage message) + { + return message.get(FLAG_FLEET_EXTENSION); + } + + public static boolean hasGPSFlag(BinaryMessage message) + { + return message.get(FLAG_GPS_EXTENSION); + } + + public static boolean hasStatusFlag(BinaryMessage message) + { + return message.get(FLAG_STATUS); + } + +} diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/message/LocationReport.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/message/LocationReport.java new file mode 100644 index 00000000..f7499dc5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/message/LocationReport.java @@ -0,0 +1,250 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.fleetsync2.message; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import org.jdesktop.swingx.mapviewer.GeoPosition; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.List; + +/** + * Status message containing a numeric status indicator where the numeric value is pre-defined. + */ +public class LocationReport extends Fleetsync2Message +{ + //Message Block 3 + private static int[] GPS_HOURS = {172, 173, 174, 175, 176}; + private static int[] GPS_MINUTES = {177, 178, 179, 180, 181, 182}; + private static int[] GPS_SECONDS = {183, 184, 185, 186, 187, 188}; + + //Message Block 4 + private static int[] GPS_CHECKSUM = {213, 214, 215, 216, 217, 218, 219, 220}; + private static int[] LATITUDE_DEGREES_MINUTES = {221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236}; + private static int[] LATITUDE_FRACTIONAL_MINUTES = {238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251}; + private static int[] SPEED = {252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, + 269, 270, 271, 272, 273, 274, 275, 276}; + + //Message Block 5 + private static int[] GPS_CENTURY = {277, 278, 279, 280, 281, 282, 283, 284}; + private static int[] GPS_YEAR = {284, 286, 287, 288, 289, 290, 291}; + private static int[] GPS_MONTH = {292, 293, 294, 295}; + private static int[] GPS_DAY = {296, 297, 298, 299, 300}; + private static int[] LONGITUDE_DEGREES_MINUTES = {301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316}; + + //TODO: this overlaps with the CRC block address space ... + private static int[] LONGITUDE_FRACTIONAL_MINUTES = {318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331}; + + //Message Block 6 + private static int[] GPS_HEADING = {353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365}; + + //Message Block 7 + + //Message Block 8 + private static int[] GPS_SPEED = {484, 485, 486, 487, 488, 489, 490, 491}; + private static int[] GPS_SPEED_FRACTIONAL = {492, 493, 494, 495, 496, 497, 498, 499}; + + private GeoPosition mGeoPosition; + private Long mGPSTimestamp; + private Double mGPSHeading; + private Double mGPSSpeed; + private List mIdentifers; + + public LocationReport(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + protected int getBlockCount() + { + return 8; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("GPS FROM:").append(getFromIdentifier()); + sb.append(" TO:").append(getToIdentifier()); + sb.append(" ").append(getLocation()); + sb.append(" HEADING:").append(getHeading()); + sb.append(" SPEED:").append(getSpeed()); + sb.append(" TIME:").append(new Date(getGPSTime())); + + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + if(mIdentifers == null) + { + mIdentifers = new ArrayList<>(); + mIdentifers.add(getFromIdentifier()); + mIdentifers.add(getToIdentifier()); + } + + return mIdentifers; + } + + /** + * GPS Heading in degrees relative to true North. + */ + public double getHeading() + { + if(mGPSHeading == null) + { + if(hasHeading()) + { + mGPSHeading = (double)getHeadingValue() / 10.0; + } + else + { + mGPSHeading = 0.0; + } + } + + return mGPSHeading; + } + + private int getHeadingValue() + { + return getMessage().getInt(GPS_HEADING); + } + + private boolean hasHeading() + { + return getHeadingValue() != 4095; + } + + /** + * Speed in kph + */ + public double getSpeed() + { + if(mGPSSpeed == null) + { + mGPSSpeed = (double)getMessage().getInt(GPS_SPEED) + ((double)getMessage().getInt(GPS_SPEED_FRACTIONAL) / 255.0); + } + + return mGPSSpeed; + } + + /** + * Latitude and Longitude location/position. + */ + public GeoPosition getLocation() + { + if(mGeoPosition == null) + { + mGeoPosition = new GeoPosition(getLatitude(), getLongitude()); + } + + return mGeoPosition; + } + + private double getLatitude() + { + //TODO: determine the correct hemisphere indicator and replace this + return convertDDMToDD(0, getMessage().getInt(LATITUDE_DEGREES_MINUTES), + getMessage().getInt(LATITUDE_FRACTIONAL_MINUTES)); + } + + private double getLongitude() + { + //TODO: determine the correct hemisphere indicator and replace this + return convertDDMToDD(1, getMessage().getInt(LONGITUDE_DEGREES_MINUTES), + getMessage().getInt(LONGITUDE_FRACTIONAL_MINUTES)); + } + + /** + * Converts Degrees Decimal Minutes to Decimal Degrees + * + * Latitude and Longitude values are represented by: + * + * @param hemisphere - 0=North & East, 1=South & West + * @param degreesMinutes - an integer value with the first 2-3 digits representing + * the degrees and the last two digits representing the minutes + * @param decimalDegrees - an integer value representing the fractional + * minutes + * @return - decimal degrees formatted value + */ + public double convertDDMToDD(int hemisphere, int degreesMinutes, int decimalDegrees) + { + double retVal = 0.0; + + if(degreesMinutes != 0) + { + //Degrees - divide value by 100 and retain the whole number value (ie degrees) + retVal += (double)(degreesMinutes / 100); + + //Minutes - modulus by 100 to get the whole minutes value + int wholeMinutes = degreesMinutes % 100; + + if(wholeMinutes != 0) + { + retVal += (double)(wholeMinutes / 60.0D); + } + } + + if(decimalDegrees != 0) + { + //Fractional Minutes - divide by 10,000 to get the decimal place correct + //then divide by 60 (minutes) to get the decimal value + //10,000 * 60 = 600,000 + retVal += (double)(decimalDegrees / 600000.0D); + } + + //Adjust the value +/- for the hemisphere + if(hemisphere == 1) //South and West values + { + retVal = -retVal; + } + + return retVal; + } + + /** + * GPS Timestamp in milliseconds + */ + public long getGPSTime() + { + if(mGPSTimestamp == null) + { + Calendar calendar = new GregorianCalendar(); + calendar.add(Calendar.YEAR, 2000 + getMessage().getInt(GPS_YEAR)); + calendar.add(Calendar.MONTH, getMessage().getInt(GPS_MONTH)); + calendar.add(Calendar.DAY_OF_MONTH, getMessage().getInt(GPS_DAY)); + calendar.add(Calendar.HOUR_OF_DAY, getMessage().getInt(GPS_HOURS)); + calendar.add(Calendar.MINUTE, getMessage().getInt(GPS_MINUTES)); + calendar.add(Calendar.SECOND, getMessage().getInt(GPS_SECONDS)); + + mGPSTimestamp = calendar.getTimeInMillis(); + } + + return mGPSTimestamp; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/message/Status.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/message/Status.java new file mode 100644 index 00000000..09e7eae3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/message/Status.java @@ -0,0 +1,110 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.fleetsync2.message; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.fleetsync2.identifier.FleetsyncStatus; + +import java.util.ArrayList; +import java.util.List; + +/** + * Status message containing a numeric status indicator where the numeric value is pre-defined. + */ +public class Status extends Fleetsync2Message +{ + private static int[] STATUS = {21, 22, 23, 24, 25, 26, 27}; + + private FleetsyncStatus mStatusIdentifier; + private List mIdentifers; + + public Status(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + protected int getBlockCount() + { + return hasFleetExtensionFlag(getMessage()) ? 2 : 1; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("FROM:").append(getFromIdentifier()).append(" TO:").append(getToIdentifier()); + + sb.append(" STATUS:").append(getStatus()); + + if(hasEmergencyFlag(getMessage())) + { + sb.append(" EMERGENCY"); + } + + if(hasLoneWorkerFlag(getMessage())) + { + sb.append(" LONE WORKER"); + } + + if(hasPagingFlag(getMessage())) + { + sb.append(" PAGING"); + } + + if(hasAcknowledgeFlag(getMessage())) + { + sb.append(" ACKNOWLEDGE"); + } + + if(hasANIFlag(getMessage())) + { + sb.append(" ANI"); + } + + return sb.toString(); + } + + public FleetsyncStatus getStatus() + { + if(mStatusIdentifier == null) + { + mStatusIdentifier = FleetsyncStatus.createUserStatus(getMessage().getInt(STATUS)); + } + + return mStatusIdentifier; + } + + @Override + public List getIdentifiers() + { + if(mIdentifers == null) + { + mIdentifers = new ArrayList<>(); + mIdentifers.add(getFromIdentifier()); + mIdentifers.add(getToIdentifier()); + mIdentifers.add(getStatus()); + } + + return mIdentifers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/Header.java b/src/main/java/io/github/dsheirer/module/decode/ip/Header.java new file mode 100644 index 00000000..00ce9941 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/Header.java @@ -0,0 +1,74 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip; + +import io.github.dsheirer.bits.BinaryMessage; + +public abstract class Header implements IHeader +{ + private BinaryMessage mMessage; + private int mOffset; + private boolean mValid = true; + + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * @param message containing the header + * @param offset to the header within the message + */ + public Header(BinaryMessage message, int offset) + { + mMessage = message; + mOffset = offset; + } + + /** + * Underlying message that contains this header + */ + public BinaryMessage getMessage() + { + return mMessage; + } + + /** + * Offset to this header in the underlying message + */ + public int getOffset() + { + return mOffset; + } + + /** + * Indicates if this header is valid + */ + @Override + public boolean isValid() + { + return mValid; + } + + /** + * Sets the valid flag for this header + */ + protected void setValid(boolean valid) + { + mValid = valid; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/IHeader.java b/src/main/java/io/github/dsheirer/module/decode/ip/IHeader.java new file mode 100644 index 00000000..8def15a1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/IHeader.java @@ -0,0 +1,39 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip; + +/** + * Data Packet Header + */ +public interface IHeader +{ + /** + * Indicates that this header is valid and that it meets the length requirements. + */ + boolean isValid(); + + /** + * Indicates the length of this header. An implicit understanding is that any data that follows + * the header (ie payload) starts after the header length. + * @return length of this header in bytes. + */ + int getLength(); +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/IPProtocol.java b/src/main/java/io/github/dsheirer/module/decode/ip/IPProtocol.java similarity index 80% rename from src/main/java/io/github/dsheirer/module/decode/p25/reference/IPProtocol.java rename to src/main/java/io/github/dsheirer/module/decode/ip/IPProtocol.java index 865d3f0b..bcb231b6 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/IPProtocol.java +++ b/src/main/java/io/github/dsheirer/module/decode/ip/IPProtocol.java @@ -1,4 +1,24 @@ -package io.github.dsheirer.module.decode.p25.reference; +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip; public enum IPProtocol { diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/IPacket.java b/src/main/java/io/github/dsheirer/module/decode/ip/IPacket.java new file mode 100644 index 00000000..90d1e4f6 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/IPacket.java @@ -0,0 +1,43 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip; + +/** + * Data packet interface + */ +public interface IPacket +{ + /** + * Header for the data packet. + */ + public IHeader getHeader(); + + /** + * Optional payload for the packet + * @return null if there is no payload + */ + public IPacket getPayload(); + + /** + * Indicates if this packet contains an optional payload + */ + public boolean hasPayload(); +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/Packet.java b/src/main/java/io/github/dsheirer/module/decode/ip/Packet.java new file mode 100644 index 00000000..8392fcb0 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/Packet.java @@ -0,0 +1,62 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip; + +import io.github.dsheirer.bits.BinaryMessage; + +public abstract class Packet implements IPacket +{ + private BinaryMessage mMessage; + private int mOffset; + + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * @param message containing the packet + * @param offset to the packet within the message + */ + public Packet(BinaryMessage message, int offset) + { + mMessage = message; + mOffset = offset; + } + + /** + * Underlying message that contains this packet + */ + public BinaryMessage getMessage() + { + return mMessage; + } + + /** + * Offset to this packet in the underlying message + */ + public int getOffset() + { + return mOffset; + } + + @Override + public boolean hasPayload() + { + return getPayload() != null; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/PacketMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/ip/PacketMessageFactory.java new file mode 100644 index 00000000..fc4ee771 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/PacketMessageFactory.java @@ -0,0 +1,108 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.module.decode.ip.ars.ARSPacket; +import io.github.dsheirer.module.decode.ip.ipv4.IPV4Header; +import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet; +import io.github.dsheirer.module.decode.ip.udp.UDPPacket; +import io.github.dsheirer.module.decode.p25.message.pdu.packet.sndcp.SNDCPPacketHeader; +import io.github.dsheirer.module.decode.p25.reference.IPHeaderCompression; + +/** + * Message parser factory for packet based binary sequences. + */ +public class PacketMessageFactory +{ + /** + * Creates an IP packet message parser + * + * @param message containing an IP packet + * @param offset to the IP packet within the message + * @return constructed packet message parser + */ + public static IPacket create(SNDCPPacketHeader sndcpPacketHeader, BinaryMessage message, int offset) + { + if(sndcpPacketHeader.getIPHeaderCompression() == IPHeaderCompression.NONE) + { + int version = IPV4Header.getIPVersion(message, offset); + + switch(version) + { + case 4: + return new IPV4Packet(message, offset); + default: + return new UnknownPacket(message, offset); + } + } + else + { + return new UnknownCompressedHeaderPacket(sndcpPacketHeader, message, offset); + } + } + + /** + * Creates an IP packet protocol payload message parser. + * + * @param protocol of the IP packet payload + * @param binaryMessage containing the IP packet and payload + * @param offset to the start of the IP packet payload + * @return constructed packet message parser + */ + public static IPacket create(IPProtocol protocol, BinaryMessage binaryMessage, int offset) + { + switch(protocol) + { + case UDP: + return new UDPPacket(binaryMessage, offset); + default: + return new UnknownPacket(binaryMessage, offset); + } + } + + /** + * Creates a UDP/IP packet payload parser + * + * @param destinationPort for the packet (to identify the protocol/format) + * @param binaryMessage containing the IP/UDP payload + * @param offset in the message to the start of the payload + * @return constructed packet messge parser + */ + public static IPacket createUDPPayload(int destinationPort, BinaryMessage binaryMessage, int offset) + { + switch(destinationPort) + { + case 4001: //Location Service + break; + case 4004: //XCMP Service + break; + case 4005: //Automatic Registration Service + return new ARSPacket(binaryMessage, offset); + case 4007: //Text Message Service + break; + case 4008: //Telemetry Service + break; + } + + return new UnknownPacket(binaryMessage, offset); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/UnknownCompressedHeaderPacket.java b/src/main/java/io/github/dsheirer/module/decode/ip/UnknownCompressedHeaderPacket.java new file mode 100644 index 00000000..e8fe2b59 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/UnknownCompressedHeaderPacket.java @@ -0,0 +1,90 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.packet.sndcp.SNDCPPacketHeader; + +public class UnknownCompressedHeaderPacket implements IPacket +{ + private SNDCPPacketHeader mSNDCPPacketHeader; + private BinaryMessage mMessage; + private int mOffset; + + public UnknownCompressedHeaderPacket(SNDCPPacketHeader SNDCPPacketHeader, BinaryMessage message, int offset) + { + mSNDCPPacketHeader = SNDCPPacketHeader; + mMessage = message; + mOffset = offset; + } + + public SNDCPPacketHeader getSNDCPHeader() + { + return mSNDCPPacketHeader; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getSNDCPHeader()); + sb.append(" PACKET:"); + + if(getMessage().size() > getOffset()) + { + sb.append(mMessage.getSubMessage(getOffset(), getMessage().size()).toHexString()); + } + else + { + sb.append("(EMPTY)"); + } + + return sb.toString(); + } + + public BinaryMessage getMessage() + { + return mMessage; + } + + public int getOffset() + { + return mOffset; + } + + @Override + public IHeader getHeader() + { + return null; + } + + @Override + public IPacket getPayload() + { + return null; + } + + @Override + public boolean hasPayload() + { + return false; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/UnknownPacket.java b/src/main/java/io/github/dsheirer/module/decode/ip/UnknownPacket.java new file mode 100644 index 00000000..e154583e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/UnknownPacket.java @@ -0,0 +1,81 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip; + +import io.github.dsheirer.bits.BinaryMessage; + +public class UnknownPacket implements IPacket +{ + private BinaryMessage mMessage; + private int mOffset; + + public UnknownPacket(BinaryMessage message, int offset) + { + mMessage = message; + mOffset = offset; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("PACKET:"); + + if(getMessage().size() > getOffset()) + { + sb.append(mMessage.getSubMessage(getOffset(), getMessage().size()).toHexString()); + } + else + { + sb.append("(EMPTY)"); + } + + return sb.toString(); + } + + public BinaryMessage getMessage() + { + return mMessage; + } + + public int getOffset() + { + return mOffset; + } + + @Override + public IHeader getHeader() + { + return null; + } + + @Override + public IPacket getPayload() + { + return null; + } + + @Override + public boolean hasPayload() + { + return false; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/ars/ARSHeader.java b/src/main/java/io/github/dsheirer/module/decode/ip/ars/ARSHeader.java new file mode 100644 index 00000000..6f767e9a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/ars/ARSHeader.java @@ -0,0 +1,144 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip.ars; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.module.decode.ip.Header; + +public class ARSHeader extends Header +{ + private static final int[] LENGTH = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int HEADER_EXTENSION_FLAG = 16; + private static final int ACKNOWLEDGEMENT_FLAG = 17; + private static final int PRIORITY_FLAG = 18; + private static final int CONTROL_USER_FLAG = 19; + private static final int[] PDU_TYPE = {20, 21, 22, 23}; + + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param message containing the header + * @param offset to the header within the message + */ + public ARSHeader(BinaryMessage message, int offset) + { + super(message, offset); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("ARS ").append(getPDUType()); + + if(getPDUType() == ARSPDUType.UNKNOWN) + { + sb.append(" PACKET:").append(getMessage().toHexString()); + } + + return sb.toString(); + } + + /** + * Checks that the underlying message is long enough to contain the PDU + */ + private void checkValid() + { + setValid(getMessage().size() >= (getOffset() + getLength() + getPayloadLength())); + } + + /** + * Indicates the type of ARS PDU + */ + public ARSPDUType getPDUType() + { + return getPDUType(getMessage(), getOffset()); + } + + /** + * Indicates the type of ARS PDU contained in the message starting at the offset index. + */ + public static ARSPDUType getPDUType(BinaryMessage message, int offset) + { + return ARSPDUType.fromValue(message.getInt(PDU_TYPE, offset)); + } + + /** + * Indicates if this message has a header extension byte + */ + public boolean hasHeaderExtension() + { + return getMessage().get(HEADER_EXTENSION_FLAG + getOffset()); + } + + /** + * Indicates if this is an acknowledgement type PDU + */ + public boolean isAcknowledge() + { + return getMessage().get(ACKNOWLEDGEMENT_FLAG + getOffset()); + } + + /** + * Indicates if this PDU was sent with the priority flag + */ + public boolean isPriority() + { + return getMessage().get(PRIORITY_FLAG + getOffset()); + } + + /** + * Indicates if this packet is a control packet (true) or a user packet (false). + */ + public boolean isControl() + { + return getMessage().get(CONTROL_USER_FLAG + getOffset()); + } + + /** + * Length of the payload + * @return + */ + public int getPayloadLength() + { + return getMessage().getInt(LENGTH, getOffset()); + } + + /** + * Length of this header in bits + * + * Note this length value includes the first 2 bytes of the message that indicates the overall + * message length and the length of the PDU header byte(s). + * + * This method may need to be overridden if there is more than 1 extension header in a PDU. + */ + @Override + public int getLength() + { + if(hasHeaderExtension()) + { + return 16 + 16; + } + else + { + return 16 + 8; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/ars/ARSHeaderFactory.java b/src/main/java/io/github/dsheirer/module/decode/ip/ars/ARSHeaderFactory.java new file mode 100644 index 00000000..1bf7f985 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/ars/ARSHeaderFactory.java @@ -0,0 +1,50 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip.ars; + +import io.github.dsheirer.bits.BinaryMessage; + +public class ARSHeaderFactory +{ + public static ARSHeader create(ARSPDUType pduType, BinaryMessage message, int offset) + { + switch(pduType) + { + case DEVICE_REGISTRATION: + return new DeviceRegistration(message, offset); + case DEVICE_DEREGISTRATION: + return new DeviceDeRegistration(message, offset); + case QUERY: + return new QueryMessage(message, offset); + case USER_REGISTRATION: + return new UserRegistration(message, offset); + case USER_DEREGISTRATION: + return new UserDeRegistration(message, offset); + case USER_REGISTRATION_ACKNOWLEDGEMENT: + return new UserRegistrationAcknowledge(message, offset); + case REGISTRATION_ACKNOWLEDGEMENT: + return new RegistrationAcknowledgement(message, offset); + case UNKNOWN: + default: + return new ARSHeader(message, offset); + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/ars/ARSPDUType.java b/src/main/java/io/github/dsheirer/module/decode/ip/ars/ARSPDUType.java new file mode 100644 index 00000000..9137d4b8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/ars/ARSPDUType.java @@ -0,0 +1,63 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip.ars; + +public enum ARSPDUType +{ + DEVICE_REGISTRATION(0x0), + DEVICE_DEREGISTRATION(0x1), + QUERY(0x4), + USER_REGISTRATION(0x5), + USER_DEREGISTRATION(0x6), + USER_REGISTRATION_ACKNOWLEDGEMENT(0x7), + REGISTRATION_ACKNOWLEDGEMENT(0xF), + UNKNOWN(-1); + + private int mValue; + + ARSPDUType(int value) + { + mValue = value; + } + + public static ARSPDUType fromValue(int value) + { + switch(value) + { + case 0x0: + return DEVICE_REGISTRATION; + case 0x1: + return DEVICE_DEREGISTRATION; + case 0x4: + return QUERY; + case 0x5: + return USER_REGISTRATION; + case 0x6: + return USER_DEREGISTRATION; + case 0x7: + return USER_REGISTRATION_ACKNOWLEDGEMENT; + case 0xF: + return REGISTRATION_ACKNOWLEDGEMENT; + } + + return UNKNOWN; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/ars/ARSPacket.java b/src/main/java/io/github/dsheirer/module/decode/ip/ars/ARSPacket.java new file mode 100644 index 00000000..5f0d5bfa --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/ars/ARSPacket.java @@ -0,0 +1,67 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip.ars; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.module.decode.ip.IPacket; +import io.github.dsheirer.module.decode.ip.Packet; + +public class ARSPacket extends Packet +{ + private ARSHeader mHeader; + + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param message containing the packet + * @param offset to the packet within the message + */ + public ARSPacket(BinaryMessage message, int offset) + { + super(message, offset); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("ARS ").append(getHeader()); + return sb.toString(); + } + + @Override + public ARSHeader getHeader() + { + if(mHeader == null) + { + ARSPDUType pduType = ARSHeader.getPDUType(getMessage(), getOffset()); + mHeader = ARSHeaderFactory.create(pduType, getMessage(), getOffset()); + } + + return mHeader; + } + + @Override + public IPacket getPayload() + { + //There are no payloads for ARS packets ... the header parses all of the information. + return null; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/ars/DeviceDeRegistration.java b/src/main/java/io/github/dsheirer/module/decode/ip/ars/DeviceDeRegistration.java new file mode 100644 index 00000000..361211b3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/ars/DeviceDeRegistration.java @@ -0,0 +1,44 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip.ars; + +import io.github.dsheirer.bits.BinaryMessage; + +public class DeviceDeRegistration extends ARSHeader +{ + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param message containing the header + * @param offset to the header within the message + */ + public DeviceDeRegistration(BinaryMessage message, int offset) + { + super(message, offset); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("ARS DEVICE/USER DE-REGISTRATION"); + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/ars/DeviceRegistration.java b/src/main/java/io/github/dsheirer/module/decode/ip/ars/DeviceRegistration.java new file mode 100644 index 00000000..8d3271ce --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/ars/DeviceRegistration.java @@ -0,0 +1,153 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip.ars; + +import io.github.dsheirer.bits.BinaryMessage; + +public class DeviceRegistration extends ARSHeader +{ + private static final int SECOND_HEADER_EXTENSION_FLAG = 24; + private static final int[] EVENT = {25, 26}; + private static final int[] ENCODING = {27, 28, 29, 30, 31}; + private static final int DEVICE_IDENTIFIER_START = 24; + private static final int DEVICE_IDENTIFIER_START_EXTENDED_HEADER = 32; + + private static final int[] BYTE_VALUE = {0, 1, 2, 3, 4, 5, 6, 7}; + + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param message containing the header + * @param offset to the header within the message + */ + public DeviceRegistration(BinaryMessage message, int offset) + { + super(message, offset); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("DEVICE REGISTRATION"); + + if(isValid()) + { + if(hasHeaderExtension()) + { + sb.append(isInitialEvent() ? "-INITIAL " : "-REFRESH "); + } + + sb.append(" ").append(getPayload()); + } + else + { + sb.append(" - ERROR INVALID MESSAGE LENGTH"); + } + return sb.toString(); + } + + /** + * Indicates if this is an initial registration event (true) or refresh event (false) + */ + public boolean isInitialEvent() + { + return getMessage().getInt(EVENT, getOffset()) == 1; + } + + /** + * Device, user and password values contained in the registration packet. + */ + public String getPayload() + { + StringBuilder sb = new StringBuilder(); + sb.append("DEVICE:"); + + int pointer = getOffset(); + + if(hasHeaderExtension()) + { + pointer += DEVICE_IDENTIFIER_START_EXTENDED_HEADER; + } + else + { + pointer += DEVICE_IDENTIFIER_START; + } + + int identifierSize = getMessage().getInt(BYTE_VALUE, pointer += 8); + + if(identifierSize > 0) + { + for(int x = 0; x < identifierSize; x++) + { + sb.append(getCharacter(pointer += 8)); + } + } + else + { + sb.append("(none)"); + } + + sb.append(" USER:"); + + int userIdentifierSize = getMessage().getInt(BYTE_VALUE, pointer += 8); + + if(userIdentifierSize > 0) + { + for(int x = 0; x < userIdentifierSize; x++) + { + sb.append(getCharacter(pointer += 8)); + } + } + else + { + sb.append("(none)"); + } + + sb.append(" PASSWORD:"); + + int passwordSize = getMessage().getInt(BYTE_VALUE, pointer += 8); + + if(passwordSize > 0) + { + for(int x = 0; x < passwordSize; x++) + { + sb.append(getCharacter(pointer += 8)); + } + } + else + { + sb.append("(none)"); + } + + return sb.toString(); + } + + /** + * Returns a UTF-8 encoded character that starts at the specified offset + * + * @param offset of the start of the 8-bit UTF-8 encoded character + * @return character + */ + private char getCharacter(int offset) + { + return (char)getMessage().getByte(BYTE_VALUE, offset); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/ars/DeviceRegistrationAcknowledge.java b/src/main/java/io/github/dsheirer/module/decode/ip/ars/DeviceRegistrationAcknowledge.java new file mode 100644 index 00000000..4f24320a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/ars/DeviceRegistrationAcknowledge.java @@ -0,0 +1,44 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip.ars; + +import io.github.dsheirer.bits.BinaryMessage; + +public class DeviceRegistrationAcknowledge extends ARSHeader +{ + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param message containing the header + * @param offset to the header within the message + */ + public DeviceRegistrationAcknowledge(BinaryMessage message, int offset) + { + super(message, offset); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("DEVICE REGISTRATION ACKNOWLEDGED"); + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/ars/QueryMessage.java b/src/main/java/io/github/dsheirer/module/decode/ip/ars/QueryMessage.java new file mode 100644 index 00000000..e1c08371 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/ars/QueryMessage.java @@ -0,0 +1,44 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip.ars; + +import io.github.dsheirer.bits.BinaryMessage; + +public class QueryMessage extends ARSHeader +{ + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param message containing the header + * @param offset to the header within the message + */ + public QueryMessage(BinaryMessage message, int offset) + { + super(message, offset); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("ARS QUERY MESSAGE"); + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/ars/RegistrationAcknowledgement.java b/src/main/java/io/github/dsheirer/module/decode/ip/ars/RegistrationAcknowledgement.java new file mode 100644 index 00000000..6259d20c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/ars/RegistrationAcknowledgement.java @@ -0,0 +1,96 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip.ars; + +import io.github.dsheirer.bits.BinaryMessage; + +public class RegistrationAcknowledgement extends ARSHeader +{ + private static final int[] TIMER_OR_REASON = {25, 26, 27, 28, 29, 30, 31}; + + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param message containing the header + * @param offset to the header within the message + */ + public RegistrationAcknowledgement(BinaryMessage message, int offset) + { + super(message, offset); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(isValid()) + { + sb.append("REGISTRATION "); + + if(isSuccessful()) + { + sb.append("SUCCESS"); + + int units = getPayloadValue(); + + sb.append(" REFRESH IN:").append(units * 30).append("mins"); + } + else + { + sb.append("FAIL"); + + int units = getPayloadValue(); + + if(units == 0) + { + sb.append(" - UNAUTHORIZED DEVICE"); + } + else + { + sb.append(" REASON:").append(units); + } + } + } + else + { + sb.append("REGISTRATION ACKNOWLEDGE - ERROR - INVALID MESSAGE LENGTH"); + } + + return sb.toString(); + } + + /** + * Indicates if the registration succeeded (true) or failed (false) + */ + public boolean isSuccessful() + { + return !isAcknowledge(); + } + + /** + * Payload value. If successful, this is the number of 30 minute units until a registration + * refresh is required. If unsuccessful, this is the reason (0-unauthorized device). + */ + public int getPayloadValue() + { + return getMessage().getInt(TIMER_OR_REASON, getOffset()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/ars/UserDeRegistration.java b/src/main/java/io/github/dsheirer/module/decode/ip/ars/UserDeRegistration.java new file mode 100644 index 00000000..880994fe --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/ars/UserDeRegistration.java @@ -0,0 +1,44 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip.ars; + +import io.github.dsheirer.bits.BinaryMessage; + +public class UserDeRegistration extends ARSHeader +{ + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param message containing the header + * @param offset to the header within the message + */ + public UserDeRegistration(BinaryMessage message, int offset) + { + super(message, offset); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("ARS USER DE-REGISTRATION"); + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/ars/UserDeRegistrationAcknowledge.java b/src/main/java/io/github/dsheirer/module/decode/ip/ars/UserDeRegistrationAcknowledge.java new file mode 100644 index 00000000..0cb2e478 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/ars/UserDeRegistrationAcknowledge.java @@ -0,0 +1,44 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip.ars; + +import io.github.dsheirer.bits.BinaryMessage; + +public class UserDeRegistrationAcknowledge extends ARSHeader +{ + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param message containing the header + * @param offset to the header within the message + */ + public UserDeRegistrationAcknowledge(BinaryMessage message, int offset) + { + super(message, offset); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("ARS USER DE-REGISTRATION ACKNOWLEDGED"); + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/ars/UserRegistration.java b/src/main/java/io/github/dsheirer/module/decode/ip/ars/UserRegistration.java new file mode 100644 index 00000000..8208d8da --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/ars/UserRegistration.java @@ -0,0 +1,140 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip.ars; + +import io.github.dsheirer.bits.BinaryMessage; + +public class UserRegistration extends ARSHeader +{ + private static final int HEADER_EXTENSION_FLAG = 24; + private static final int[] EVENT = {25, 26}; + private static final int[] ENCODING = {27, 28, 29, 30, 31}; + private static final int DEVICE_IDENTIFIER_START = 24; + private static final int DEVICE_IDENTIFIER_START_EXTENDED_HEADER = 32; + + private static final int[] BYTE_VALUE = {0, 1, 2, 3, 4, 5, 6, 7}; + + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param message containing the header + * @param offset to the header within the message + */ + public UserRegistration(BinaryMessage message, int offset) + { + super(message, offset); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("USER REGISTRATION"); + + if(isValid()) + { + sb.append(" ").append(getPayload()); + } + else + { + sb.append(" - ERROR INVALID MESSAGE LENGTH"); + } + return sb.toString(); + } + + /** + * Device, user and password values contained in the registration packet. + */ + public String getPayload() + { + StringBuilder sb = new StringBuilder(); + sb.append("DEVICE:"); + + int pointer = getOffset(); + + if(hasHeaderExtension()) + { + pointer += DEVICE_IDENTIFIER_START_EXTENDED_HEADER; + } + else + { + pointer += DEVICE_IDENTIFIER_START; + } + + int identifierSize = getMessage().getInt(BYTE_VALUE, pointer += 8); + + if(identifierSize > 0) + { + for(int x = 0; x < identifierSize; x++) + { + sb.append(getCharacter(pointer += 8)); + } + } + else + { + sb.append("(none)"); + } + + sb.append(" USER:"); + + int userIdentifierSize = getMessage().getInt(BYTE_VALUE, pointer += 8); + + if(userIdentifierSize > 0) + { + for(int x = 0; x < userIdentifierSize; x++) + { + sb.append(getCharacter(pointer += 8)); + } + } + else + { + sb.append("(none)"); + } + + sb.append(" PASSWORD:"); + + int passwordSize = getMessage().getInt(BYTE_VALUE, pointer += 8); + + if(passwordSize > 0) + { + for(int x = 0; x < passwordSize; x++) + { + sb.append(getCharacter(pointer += 8)); + } + } + else + { + sb.append("(none)"); + } + + return sb.toString(); + } + + /** + * Returns a UTF-8 encoded character that starts at the specified offset + * + * @param offset of the start of the 8-bit UTF-8 encoded character + * @return character + */ + private char getCharacter(int offset) + { + return (char)getMessage().getByte(BYTE_VALUE, offset); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/ars/UserRegistrationAcknowledge.java b/src/main/java/io/github/dsheirer/module/decode/ip/ars/UserRegistrationAcknowledge.java new file mode 100644 index 00000000..a3178957 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/ars/UserRegistrationAcknowledge.java @@ -0,0 +1,88 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip.ars; + +import io.github.dsheirer.bits.BinaryMessage; + +public class UserRegistrationAcknowledge extends ARSHeader +{ + private static final int[] SESSION_TIME_OR_REASON = {25, 26, 27, 28, 29, 30, 31}; + + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param message containing the header + * @param offset to the header within the message + */ + public UserRegistrationAcknowledge(BinaryMessage message, int offset) + { + super(message, offset); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + if(isSuccessful()) + { + sb.append("USER REGISTRATION SUCCESS - SESSION TIME:").append(getSessionTime()); + } + else + { + sb.append("USER REGISTRATION FAILED - REASON:").append(getFailureReason()); + } + return sb.toString(); + } + + public boolean isSuccessful() + { + return !isAcknowledge(); + } + + public int getSessionTime() + { + if(hasHeaderExtension()) + { + return getMessage().getInt(SESSION_TIME_OR_REASON, getOffset()); + } + + return 0; + } + + public String getFailureReason() + { + if(hasHeaderExtension()) + { + int reason = getMessage().getInt(SESSION_TIME_OR_REASON, getOffset()); + + switch(reason) + { + case 0x01: + return "USER VALIDATION FAILED"; + case 0x02: + return "USER VALIDATION TIMEOUT"; + default: + return "TRANSMISSION FAILURE"; + } + } + + return "NONE"; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/ipv4/IPV4Header.java b/src/main/java/io/github/dsheirer/module/decode/ip/ipv4/IPV4Header.java new file mode 100644 index 00000000..d05c8c11 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/ipv4/IPV4Header.java @@ -0,0 +1,161 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip.ipv4; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.ipv4.IPV4Identifier; +import io.github.dsheirer.module.decode.ip.Header; +import io.github.dsheirer.module.decode.ip.IPProtocol; +import io.github.dsheirer.module.decode.p25.identifier.ipv4.APCO25IpAddress; + +public class IPV4Header extends Header +{ + private static final int[] VERSION = {0, 1, 2, 3}; + private static final int[] HEADER_LENGTH = {4, 5, 6, 7}; + private static final int[] TOTAL_LENGTH = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] PROTOCOL = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] FROM_ADDRESS = {96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, + 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127}; + private static final int[] TO_ADDRESS = {128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, + 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159}; + + private IPV4Identifier mFromAddress; + private IPV4Identifier mToAddress; + + public IPV4Header(BinaryMessage message, int offset) + { + super(message, offset); + checkValid(); + } + + /** + * Performs simple header and packet length validations relative to the packet offset within the binary message. + */ + private void checkValid() + { + int headerLength = getLength(); + + if(headerLength < 40) + { + setValid(false); + return; + } + + if(getMessage().size() < headerLength + getOffset()) + { + setValid(false); + return; + } + + int totalLength = getTotalLength(); + + if(totalLength < headerLength) + { + setValid(false); + return; + } + + if(getMessage().size() < totalLength + getOffset()) + { + setValid(false); + return; + } + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("IP FROM:").append(getFromAddress()); + sb.append(" TO:").append(getToAddress()); + return sb.toString(); + } + + /** + * Determines the IP version of the packet that starts at the indicated offset within the binary message. + * + * @param message message containing an IP packet + * @param offset to the start of the packet within the binary message + * @return + */ + public static int getIPVersion(BinaryMessage message, int offset) + { + return message.getInt(VERSION, offset); + } + + /** + * IP Version of this packet + */ + public int getIPVersion() + { + return getIPVersion(getMessage(), getOffset()); + } + + /** + * Length of this IPV4 header in bits (NOT BYTES) + */ + public int getLength() + { + return getMessage().getInt(HEADER_LENGTH, getOffset()) * 32; + } + + /** + * Total Length of this IPV4 packet in bits (NOT BYTES) + */ + public int getTotalLength() + { + return getMessage().getInt(TOTAL_LENGTH, getOffset()) * 8; + } + + /** + * Protocol for the payload being carried by this IP packet + */ + public IPProtocol getProtocol() + { + return IPProtocol.fromValue(getMessage().getInt(PROTOCOL, getOffset())); + } + + /** + * From IP Address + */ + public IPV4Identifier getFromAddress() + { + if(mFromAddress == null) + { + mFromAddress = APCO25IpAddress.createFrom(getMessage().getInt(FROM_ADDRESS, getOffset())); + } + + return mFromAddress; + } + + /** + * To IP Address + */ + public IPV4Identifier getToAddress() + { + if(mToAddress == null) + { + mToAddress = APCO25IpAddress.createTo(getMessage().getInt(TO_ADDRESS, getOffset())); + } + + return mToAddress; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/ipv4/IPV4Packet.java b/src/main/java/io/github/dsheirer/module/decode/ip/ipv4/IPV4Packet.java new file mode 100644 index 00000000..33eeb32e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/ipv4/IPV4Packet.java @@ -0,0 +1,112 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip.ipv4; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.module.decode.ip.IPacket; +import io.github.dsheirer.module.decode.ip.Packet; +import io.github.dsheirer.module.decode.ip.PacketMessageFactory; +import io.github.dsheirer.module.decode.ip.UnknownPacket; + +/** + * IPV4 packet + */ +public class IPV4Packet extends Packet +{ + private IPV4Header mHeader; + private IPacket mPayload; + + /** + * Constructs an IPV4 packet parser + * + * @param message containing an IPV4 packet + * @param offset to the start of the packet within the binary message + */ + public IPV4Packet(BinaryMessage message, int offset) + { + super(message, offset); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + if(getHeader().isValid()) + { + sb.append(getHeader()); + } + else + { + sb.append("IPV4 - INVALID HEADER"); + sb.append(" HDR:").append(getHeader().getMessage().toHexString()); + } + + if(hasPayload()) + { + if(getPayload() instanceof UnknownPacket) + { + sb.append(" PROTOCOL:").append(getHeader().getProtocol()); + } + + sb.append(" ").append(getPayload().toString()); + } + else + { + sb.append(" NO PAYLOAD!"); + } + + return sb.toString(); + } + + @Override + public IPV4Header getHeader() + { + if(mHeader == null) + { + mHeader = new IPV4Header(getMessage(), getOffset()); + } + + return mHeader; + } + + /** + * Payload packet for this IP packet. + * + * @return payload packet or null + */ + @Override + public IPacket getPayload() + { + if(mPayload == null) + { + if(getHeader().isValid()) + { + mPayload = PacketMessageFactory.create(getHeader().getProtocol(), getMessage(), + getOffset() + getHeader().getLength()); + } + else + { + mPayload = new UnknownPacket(getMessage(), getOffset()); + } + } + + return mPayload; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/udp/UDPHeader.java b/src/main/java/io/github/dsheirer/module/decode/ip/udp/UDPHeader.java new file mode 100644 index 00000000..98376267 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/udp/UDPHeader.java @@ -0,0 +1,78 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip.udp; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.module.decode.ip.Header; + +public class UDPHeader extends Header +{ + private static final int[] SOURCE_PORT = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] DESTINATION_PORT = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param message containing the header + * @param offset to the header within the message + */ + public UDPHeader(BinaryMessage message, int offset) + { + super(message, offset); + checkValid(); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("UDP PORT FROM:").append(getSourcePort()); + sb.append(" TO:").append(getDestinationPort()); + return sb.toString(); + } + + private void checkValid() + { + setValid(getMessage().size() >= getOffset() + getLength()); + } + + @Override + public int getLength() + { + return 64; + } + + /** + * Source UDP port number + * @return + */ + public int getSourcePort() + { + return getMessage().getInt(SOURCE_PORT, getOffset()); + } + + /** + * Destination UDP port number + */ + public int getDestinationPort() + { + return getMessage().getInt(DESTINATION_PORT, getOffset()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/udp/UDPPacket.java b/src/main/java/io/github/dsheirer/module/decode/ip/udp/UDPPacket.java new file mode 100644 index 00000000..570e693d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ip/udp/UDPPacket.java @@ -0,0 +1,88 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ip.udp; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.module.decode.ip.IPacket; +import io.github.dsheirer.module.decode.ip.Packet; +import io.github.dsheirer.module.decode.ip.PacketMessageFactory; +import io.github.dsheirer.module.decode.ip.UnknownPacket; + +public class UDPPacket extends Packet +{ + private UDPHeader mHeader; + private IPacket mPayload; + + /** + * Constructs a parser for a header contained within a binary message starting at the offset. + * + * @param message containing the packet + * @param offset to the packet within the message + */ + public UDPPacket(BinaryMessage message, int offset) + { + super(message, offset); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + if(getHeader().isValid()) + { + sb.append(getHeader()); + } + else + { + sb.append("INVALID HEADER"); + } + + sb.append(" ").append(getPayload()); + + return sb.toString(); + } + + @Override + public UDPHeader getHeader() + { + if(mHeader == null) + { + mHeader = new UDPHeader(getMessage(), getOffset()); + } + + return mHeader; + } + + @Override + public IPacket getPayload() + { + if(mPayload == null && getHeader().isValid()) + { + mPayload = PacketMessageFactory.createUDPPayload(getHeader().getDestinationPort(), getMessage(), getOffset() + getHeader().getLength()); + } + else + { + //Set the offset to the message offset plus 64 bits for UDP header + mPayload = new UnknownPacket(getMessage(), getOffset() + 64); + } + + return mPayload; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/lj1200/FunctionAndReplyCode.java b/src/main/java/io/github/dsheirer/module/decode/lj1200/FunctionAndReplyCode.java new file mode 100644 index 00000000..b45e6645 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/lj1200/FunctionAndReplyCode.java @@ -0,0 +1,52 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.lj1200; + +/** + * LoJack Function and Reply Code + */ +public class FunctionAndReplyCode +{ + private LJ1200Message.Function mFunction; + private String mReplyCode; + + public FunctionAndReplyCode(LJ1200Message.Function function, String replyCode) + { + mFunction = function; + mReplyCode = replyCode; + } + + public LJ1200Message.Function getFunction() + { + return mFunction; + } + + public String getReplyCode() + { + return mReplyCode; + } + + @Override + public String toString() + { + return "FUNCTION:" + mFunction + " REPLY CODE:" + mReplyCode; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/lj1200/FunctionAndReplyCodeIdentifier.java b/src/main/java/io/github/dsheirer/module/decode/lj1200/FunctionAndReplyCodeIdentifier.java new file mode 100644 index 00000000..1372e886 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/lj1200/FunctionAndReplyCodeIdentifier.java @@ -0,0 +1,46 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.lj1200; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.protocol.Protocol; + +public class FunctionAndReplyCodeIdentifier extends Identifier +{ + public FunctionAndReplyCodeIdentifier(FunctionAndReplyCode functionAndReplyCode) + { + super(functionAndReplyCode, IdentifierClass.NETWORK, Form.LOJACK, Role.TO); + } + + @Override + public Protocol getProtocol() + { + return Protocol.LOJACK; + } + + public static FunctionAndReplyCodeIdentifier create(LJ1200Message.Function function, String replyCode) + { + return new FunctionAndReplyCodeIdentifier(new FunctionAndReplyCode(function, replyCode)); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200CallEvent.java b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200CallEvent.java index c90d3df1..15713a3b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200CallEvent.java +++ b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200CallEvent.java @@ -1,20 +1,22 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.lj1200; import io.github.dsheirer.alias.Alias; @@ -24,36 +26,26 @@ import io.github.dsheirer.module.decode.event.CallEvent; public class LJ1200CallEvent extends CallEvent { - public LJ1200CallEvent( CallEventType callEventType, - AliasList aliasList, - String fromID, - String toID, - String details ) + public LJ1200CallEvent(CallEventType callEventType, AliasList aliasList, String fromID, String toID, String details) { - super( DecoderType.LJ_1200, callEventType, - aliasList, fromID, toID, details ); + super(DecoderType.LJ_1200, callEventType, aliasList, fromID, toID, details); } - private Alias getAlias( String ident ) + private Alias getAlias(String ident) { - if( hasAliasList() ) - { - return getAliasList().getFleetsyncAlias( ident ); - } - return null; } - + @Override public Alias getFromIDAlias() { - return getAlias( getFromID() ); + return getAlias(getFromID()); } @Override public Alias getToIDAlias() { - return getAlias( getToID() ); + return getAlias(getToID()); } @Override @@ -79,30 +71,30 @@ public class LJ1200CallEvent extends CallEvent private String mToID; private String mDetails; - public Builder( CallEventType callEventType ) + public Builder(CallEventType callEventType) { mCallEventType = callEventType; } - - public Builder aliasList( AliasList aliasList ) + + public Builder aliasList(AliasList aliasList) { mAliasList = aliasList; return this; } - - public Builder from( String val ) + + public Builder from(String val) { mFromID = val; return this; -} + } - public Builder details( String details ) + public Builder details(String details) { mDetails = details; return this; } - - public Builder to( String toID ) + + public Builder to(String toID) { mToID = toID; return this; @@ -110,29 +102,25 @@ public class LJ1200CallEvent extends CallEvent public LJ1200CallEvent build() { - return new LJ1200CallEvent( this ); + return new LJ1200CallEvent(this); } } /** * Private constructor for the builder */ - private LJ1200CallEvent( Builder builder ) + private LJ1200CallEvent(Builder builder) { - this( builder.mCallEventType, - builder.mAliasList, - builder.mFromID, - builder.mToID, - builder.mDetails ); + this(builder.mCallEventType, + builder.mAliasList, + builder.mFromID, + builder.mToID, + builder.mDetails); } - - public static LJ1200CallEvent getLJ1200Event( LJ1200Message message ) + + public static LJ1200CallEvent getLJ1200Event(LJ1200Message message) { CallEventType type = CallEventType.COMMAND; - - return new LJ1200CallEvent.Builder( type ) - .to( message.getAddress() ) - .details( message.getMessage() ) - .build(); + return new LJ1200CallEvent.Builder(type).to(message.getAddress()).details(message.toString()).build(); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200Decoder.java b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200Decoder.java index 62842393..a942b6aa 100644 --- a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200Decoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200Decoder.java @@ -1,21 +1,24 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.module.decode.lj1200; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.bits.IBinarySymbolProcessor; import io.github.dsheirer.bits.MessageFramer; import io.github.dsheirer.bits.SyncPattern; @@ -35,26 +38,26 @@ public class LJ1200Decoder extends AbstractAFSKDecoder implements IBinarySymbolP private MessageFramer mTransponderMessageFramer; private LJ1200MessageProcessor mMessageProcessor; - protected LJ1200Decoder(AFSK1200Decoder decoder, AliasList aliasList) + protected LJ1200Decoder(AFSK1200Decoder decoder) { super(decoder); - init(aliasList); + init(); } - public LJ1200Decoder(AliasList aliasList) + public LJ1200Decoder() { super(AFSK1200Decoder.Output.NORMAL); - init(aliasList); + init(); } - private void init(AliasList aliasList) + private void init() { getDecoder().setSymbolProcessor(this); mTowerMessageFramer = new MessageFramer(SyncPattern.LJ1200.getPattern(), MESSAGE_LENGTH); mTransponderMessageFramer = new MessageFramer(SyncPattern.LJ1200_TRANSPONDER.getPattern(), MESSAGE_LENGTH); - mMessageProcessor = new LJ1200MessageProcessor(aliasList); + mMessageProcessor = new LJ1200MessageProcessor(); mTowerMessageFramer.addMessageListener(mMessageProcessor); mTransponderMessageFramer.addMessageListener(mMessageProcessor); mMessageProcessor.setMessageListener(getMessageListener()); @@ -74,10 +77,10 @@ public class LJ1200Decoder extends AbstractAFSKDecoder implements IBinarySymbolP } @Override - public void receive(boolean symbol) + public void process(boolean symbol) { - mTowerMessageFramer.receive(symbol); - mTransponderMessageFramer.receive(symbol); + mTowerMessageFramer.process(symbol); + mTransponderMessageFramer.process(symbol); } public MessageFramer getTowerMessageFramer() diff --git a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderInstrumented.java index 735dd2d7..c92aeee6 100644 --- a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderInstrumented.java @@ -29,7 +29,7 @@ public class LJ1200DecoderInstrumented extends LJ1200Decoder implements IInstrum public LJ1200DecoderInstrumented() { - super(new AFSK1200DecoderInstrumented(false), null); + super(new AFSK1200DecoderInstrumented(false)); } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderState.java index e479c434..18b156d6 100644 --- a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderState.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,20 +15,19 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.module.decode.lj1200; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.alias.id.AliasIDType; -import io.github.dsheirer.channel.metadata.AliasedStringAttributeMonitor; -import io.github.dsheirer.channel.metadata.Attribute; import io.github.dsheirer.channel.state.DecoderState; import io.github.dsheirer.channel.state.DecoderStateEvent; import io.github.dsheirer.channel.state.DecoderStateEvent.Event; import io.github.dsheirer.channel.state.State; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.MutableIdentifierCollection; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; +import io.github.dsheirer.module.decode.event.DecodeEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,14 +43,8 @@ public class LJ1200DecoderState extends DecoderState private Set mAddresses = new TreeSet(); - private AliasedStringAttributeMonitor mAddressAttribute; - - public LJ1200DecoderState(AliasList aliasList) + public LJ1200DecoderState() { - super(aliasList); - - mAddressAttribute = new AliasedStringAttributeMonitor(Attribute.SECONDARY_ADDRESS_TO, - getAttributeChangeRequestListener(), getAliasList(), AliasIDType.SITE); } @Override @@ -60,27 +54,49 @@ public class LJ1200DecoderState extends DecoderState } @Override - public void receive(Message message) + public void receive(IMessage message) { if(message instanceof LJ1200Message) { - LJ1200Message lj = (LJ1200Message) message; + LJ1200Message lj = (LJ1200Message)message; if(lj.isValid()) { String address = lj.getAddress(); - mAddressAttribute.process(address); mAddresses.add(address); - broadcast(LJ1200CallEvent.getLJ1200Event(lj)); + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(lj.getIdentifiers()); + DecodeEvent event = DecodeEvent.builder(System.currentTimeMillis()) + .identifiers(ic) + .channel(getCurrentChannel()) + .details("LOJACK") + .eventDescription(lj.toString()) + .build(); + + broadcast(event); broadcast(new DecoderStateEvent(this, Event.DECODE, State.DATA)); } } else if(message instanceof LJ1200TransponderMessage) { + LJ1200TransponderMessage transponder = (LJ1200TransponderMessage)message; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(transponder.getIdentifiers()); + + DecodeEvent transponderEvent = DecodeEvent.builder(System.currentTimeMillis()) + .identifiers(ic) + .channel(getCurrentChannel()) + .details("LOJACK TRANSPONDER") + .eventDescription(transponder.toString()) + .build(); + + broadcast(transponderEvent); } } @@ -120,9 +136,9 @@ public class LJ1200DecoderState extends DecoderState } } - private void resetState() + protected void resetState() { - mAddressAttribute.reset(); + super.resetState(); } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200Message.java b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200Message.java index 962d1a82..607894ec 100644 --- a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200Message.java +++ b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200Message.java @@ -1,29 +1,30 @@ /******************************************************************************* * 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 ******************************************************************************/ package io.github.dsheirer.module.decode.lj1200; -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.edac.CRC; import io.github.dsheirer.edac.CRCLJ; -import io.github.dsheirer.map.Plottable; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.esn.ESNIdentifier; import io.github.dsheirer.message.Message; +import io.github.dsheirer.protocol.Protocol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,388 +33,305 @@ import java.util.List; public class LJ1200Message extends Message { - private final static Logger mLog = LoggerFactory.getLogger( LJ1200Message.class ); - - public static final String[] REPLY_CODE = { "0","1","2","3","4","5","6","7", - "8","9","A","C","D","E","F","G","H","J","K","L","M","N","P","Q","R","S", - "T","U","V","W","X","Y" }; + private final static Logger mLog = LoggerFactory.getLogger(LJ1200Message.class); - public static int[] SYNC = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 }; + public static final String[] REPLY_CODE = {"0", "1", "2", "3", "4", "5", "6", "7", + "8", "9", "A", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", + "T", "U", "V", "W", "X", "Y"}; - public static int[] VRC = { 23,22,21,20,19,18,17,16 }; - - public static int[] LRC = { 31,30,29,28,27,26,25,24 }; + public static int[] SYNC = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + public static int[] VRC = {23, 22, 21, 20, 19, 18, 17, 16}; + public static int[] LRC = {31, 30, 29, 28, 27, 26, 25, 24}; + public static int[] FUNCTION = {35, 34, 33, 32}; + public static int[] ADDRESS = {63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, + 42, 41, 40, 39, 38, 37, 36}; + public static int[] REPLY_1 = {39, 38, 37, 36, 43}; + public static int[] REPLY_2 = {42, 41, 40, 47, 46}; + public static int[] REPLY_3 = {45, 44, 51, 50, 49}; + public static int[] REPLY_4 = {48, 55, 54, 53, 52}; + public static int[] REPLY_5 = {59, 58, 57, 56, 63}; - public static int[] FUNCTION = { 35,34,33,32 }; + public static int[] MESSAGE_CRC = {79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64}; - public static int[] ADDRESS = { 63,62,61,60,59,58,57,56,55,54,53,52,51,50, - 49,48,47,46,45,44,43,42,41,40,39,38,37,36 }; - - public static int[] REPLY_1 = { 39,38,37,36,43 }; - public static int[] REPLY_2 = { 42,41,40,47,46 }; - public static int[] REPLY_3 = { 45,44,51,50,49 }; - public static int[] REPLY_4 = { 48,55,54,53,52 }; - public static int[] REPLY_5 = { 59,58,57,56,63 }; - - public static int[] MESSAGE_CRC = { 79,78,77,76,75,74,73,72,71,70,69,68,67, - 66,65,64 }; - private BinaryMessage mMessage; - private AliasList mAliasList; private CRC mCRC; - - public LJ1200Message( BinaryMessage message, AliasList list ) + private FunctionAndReplyCodeIdentifier mFunctionAndReplyCodeIdentifier; + private ESNIdentifier mTransponderIdentifier; + private List mIdentifiers; + + public LJ1200Message(BinaryMessage message) { - mMessage = message; - mAliasList = list; - + mMessage = message; + checkCRC(); - switch( mCRC ) + switch(mCRC) { - case CORRECTED: - mLog.debug( "CORR:" + message.toString() ); - break; - case FAILED_CRC: - mLog.debug( "FAIL:" + message.toString() ); - break; - case PASSED: - mLog.debug( "PASS:" + message.toString() ); - break; - default: - break; + case CORRECTED: + mLog.debug("CORR:" + message.toString()); + break; + case FAILED_CRC: + mLog.debug("FAIL:" + message.toString()); + break; + case PASSED: + mLog.debug("PASS:" + message.toString()); + break; + default: + break; } } - + private void checkCRC() { - mCRC = CRCLJ.checkAndCorrect( mMessage ); + mCRC = CRCLJ.checkAndCorrect(mMessage); } - + public boolean isValid() { - return mCRC == CRC.PASSED || mCRC == CRC.CORRECTED; + return mCRC == CRC.PASSED || mCRC == CRC.CORRECTED; } - + + public FunctionAndReplyCodeIdentifier getFunctionAndReplyCodeIdentifier() + { + if(mFunctionAndReplyCodeIdentifier == null) + { + mFunctionAndReplyCodeIdentifier = FunctionAndReplyCodeIdentifier.create(getFunction(), getReplyCode()); + } + + return mFunctionAndReplyCodeIdentifier; + } + + public ESNIdentifier getTransponder() + { + if(mTransponderIdentifier == null) + { + mTransponderIdentifier = ESNIdentifier.create(getAddress(), Protocol.LOJACK, Role.TO); + } + + return mTransponderIdentifier; + } + public String getVRC() { - return mMessage.getHex( VRC, 2 ); + return mMessage.getHex(VRC, 2); } - + public String getLRC() { - return mMessage.getHex( LRC, 2 ); + return mMessage.getHex(LRC, 2); } - + public String getCRC() { - return mMessage.getHex( MESSAGE_CRC, 4 ); + return mMessage.getHex(MESSAGE_CRC, 4); } - + public Function getFunction() { - return Function.fromValue( mMessage.getInt( FUNCTION ), - mMessage.getInt( REPLY_3 ) ); + return Function.fromValue(mMessage.getInt(FUNCTION), mMessage.getInt(REPLY_3)); } - + public String getAddress() { - return mMessage.getHex( ADDRESS, 7 ); - } - - public Alias getTransponderAlias() - { - if( mAliasList != null ) - { - return mAliasList.getESNAlias( getAddress() ); - } - - return null; - } - - public Alias getFunctionAndIDAlias() - { - if( mAliasList != null ) - { - return mAliasList.getLoJackAlias( getFunction(), getReplyCode() ); - } - - return null; + return mMessage.getHex(ADDRESS, 7); } /** * 5 character reply code for function E and F, transponder activation. + * * @return - reply code */ public String getReplyCode() { - StringBuilder sb = new StringBuilder(); - - sb.append( REPLY_CODE[ mMessage.getInt( REPLY_1 ) ] ); - sb.append( REPLY_CODE[ mMessage.getInt( REPLY_2 ) ] ); - sb.append( REPLY_CODE[ mMessage.getInt( REPLY_3 ) ] ); - sb.append( REPLY_CODE[ mMessage.getInt( REPLY_4 ) ] ); - sb.append( REPLY_CODE[ mMessage.getInt( REPLY_5 ) ] ); - - return sb.toString(); + StringBuilder sb = new StringBuilder(); + + sb.append(REPLY_CODE[mMessage.getInt(REPLY_1)]); + sb.append(REPLY_CODE[mMessage.getInt(REPLY_2)]); + sb.append(REPLY_CODE[mMessage.getInt(REPLY_3)]); + sb.append(REPLY_CODE[mMessage.getInt(REPLY_4)]); + sb.append(REPLY_CODE[mMessage.getInt(REPLY_5)]); + + return sb.toString(); } - + @Override public String toString() { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); - Function function = getFunction(); - - sb.append( "FUNCTION: " ); - sb.append( function.toString() ); + Function function = getFunction(); - switch( function ) - { - case F1_SITE_ID: - sb.append( " SITE [" ); - break; - case F1_SPEED_UP: - case F2_TEST: - case F3_DEACTIVATE: - case F4_ACTIVATE: - case FF_TRACK_PULSE: - default: - sb.append( " REPLY CODE [" ); - break; - } - - sb.append( getReplyCode() ); + sb.append("FUNCTION: "); + sb.append(function.toString()); - Alias site = getFunctionAndIDAlias(); - - if( site != null ) - { - sb.append( "/" ); - sb.append( site.getName() ); - } - - sb.append( "] ADDRESS [" ); - sb.append( getAddress() ); - sb.append( "] VRC [" + getVRC() ); - sb.append( "] LRC [" + getLRC() ); - sb.append( "] CRC [" + getCRC() ); - sb.append( "]" ); + switch(function) + { + case F1_SITE_ID: + sb.append(" SITE ["); + break; + case F1_SPEED_UP: + case F2_TEST: + case F3_DEACTIVATE: + case F4_ACTIVATE: + case FF_TRACK_PULSE: + default: + sb.append(" REPLY CODE ["); + break; + } - return sb.toString(); + sb.append(getReplyCode()); + + sb.append("] ADDRESS ["); + sb.append(getAddress()); + sb.append("] VRC [" + getVRC()); + sb.append("] LRC [" + getLRC()); + sb.append("] CRC [" + getCRC()); + sb.append("]"); + + return sb.toString(); } /** * Pads spaces onto the end of the value to make it 'places' long */ - public String pad( String value, int places, String padCharacter ) + public String pad(String value, int places, String padCharacter) { - StringBuilder sb = new StringBuilder(); - - sb.append( value ); - - while( sb.length() < places ) - { - sb.append( padCharacter ); - } - - return sb.toString(); + StringBuilder sb = new StringBuilder(); + + sb.append(value); + + while(sb.length() < places) + { + sb.append(padCharacter); + } + + return sb.toString(); } - + /** * Pads an integer value with additional zeroes to make it decimalPlaces long */ - public String format( int number, int decimalPlaces ) + public String format(int number, int decimalPlaces) { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); - int paddingRequired = decimalPlaces - ( String.valueOf( number ).length() ); + int paddingRequired = decimalPlaces - (String.valueOf(number).length()); - for( int x = 0; x < paddingRequired; x++) - { - sb.append( "0" ); - } - - sb.append( number ); - - return sb.toString(); - } - - @Override - public String getBinaryMessage() - { - return mMessage.toString(); - } + for(int x = 0; x < paddingRequired; x++) + { + sb.append("0"); + } - @Override - public String getProtocol() - { - return "LJ-1200"; + sb.append(number); + + return sb.toString(); } - @Override - public String getEventType() + @Override + public Protocol getProtocol() { - if( getFunction() == Function.F1_SPEED_UP ) - { - return "TRANSPONDER"; - } - - return "TOWER"; + return Protocol.LOJACK; } - @Override - public String getFromID() + public enum Function { - return getReplyCode(); + /* Big Endian Format */ + F0_UNKNOWN("0-UNKNOWN"), + F1_SITE_ID("1Y-SITE ID"), + F1_SPEED_UP("1-SPEED UP"), + F2_TEST("2-TOWER TEST"), + F3_DEACTIVATE("3-DEACTIVATE"), + F4_ACTIVATE("4-ACTIVATE"), + F5_TRANSPONDER_TEST_REPLY("5-TRANSPONDER TEST REPLY"), + F6_TRANSPONDER_TEST_COMMAND("6-TRANSPONDER TEST COMMAND"), + F7_QUIET_COMMAND("7-QUIET COMMAND"), + F8_UNKNOWN("8-UNKNOWN"), + F9_UNKNOWN("9-UNKNOWN"), + FA_UNKNOWN("A-UNKNOWN"), + FB_UNKNOWN("B-UNKNOWN"), + FC_ALT_DEACTIVATE("C-ALT-DEACTIVATE"), + FD_UNKNOWN("D-UNKNOWN"), + FE_UNKNOWN("E-UNKNOWN"), + FF_TRACK_PULSE("F-TRACK PULSE"), + + UNKNOWN("UNKNOWN"); + + private String mLabel; + + private Function(String label) + { + mLabel = label; + } + + public String getLabel() + { + return mLabel; + } + + public String toString() + { + return getLabel(); + } + + public static Function fromValue(int value, int replyCodeDigit3) + { + switch(value) + { + case 0: + return Function.F0_UNKNOWN; + case 1: + if(replyCodeDigit3 == 31) /* 'Y' middle character */ + { + return Function.F1_SITE_ID; + } + else + { + return Function.F1_SPEED_UP; + } + case 2: + return Function.F2_TEST; + case 3: + return Function.F3_DEACTIVATE; + case 4: + return Function.F4_ACTIVATE; + case 5: + return Function.F5_TRANSPONDER_TEST_REPLY; + case 6: + return Function.F6_TRANSPONDER_TEST_COMMAND; + case 7: + return Function.F7_QUIET_COMMAND; + case 8: + return Function.F8_UNKNOWN; + case 9: + return Function.F9_UNKNOWN; + case 10: + return Function.FA_UNKNOWN; + case 11: + return Function.FB_UNKNOWN; + case 12: + return Function.FC_ALT_DEACTIVATE; + case 13: + return Function.FD_UNKNOWN; + case 14: + return Function.FE_UNKNOWN; + case 15: + return Function.FF_TRACK_PULSE; + default: + return Function.UNKNOWN; + } + } } - @Override - public Alias getFromIDAlias() + @Override + public List getIdentifiers() { - return getFunctionAndIDAlias(); - } + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getFunctionAndReplyCodeIdentifier()); + mIdentifiers.add(getTransponder()); + } - @Override - public String getToID() - { - return getAddress(); - } - - @Override - public Alias getToIDAlias() - { - return null; + return mIdentifiers; } - - @Override - public String getMessage() - { - return toString(); - } - - @Override - public String getErrorStatus() - { - return mCRC.getDisplayText(); - } - - @Override - public Plottable getPlottable() - { - return null; - } - - public enum Function - { - /* Big Endian Format */ - F0_UNKNOWN( "0-UNKNOWN" ), - F1_SITE_ID( "1Y-SITE ID" ), - F1_SPEED_UP( "1-SPEED UP" ), - F2_TEST( "2-TOWER TEST" ), - F3_DEACTIVATE( "3-DEACTIVATE" ), - F4_ACTIVATE( "4-ACTIVATE" ), - F5_TRANSPONDER_TEST_REPLY( "5-TRANSPONDER TEST REPLY" ), - F6_TRANSPONDER_TEST_COMMAND( "6-TRANSPONDER TEST COMMAND" ), - F7_QUIET_COMMAND( "7-QUIET COMMAND" ), - F8_UNKNOWN( "8-UNKNOWN" ), - F9_UNKNOWN( "9-UNKNOWN" ), - FA_UNKNOWN( "A-UNKNOWN" ), - FB_UNKNOWN( "B-UNKNOWN" ), - FC_ALT_DEACTIVATE( "C-ALT-DEACTIVATE" ), - FD_UNKNOWN( "D-UNKNOWN" ), - FE_UNKNOWN( "E-UNKNOWN" ), - FF_TRACK_PULSE( "F-TRACK PULSE" ), - - UNKNOWN( "UNKNOWN" ); - - private String mLabel; - - private Function( String label ) - { - mLabel = label; - } - - public String getLabel() - { - return mLabel; - } - - public String toString() - { - return getLabel(); - } - - public static Function fromValue( int value, int replyCodeDigit3 ) - { - switch( value ) - { - case 0: - return Function.F0_UNKNOWN; - case 1: - if( replyCodeDigit3 == 31 ) /* 'Y' middle character */ - { - return Function.F1_SITE_ID; - } - else - { - return Function.F1_SPEED_UP; - } - case 2: - return Function.F2_TEST; - case 3: - return Function.F3_DEACTIVATE; - case 4: - return Function.F4_ACTIVATE; - case 5: - return Function.F5_TRANSPONDER_TEST_REPLY; - case 6: - return Function.F6_TRANSPONDER_TEST_COMMAND; - case 7: - return Function.F7_QUIET_COMMAND; - case 8: - return Function.F8_UNKNOWN; - case 9: - return Function.F9_UNKNOWN; - case 10: - return Function.FA_UNKNOWN; - case 11: - return Function.FB_UNKNOWN; - case 12: - return Function.FC_ALT_DEACTIVATE; - case 13: - return Function.FD_UNKNOWN; - case 14: - return Function.FE_UNKNOWN; - case 15: - return Function.FF_TRACK_PULSE; - default: - return Function.UNKNOWN; - } - } - } - - /** - * Provides a listing of aliases contained in the message. - */ - public List getAliases() - { - List aliases = new ArrayList(); - - Alias functionAndID = getFunctionAndIDAlias(); - - if( functionAndID != null ) - { - aliases.add( functionAndID ); - } - - Alias transponder = getTransponderAlias(); - - if( transponder != null ) - { - aliases.add( transponder ); - } - - return aliases; - } } diff --git a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200MessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200MessageFilter.java index 64faf7c9..96c4916f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200MessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200MessageFilter.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,35 +19,34 @@ package io.github.dsheirer.module.decode.lj1200; import io.github.dsheirer.filter.Filter; import io.github.dsheirer.filter.FilterElement; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; import java.util.Collections; import java.util.List; -public class LJ1200MessageFilter extends Filter +public class LJ1200MessageFilter extends Filter { - - public LJ1200MessageFilter() - { - super( "LJ-1200 Message Filter" ); - } - - @Override - public boolean passes( Message message ) + + public LJ1200MessageFilter() { - return mEnabled && canProcess( message ); + super("LJ-1200 Message Filter"); } - @Override - public boolean canProcess( Message message ) + @Override + public boolean passes(IMessage message) { - return message instanceof LJ1200Message || - message instanceof LJ1200TransponderMessage; + return mEnabled && canProcess(message); } - @Override + @Override + public boolean canProcess(IMessage message) + { + return message instanceof LJ1200Message || message instanceof LJ1200TransponderMessage; + } + + @Override public List> getFilterElements() { - return Collections.EMPTY_LIST; + return Collections.EMPTY_LIST; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200MessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200MessageProcessor.java index 4b212b5e..a3fc2645 100644 --- a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200MessageProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200MessageProcessor.java @@ -1,82 +1,78 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.lj1200; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.sample.Listener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class LJ1200MessageProcessor implements Listener +public class LJ1200MessageProcessor implements Listener { - private final static Logger mLog = - LoggerFactory.getLogger( LJ1200MessageProcessor.class ); + private final static Logger mLog = LoggerFactory.getLogger(LJ1200MessageProcessor.class); - public static int[] SYNC = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 }; + public static int[] SYNC = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; - public static int SYNC_TOWER = 0x550F; - public static int SYNC_TRANSPONDER = 0x2AD5; + public static int SYNC_TOWER = 0x550F; + public static int SYNC_TRANSPONDER = 0x2AD5; - private Listener mMessageListener; - - private AliasList mAliasList; - - public LJ1200MessageProcessor( AliasList list ) - { - mAliasList = list; - } - - public void dispose() - { - mAliasList = null; - mMessageListener = null; - } - - @Override - public void receive( BinaryMessage buffer ) + private Listener mMessageListener; + + public LJ1200MessageProcessor() { - int sync = buffer.getInt( SYNC ); - - if( sync == SYNC_TOWER ) - { - if( mMessageListener != null ) - { - mMessageListener.receive( new LJ1200Message( buffer, mAliasList ) ); - } - } - else if( sync == SYNC_TRANSPONDER ) - { - if( mMessageListener != null ) - { - mMessageListener.receive( new LJ1200TransponderMessage( buffer, mAliasList ) ); - } - } } - - public void setMessageListener( Listener listener ) + + public void dispose() { - mMessageListener = listener; + mMessageListener = null; + } + + @Override + public void receive(CorrectedBinaryMessage message) + { + int sync = message.getInt(SYNC); + + if(sync == SYNC_TOWER) + { + if(mMessageListener != null) + { + mMessageListener.receive(new LJ1200Message(message)); + } + } + else if(sync == SYNC_TRANSPONDER) + { + if(mMessageListener != null) + { + mMessageListener.receive(new LJ1200TransponderMessage(message)); + } + } + } + + public void setMessageListener(Listener listener) + { + mMessageListener = listener; } public void removeMessageListener() { - mMessageListener = null; + mMessageListener = null; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200TransponderMessage.java b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200TransponderMessage.java index 2ebd0b70..33654be2 100644 --- a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200TransponderMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200TransponderMessage.java @@ -1,194 +1,145 @@ /******************************************************************************* * 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 ******************************************************************************/ package io.github.dsheirer.module.decode.lj1200; -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.edac.CRC; -import io.github.dsheirer.map.Plottable; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.message.Message; +import io.github.dsheirer.protocol.Protocol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.List; public class LJ1200TransponderMessage extends Message { - private final static Logger mLog = LoggerFactory.getLogger( LJ1200TransponderMessage.class ); + private final static Logger mLog = LoggerFactory.getLogger(LJ1200TransponderMessage.class); - public static int[] SYNC = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 }; + public static int[] SYNC = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; - public static int[] MESSAGE_CRC = { 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79 }; - - private static SimpleDateFormat mSDF = new SimpleDateFormat( "yyyyMMdd HHmmss" ); + public static int[] MESSAGE_CRC = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; - private BinaryMessage mMessage; - private AliasList mAliasList; + private static SimpleDateFormat mSDF = new SimpleDateFormat("yyyyMMdd HHmmss"); + + private CorrectedBinaryMessage mMessage; private CRC mCRC; - - public LJ1200TransponderMessage( BinaryMessage message, AliasList list ) + + public LJ1200TransponderMessage(CorrectedBinaryMessage message) { - mMessage = message; - mAliasList = list; - + mMessage = message; + checkCRC(); - switch( mCRC ) + switch(mCRC) { - case CORRECTED: - mLog.debug( "CORR:" + message.toString() ); - break; - case FAILED_CRC: - mLog.debug( "FAIL:" + message.toString() ); - break; - case PASSED: - mLog.debug( "PASS:" + message.toString() ); - break; - case UNKNOWN: - mLog.debug( "UNKNOWN:" + message.toString() ); - break; - default: - break; + case CORRECTED: + mLog.debug("CORR:" + message.toString()); + break; + case FAILED_CRC: + mLog.debug("FAIL:" + message.toString()); + break; + case PASSED: + mLog.debug("PASS:" + message.toString()); + break; + case UNKNOWN: + mLog.debug("UNKNOWN:" + message.toString()); + break; + default: + break; } } - + private void checkCRC() { - mCRC = CRC.UNKNOWN; + mCRC = CRC.UNKNOWN; } - + public boolean isValid() { // return mCRC == CRC.PASSED || mCRC == CRC.CORRECTED; - - /* Override validity check until proper CRC is implemented */ - return true; + + /* Override validity check until proper CRC is implemented */ + return true; } - + public String getCRC() { - return mMessage.getHex( MESSAGE_CRC, 4 ); + return mMessage.getHex(MESSAGE_CRC, 4); } - + @Override public String toString() { - StringBuilder sb = new StringBuilder(); - - sb.append( "TRANSPONDER TEST" ); + StringBuilder sb = new StringBuilder(); - return sb.toString(); + sb.append("TRANSPONDER TEST"); + + return sb.toString(); } /** * Pads spaces onto the end of the value to make it 'places' long */ - public String pad( String value, int places, String padCharacter ) + public String pad(String value, int places, String padCharacter) { - StringBuilder sb = new StringBuilder(); - - sb.append( value ); - - while( sb.length() < places ) - { - sb.append( padCharacter ); - } - - return sb.toString(); + StringBuilder sb = new StringBuilder(); + + sb.append(value); + + while(sb.length() < places) + { + sb.append(padCharacter); + } + + return sb.toString(); } - + /** * Pads an integer value with additional zeroes to make it decimalPlaces long */ - public String format( int number, int decimalPlaces ) + public String format(int number, int decimalPlaces) { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); - int paddingRequired = decimalPlaces - ( String.valueOf( number ).length() ); + int paddingRequired = decimalPlaces - (String.valueOf(number).length()); - for( int x = 0; x < paddingRequired; x++) - { - sb.append( "0" ); - } - - sb.append( number ); - - return sb.toString(); - } - - @Override - public String getBinaryMessage() - { - return mMessage.toString(); - } + for(int x = 0; x < paddingRequired; x++) + { + sb.append("0"); + } - @Override - public String getProtocol() - { - return "LJ-1200"; + sb.append(number); + + return sb.toString(); } - @Override - public String getEventType() + @Override + public Protocol getProtocol() { - return "TRANSPONDER"; + return Protocol.LOJACK; } - @Override - public String getFromID() + @Override + public List getIdentifiers() { - return null; - } - - @Override - public Alias getFromIDAlias() - { - return null; - } - - @Override - public String getToID() - { - return null; - } - - @Override - public Alias getToIDAlias() - { - return null; - } - - @Override - public String getMessage() - { - return toString(); - } - - @Override - public String getErrorStatus() - { - return mCRC.getDisplayText(); - } - - @Override - public Plottable getPlottable() - { - return null; + return Collections.EMPTY_LIST; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRCallEvent.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRCallEvent.java deleted file mode 100644 index 6ff1d189..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRCallEvent.java +++ /dev/null @@ -1,212 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.ltrnet; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.module.decode.event.CallEvent; - -public class LTRCallEvent extends CallEvent -{ - private String mChannel; - private long mFrequency; - - public LTRCallEvent( DecoderType type, - CallEventType callEventType, - AliasList aliasList, - String fromID, - String toID, - String channel, - long frequency, - String details ) - { - super( type, callEventType, aliasList, fromID, toID, details ); - - mChannel = channel; - mFrequency = frequency; - } - - /** - * Checks if the message's talkgroup matches this call event. Counts the - * number of messages that match this call event to use in determining if - * a call event is valid or an isolated (errant) message. - * - * See: isValid() - */ - public boolean isMatchingTalkgroup( LTRNetMessage message ) - { - String talkgroup = message.getTalkgroupID(); - - if( talkgroup != null && getToID() != null && - talkgroup.contentEquals( getToID() ) ) - { - return true; - } - - return isMatchingTalkgroup( message.getTalkgroupID() ); - } - - public boolean isMatchingTalkgroup( String talkgroup ) - { - return talkgroup != null && getToID() != null && talkgroup.contentEquals( getToID() ); - } - - private Alias getAlias(String talkgroup ) - { - if( hasAliasList() ) - { - return getAliasList().getTalkgroupAlias( talkgroup ); - } - - return null; - } - - @Override - public Alias getFromIDAlias() - { - if( mCallEventType == CallEventType.CALL || - mCallEventType == CallEventType.REGISTER || - mCallEventType == CallEventType.ID_UNIQUE ) - { - if( hasAliasList() ) - { - try - { - int radioID = Integer.parseInt( getFromID() ); - - return mAliasList.getUniqueID( radioID ); - } - catch( Exception e ) - { - //Do nothing, we couldn't parse the int radio id value - } - } - } - else if( mCallEventType == CallEventType.REGISTER_ESN ) - { - if( hasAliasList() ) - { - return mAliasList.getESNAlias( getFromID() ); - } - } - else - { - return getAlias( getFromID() ); - } - - return null; - } - - @Override - public Alias getToIDAlias() - { - return getAlias( getToID() ); - } - - @Override - public String getChannel() - { - return mChannel; - } - - @Override - public long getFrequency() - { - return mFrequency; - } - - public static class Builder - { - /* Required parameters */ - private DecoderType mDecoderType; - private CallEventType mCallEventType; - - /* Optional parameters */ - private AliasList mAliasList; - private String mFromID; - private String mToID; - private String mDetails; - private String mChannel; - private long mFrequency; - - public Builder( DecoderType decoderType, CallEventType callEventType ) - { - mDecoderType = decoderType; - mCallEventType = callEventType; - } - - public Builder aliasList( AliasList aliasList ) - { - mAliasList = aliasList; - return this; - } - - public Builder from( String val ) - { - mFromID = val; - return this; - } - - public Builder channel( String channel ) - { - mChannel = channel; - return this; - } - - public Builder details( String details ) - { - mDetails = details; - return this; - } - - public Builder frequency( long frequency ) - { - mFrequency = frequency; - return this; - } - - public Builder to( String toID ) - { - mToID = toID; - return this; - } - - public LTRCallEvent build() - { - return new LTRCallEvent( this ); - } - - } - - /** - * Private constructor for the builder - */ - private LTRCallEvent( Builder builder ) - { - this( builder.mDecoderType, - builder.mCallEventType, - builder.mAliasList, - builder.mFromID, - builder.mToID, - builder.mChannel, - builder.mFrequency, - builder.mDetails ); - } - -} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoder.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoder.java index 8d749bb5..2a546c92 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoder.java @@ -15,7 +15,6 @@ ******************************************************************************/ package io.github.dsheirer.module.decode.ltrnet; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.bits.MessageFramer; import io.github.dsheirer.bits.SyncPattern; import io.github.dsheirer.dsp.fsk.LTRDecoder; @@ -37,7 +36,7 @@ public class LTRNetDecoder extends Decoder implements IReusableBufferListener, L * LTR-Net Decoder. Decodes unfiltered (e.g. demodulated but with no DC or * audio filtering) samples and produces LTR-Net messages. */ - public LTRNetDecoder(DecodeConfigLTRNet config, AliasList aliasList, LTRDecoder ltrDecoder) + public LTRNetDecoder(DecodeConfigLTRNet config, LTRDecoder ltrDecoder) { mLTRDecoder = ltrDecoder; @@ -52,7 +51,7 @@ public class LTRNetDecoder extends Decoder implements IReusableBufferListener, L mLTRDecoder.setMessageFramer(mLTRMessageFramer); mLTRMessageFramer.setSyncDetectListener(mLTRDecoder); - mLTRMessageProcessor = new LTRNetMessageProcessor(config.getMessageDirection(), aliasList); + mLTRMessageProcessor = new LTRNetMessageProcessor(config.getMessageDirection()); mLTRMessageFramer.addMessageListener(mLTRMessageProcessor); mLTRMessageProcessor.setMessageListener(getMessageListener()); } @@ -61,9 +60,9 @@ public class LTRNetDecoder extends Decoder implements IReusableBufferListener, L * LTR-Net Decoder. Decodes unfiltered (e.g. demodulated but with no DC or * audio filtering) samples and produces LTR-Net messages. */ - public LTRNetDecoder(DecodeConfigLTRNet config, AliasList aliasList) + public LTRNetDecoder(DecodeConfigLTRNet config) { - this(config, aliasList, new LTRDecoder(LTR_NET_MESSAGE_LENGTH)); + this(config, new LTRDecoder(LTR_NET_MESSAGE_LENGTH)); } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderInstrumented.java index ea0e279b..2e0055d9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderInstrumented.java @@ -15,7 +15,6 @@ ******************************************************************************/ package io.github.dsheirer.module.decode.ltrnet; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.dsp.fsk.LTRDecoderInstrumented; import io.github.dsheirer.sample.buffer.ReusableFloatBuffer; import javafx.beans.property.SimpleIntegerProperty; @@ -24,9 +23,9 @@ public class LTRNetDecoderInstrumented extends LTRNetDecoder { public SimpleIntegerProperty bufferCount = new SimpleIntegerProperty(); - public LTRNetDecoderInstrumented(DecodeConfigLTRNet config, AliasList aliasList) + public LTRNetDecoderInstrumented(DecodeConfigLTRNet config) { - super(config, aliasList, new LTRDecoderInstrumented(LTR_NET_MESSAGE_LENGTH)); + super(config, new LTRDecoderInstrumented(LTR_NET_MESSAGE_LENGTH)); } public LTRDecoderInstrumented getLTRDecoder() diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderState.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderState.java index 7d8ce721..333e5c28 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderState.java @@ -1,84 +1,94 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,2015 Dennis Sheirer +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 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. + * 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 - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.module.decode.ltrnet; -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.alias.id.AliasIDType; -import io.github.dsheirer.channel.metadata.AliasedIntegerAttributeMonitor; -import io.github.dsheirer.channel.metadata.AliasedStringAttributeMonitor; -import io.github.dsheirer.channel.metadata.Attribute; -import io.github.dsheirer.channel.metadata.AttributeChangeRequest; +import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.channel.state.DecoderState; import io.github.dsheirer.channel.state.DecoderStateEvent; import io.github.dsheirer.channel.state.DecoderStateEvent.Event; import io.github.dsheirer.channel.state.State; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.MutableIdentifierCollection; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier; +import io.github.dsheirer.identifier.esn.ESNIdentifier; +import io.github.dsheirer.identifier.talkgroup.LTRTalkgroup; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.message.MessageDirection; import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.module.decode.event.CallEvent; -import io.github.dsheirer.util.StringUtils; +import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.ltrnet.channel.LtrNetChannel; +import io.github.dsheirer.module.decode.ltrnet.identifier.UniqueIdentifier; +import io.github.dsheirer.module.decode.ltrnet.message.LtrNetMessage; +import io.github.dsheirer.module.decode.ltrnet.message.isw.IswCallEnd; +import io.github.dsheirer.module.decode.ltrnet.message.isw.IswCallStart; +import io.github.dsheirer.module.decode.ltrnet.message.isw.IswUniqueId; +import io.github.dsheirer.module.decode.ltrnet.message.isw.RegistrationRequestEsnHigh; +import io.github.dsheirer.module.decode.ltrnet.message.isw.RegistrationRequestEsnLow; +import io.github.dsheirer.module.decode.ltrnet.message.isw.RequestAccess; +import io.github.dsheirer.module.decode.ltrnet.message.osw.ChannelMapHigh; +import io.github.dsheirer.module.decode.ltrnet.message.osw.ChannelMapLow; +import io.github.dsheirer.module.decode.ltrnet.message.osw.NeighborId; +import io.github.dsheirer.module.decode.ltrnet.message.osw.OswCallEnd; +import io.github.dsheirer.module.decode.ltrnet.message.osw.OswCallStart; +import io.github.dsheirer.module.decode.ltrnet.message.osw.ReceiveFrequencyHigh; +import io.github.dsheirer.module.decode.ltrnet.message.osw.ReceiveFrequencyLow; +import io.github.dsheirer.module.decode.ltrnet.message.osw.RegistrationAccept; +import io.github.dsheirer.module.decode.ltrnet.message.osw.SiteId; +import io.github.dsheirer.module.decode.ltrnet.message.osw.SystemIdle; +import io.github.dsheirer.module.decode.ltrnet.message.osw.TransmitFrequencyHigh; +import io.github.dsheirer.module.decode.ltrnet.message.osw.TransmitFrequencyLow; +import io.github.dsheirer.protocol.Protocol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; -import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.TreeSet; public class LTRNetDecoderState extends DecoderState { private final static Logger mLog = LoggerFactory.getLogger(LTRNetDecoderState.class); - private DecimalFormat mDecimalFormatter = new DecimalFormat("0.00000"); + private ChannelMapHigh mChannelMapHigh; + private ChannelMapLow mChannelMapLow; + private Map mChannelMap = new HashMap<>(); + private Map mCallDetectMap = new HashMap<>(); + private Map mNeighborMap = new HashMap<>(); + private SiteId mCurrentSite; + private int mCurrentChannelNumber; + private Set mTalkgroups = new TreeSet<>(); + private Set mUniqueIdentifiers = new TreeSet<>(); + private Set mESNIdentifiers = new TreeSet<>(); + private DecodeEvent mCurrentCallEvent; + private LTRTalkgroup mCurrentCallTalkgroup; - private TreeSet mTalkgroups = new TreeSet(); - private TreeSet mTalkgroupsFirstHeard = new TreeSet(); - private TreeSet mESNs = new TreeSet(); - private TreeSet mUniqueIDs = new TreeSet(); - private TreeSet mNeighborIDs = new TreeSet(); - private TreeSet mSiteIDs = new TreeSet(); - private HashMap mReceiveFrequencies = - new HashMap(); - private HashMap mTransmitFrequencies = - new HashMap(); - private HashMap mActiveCalls = new HashMap(); - - private AliasedStringAttributeMonitor mToAttribute; - private AliasedIntegerAttributeMonitor mFromUIDAttribute; - private AliasedStringAttributeMonitor mESNAttribute; - private String mMessage; - private String mMessageType; - private int mChannelNumber; - private long mFrequency = 0; - - public LTRNetDecoderState(AliasList aliasList) + public LTRNetDecoderState() { - super(aliasList); - - mToAttribute = new AliasedStringAttributeMonitor(Attribute.PRIMARY_ADDRESS_TO, - getAttributeChangeRequestListener(), getAliasList(), AliasIDType.TALKGROUP); - mToAttribute.addIllegalValue("0-00-000"); - - mFromUIDAttribute = new AliasedIntegerAttributeMonitor(Attribute.PRIMARY_ADDRESS_TO, - getAttributeChangeRequestListener(), getAliasList(), AliasIDType.LTR_NET_UID); - - mESNAttribute = new AliasedStringAttributeMonitor(Attribute.SECONDARY_ADDRESS_FROM, - getAttributeChangeRequestListener(), getAliasList(), AliasIDType.ESN); } @Override @@ -88,235 +98,412 @@ public class LTRNetDecoderState extends DecoderState } @Override - public void receive(Message message) + protected IChannelDescriptor getCurrentChannel() { - if(message.isValid()) + if(mCurrentChannelNumber > 0) { - State state = State.IDLE; + return mChannelMap.get(mCurrentChannelNumber); + } - if(message instanceof LTRNetOSWMessage) + return null; + } + + /** + * Performs a full reset + */ + public void reset() + { + mChannelMapHigh = null; + mChannelMapLow = null; + mChannelMap.clear(); + mCallDetectMap.clear(); + mNeighborMap.clear(); + mCurrentSite = null; + mCurrentChannelNumber = 0; + mTalkgroups.clear(); + mUniqueIdentifiers.clear(); + mESNIdentifiers.clear(); + resetState(); + } + + private void processCallEnd(int channel, LTRTalkgroup talkgroup, long timestamp) + { + setCurrentChannelNumber(channel); + + if(mCurrentCallTalkgroup != null && mCurrentCallTalkgroup.equals(talkgroup)) + { + mCurrentCallEvent.end(timestamp); + broadcast(mCurrentCallEvent); + mCurrentCallEvent = null; + mCurrentCallTalkgroup = null; + getIdentifierCollection().remove(IdentifierClass.USER); + } + + if(talkgroup.getTalkgroup() == 254) + { + //Process CW Station ID broadcasts as an active state + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); + } + else + { + broadcast(new DecoderStateEvent(this, Event.END, State.CALL)); + } + } + + private void processCallStart(int channel, LTRTalkgroup talkgroup, long timestamp, MessageDirection direction) + { + if(direction == MessageDirection.ISW) + { + setCurrentChannelNumber(channel); + } + + if(channel == mCurrentChannelNumber || mCurrentChannelNumber == 0) + { + setCurrentChannelNumber(channel); + + if(mCurrentCallTalkgroup == null || !mCurrentCallTalkgroup.equals(talkgroup)) { - LTRNetOSWMessage ltr = (LTRNetOSWMessage) message; + getIdentifierCollection().remove(IdentifierClass.USER); + getIdentifierCollection().update(talkgroup); + mCurrentCallEvent = DecodeEvent.builder(timestamp) + .protocol(Protocol.LTR_NET) + .channel(getCurrentChannel()) + .identifiers(getIdentifierCollection().copyOf()) + .build(); + mCurrentCallTalkgroup = talkgroup; + } - switch(ltr.getMessageType()) + //This updates the timestamp + mCurrentCallEvent.update(timestamp); + + if(talkgroup.getTalkgroup() == 253) + { + mCurrentCallEvent.setEventDescription("Register"); + broadcast(new DecoderStateEvent(this, Event.START, State.DATA)); + } + else if(talkgroup.getTalkgroup() == 254) + { + mCurrentCallEvent.setEventDescription("FCC CWID"); + broadcast(new DecoderStateEvent(this, Event.START, State.DATA)); + } + else + { + mCurrentCallEvent.setEventDescription("Call"); + broadcast(new DecoderStateEvent(this, Event.START, State.CALL)); + } + + broadcast(mCurrentCallEvent); + } + else + { + DecodeEvent decodeEvent = mCallDetectMap.get(channel); + + if(decodeEvent == null) + { + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(talkgroup); + + decodeEvent = DecodeEvent.builder(timestamp) + .eventDescription("Call Detect") + .channel(mChannelMap.get(channel)) + .protocol(Protocol.LTR_NET) + .identifiers(ic) + .build(); + mCallDetectMap.put(channel, decodeEvent); + } + else + { + Identifier eventTalkgroup = decodeEvent.getIdentifierCollection() + .getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.TO); + + if(eventTalkgroup == null || !eventTalkgroup.equals(talkgroup) || + (timestamp - decodeEvent.getTimeStart() - decodeEvent.getDuration() > 2000)) { - case CA_ENDD: - if(mChannelNumber == 0) - { - setChannelNumber(ltr.getChannel()); - } + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(talkgroup); - /* Process FCC Station ID Events */ - if(ltr.getGroup() == 254) - { - if(mCurrentCallEvent == null || - mCurrentCallEvent.getCallEventType() != - CallEvent.CallEventType.STATION_ID) - { - mCurrentCallEvent = new LTRCallEvent.Builder( - DecoderType.LTR_NET, CallEvent.CallEventType.STATION_ID) - .aliasList(getAliasList()) - .channel(String.valueOf(mChannelNumber)) - .frequency(mFrequency) - .to(ltr.getTalkgroupID()) - .build(); - - broadcast(mCurrentCallEvent); - - broadcast(new DecoderStateEvent(this, Event.START, State.DATA)); - } - else - { - broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA)); - } - } - else - { - processCallEndMessage(ltr); - } - break; - case CA_STRT: - if(mChannelNumber == 0) - { - setChannelNumber(ltr.getChannel()); - } - - /* If the call event channel matches our current channel - * then it's a call, otherwise it's a call detect. */ - if(ltr.getChannel() == mChannelNumber) - { - processCallMessage(ltr); - } - else - { - processCallDetectMessage(ltr); - } - - break; - case SY_IDLE: - if(mChannelNumber != ltr.getChannel()) - { - setChannelNumber(ltr.getChannel()); - } - break; - case MA_CHNH: - break; - case MA_CHNL: - break; - case FQ_RXHI: - case FQ_RXLO: - if(ltr.getFrequency() > 0) - { - mReceiveFrequencies.put(ltr.getHomeRepeater(), ltr.getFrequency()); - } - break; - case FQ_TXHI: - case FQ_TXLO: - if(ltr.getFrequency() > 0) - { - mTransmitFrequencies.put(ltr.getHomeRepeater(), ltr.getFrequency()); - } - break; - case ID_NBOR: - String neighborID = ltr.getNeighborID(); - - if(neighborID != null) - { - mNeighborIDs.add(neighborID); - } - break; - case ID_UNIQ: - state = State.DATA; - - int uniqueID = ltr.getRadioUniqueID(); - - if(uniqueID != LTRNetOSWMessage.INT_NULL_VALUE) - { - mUniqueIDs.add(uniqueID); - } - - if(getCurrentLTRCallEvent() == null) - { - mCurrentCallEvent = new LTRCallEvent.Builder( - DecoderType.LTR_NET, CallEvent.CallEventType.REGISTER) - .aliasList(getAliasList()) - .channel(String.valueOf(mChannelNumber)) - .frequency(mFrequency) - .from(String.valueOf(uniqueID)) - .build(); - } - else - { - mCurrentCallEvent.setFromID( - String.valueOf(ltr.getRadioUniqueID())); - mCurrentCallEvent.setDetails("Unique ID"); - - broadcast(mCurrentCallEvent); - } - - mFromUIDAttribute.process(uniqueID); - break; - case ID_SITE: - String siteID = ltr.getSiteID(); - - if(siteID != null) - { - mSiteIDs.add(siteID); - } - break; - default: - break; + decodeEvent = DecodeEvent.builder(timestamp) + .eventDescription("Call Detect") + .channel(mChannelMap.get(channel)) + .protocol(Protocol.LTR_NET) + .identifiers(ic) + .build(); + mCallDetectMap.put(channel, decodeEvent); } } - else if(message instanceof LTRNetISWMessage) + + decodeEvent.update(timestamp); + broadcast(decodeEvent); + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); + } + } + + /** + * Updates the current call event whenever any identifiers related to the call change + */ + private void updateCurrentCallIdentifiers() + { + if(mCurrentCallEvent != null) + { + mCurrentCallEvent.setIdentifierCollection(getIdentifierCollection().copyOf()); + broadcast(mCurrentCallEvent); + } + } + + /** + * Updates the uplink or receive frequency for the repeater identified by the Logical Channel Number (LCN). + */ + private void updateReceiveFrequency(int channel, long frequency) + { + if(0 < channel && channel <= 20 && frequency > 0) + { + LtrNetChannel ltrNetChannel = mChannelMap.get(channel); + + if(ltrNetChannel == null) { - LTRNetISWMessage ltr = ((LTRNetISWMessage) message); - - switch(ltr.getMessageType()) - { - case CA_STRT: - processCallMessage(ltr); - break; - case CA_ENDD: - processCallEndMessage(ltr); - break; - case ID_ESNH: - case ID_ESNL: - state = State.DATA; - - String esn = ltr.getESN(); - - if(!esn.contains("xxxx")) - { - mESNs.add(ltr.getESN()); - } - - setMessageType("ESN"); - mESNAttribute.process(ltr.getESN()); - - broadcast(new DecoderStateEvent(this, Event.DECODE, State.DATA)); - - if(mCurrentCallEvent == null) - { - mCurrentCallEvent = new LTRCallEvent.Builder( - DecoderType.LTR_NET, CallEvent.CallEventType.REGISTER_ESN) - .aliasList(getAliasList()) - .details("ESN:" + ltr.getESN()) - .frequency(mFrequency) - .from(ltr.getESN()) - .build(); - - broadcast(mCurrentCallEvent); - } - - break; - case ID_UNIQ: - state = State.DATA; - - int uniqueid = ltr.getRadioUniqueID(); - - if(uniqueid != LTRNetISWMessage.INT_NULL_VALUE) - { - mUniqueIDs.add(uniqueid); - - setMessageType("REGISTER UID"); - mFromUIDAttribute.process(uniqueid); - - if(getCurrentLTRCallEvent() == null) - { - mCurrentCallEvent = new LTRCallEvent.Builder( - DecoderType.LTR_NET, CallEvent.CallEventType.REGISTER) - .aliasList(getAliasList()) - .channel(String.valueOf(mChannelNumber)) - .frequency(mFrequency) - .from(String.valueOf(uniqueid)) - .build(); - } - else - { - mCurrentCallEvent.setFromID( - String.valueOf(ltr.getRadioUniqueID())); - mCurrentCallEvent.setDetails("Unique ID"); - - broadcast(mCurrentCallEvent); - } - } - break; - default: - break; - } + ltrNetChannel = new LtrNetChannel(channel); + ltrNetChannel.setUplink(frequency); + mChannelMap.put(channel, ltrNetChannel); + } + else + { + ltrNetChannel.setUplink(frequency); } } } - public LTRCallEvent getCurrentLTRCallEvent() + /** + * Updates the downlink or transmit frequency for the repeater identified by the Logical Channel Number (LCN). + */ + private void updateTransmitFrequency(int channel, long frequency) { - if(mCurrentCallEvent != null) + if(0 < channel && channel <= 20 && frequency > 0) { - return (LTRCallEvent) mCurrentCallEvent; - } + LtrNetChannel ltrNetChannel = mChannelMap.get(channel); - return null; + if(ltrNetChannel == null) + { + ltrNetChannel = new LtrNetChannel(channel); + ltrNetChannel.setDownlink(frequency); + mChannelMap.put(channel, ltrNetChannel); + } + else + { + ltrNetChannel.setDownlink(frequency); + } + } + } + + @Override + public void receive(IMessage message) + { + if(message.isValid() && message instanceof LtrNetMessage) + { + switch(((LtrNetMessage)message).getLtrNetMessageType()) + { + case ISW_CALL_END: + if(message instanceof IswCallEnd) + { + IswCallEnd callEnd = (IswCallEnd)message; + mTalkgroups.add(callEnd.getTalkgroup()); + processCallEnd(callEnd.getChannel(), callEnd.getTalkgroup(), callEnd.getTimestamp()); + } + break; + case ISW_CALL_START: + if(message instanceof IswCallStart) + { + IswCallStart callStart = (IswCallStart)message; + mTalkgroups.add(callStart.getTalkgroup()); + processCallStart(callStart.getChannel(), callStart.getTalkgroup(), callStart.getTimestamp(), + MessageDirection.ISW); + } + break; + case ISW_REGISTRATION_REQUEST_ESN_HIGH: + if(message instanceof RegistrationRequestEsnHigh) + { + RegistrationRequestEsnHigh registrationRequestEsn = (RegistrationRequestEsnHigh)message; + + if(registrationRequestEsn.isCompleteEsn()) + { + getIdentifierCollection().update(registrationRequestEsn.getESN()); + mESNIdentifiers.add(registrationRequestEsn.getESN()); + } + + broadcast(DecodeEvent.builder(message.getTimestamp()) + .eventDescription("Registration Request") + .details(registrationRequestEsn.toString()) + .identifiers(getIdentifierCollection().copyOf()) + .channel(getCurrentChannel()) + .protocol(Protocol.LTR_NET) + .build()); + + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA)); + } + break; + case ISW_REGISTRATION_REQUEST_ESN_LOW: + if(message instanceof RegistrationRequestEsnLow) + { + RegistrationRequestEsnLow registrationRequestEsn = (RegistrationRequestEsnLow)message; + + if(registrationRequestEsn.isCompleteEsn()) + { + getIdentifierCollection().update(registrationRequestEsn.getESN()); + mESNIdentifiers.add(registrationRequestEsn.getESN()); + } + + broadcast(DecodeEvent.builder(message.getTimestamp()) + .eventDescription("Registration Request") + .details(registrationRequestEsn.toString()) + .identifiers(getIdentifierCollection().copyOf()) + .channel(getCurrentChannel()) + .protocol(Protocol.LTR_NET) + .build()); + + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA)); + } + break; + case ISW_REQUEST_ACCESS: + if(message instanceof RequestAccess) + { + RequestAccess requestAccess = (RequestAccess)message; + + getIdentifierCollection().update(requestAccess.getTalkgroup()); + mTalkgroups.add(requestAccess.getTalkgroup()); + + broadcast(DecodeEvent.builder(message.getTimestamp()) + .eventDescription("Access Request") + .details(requestAccess.toString()) + .identifiers(getIdentifierCollection().copyOf()) + .channel(getCurrentChannel()) + .protocol(Protocol.LTR_NET) + .build()); + + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA)); + } + break; + case ISW_UNIQUE_ID: + if(message instanceof IswUniqueId) + { + IswUniqueId iswUniqueId = (IswUniqueId)message; + getIdentifierCollection().update(iswUniqueId.getUniqueID()); + mUniqueIdentifiers.add(iswUniqueId.getUniqueID()); + updateCurrentCallIdentifiers(); + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA)); + } + break; + case ISW_UNKNOWN: + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); + break; + case OSW_CALL_END: + if(message instanceof OswCallEnd) + { + OswCallEnd callEnd = (OswCallEnd)message; + mTalkgroups.add(callEnd.getTalkgroup()); + processCallEnd(callEnd.getChannel(), callEnd.getTalkgroup(), callEnd.getTimestamp()); + } + break; + case OSW_CALL_START: + if(message instanceof OswCallStart) + { + OswCallStart callStart = (OswCallStart)message; + mTalkgroups.add(callStart.getTalkgroup()); + processCallStart(callStart.getChannel(), callStart.getTalkgroup(), callStart.getTimestamp(), + MessageDirection.OSW); + } + break; + case OSW_CHANNEL_MAP_HIGH: + if(message instanceof ChannelMapHigh) + { + mChannelMapHigh = (ChannelMapHigh)message; + } + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); + break; + case OSW_CHANNEL_MAP_LOW: + if(message instanceof ChannelMapLow) + { + mChannelMapLow = (ChannelMapLow)message; + } + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); + break; + case OSW_SYSTEM_IDLE: + if(message instanceof SystemIdle) + { + setCurrentChannelNumber(((SystemIdle)message).getChannel()); + } + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); + break; + case OSW_NEIGHBOR_ID: + if(message instanceof NeighborId) + { + NeighborId neighborId = (NeighborId)message; + mNeighborMap.put(neighborId.getNeighborRank(), neighborId); + } + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); + break; + case OSW_RECEIVE_FREQUENCY_HIGH: + if(message instanceof ReceiveFrequencyHigh) + { + ReceiveFrequencyHigh receiveFrequency = (ReceiveFrequencyHigh)message; + updateReceiveFrequency(receiveFrequency.getChannel(), receiveFrequency.getFrequency()); + } + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); + break; + case OSW_RECEIVE_FREQUENCY_LOW: + if(message instanceof ReceiveFrequencyLow) + { + ReceiveFrequencyLow receiveFrequency = (ReceiveFrequencyLow)message; + updateReceiveFrequency(receiveFrequency.getChannel(), receiveFrequency.getFrequency()); + } + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); + break; + case OSW_REGISTRATION_ACCEPT: + if(message instanceof RegistrationAccept) + { + RegistrationAccept registrationAccept = (RegistrationAccept)message; + mUniqueIdentifiers.add(registrationAccept.getUniqueID()); + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(message.getIdentifiers()); + + broadcast(DecodeEvent.builder(message.getTimestamp()) + .protocol(Protocol.LTR_NET) + .channel(getCurrentChannel()) + .identifiers(ic) + .eventDescription("Registration Accept") + .details(registrationAccept.toString()) + .build()); + } + break; + case OSW_SITE_ID: + if(message instanceof SiteId) + { + mCurrentSite = (SiteId)message; + } + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); + break; + case OSW_TRANSMIT_FREQUENCY_HIGH: + if(message instanceof TransmitFrequencyHigh) + { + TransmitFrequencyHigh transmitFrequency = (TransmitFrequencyHigh)message; + updateTransmitFrequency(transmitFrequency.getChannel(), transmitFrequency.getFrequency()); + } + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); + break; + case OSW_TRANSMIT_FREQUENCY_LOW: + if(message instanceof TransmitFrequencyLow) + { + TransmitFrequencyLow transmitFrequency = (TransmitFrequencyLow)message; + updateTransmitFrequency(transmitFrequency.getChannel(), transmitFrequency.getFrequency()); + } + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); + break; + case OSW_UNKNOWN: + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); + break; + } + } } @Override @@ -326,102 +513,55 @@ public class LTRNetDecoderState extends DecoderState sb.append("Activity Summary\n"); sb.append("Decoder:\tLTR-Net\n\n"); + sb.append("Site:\t").append(mCurrentSite != null ? mCurrentSite.getSiteID() : "Unknown").append("\n"); - if(mSiteIDs.isEmpty()) + sb.append("\nLCNs (transmit / receive)\n"); + + if(mChannelMapLow != null) { - sb.append("Site:\tUnknown\n"); + for(int channel: mChannelMapLow.getChannels()) + { + LtrNetChannel ltrNetChannel = mChannelMap.get(channel); + sb.append(" ").append(channel).append(": ").append(ltrNetChannel != null ? ltrNetChannel.description() : + "Unknown").append(mCurrentChannelNumber == channel ? " (Current)\n" : "\n"); + } } else { - Iterator it = mSiteIDs.iterator(); - - while(it.hasNext()) - { - sb.append("Site:\t"); - - String siteID = it.next(); - - sb.append(siteID); - - if(hasAliasList()) - { - Alias siteAlias = getAliasList().getSiteID(String.valueOf(siteID)); - - if(siteAlias != null) - { - sb.append(" "); - sb.append(siteAlias.getName()); - } - } - - sb.append("\n"); - } + sb.append("Channel Map 1-10: Unknown\n"); } - sb.append("\nLCNs (transmit | receive)\n"); + if(mChannelMapHigh != null) + { + for(int channel: mChannelMapHigh.getChannels()) + { + LtrNetChannel ltrNetChannel = mChannelMap.get(channel); + sb.append(" ").append(channel).append(": ").append(ltrNetChannel != null ? ltrNetChannel.description() : + "Unknown").append(mCurrentChannelNumber == channel ? " (Current)\n" : "\n"); + } + } + else + { + sb.append("Channel Map 11-20: Unknown\n"); + } - if(mReceiveFrequencies.isEmpty() && mTransmitFrequencies.isEmpty()) + sb.append("\nNeighbor Sites (Rank: ID)\n"); + + if(mNeighborMap.isEmpty()) { sb.append(" None\n"); } else { - for(int x = 1; x < 21; x++) + List ranks = new ArrayList<>(mNeighborMap.keySet()); + Collections.sort(ranks); + for(Integer rank: ranks) { - long rcv = 0; - - if(mReceiveFrequencies.containsKey(x)) - { - rcv = mReceiveFrequencies.get(x); - } - - long xmt = 0; - - if(mTransmitFrequencies.containsKey(x)) - { - xmt = mTransmitFrequencies.get(x); - } - - if(rcv > 0 || xmt > 0) - { - if(x < 10) - { - sb.append(" "); - } - - sb.append(x); - sb.append(": "); - - if(xmt == 0) - { - sb.append("---.-----"); - } - else - { - sb.append(mDecimalFormatter.format((double) xmt / 1E6d)); - } - sb.append(" | "); - - if(rcv == 0) - { - sb.append("---.-----"); - } - else - { - sb.append(mDecimalFormatter.format((double) rcv / 1E6d)); - } - - if(x == mChannelNumber) - { - sb.append(" **"); - } - - sb.append("\n"); - } + sb.append(" ").append(rank).append(": ").append(mNeighborMap.get(rank).getNeighborID()).append("\n"); } } - sb.append("\nTalkgroups\n"); + sb.append("\nActive Talkgroups\n"); if(mTalkgroups.isEmpty()) { @@ -429,355 +569,90 @@ public class LTRNetDecoderState extends DecoderState } else { - Iterator it = mTalkgroups.iterator(); + List talkgroups = new ArrayList<>(mTalkgroups); + Collections.sort(talkgroups, Comparator.comparingInt(Identifier::getValue)); - while(it.hasNext()) + for(LTRTalkgroup talkgroup: talkgroups) { - String tgid = it.next(); - - sb.append(" "); - sb.append(tgid); - sb.append(" "); - - if(hasAliasList()) - { - Alias alias = getAliasList().getTalkgroupAlias(tgid); - - if(alias != null) - { - sb.append(alias.getName()); - } - } - - sb.append("\n"); + sb.append(" ").append(talkgroup.formatted()).append("\n"); } } - sb.append("\nRadio Unique IDs\n"); + sb.append("\nActive Radio Unique IDs\n"); - if(mUniqueIDs.isEmpty()) + if(mUniqueIdentifiers.isEmpty()) { sb.append(" None\n"); } else { - Iterator it = mUniqueIDs.iterator(); + List uniqueIdentifiers = new ArrayList<>(mUniqueIdentifiers); + Collections.sort(uniqueIdentifiers, Comparator.comparingInt(Identifier::getValue)); - while(it.hasNext()) + for(UniqueIdentifier uniqueIdentifier: uniqueIdentifiers) { - int uid = it.next(); - - sb.append(" "); - sb.append(uid); - sb.append(" "); - - if(hasAliasList()) - { - Alias alias = getAliasList().getUniqueID(uid); - - if(alias != null) - { - sb.append(alias.getName()); - } - } - - sb.append("\n"); + sb.append(" ").append(uniqueIdentifier).append("\n"); } } - sb.append("\nESNs\n"); + sb.append("\nActive ESNs\n"); - if(mESNs.isEmpty()) + if(mESNIdentifiers.isEmpty()) { sb.append(" None\n"); } else { - Iterator it = mESNs.iterator(); + List esnIdentifiers = new ArrayList<>(mESNIdentifiers); + Collections.sort(esnIdentifiers); - while(it.hasNext()) + for(ESNIdentifier esnIdentifier: esnIdentifiers) { - String esn = it.next(); - - sb.append(" "); - sb.append(esn); - sb.append(" "); - - if(hasAliasList()) - { - Alias alias = getAliasList().getESNAlias(esn); - - if(alias != null) - { - sb.append(alias.getName()); - } - } - - sb.append("\n"); - } - } - - sb.append("\nNeighbor Sites\n"); - - if(mNeighborIDs.isEmpty()) - { - sb.append(" None\n"); - } - else - { - Iterator it = mNeighborIDs.iterator(); - - while(it.hasNext()) - { - String neighbor = it.next(); - - sb.append(" "); - sb.append(neighbor); - sb.append(" "); - - if(hasAliasList()) - { - Alias alias = getAliasList().getSiteID(String.valueOf(neighbor)); - - if(alias != null) - { - sb.append(alias.getName()); - } - } - - sb.append("\n"); + sb.append(" ").append(esnIdentifier).append("\n"); } } return sb.toString(); } - /** - * Call Detect - current channel messages indicate a call on another channel - */ - private void processCallDetectMessage(LTRNetMessage message) - { - if(!mActiveCalls.containsKey(message.getChannel()) || - !mActiveCalls.get(message.getChannel()).contentEquals(message.getTalkgroupID())) - { - mActiveCalls.put(message.getChannel(), message.getTalkgroupID()); - - int channel = message.getChannel(); - - long frequency = 0; - - if(mTransmitFrequencies.containsKey(channel)) - { - frequency = mTransmitFrequencies.get(channel); - } - - broadcast(new LTRCallEvent.Builder(DecoderType.LTR_NET, CallEvent.CallEventType.CALL_DETECT) - .aliasList(getAliasList()) - .channel(String.valueOf(message.getChannel())) - .frequency(frequency) - .to(message.getTalkgroupID()) - .build()); - } - - broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); - } - - /** - * Indicates if the talkgroup is different than the talkgroup specified in - * the current call event - */ - private boolean isDifferentTalkgroup(String talkgroup) - { - return talkgroup != null && - mCurrentCallEvent != null && - mCurrentCallEvent.getToID() != null && - !mCurrentCallEvent.getToID().contentEquals(talkgroup); - } - - private boolean isValidTalkgroup(String talkgroup) - { - return talkgroup != null && !talkgroup.matches("0-00-000"); - } - - private void processCallMessage(LTRNetMessage message) - { - int group = message.getGroup(); - - /* Process call registration */ - if(group == 253) - { - setMessageType("REGISTER"); - broadcast(new DecoderStateEvent(this, Event.START, State.DATA)); - } - /* Process call */ - else - { - String talkgroup = message.getTalkgroupID(); - - if(isValidTalkgroup(talkgroup)) - { - mToAttribute.process(talkgroup); - - final LTRCallEvent current = getCurrentLTRCallEvent(); - - //If this is a new call or the talkgroup is different from the current call, create a new call event - if(current == null || isDifferentTalkgroup(talkgroup)) - { - /* Invalidate the current call */ - if(current != null) - { - current.setValid(false); - broadcast(current); - } - - /* A talkgroup must be seen at least once before it will be added - * to the mTalkgroups list that is used in the activity summary, - * so that we don't pollute the summary with one-off error talkgroups */ - if(mTalkgroupsFirstHeard.contains(talkgroup)) - { - mTalkgroups.add(talkgroup); - } - else - { - mTalkgroupsFirstHeard.add(talkgroup); - } - - CallEvent callEvent = new LTRCallEvent.Builder(DecoderType.LTR_NET, CallEvent.CallEventType.CALL) - .aliasList(getAliasList()) - .channel(String.valueOf(message.getChannel())) - .frequency(mFrequency) - .to(message.getTalkgroupID()) - .build(); - - broadcast(callEvent); - - mCurrentCallEvent = callEvent; - - broadcast(new DecoderStateEvent(this, Event.START, State.CALL)); - } - - broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL)); - } - } - } - - private void processCallEndMessage(LTRNetMessage message) - { - if(mCurrentCallEvent != null && - mCurrentCallEvent.getCallEventType() == CallEvent.CallEventType.CALL) - { - mCurrentCallEvent.setEnd(System.currentTimeMillis()); - broadcast(mCurrentCallEvent); - } - - mCurrentCallEvent = null; - - broadcast(new DecoderStateEvent(this, Event.END, State.FADE)); - } - - /** - * Performs a full reset - */ - public void reset() - { - mChannelNumber = 0; - - mActiveCalls.clear(); - mESNs.clear(); - mNeighborIDs.clear(); - mReceiveFrequencies.clear(); - mSiteIDs.clear(); - mTalkgroups.clear(); - mTransmitFrequencies.clear(); - mUniqueIDs.clear(); - - resetState(); - } - @Override public void start() { - } @Override public void stop() { - } @Override public void init() { - } /** * Resets the decoder state after a call or other decode event */ - private void resetState() + protected void resetState() { - if(mCurrentCallEvent != null && mCurrentCallEvent - .getCallEventType() == CallEvent.CallEventType.CALL) - { - mCurrentCallEvent.end(); - broadcast(mCurrentCallEvent); - } - + super.resetState(); mCurrentCallEvent = null; - - mToAttribute.reset(); - mESNAttribute.reset(); - mFromUIDAttribute.reset(); - mMessage = null; - mMessageType = null; + mCurrentCallTalkgroup = null; } - public String getMessage() + public void setCurrentChannelNumber(int channelNumber) { - return mMessage; - } - - public void setMessage(String message) - { - if(!StringUtils.isEqual(mMessage, message)) + if(0 < channelNumber && channelNumber <= 20) { - mMessage = message; - broadcast(new AttributeChangeRequest(Attribute.MESSAGE, mMessageType)); - } - } + mCurrentChannelNumber = channelNumber; - public String getMessageType() - { - return mMessageType; - } + LtrNetChannel currentChannel = mChannelMap.get(mCurrentChannelNumber); - private void setMessageType(String messageType) - { - if(!StringUtils.isEqual(mMessageType, messageType)) - { - mMessageType = messageType; - broadcast(new AttributeChangeRequest(Attribute.MESSAGE_TYPE, mMessageType)); - } - } - - public boolean hasChannelNumber() - { - return mChannelNumber != 0; - } - - public int getChannelNumber() - { - return mChannelNumber; - } - - public void setChannelNumber(int channelNumber) - { - if(mChannelNumber != channelNumber) - { - mChannelNumber = channelNumber; - broadcast(new AttributeChangeRequest(Attribute.CHANNEL_FREQUENCY_LABEL, - "LCN:" + getChannelNumber())); + if(currentChannel != null) + { + getIdentifierCollection().update(DecoderLogicalChannelNameIdentifier + .create(String.valueOf(mCurrentChannelNumber), Protocol.LTR_NET)); + } } } @@ -789,9 +664,6 @@ public class LTRNetDecoderState extends DecoderState case RESET: resetState(); break; - case SOURCE_FREQUENCY: - mFrequency = event.getFrequency(); - break; default: break; } diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetISWMessage.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetISWMessage.java deleted file mode 100644 index 0f0b6c19..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetISWMessage.java +++ /dev/null @@ -1,345 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.ltrnet; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRC; -import io.github.dsheirer.edac.CRCLTR; -import io.github.dsheirer.map.Plottable; -import io.github.dsheirer.message.MessageDirection; -import io.github.dsheirer.message.MessageType; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -public class LTRNetISWMessage extends LTRNetMessage -{ - private static final int sCHIU_UNIQUE_ID = 24; - private static final int sCHIU_ESN_LOW = 27; - private static final int sCHIU_ESN_HIGH = 29; - - private static final int sFREE_CALL = 21; - private static final int sFREE_END_CALL = 23; - private static final int sFREE_REQUEST_ACCESS = 31; - - - private LTRNetISWMessage mAuxMessage; - - public LTRNetISWMessage( BinaryMessage message, AliasList list ) - { - super( message, MessageDirection.ISW, list ); - - /** - * If the CRC fails, test for a transmitted CRC of 127 and then check - * the Free field for special messages - */ - if( mCRC == CRC.FAILED_CRC && CRCLTR.getTransmittedChecksum( mMessage ) == 127 ) - { - if( getFree() == sFREE_REQUEST_ACCESS || - getFree() == sFREE_END_CALL ) - { - mCRC = CRC.PASSED; - } - } - - mMessageType = getMessageType(); - } - - public void setAuxiliaryMessage( LTRNetISWMessage message ) - { - if( message != null ) - { - mAuxMessage = message; - } - } - - public String getESN() - { - if( mMessageType == MessageType.ID_ESNH ) - { - if( mAuxMessage != null && - mAuxMessage.getMessageType() == MessageType.ID_ESNL ) - { - return getESNHigh() + mAuxMessage.getESNLow(); - } - else - { - return getESNHigh() + "xxxx"; - } - } - else - { - if( mAuxMessage != null && - mAuxMessage.getMessageType() == MessageType.ID_ESNH ) - { - return mAuxMessage.getESNHigh() + getESNLow(); - } - else - { - return "xxxx" + getESNLow(); - } - } - } - - public Alias getESNAlias() - { - if( mAliasList != null ) - { - return mAliasList.getESNAlias( getESN() ); - } - - return null; - } - - public String getESNHigh() - { - int esnHigh = getInt( sSIXTEEN_BITS ); - - return String.format("%04X", esnHigh & 0xFFFF ); - } - - public String getESNLow() - { - int esnLow = getInt( sSIXTEEN_BITS ); - - return String.format("%04X", esnLow & 0xFFFF ); - } - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append( mDatestampFormatter.format( new Date( System.currentTimeMillis() ) ) ); - sb.append( " LTRNet ISW [" ); - sb.append( mCRC.getAbbreviation() ); - sb.append( "] " ); - sb.append( getMessage() ); - pad( sb, 100 ); - sb.append( mMessage.toString() ); - - return sb.toString(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - switch( mMessageType ) - { - case CA_STRT: - sb.append( "CALL LCN:" ); - sb.append( format( getChannel(), 2 ) ); - sb.append( " TG [ " ); - sb.append( getTalkgroupID() ); - sb.append( "/" ); - sb.append( getTalkgroupIDAlias() ); - sb.append( " ] FREE:" ); - sb.append( format( getFree(), 2 ) ); - break; - case CA_ENDD: - sb.append( "END* AREA:" ); - sb.append( getArea() ); - sb.append( " LCN:" ); - sb.append( format( getChannel(), 2 ) ); - sb.append( " TG [" ); - sb.append( getTalkgroupID() ); - sb.append( "/" ); - sb.append( getTalkgroupIDAlias() ); - sb.append( "] FREE:" ); - sb.append( format( getFree(), 2 ) ); - break; - case RQ_ACCE: - sb.append( "REQUEST ACCESS LCN:" ); - sb.append( format( getChannel(), 2 ) ); - sb.append( " TG [ " ); - sb.append( getTalkgroupID() ); - sb.append( "/" ); - sb.append( getTalkgroupIDAlias() ); - sb.append( " ] FREE:" ); - sb.append( format( getFree(), 2 ) ); - break; - case ID_ESNL: - sb.append( "ESN LOW " ); - sb.append( getESN() ); - sb.append( "/" ); - sb.append( getESNAlias() ); - break; - case ID_ESNH: - sb.append( "ESN HIGH " ); - sb.append( getESN() ); - sb.append( "/" ); - sb.append( getESNAlias() ); - break; - case ID_UNIQ: - sb.append( "RADIO UNIQUE ID " ); - sb.append( getRadioUniqueID() ); - - if( getRadioUniqueIDAlias() != null ) - { - sb.append( "/" ); - sb.append( getRadioUniqueIDAlias().getName() ); - } - break; - default: - sb.append( "UNKNOWN " ); - sb.append( getArea() ); - sb.append( " " ); - sb.append( format( getChannel(), 2 ) ); - sb.append( " " ); - sb.append( format( getHomeRepeater(), 2 ) ); - sb.append( " " ); - sb.append( format( getGroup(), 3 ) ); - sb.append( " " ); - sb.append( format( getFree(), 2 ) ); - sb.append( " CRC:" ); - sb.append( CRCLTR.getTransmittedChecksum( mMessage ) ); - sb.append( " CALC:" ); - sb.append( CRCLTR.getCalculatedChecksum( mMessage ) ); - } - - return sb.toString(); - } - - public int getRadioUniqueID() - { - int retVal = INT_NULL_VALUE; - - if( mMessageType == MessageType.ID_UNIQ ) - { - retVal = getInt( sSIXTEEN_BITS ); - } - - return retVal; - } - - public Alias getRadioUniqueIDAlias() - { - if( mAliasList != null ) - { - return mAliasList.getUniqueID( getRadioUniqueID() ); - } - - return null; - } - - public MessageType getMessageType() - { - MessageType retVal = MessageType.UN_KNWN; - - int channel = getChannel(); - - if( channel == 31 ) - { - retVal = MessageType.CA_ENDD; - } - else if( channel > 20 ) - { - switch( channel ) - { - case sCHIU_UNIQUE_ID: - retVal = MessageType.ID_UNIQ; - break; - case sCHIU_ESN_LOW: - retVal = MessageType.ID_ESNH; - break; - case sCHIU_ESN_HIGH: - retVal = MessageType.ID_ESNL; - break; - } - } - else if( channel > 0 ) - { - int free = getFree(); - - switch( free ) - { - case sFREE_CALL: - retVal = MessageType.CA_STRT; - break; - case sFREE_END_CALL: - retVal = MessageType.CA_ENDD; - break; - case sFREE_REQUEST_ACCESS: - retVal = MessageType.RQ_ACCE; - break; - } - } - - return retVal; - } - @Override - public String getFromID() - { - return null; - } - - @Override - public Alias getFromIDAlias() - { - return null; - } - - @Override - public void setAuxiliaryMessage( LTRNetOSWMessage message ) - { - // TODO Auto-generated method stub - - } - - @Override - public Plottable getPlottable() - { - // TODO Auto-generated method stub - return null; - } - - /** - * Provides a listing of aliases contained in the message. - */ - public List getAliases() - { - List aliases = new ArrayList(); - - Alias from = getFromIDAlias(); - - if( from != null ) - { - aliases.add( from ); - } - - Alias to = getToIDAlias(); - - if( to != null ) - { - aliases.add( to ); - } - - Alias esn = getESNAlias(); - - if( esn != null ) - { - aliases.add( esn ); - } - - return aliases; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetMessage.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetMessage.java deleted file mode 100644 index 4973229f..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetMessage.java +++ /dev/null @@ -1,243 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.ltrnet; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRC; -import io.github.dsheirer.edac.CRCLTR; -import io.github.dsheirer.message.Message; -import io.github.dsheirer.message.MessageDirection; -import io.github.dsheirer.message.MessageType; -import io.github.dsheirer.module.decode.DecoderType; - -import java.text.DecimalFormat; -import java.text.SimpleDateFormat; - -public abstract class LTRNetMessage extends Message -{ - protected static final String sUNKNOWN = "**UNKNOWN**"; - protected static final int INT_NULL_VALUE = -1; - protected static final double sDOUBLE_NULL_VALUE = -1.0D; - protected SimpleDateFormat mDatestampFormatter = - new SimpleDateFormat( "yyyyMMdd HHmmss" ); - protected DecimalFormat mDecimalFormatter = new DecimalFormat("0.00000"); - - protected static final int[] sSYNC = { 8,7,6,5,4,3,2,1,0 }; - protected static final int[] sAREA = { 9 }; - protected static final int[] sCHANNEL = { 14,13,12,11,10 }; - protected static final int[] sHOME_REPEATER = { 19,18,17,16,15 }; - protected static final int[] sGROUP = { 27,26,25,24,23,22,21,20 }; - protected static final int[] sFREE = { 32,31,30,29,28 }; - protected static final int[] sCRC = { 39,38,37,36,35,34,33 }; - protected static final int[] sSIXTEEN_BITS = { 32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17 }; - protected static final int[] sMANUFACTURER_ID = { 23,22,21,20,19,18,17 }; - - protected BinaryMessage mMessage; - protected MessageType mMessageType; - protected CRC mCRC; - protected AliasList mAliasList; - - public LTRNetMessage( BinaryMessage message, - MessageDirection direction, - AliasList list ) - { - mMessage = message; - mCRC = CRCLTR.check( message, direction ); - mAliasList = list; - } - - public MessageType getMessageType() - { - return mMessageType; - } - - public abstract void setAuxiliaryMessage( LTRNetOSWMessage message ); - - public boolean isValid() - { - return mCRC != CRC.FAILED_CRC && mCRC != CRC.FAILED_PARITY; - } - - /** - * Appends spaces to the end of the stringbuilder to make it length long - */ - protected void pad( StringBuilder sb, int length ) - { - while( sb.length() < length ) - { - sb.append( " " ); - } - } - - @Override - public String getBinaryMessage() - { - return mMessage.toString(); - } - - /** - * Returns a value formatted in hex notation. - * - * @param value - integer value - * @return - hex digits returned - */ - protected static String getHex( int value, int digits ) - { - return String.format( "%0" + digits + "X", value ); - } - - protected int getInt( int[] bits ) - { - int retVal = 0; - - for( int x = 0; x < bits.length; x++ ) - { - if( mMessage.get( bits[ x ] ) ) - { - retVal += 1< +public class LTRNetMessageFilter extends Filter { - private HashMap> mElements = - new HashMap>(); - - public LTRNetMessageFilter() - { - super( "LTR-Net Message Filter" ); + private Map> mElements = new HashMap<>(); - mElements.put( MessageType.CA_STRT, - new FilterElement( MessageType.CA_STRT ) ); - mElements.put( MessageType.CA_ENDD, - new FilterElement( MessageType.CA_ENDD ) ); - mElements.put( MessageType.SY_IDLE, - new FilterElement( MessageType.SY_IDLE ) ); - mElements.put( MessageType.ID_UNIQ, - new FilterElement( MessageType.ID_UNIQ ) ); - mElements.put( MessageType.ID_ESNH, - new FilterElement( MessageType.ID_ESNH ) ); - mElements.put( MessageType.ID_ESNL, - new FilterElement( MessageType.ID_ESNL ) ); - mElements.put( MessageType.RQ_ACCE, - new FilterElement( MessageType.RQ_ACCE ) ); - mElements.put( MessageType.ID_SITE, - new FilterElement( MessageType.ID_SITE ) ); - mElements.put( MessageType.FQ_RXHI, - new FilterElement( MessageType.FQ_RXHI ) ); - mElements.put( MessageType.FQ_RXLO, - new FilterElement( MessageType.FQ_RXLO ) ); - mElements.put( MessageType.FQ_TXHI, - new FilterElement( MessageType.FQ_TXHI ) ); - mElements.put( MessageType.FQ_TXLO, - new FilterElement( MessageType.FQ_TXLO ) ); - mElements.put( MessageType.ID_NBOR, - new FilterElement( MessageType.ID_NBOR ) ); - mElements.put( MessageType.MA_CHNH, - new FilterElement( MessageType.MA_CHNH ) ); - mElements.put( MessageType.MA_CHNL, - new FilterElement( MessageType.MA_CHNL ) ); - } - - @Override - public boolean passes( Message message ) + public LTRNetMessageFilter() { - if( mEnabled && canProcess( message ) ) - { - LTRNetMessage ltr = (LTRNetMessage)message; - - if( mElements.containsKey( ltr.getMessageType() ) ) - { - return mElements.get( ltr.getMessageType() ).isEnabled(); - } - } - - return false; + super("LTR-Net Message Filter"); + + for(LtrNetMessageType type: LtrNetMessageType.values()) + { + mElements.put(type, new FilterElement<>(type)); + } } - @Override - public boolean canProcess( Message message ) + @Override + public boolean passes(IMessage message) { - return message instanceof LTRNetMessage; + if(mEnabled && canProcess(message)) + { + LtrNetMessage ltr = (LtrNetMessage) message; + + if(mElements.containsKey(ltr.getLtrNetMessageType())) + { + return mElements.get(ltr.getLtrNetMessageType()).isEnabled(); + } + } + + return false; } - @Override + @Override + public boolean canProcess(IMessage message) + { + return message instanceof LtrNetMessage; + } + + @Override public List> getFilterElements() { - return new ArrayList>( mElements.values() ); + return new ArrayList>(mElements.values()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetMessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetMessageProcessor.java index b40268cf..4dab1a1e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetMessageProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetMessageProcessor.java @@ -1,164 +1,132 @@ /******************************************************************************* * 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 ******************************************************************************/ package io.github.dsheirer.module.decode.ltrnet; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.MessageDirection; -import io.github.dsheirer.message.MessageType; +import io.github.dsheirer.module.decode.ltrnet.message.LtrNetMessage; +import io.github.dsheirer.module.decode.ltrnet.message.LtrNetMessageFactory; +import io.github.dsheirer.module.decode.ltrnet.message.isw.RegistrationRequestEsnHigh; +import io.github.dsheirer.module.decode.ltrnet.message.isw.RegistrationRequestEsnLow; +import io.github.dsheirer.module.decode.ltrnet.message.osw.ReceiveFrequencyHigh; +import io.github.dsheirer.module.decode.ltrnet.message.osw.ReceiveFrequencyLow; +import io.github.dsheirer.module.decode.ltrnet.message.osw.TransmitFrequencyHigh; +import io.github.dsheirer.module.decode.ltrnet.message.osw.TransmitFrequencyLow; import io.github.dsheirer.sample.Listener; import java.util.HashMap; +import java.util.Map; -public class LTRNetMessageProcessor implements Listener +public class LTRNetMessageProcessor implements Listener { - private Listener mMessageListener; + private Listener mMessageListener; - private HashMap mReceiveHighAuxMessages = - new HashMap(); - private HashMap mReceiveLowAuxMessages = - new HashMap(); + private Map mReceiveHighMessageMap = new HashMap<>(); + private Map mReceiveLowMessageMap = new HashMap<>(); + private Map mTransmitHighMessageMap = new HashMap<>(); + private Map mTransmitLowMessageMap = new HashMap<>(); - private HashMap mTransmitHighAuxMessages = - new HashMap(); - private HashMap mTransmitLowAuxMessages = - new HashMap(); + private RegistrationRequestEsnHigh mRegistrationRequestEsnHighMessage; + private RegistrationRequestEsnLow mRegistrationRequestEsnLowMessage; - private LTRNetISWMessage mESNHighMessage; - private LTRNetISWMessage mESNLowMessage; - - private MessageDirection mDirection; - private AliasList mAliasList; - - public LTRNetMessageProcessor( MessageDirection direction, AliasList list ) - { - mDirection = direction; - mAliasList = list; - } - - @Override - public void receive( BinaryMessage buffer ) + private MessageDirection mDirection; + + public LTRNetMessageProcessor(MessageDirection direction) { - if( mMessageListener != null ) - { - LTRNetMessage message; - - if( mDirection == MessageDirection.OSW ) - { - message = new LTRNetOSWMessage( buffer, mAliasList ); - - if( message.isValid() ) - { - if( message.getMessageType() == MessageType.FQ_TXHI ) - { - mTransmitHighAuxMessages.put( message.getHomeRepeater(), (LTRNetOSWMessage)message ); - - message.setAuxiliaryMessage( mTransmitLowAuxMessages.get(message.getHomeRepeater() ) ); - } - else if( message.getMessageType() == MessageType.FQ_TXLO ) - { - mTransmitLowAuxMessages.put( message.getHomeRepeater(), - (LTRNetOSWMessage)message ); - - message.setAuxiliaryMessage( mTransmitHighAuxMessages.get( - message.getHomeRepeater() ) ); - } - else if( message.getMessageType() == MessageType.FQ_RXHI ) - { - mReceiveHighAuxMessages.put( message.getHomeRepeater(), - (LTRNetOSWMessage)message ); - - message.setAuxiliaryMessage( mReceiveLowAuxMessages.get( - message.getHomeRepeater() ) ); - } - else if( message.getMessageType() == MessageType.FQ_RXLO ) - { - mReceiveLowAuxMessages.put( message.getHomeRepeater(), - (LTRNetOSWMessage)message ); - message.setAuxiliaryMessage( mReceiveHighAuxMessages.get( - message.getHomeRepeater() ) ); - } - } - } - else - { - buffer.flip( 0, 40 ); - - message = new LTRNetISWMessage( buffer, mAliasList ); - - if( message.isValid() ) - { - /** - * Catch ESN High messages to marry up with ESN Low messages. Purge - * the ESN high message after 2 messages, so that it doesn't get - * paired with the wrong esn low message - */ - if( message.getMessageType() == MessageType.ID_ESNH ) - { - mESNHighMessage = (LTRNetISWMessage)message; - - if( mESNLowMessage != null ) - { - /* Check that the stored message is not older than - * 5 seconds */ - if( System.currentTimeMillis() - - mESNLowMessage.getTimeReceived() < 5000 ) - { - ((LTRNetISWMessage)message).setAuxiliaryMessage( mESNLowMessage ); - } - else - { - mESNLowMessage = null; - } - } - } - else if( message.getMessageType() == MessageType.ID_ESNL ) - { - mESNLowMessage = (LTRNetISWMessage)message; - - if( mESNHighMessage != null ) - { - /* Check that the stored message is not older than 5 seconds */ - if( System.currentTimeMillis() - mESNHighMessage.getTimeReceived() < 5000 ) - { - ((LTRNetISWMessage)message).setAuxiliaryMessage( mESNHighMessage ); - } - else - { - mESNHighMessage = null; - } - } - } - } - } - - mMessageListener.receive( message ); - } + mDirection = direction; } - - public void setMessageListener( Listener listener ) + + @Override + public void receive(CorrectedBinaryMessage buffer) { - mMessageListener = listener; + LtrNetMessage message = LtrNetMessageFactory.create(mDirection, buffer, System.currentTimeMillis()); + + if(message.isValid()) + { + switch(message.getLtrNetMessageType()) + { + case OSW_TRANSMIT_FREQUENCY_HIGH: + if(message instanceof TransmitFrequencyHigh) + { + TransmitFrequencyHigh transmitFrequencyHigh = (TransmitFrequencyHigh)message; + mTransmitHighMessageMap.put(transmitFrequencyHigh.getChannel(), transmitFrequencyHigh); + transmitFrequencyHigh.setFrequencyLow(mTransmitLowMessageMap.get(transmitFrequencyHigh.getChannel())); + } + break; + case OSW_TRANSMIT_FREQUENCY_LOW: + if(message instanceof TransmitFrequencyLow) + { + TransmitFrequencyLow transmitFrequencyLow = (TransmitFrequencyLow)message; + mTransmitLowMessageMap.put(transmitFrequencyLow.getChannel(), transmitFrequencyLow); + transmitFrequencyLow.setFrequencyHigh(mTransmitHighMessageMap.get(transmitFrequencyLow.getChannel())); + } + break; + case OSW_RECEIVE_FREQUENCY_HIGH: + if(message instanceof ReceiveFrequencyHigh) + { + ReceiveFrequencyHigh receiveFrequencyHigh = (ReceiveFrequencyHigh)message; + mReceiveHighMessageMap.put(receiveFrequencyHigh.getChannel(), receiveFrequencyHigh); + receiveFrequencyHigh.setFrequencyLow(mReceiveLowMessageMap.get(receiveFrequencyHigh.getChannel())); + } + break; + case OSW_RECEIVE_FREQUENCY_LOW: + if(message instanceof ReceiveFrequencyLow) + { + ReceiveFrequencyLow receiveFrequencyLow = (ReceiveFrequencyLow)message; + mReceiveLowMessageMap.put(receiveFrequencyLow.getChannel(), receiveFrequencyLow); + receiveFrequencyLow.setFrequencyHigh(mReceiveHighMessageMap.get(receiveFrequencyLow.getChannel())); + } + break; + case ISW_REGISTRATION_REQUEST_ESN_HIGH: + if(message instanceof RegistrationRequestEsnHigh) + { + ((RegistrationRequestEsnHigh)message).setEsnLowMessage(mRegistrationRequestEsnLowMessage); + } + break; + case ISW_REGISTRATION_REQUEST_ESN_LOW: + if(message instanceof RegistrationRequestEsnLow) + { + ((RegistrationRequestEsnLow)message).setESNHighMessage(mRegistrationRequestEsnHighMessage); + } + break; + case ISW_CALL_START: + case ISW_CALL_END: + //Reset the esn messages + mRegistrationRequestEsnHighMessage = null; + mRegistrationRequestEsnLowMessage = null; + break; + } + + if(mMessageListener != null) + { + mMessageListener.receive(message); + } + } + } + + public void setMessageListener(Listener listener) + { + mMessageListener = listener; } public void removeMessageListener() { - mMessageListener = null; + mMessageListener = null; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetOSWMessage.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetOSWMessage.java deleted file mode 100644 index 89368985..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetOSWMessage.java +++ /dev/null @@ -1,644 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.ltrnet; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.map.Plottable; -import io.github.dsheirer.message.MessageDirection; -import io.github.dsheirer.message.MessageType; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -public class LTRNetOSWMessage extends LTRNetMessage -{ - private static final int CHANNEL_FREQUENCY_MESSAGE_TYPE_BIT = 20; - private static final int CHANNEL_MAP_MESSAGE_TYPE_BIT = 17; - - private static final int CHIU_UNIQUE_ID = 17; - private static final int CHIU_SITE_ID = 18; - private static final int CHIU_TRANSMIT_FREQUENCY = 24; - private static final int CHIU_RECEIVE_FREQUENCY = 25; - private static final int CHIU_NEIGHBOR = 26; - private static final int CHIU_MAP = 28; - private static final int CHIU_CALL_END = 31; - - private static final int HOME_DIRECTED_GROUP_CALL = 29; - private static final int HOME_CHANNEL_IN_USE_MUTE_AUDIO = 30; - - private static final int GROUP_DO_NOTHING = 253; - private static final int GROUP_CWID = 254; - private static final int GROUP_CHANNEL_IDLE = 255; - - private static final int FREE_ALL_CHANNELS_BUSY = 0; - private static final int FREE_LTR_GROUP_CALL = 30; - private static final int FREE_LTRNET_GROUP_CALL = 31; - - private LTRNetOSWMessage mAuxMessage; - - public LTRNetOSWMessage( BinaryMessage message, AliasList list ) - { - super( message, MessageDirection.OSW, list ); - mMessageType = getType(); - } - - public void setAuxiliaryMessage( LTRNetOSWMessage message ) - { - if( message != null ) - { - mAuxMessage = message; - } - } - - public String getRadioUniqueIDText() - { - StringBuilder sb = new StringBuilder(); - - int radioId = getRadioUniqueID(); - - if( radioId != INT_NULL_VALUE ) - { - sb.append( " RADIO UNIQUE ID: " ); - sb.append( radioId ); - } - - return sb.toString(); - } - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append( mDatestampFormatter.format( - new Date( System.currentTimeMillis() ) ) ); - sb.append( " LTRNet OSW [" ); - sb.append( mCRC.getAbbreviation() ); - sb.append( "] " ); - sb.append( getMessage() ); - pad( sb, 100 ); - sb.append( mMessage.toString() ); - - return sb.toString(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - switch( mMessageType ) - { - case SY_IDLE: - sb.append( "IDLE AREA:" ); - sb.append( getArea() ); - sb.append( " LCN:" ); - sb.append( format( getChannel(), 2 ) ); - sb.append( " HOME:" ); - sb.append( format( getHomeRepeater(), 2 ) ); - sb.append( " GRP:" ); - sb.append( format( getGroup(), 3 ) ); - sb.append( " FREE:" ); - sb.append( format( getFree(), 2 ) ); - break; - case CA_STRT: - sb.append( "CALL LCN:" ); - sb.append( format( getChannel(), 2 ) ); - sb.append( " TG [ " ); - sb.append( getTalkgroupID() ); - sb.append( "/" ); - sb.append( getTalkgroupIDAlias() ); - sb.append( " ] FREE:" ); - sb.append( format( getFree(), 2 ) ); - break; - case CA_ENDD: - sb.append( "END* AREA:" ); - sb.append( getArea() ); - sb.append( " LCN:" ); - sb.append( format( getChannel(), 2 ) ); - sb.append( " TG [" ); - sb.append( getTalkgroupID() ); - sb.append( "/" ); - sb.append( getTalkgroupIDAlias() ); - sb.append( "] FREE:" ); - sb.append( format( getFree(), 2 ) ); - break; - case MA_CHNL: - sb.append( "CHAN MAP LOW LCN [" ); - sb.append( getChannelMapAsString( getChannelMapLow() ) ); - sb.append( "]" ); - break; - case MA_CHNH: - sb.append( "CHAN MAP HIGH LCN [" ); - sb.append( getChannelMapAsString( getChannelMapHigh() ) ); - sb.append( "]" ); - break; - case FQ_RXLO: - sb.append( "CHAN RX LOW LCN:" ); - sb.append( format( getHomeRepeater(), 2 ) ); - sb.append( " = " ); - long rxFreqLo = getFrequency(); - if( rxFreqLo > 0 ) - { - sb.append( mDecimalFormatter.format( (double)rxFreqLo / 1E6d ) ); - } - else - { - sb.append( "000.00000" ); - } - sb.append( " MHz" ); - break; - case FQ_RXHI: - sb.append( "CHAN RX HIGH LCN:" ); - sb.append( format( getHomeRepeater(), 2 ) ); - sb.append( " = " ); - long rxfreqhi = getFrequency(); - if( rxfreqhi > 0 ) - { - sb.append( mDecimalFormatter.format( (double)rxfreqhi / 1E6d ) ); - } - else - { - sb.append( "000.00000" ); - } - sb.append( " MHz" ); - break; - case FQ_TXHI: - sb.append( "CHAN TX LOW LCN:" ); - sb.append( format( getHomeRepeater(), 2 ) ); - sb.append( " = " ); - long txfreq = getFrequency(); - if( txfreq > 0 ) - { - sb.append( mDecimalFormatter.format( (double)txfreq / 1E6d ) ); - } - else - { - sb.append( "000.00000" ); - } - sb.append( " MHz" ); - break; - case FQ_TXLO: - sb.append( "CHAN TX HIGH LCN:" ); - sb.append( format( getHomeRepeater(), 2 ) ); - sb.append( " = " ); - long txFreqLo = getFrequency(); - if( txFreqLo > 0 ) - { - sb.append( mDecimalFormatter.format( (double)txFreqLo / 1E6d ) ); - } - else - { - sb.append( "000.00000" ); - } - sb.append( " MHz" ); - break; - case ID_SITE: - String siteId = getSiteID(); - if( siteId != null ) - { - sb.append( "SITE ID: " ); - sb.append( siteId ); - sb.append( " " ); - sb.append( getSiteIDAlias() ); - } - break; - case ID_NBOR: - int neighborRank = getNeighborRank(); - String neighborId = getNeighborID(); - - if( neighborRank != INT_NULL_VALUE && - neighborId != null ) - { - sb.append( "NEIGHBOR SITE " ); - sb.append( neighborRank ); - sb.append( ": " ); - sb.append( neighborId ); - sb.append( " " ); - sb.append( getNeighborIDAlias() ); - } - break; - case ID_UNIQ: - sb.append( "REGISTER RADIO UNIQUE ID:" ); - sb.append( getRadioUniqueID() ); - break; - default: - sb.append( "UNKNOWN " ); - sb.append( getArea() ); - sb.append( " " + format( getChannel(), 2 ) ); - sb.append( " " + format( getHomeRepeater(), 2 ) ); - sb.append( " " + format( getGroup(), 3 ) ); - sb.append( " " + format( getFree(), 2 ) ); - sb.append( " A:" ); - sb.append( getArea() ); - sb.append( " C:" ); - sb.append( format( getChannel(), 2 ) ); - sb.append( " H:" ); - sb.append( format( getHomeRepeater(), 2 ) ); - sb.append( " G:" ); - sb.append( format( getGroup(), 3 ) ); - sb.append( " F:" ); - sb.append( format( getFree(), 2 ) ); - } - - return sb.toString(); - } - - public String getTalkgroupID() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getArea() ); - sb.append( "-" ); - sb.append( format( getHomeRepeater(), 2 ) ); - sb.append( "-" ); - sb.append( format( getGroup(), 3 ) ); - - return sb.toString(); - } - - public Alias getTalkgroupIDAlias() - { - if( mAliasList != null ) - { - return mAliasList.getTalkgroupAlias( getTalkgroupID() ); - } - - return null; - } - - public ArrayList getChannelMapLow() - { - ArrayList retVal = new ArrayList(); - - if( mMessageType == MessageType.MA_CHNL ) - { - for( int x = 27; x >= 18; x-- ) - { - if( mMessage.get( x ) ) - { - retVal.add( 28 - x ); - } - } - } - - return retVal; - } - - public ArrayList getChannelMapHigh() - { - ArrayList retVal = new ArrayList(); - - if( mMessageType == MessageType.MA_CHNH ) - { - for( int x = 27; x >= 18; x-- ) - { - if( mMessage.get( x ) ) - { - retVal.add( 38 - x ); - } - } - } - - return retVal; - } - - public String getChannelMapAsString( ArrayList channels ) - { - StringBuilder sb = new StringBuilder(); - - for( Integer channel: channels ) - { - sb.append( channel ); - sb.append( " " ); - } - - return sb.toString(); - } - - public int getRadioUniqueID() - { - int retVal = INT_NULL_VALUE; - - if( mMessageType == MessageType.ID_UNIQ ) - { - retVal = mMessage.getInt( 17, 32 ); - } - - return retVal; - } - - public Alias getRadioUniqueIDAlias() - { - if( mAliasList != null ) - { - return mAliasList.getUniqueID( getRadioUniqueID() ); - } - - return null; - } - - public String getSiteID() - { - if( mMessageType == MessageType.ID_SITE ) - { - return String.valueOf( mMessage.getInt( 23, 32 ) ); - } - - return null; - } - - public Alias getSiteIDAlias() - { - if( mAliasList != null ) - { - return mAliasList.getSiteID( getSiteID() ); - } - - return null; - } - - public String getNeighborID() - { - if( mMessageType == MessageType.ID_NBOR ) - { - int id = mMessage.getInt( 23, 32 ); - - if( id > 0 ) - { - return String.valueOf( id ); - } - } - - return null; - } - - private Alias getNeighborIDAlias() - { - if( mAliasList != null ) - { - return mAliasList.getSiteID( getNeighborID() ); - } - - return null; - } - - public int getNeighborRank() - { - int retVal = INT_NULL_VALUE; - - if( mMessageType == MessageType.ID_NBOR ) - { - retVal = mMessage.getInt( 15, 18 ) + 1; - } - - return retVal; - } - - public long getFrequency() - { - long retVal = 0; - - if( mAuxMessage != null ) - { - int high; - int low; - - MessageType type = getMessageType(); - - if( type == MessageType.FQ_RXHI || type == MessageType.FQ_TXHI ) - { - high = getHighChannelUnits(); - low = mAuxMessage.getLowChannelUnits(); - } - else - { - high = mAuxMessage.getHighChannelUnits(); - low = getLowChannelUnits(); - } - - if( high != INT_NULL_VALUE && low != INT_NULL_VALUE ) - { - retVal = ( high + low ) * 1250 + 150000000; - } - } - - return retVal; - } - - /** - * Returns the value of the high order (15 - 12) frequency bits which are - * the lower 4 bits of the Free field, left shifted by 12, representing the - * frequency value in units of .00125 MHz - */ - public int getHighChannelUnits() - { - int retVal = INT_NULL_VALUE; - - if( mAuxMessage != null && - ( mMessageType == MessageType.FQ_RXHI || - mMessageType == MessageType.FQ_TXHI ) ) - { - retVal = mMessage.getInt( 29, 32 ); - - retVal = Integer.rotateLeft( retVal, 12 ); - } - - return retVal; - } - - /** - * Returns the value of the low order (11 - 0) frequency bits which are - * contained in the Group and Free fields representing the frequency value - * units of .00125 MHz - */ - public int getLowChannelUnits() - { - int retVal = INT_NULL_VALUE; - - if( mMessageType == MessageType.FQ_RXLO || - mMessageType == MessageType.FQ_TXLO ) - { - retVal = mMessage.getInt( 21, 32 ); - } - - return retVal; - } - - public MessageType getType() - { - MessageType retVal = MessageType.UN_KNWN; - - int channel = getChannel(); - - int home = getHomeRepeater(); - - //LTR-Net messages - if( home != 31 && ( channel > 20 || home > 20 ) ) - { - switch( channel ) - { - case CHIU_UNIQUE_ID: - retVal = MessageType.ID_UNIQ; - break; - case CHIU_SITE_ID: - retVal = MessageType.ID_SITE; - break; - case CHIU_TRANSMIT_FREQUENCY: - if( getHomeRepeater() == 24 ) - { - retVal = MessageType.ID_UNIQ; - } - else - { - if( mMessage.get( CHANNEL_FREQUENCY_MESSAGE_TYPE_BIT ) ) - { - retVal = MessageType.FQ_TXHI; - } - else - { - retVal = MessageType.FQ_TXLO; - } - } - break; - case CHIU_RECEIVE_FREQUENCY: - if( mMessage.get( CHANNEL_FREQUENCY_MESSAGE_TYPE_BIT ) ) - { - retVal = MessageType.FQ_RXHI; - } - else - { - retVal = MessageType.FQ_RXLO; - } - break; - case CHIU_NEIGHBOR: - retVal = MessageType.ID_NBOR; - break; - case CHIU_MAP: - if( mMessage.get( CHANNEL_MAP_MESSAGE_TYPE_BIT ) ) - { - retVal = MessageType.MA_CHNH; - } - else - { - retVal = MessageType.MA_CHNL; - } - break; - case CHIU_CALL_END: - retVal = MessageType.CA_ENDD; - } - } - else - { - int group = getGroup(); - - if( group == GROUP_CHANNEL_IDLE ) - { - retVal = MessageType.SY_IDLE; - } - else - { - retVal = MessageType.CA_STRT; - } - } - - return retVal; - } - - @Override - public String getFromID() - { - if( mMessageType == MessageType.CA_STRT || - mMessageType == MessageType.CA_ENDD ) - { - return getTalkgroupID(); - } - else - { - return null; - } - } - - @Override - public Alias getFromIDAlias() - { - if( mMessageType == MessageType.CA_STRT || - mMessageType == MessageType.CA_ENDD ) - { - return getTalkgroupIDAlias(); - } - else - { - return null; - } - } - - @Override - public Plottable getPlottable() - { - // TODO Auto-generated method stub - return null; - } - - /** - * Provides a listing of aliases contained in the message. - */ - public List getAliases() - { - List aliases = new ArrayList(); - - Alias from = getFromIDAlias(); - - if( from != null ) - { - aliases.add( from ); - } - - Alias to = getToIDAlias(); - - if( to != null ) - { - aliases.add( to ); - } - - Alias neighbor = getNeighborIDAlias(); - - if( neighbor != null ) - { - aliases.add( neighbor ); - } - - Alias uid = getRadioUniqueIDAlias(); - - if( uid != null ) - { - aliases.add( uid ); - } - - Alias site = getSiteIDAlias(); - - if( site != null ) - { - aliases.add( site ); - } - - return aliases; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LtrNetMessageType.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LtrNetMessageType.java new file mode 100644 index 00000000..b9a25707 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LtrNetMessageType.java @@ -0,0 +1,46 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet; + +public enum LtrNetMessageType +{ + ISW_CALL_END, + ISW_CALL_START, + ISW_REGISTRATION_REQUEST_ESN_HIGH, + ISW_REGISTRATION_REQUEST_ESN_LOW, + ISW_REQUEST_ACCESS, + ISW_UNIQUE_ID, + ISW_UNKNOWN, + + OSW_CALL_END, + OSW_CALL_START, + OSW_CHANNEL_MAP_HIGH, + OSW_CHANNEL_MAP_LOW, + OSW_SYSTEM_IDLE, + OSW_NEIGHBOR_ID, + OSW_RECEIVE_FREQUENCY_HIGH, + OSW_RECEIVE_FREQUENCY_LOW, + OSW_REGISTRATION_ACCEPT, + OSW_SITE_ID, + OSW_TRANSMIT_FREQUENCY_HIGH, + OSW_TRANSMIT_FREQUENCY_LOW, + OSW_UNKNOWN +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/channel/LtrNetChannel.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/channel/LtrNetChannel.java new file mode 100644 index 00000000..df753ccf --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/channel/LtrNetChannel.java @@ -0,0 +1,111 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.channel; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBand; +import io.github.dsheirer.protocol.Protocol; + +public class LtrNetChannel implements IChannelDescriptor, Comparable +{ + private long mDownlink; + private long mUplink; + private int mChannel; + + public LtrNetChannel(int channel) + { + mChannel = channel; + } + + public int getChannel() + { + return mChannel; + } + + public void setDownlink(long downlink) + { + mDownlink = downlink; + } + + public void setUplink(long uplink) + { + mUplink = uplink; + } + + @Override + public long getDownlinkFrequency() + { + return mDownlink; + } + + @Override + public long getUplinkFrequency() + { + return mUplink; + } + + @Override + public int[] getFrequencyBandIdentifiers() + { + return new int[0]; + } + + @Override + public void setFrequencyBand(IFrequencyBand bandIdentifier) + { + //No-op + } + + @Override + public boolean isTDMAChannel() + { + return false; + } + + @Override + public int getTimeslotCount() + { + return 0; + } + + @Override + public Protocol getProtocol() + { + return Protocol.LTR_NET; + } + + @Override + public String toString() + { + return String.valueOf(getChannel()); + } + + public String description() + { + return "TRANSMIT: " + getDownlinkFrequency() + " RECEIVE:" + getUplinkFrequency(); + } + + @Override + public int compareTo(LtrNetChannel o) + { + return Integer.compare(getChannel(), o.getChannel()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/identifier/NeighborIdentifier.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/identifier/NeighborIdentifier.java new file mode 100644 index 00000000..54882ec6 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/identifier/NeighborIdentifier.java @@ -0,0 +1,46 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.identifier; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.integer.IntegerIdentifier; +import io.github.dsheirer.protocol.Protocol; + +public class NeighborIdentifier extends IntegerIdentifier +{ + public NeighborIdentifier(int site) + { + super(site, IdentifierClass.NETWORK, Form.NEIGHBOR_SITE, Role.BROADCAST); + } + + @Override + public Protocol getProtocol() + { + return Protocol.LTR_NET; + } + + public static NeighborIdentifier create(int site) + { + return new NeighborIdentifier(site); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/identifier/SiteIdentifier.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/identifier/SiteIdentifier.java new file mode 100644 index 00000000..983cc6fb --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/identifier/SiteIdentifier.java @@ -0,0 +1,46 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.identifier; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.integer.IntegerIdentifier; +import io.github.dsheirer.protocol.Protocol; + +public class SiteIdentifier extends IntegerIdentifier +{ + public SiteIdentifier(int site) + { + super(site, IdentifierClass.NETWORK, Form.SITE, Role.BROADCAST); + } + + @Override + public Protocol getProtocol() + { + return Protocol.LTR_NET; + } + + public static SiteIdentifier create(int site) + { + return new SiteIdentifier(site); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/identifier/UniqueIdentifier.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/identifier/UniqueIdentifier.java new file mode 100644 index 00000000..b0d53d53 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/identifier/UniqueIdentifier.java @@ -0,0 +1,66 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.identifier; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.integer.IntegerIdentifier; +import io.github.dsheirer.protocol.Protocol; + +/** + * Unique ID or radio ID for LTR-Net radios is a 16-bit logical network radio identifier value. + */ +public class UniqueIdentifier extends IntegerIdentifier implements Comparable +{ + public UniqueIdentifier(int value, Role role) + { + super(value, IdentifierClass.USER, Form.UNIQUE_ID, role); + } + + @Override + public Protocol getProtocol() + { + return Protocol.LTR_NET; + } + + /** + * Creates a unique identifier for the FROM radio role + */ + public static UniqueIdentifier createFrom(int value) + { + return new UniqueIdentifier(value, Role.FROM); + } + + /** + * Creates a unique identifier for the TO radio role + */ + public static UniqueIdentifier createTo(int value) + { + return new UniqueIdentifier(value, Role.TO); + } + + @Override + public int compareTo(UniqueIdentifier o) + { + return getValue().compareTo(o.getValue()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/LtrNetMessage.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/LtrNetMessage.java new file mode 100644 index 00000000..8f6b9a83 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/LtrNetMessage.java @@ -0,0 +1,116 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.CRC; +import io.github.dsheirer.edac.CRCLTR; +import io.github.dsheirer.identifier.talkgroup.LTRTalkgroup; +import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.MessageDirection; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; +import io.github.dsheirer.protocol.Protocol; + +public abstract class LtrNetMessage extends Message +{ + protected static final int[] SYNC = {0, 1, 2, 3, 4, 5, 6, 7, 8}; + protected static final int[] AREA = {9}; + protected static final int[] CHANNEL = {10, 11, 12, 13, 14}; + protected static final int[] HOME_REPEATER = {15, 16, 17, 18, 19}; + protected static final int[] GROUP = {20, 21, 22, 23, 24, 25, 26, 27}; + protected static final int[] FREE = {28, 29, 30, 31, 32}; + protected static final int[] CRC_FIELD = {33, 34, 35, 36, 37, 38, 39}; + protected static final int[] SIXTEEN_BITS = {17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}; + + protected CorrectedBinaryMessage mMessage; + protected CRC mCRC; + private LTRTalkgroup mTalkgroup; + + public LtrNetMessage(CorrectedBinaryMessage message, MessageDirection direction, long timestamp) + { + super(timestamp); + mMessage = message; + mCRC = CRCLTR.check(message, direction); + } + + /** + * Underlying binary message + */ + protected CorrectedBinaryMessage getMessage() + { + return mMessage; + } + + public abstract LtrNetMessageType getLtrNetMessageType(); + + @Override + public Protocol getProtocol() + { + return Protocol.LTR_NET; + } + + /** + * Talkgroup identifier + */ + public LTRTalkgroup getTalkgroup() + { + if(mTalkgroup == null) + { + mTalkgroup = LTRTalkgroup.create((getHomeRepeater(getMessage()) << 8) + getGroup(getMessage())); + } + + return mTalkgroup; + } + + public boolean isValid() + { + return mCRC.passes(); + } + + public CRC getCRC() + { + return mCRC; + } + + public static int getArea(CorrectedBinaryMessage message) + { + return message.getInt(AREA); + } + + public static int getChannel(CorrectedBinaryMessage message) + { + return message.getInt(CHANNEL); + } + + public static int getHomeRepeater(CorrectedBinaryMessage message) + { + return message.getInt(HOME_REPEATER); + } + + public static int getGroup(CorrectedBinaryMessage message) + { + return message.getInt(GROUP); + } + + public static int getFree(CorrectedBinaryMessage message) + { + return message.getInt(FREE); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/LtrNetMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/LtrNetMessageFactory.java new file mode 100644 index 00000000..ddfd8c48 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/LtrNetMessageFactory.java @@ -0,0 +1,119 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.message.MessageDirection; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; +import io.github.dsheirer.module.decode.ltrnet.message.isw.IswCallEnd; +import io.github.dsheirer.module.decode.ltrnet.message.isw.IswCallStart; +import io.github.dsheirer.module.decode.ltrnet.message.isw.IswUniqueId; +import io.github.dsheirer.module.decode.ltrnet.message.isw.IswUnknown; +import io.github.dsheirer.module.decode.ltrnet.message.isw.LtrNetIswMessage; +import io.github.dsheirer.module.decode.ltrnet.message.isw.RegistrationRequestEsnHigh; +import io.github.dsheirer.module.decode.ltrnet.message.isw.RegistrationRequestEsnLow; +import io.github.dsheirer.module.decode.ltrnet.message.isw.RequestAccess; +import io.github.dsheirer.module.decode.ltrnet.message.osw.ChannelMapHigh; +import io.github.dsheirer.module.decode.ltrnet.message.osw.ChannelMapLow; +import io.github.dsheirer.module.decode.ltrnet.message.osw.LtrNetOswMessage; +import io.github.dsheirer.module.decode.ltrnet.message.osw.NeighborId; +import io.github.dsheirer.module.decode.ltrnet.message.osw.OswCallEnd; +import io.github.dsheirer.module.decode.ltrnet.message.osw.OswCallStart; +import io.github.dsheirer.module.decode.ltrnet.message.osw.OswUnknown; +import io.github.dsheirer.module.decode.ltrnet.message.osw.ReceiveFrequencyHigh; +import io.github.dsheirer.module.decode.ltrnet.message.osw.ReceiveFrequencyLow; +import io.github.dsheirer.module.decode.ltrnet.message.osw.RegistrationAccept; +import io.github.dsheirer.module.decode.ltrnet.message.osw.SiteId; +import io.github.dsheirer.module.decode.ltrnet.message.osw.SystemIdle; +import io.github.dsheirer.module.decode.ltrnet.message.osw.TransmitFrequencyHigh; +import io.github.dsheirer.module.decode.ltrnet.message.osw.TransmitFrequencyLow; + +/** + * Message factory for creating LTR-Net messages + */ +public class LtrNetMessageFactory +{ + public static LtrNetMessage create(MessageDirection messageDirection, CorrectedBinaryMessage message, long timestamp) + { + switch(messageDirection) + { + case ISW: + //Flip all of the message bits since ISW messages are inverted + message.flip(0, 40); + + LtrNetMessageType iswType = LtrNetIswMessage.getMessageType(message); + + switch(iswType) + { + case ISW_CALL_END: + return new IswCallEnd(message, timestamp); + case ISW_CALL_START: + return new IswCallStart(message, timestamp); + case ISW_REGISTRATION_REQUEST_ESN_HIGH: + return new RegistrationRequestEsnHigh(message, timestamp); + case ISW_REGISTRATION_REQUEST_ESN_LOW: + return new RegistrationRequestEsnLow(message, timestamp); + case ISW_REQUEST_ACCESS: + return new RequestAccess(message, timestamp); + case ISW_UNIQUE_ID: + return new IswUniqueId(message, timestamp); + case ISW_UNKNOWN: + default: + return new IswUnknown(message, timestamp); + } + case OSW: + LtrNetMessageType oswType = LtrNetOswMessage.getMessageType(message); + + switch(oswType) + { + case OSW_CALL_END: + return new OswCallEnd(message, timestamp); + case OSW_CALL_START: + return new OswCallStart(message, timestamp); + case OSW_CHANNEL_MAP_HIGH: + return new ChannelMapHigh(message, timestamp); + case OSW_CHANNEL_MAP_LOW: + return new ChannelMapLow(message, timestamp); + case OSW_SYSTEM_IDLE: + return new SystemIdle(message, timestamp); + case OSW_NEIGHBOR_ID: + return new NeighborId(message, timestamp); + case OSW_RECEIVE_FREQUENCY_HIGH: + return new ReceiveFrequencyHigh(message, timestamp); + case OSW_RECEIVE_FREQUENCY_LOW: + return new ReceiveFrequencyLow(message, timestamp); + case OSW_REGISTRATION_ACCEPT: + return new RegistrationAccept(message, timestamp); + case OSW_SITE_ID: + return new SiteId(message, timestamp); + case OSW_TRANSMIT_FREQUENCY_HIGH: + return new TransmitFrequencyHigh(message, timestamp); + case OSW_TRANSMIT_FREQUENCY_LOW: + return new TransmitFrequencyLow(message, timestamp); + case OSW_UNKNOWN: + default: + return new OswUnknown(message, timestamp); + } + } + + return null; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/IswCallEnd.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/IswCallEnd.java new file mode 100644 index 00000000..839a8b7e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/IswCallEnd.java @@ -0,0 +1,80 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.isw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; + +import java.util.ArrayList; +import java.util.List; + +/** + * Call End Message. + */ +public class IswCallEnd extends LtrNetIswMessage +{ + private List mIdentifiers; + + /** + * Constructs a message + */ + public IswCallEnd(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.ISW_CALL_END; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("CALL END TALKGROUP:").append(getTalkgroup().formatted()); + sb.append(" AREA:").append(getArea(getMessage())); + sb.append(" LCN:").append(getChannel(getMessage())); + sb.append(" FREE:").append(getFree(getMessage())); + return sb.toString(); + } + + /** + * LCN for the call + */ + public int getChannel() + { + return getChannel(getMessage()); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTalkgroup()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/IswCallStart.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/IswCallStart.java new file mode 100644 index 00000000..28717bf5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/IswCallStart.java @@ -0,0 +1,80 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.isw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; + +import java.util.ArrayList; +import java.util.List; + +/** + * Call Start Message. + */ +public class IswCallStart extends LtrNetIswMessage +{ + private List mIdentifiers; + + /** + * Constructs a message + */ + public IswCallStart(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.ISW_CALL_START; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("CALL START TALKGROUP:").append(getTalkgroup().formatted()); + sb.append(" AREA:").append(getArea(getMessage())); + sb.append(" LCN:").append(getChannel(getMessage())); + sb.append(" FREE:").append(getFree(getMessage())); + return sb.toString(); + } + + /** + * LCN for the call + */ + public int getChannel() + { + return getChannel(getMessage()); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTalkgroup()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/IswUniqueId.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/IswUniqueId.java new file mode 100644 index 00000000..ce2b3f76 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/IswUniqueId.java @@ -0,0 +1,83 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.isw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; +import io.github.dsheirer.module.decode.ltrnet.identifier.UniqueIdentifier; + +import java.util.ArrayList; +import java.util.List; + +/** + * ESN Low Message. ESN is transmitted over two messages with the ESNHigh message containing the high-order 16 bits + * and the ESNLow message containing the low-order 16 bits. + */ +public class IswUniqueId extends LtrNetIswMessage +{ + private UniqueIdentifier mUniqueIdentifier; + private List mIdentifiers; + + /** + * Constructs an ESN-High message + */ + public IswUniqueId(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.ISW_UNIQUE_ID; + } + + public UniqueIdentifier getUniqueID() + { + if(mUniqueIdentifier == null) + { + mUniqueIdentifier = UniqueIdentifier.createFrom(getMessage().getInt(SIXTEEN_BITS)); + } + + return mUniqueIdentifier; + } + + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("RADIO UNIQUE ID: ").append(getUniqueID()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getUniqueID()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/IswUnknown.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/IswUnknown.java new file mode 100644 index 00000000..77e6ff49 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/IswUnknown.java @@ -0,0 +1,65 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.isw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; + +import java.util.Collections; +import java.util.List; + +/** + * ISW Unknown Message. + */ +public class IswUnknown extends LtrNetIswMessage +{ + /** + * Constructs a message + */ + public IswUnknown(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.ISW_UNKNOWN; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("ISW UNKNOWN MESSAGE - AREA:").append(getArea(getMessage())); + sb.append(" LCN:").append(getChannel(getMessage())); + sb.append(" HOME:").append(getHomeRepeater(getMessage())); + sb.append(" GROUP").append(getGroup(getMessage())); + sb.append(" FREE:").append(getFree(getMessage())); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/LtrNetIswMessage.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/LtrNetIswMessage.java new file mode 100644 index 00000000..3771e7b0 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/LtrNetIswMessage.java @@ -0,0 +1,93 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.isw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.CRC; +import io.github.dsheirer.edac.CRCLTR; +import io.github.dsheirer.message.MessageDirection; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; +import io.github.dsheirer.module.decode.ltrnet.message.LtrNetMessage; + +public abstract class LtrNetIswMessage extends LtrNetMessage +{ + /** + * LTR-Net Inbound (mobile - to tower) Message + */ + public LtrNetIswMessage(CorrectedBinaryMessage message, long timestamp) + { + super(message, MessageDirection.ISW, timestamp); + + /** + * If the CRC fails, test for a transmitted CRC of 127 and then check + * the Free field for special messages + */ + if(mCRC == CRC.FAILED_CRC && CRCLTR.getTransmittedChecksum(mMessage) == 127) + { + int free = getFree(getMessage()); + + if(free == 31 || free == 23) + { + mCRC = CRC.PASSED; + } + } + } + + /** + * Determines the ISW message type + */ + public static LtrNetMessageType getMessageType(CorrectedBinaryMessage message) + { + int channel = getChannel(message); + + if(channel == 31) + { + return LtrNetMessageType.ISW_CALL_END; + } + else if(channel > 20) + { + switch(channel) + { + case 24: + return LtrNetMessageType.ISW_UNIQUE_ID; + case 27: + return LtrNetMessageType.ISW_REGISTRATION_REQUEST_ESN_LOW; + case 29: + return LtrNetMessageType.ISW_REGISTRATION_REQUEST_ESN_HIGH; + } + } + else if(channel > 0) + { + int free = getFree(message); + + switch(free) + { + case 21: + return LtrNetMessageType.ISW_CALL_START; + case 23: + return LtrNetMessageType.ISW_CALL_END; + case 31: + return LtrNetMessageType.ISW_REQUEST_ACCESS; + } + } + + return LtrNetMessageType.ISW_UNKNOWN; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/RegistrationRequestEsnHigh.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/RegistrationRequestEsnHigh.java new file mode 100644 index 00000000..975df959 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/RegistrationRequestEsnHigh.java @@ -0,0 +1,113 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.isw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.esn.ESNIdentifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; +import io.github.dsheirer.protocol.Protocol; + +import java.util.ArrayList; +import java.util.List; + +/** + * ESN High Message. ESN is transmitted over two messages with the ESNHigh message containing the high-order 16 bits + * and the ESNLow message containing the low-order 16 bits. + */ +public class RegistrationRequestEsnHigh extends LtrNetIswMessage +{ + private RegistrationRequestEsnLow mRegistrationRequestEsnLow; + private ESNIdentifier mESNIdentifier; + private List mIdentifiers; + + /** + * Constructs an ESN-High message + */ + public RegistrationRequestEsnHigh(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.ISW_REGISTRATION_REQUEST_ESN_HIGH; + } + + /** + * Sets the corresponding ESN low message that can be used to create the full ESN value + */ + public void setEsnLowMessage(RegistrationRequestEsnLow message) + { + if(message != null) + { + mRegistrationRequestEsnLow = message; + } + } + + public boolean isCompleteEsn() + { + return mRegistrationRequestEsnLow != null; + } + + /** + * ESN Identifier with at least the high-order 16-bits specified and optionally includes the low-order 16 bits. + */ + public ESNIdentifier getESN() + { + if(mESNIdentifier == null) + { + mESNIdentifier = ESNIdentifier.create(getESNHigh() + + (mRegistrationRequestEsnLow != null ? mRegistrationRequestEsnLow.getESNLow() : "xxxx"), + Protocol.LTR_NET, Role.FROM); + } + + return mESNIdentifier; + } + + public String getESNHigh() + { + int esnHigh = getMessage().getInt(SIXTEEN_BITS); + + return String.format("%04X", esnHigh & 0xFFFF); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("ESN HIGH: ").append(getESN()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getESN()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/RegistrationRequestEsnLow.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/RegistrationRequestEsnLow.java new file mode 100644 index 00000000..09e88b03 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/RegistrationRequestEsnLow.java @@ -0,0 +1,112 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.isw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.esn.ESNIdentifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; +import io.github.dsheirer.protocol.Protocol; + +import java.util.ArrayList; +import java.util.List; + +/** + * ESN Low Message. ESN is transmitted over two messages with the ESNHigh message containing the high-order 16 bits + * and the ESNLow message containing the low-order 16 bits. + */ +public class RegistrationRequestEsnLow extends LtrNetIswMessage +{ + private RegistrationRequestEsnHigh mRegistrationRequestEsnHigh; + private ESNIdentifier mESNIdentifier; + private List mIdentifiers; + + /** + * Constructs an ESN-High message + */ + public RegistrationRequestEsnLow(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.ISW_REGISTRATION_REQUEST_ESN_LOW; + } + + /** + * Sets the corresponding ESN high message that can be used to create the full ESN value + */ + public void setESNHighMessage(RegistrationRequestEsnHigh message) + { + if(message != null) + { + mRegistrationRequestEsnHigh = message; + } + } + + public boolean isCompleteEsn() + { + return mRegistrationRequestEsnHigh != null; + } + + /** + * ESN Identifier with at least the high-order 16-bits specified and optionally includes the low-order 16 bits. + */ + public ESNIdentifier getESN() + { + if(mESNIdentifier == null) + { + mESNIdentifier = ESNIdentifier.create((mRegistrationRequestEsnHigh != null ? mRegistrationRequestEsnHigh.getESNHigh() : "xxxx") + getESNLow(), + Protocol.LTR_NET, Role.FROM); + } + + return mESNIdentifier; + } + + public String getESNLow() + { + int esnLow = getMessage().getInt(SIXTEEN_BITS); + + return String.format("%04X", esnLow & 0xFFFF); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("ESN LOW: ").append(getESN()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getESN()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/RequestAccess.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/RequestAccess.java new file mode 100644 index 00000000..c9c7edbe --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/isw/RequestAccess.java @@ -0,0 +1,72 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.isw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; + +import java.util.ArrayList; +import java.util.List; + +/** + * Request access Message. + */ +public class RequestAccess extends LtrNetIswMessage +{ + private List mIdentifiers; + + /** + * Constructs a message + */ + public RequestAccess(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.ISW_REQUEST_ACCESS; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("REQUEST ACCESS TALKGROUP:").append(getTalkgroup()); + sb.append(" AREA:").append(getArea(getMessage())); + sb.append(" LCN:").append(getChannel(getMessage())); + sb.append(" FREE:").append(getFree(getMessage())); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTalkgroup()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/ChannelMapHigh.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/ChannelMapHigh.java new file mode 100644 index 00000000..6a7e95e2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/ChannelMapHigh.java @@ -0,0 +1,77 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.osw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Channel Map - High Channels (11 - 20). + */ +public class ChannelMapHigh extends LtrNetOswMessage +{ + /** + * Constructs a message + */ + public ChannelMapHigh(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.OSW_CHANNEL_MAP_HIGH; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("CHANNEL MAP HIGH ").append(getChannels()); + return sb.toString(); + } + + public List getChannels() + { + List retVal = new ArrayList<>(); + + for(int x = 27; x >= 18; x--) + { + if(getMessage().get(x)) + { + retVal.add(38 - x); + } + } + + return retVal; + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/ChannelMapLow.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/ChannelMapLow.java new file mode 100644 index 00000000..3ffddd0e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/ChannelMapLow.java @@ -0,0 +1,77 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.osw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Channel Map - Low Channels (1 - 10). + */ +public class ChannelMapLow extends LtrNetOswMessage +{ + /** + * Constructs a message + */ + public ChannelMapLow(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.OSW_CHANNEL_MAP_LOW; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("CHANNEL MAP LOW ").append(getChannels()); + return sb.toString(); + } + + public List getChannels() + { + List retVal = new ArrayList<>(); + + for(int x = 27; x >= 18; x--) + { + if(getMessage().get(x)) + { + retVal.add(28 - x); + } + } + + return retVal; + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/Frequency.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/Frequency.java new file mode 100644 index 00000000..7580744f --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/Frequency.java @@ -0,0 +1,63 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.osw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; + +/** + * Base frequency message + */ +public abstract class Frequency extends LtrNetOswMessage +{ + /** + * Constructs a message + */ + public Frequency(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + protected abstract FrequencyHigh getFrequencyHigh(); + protected abstract FrequencyLow getFrequencyLow(); + + protected abstract boolean hasFrequencyHigh(); + protected abstract boolean hasFrequencyLow(); + + /** + * Channel number for the frequency + */ + public int getChannel() + { + return getHomeRepeater(getMessage()); + } + + /** + * Frequency for the channel when both the high and low frequency messages are available + */ + public long getFrequency() + { + if(hasFrequencyHigh() && hasFrequencyLow()) + { + return 150000000 + ((getFrequencyHigh().getHighChannelUnits() + getFrequencyLow().getLowChannelUnits()) * 1250); + } + + return 0; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/FrequencyHigh.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/FrequencyHigh.java new file mode 100644 index 00000000..e65fabf0 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/FrequencyHigh.java @@ -0,0 +1,80 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.osw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; + +/** + * Base frequency message + */ +public abstract class FrequencyHigh extends Frequency +{ + private FrequencyLow mFrequencyLow; + + /** + * Constructs a message + */ + public FrequencyHigh(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + /** + * Sets the corresponding low frequency message so that the correct frequency can be calculated + */ + public void setFrequencyLow(FrequencyLow frequencyLow) + { + mFrequencyLow = frequencyLow; + } + + /** + * Returns the value of the high order (15 - 12) frequency bits which are + * the lower 4 bits of the Free field, left shifted by 12, representing the + * frequency value in units of .00125 MHz + */ + public int getHighChannelUnits() + { + return Integer.rotateLeft(getMessage().getInt(29, 32), 12); + } + + @Override + protected FrequencyHigh getFrequencyHigh() + { + return this; + } + + @Override + protected FrequencyLow getFrequencyLow() + { + return mFrequencyLow; + } + + @Override + protected boolean hasFrequencyHigh() + { + return true; + } + + @Override + protected boolean hasFrequencyLow() + { + return mFrequencyLow != null; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/FrequencyLow.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/FrequencyLow.java new file mode 100644 index 00000000..19d97bad --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/FrequencyLow.java @@ -0,0 +1,77 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.osw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; + +/** + * Base frequency message + */ +public abstract class FrequencyLow extends Frequency +{ + private FrequencyHigh mFrequencyHigh; + + /** + * Constructs a message + */ + public FrequencyLow(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + public void setFrequencyHigh(FrequencyHigh frequencyHigh) + { + mFrequencyHigh = frequencyHigh; + } + + /** + * Returns the value of the low order (11 - 0) frequency bits which are + * contained in the Group and Free fields representing the frequency value + * units of .00125 MHz + */ + public int getLowChannelUnits() + { + return getMessage().getInt(21, 32); + } + + @Override + protected FrequencyHigh getFrequencyHigh() + { + return mFrequencyHigh; + } + + @Override + protected FrequencyLow getFrequencyLow() + { + return this; + } + + @Override + protected boolean hasFrequencyHigh() + { + return mFrequencyHigh != null; + } + + @Override + protected boolean hasFrequencyLow() + { + return true; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/LtrNetOswMessage.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/LtrNetOswMessage.java new file mode 100644 index 00000000..5818526b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/LtrNetOswMessage.java @@ -0,0 +1,109 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.osw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.message.MessageDirection; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; +import io.github.dsheirer.module.decode.ltrnet.message.LtrNetMessage; + +public abstract class LtrNetOswMessage extends LtrNetMessage +{ + private static final int CHANNEL_FREQUENCY_MESSAGE_TYPE_BIT = 20; + private static final int CHANNEL_MAP_MESSAGE_TYPE_BIT = 17; + private static final int HOME_DIRECTED_GROUP_CALL = 29; + private static final int HOME_CHANNEL_IN_USE_MUTE_AUDIO = 30; + private static final int GROUP_DO_NOTHING = 253; + private static final int GROUP_CWID = 254; + private static final int GROUP_CHANNEL_IDLE = 255; + + private static final int FREE_ALL_CHANNELS_BUSY = 0; + private static final int FREE_LTR_GROUP_CALL = 30; + private static final int FREE_LTRNET_GROUP_CALL = 31; + + public LtrNetOswMessage(CorrectedBinaryMessage message, long timestamp) + { + super(message, MessageDirection.OSW, timestamp); + } + + public static LtrNetMessageType getMessageType(CorrectedBinaryMessage message) + { + int channel = getChannel(message); + int home = getHomeRepeater(message); + + //LTR-Net messages + if(home != 31 && (channel > 20 || home > 20)) + { + switch(channel) + { + case 17: + return LtrNetMessageType.OSW_REGISTRATION_ACCEPT; + case 18: + return LtrNetMessageType.OSW_SITE_ID; + case 24: + if(message.get(CHANNEL_FREQUENCY_MESSAGE_TYPE_BIT)) + { + return LtrNetMessageType.OSW_TRANSMIT_FREQUENCY_HIGH; + } + else + { + return LtrNetMessageType.OSW_TRANSMIT_FREQUENCY_LOW; + } + case 25: + if(message.get(CHANNEL_FREQUENCY_MESSAGE_TYPE_BIT)) + { + return LtrNetMessageType.OSW_RECEIVE_FREQUENCY_HIGH; + } + else + { + return LtrNetMessageType.OSW_RECEIVE_FREQUENCY_LOW; + } + case 26: + return LtrNetMessageType.OSW_NEIGHBOR_ID; + case 28: + if(message.get(CHANNEL_MAP_MESSAGE_TYPE_BIT)) + { + return LtrNetMessageType.OSW_CHANNEL_MAP_HIGH; + } + else + { + return LtrNetMessageType.OSW_CHANNEL_MAP_LOW; + } + case 31: + return LtrNetMessageType.OSW_CALL_END; + } + } + else + { + int group = getGroup(message); + + if(group == GROUP_CHANNEL_IDLE) + { + return LtrNetMessageType.OSW_SYSTEM_IDLE; + } + else + { + return LtrNetMessageType.OSW_CALL_START; + } + } + + return LtrNetMessageType.OSW_UNKNOWN; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/NeighborId.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/NeighborId.java new file mode 100644 index 00000000..46dab8d7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/NeighborId.java @@ -0,0 +1,87 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.osw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; +import io.github.dsheirer.module.decode.ltrnet.identifier.NeighborIdentifier; + +import java.util.ArrayList; +import java.util.List; + +/** + * Neighbor Identifier. + */ +public class NeighborId extends LtrNetOswMessage +{ + private NeighborIdentifier mNeighborIdentifier; + private List mIdentifiers; + + /** + * Constructs a message + */ + public NeighborId(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.OSW_NEIGHBOR_ID; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("NEIGHBOR:").append(getNeighborID()); + sb.append(" RANK:").append(getNeighborRank()); + return sb.toString(); + } + + public NeighborIdentifier getNeighborID() + { + if(mNeighborIdentifier == null) + { + mNeighborIdentifier = NeighborIdentifier.create(getMessage().getInt(23, 32)); + } + + return mNeighborIdentifier; + } + + public int getNeighborRank() + { + return getMessage().getInt(15, 18) + 1; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getNeighborID()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/OswCallEnd.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/OswCallEnd.java new file mode 100644 index 00000000..07669926 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/OswCallEnd.java @@ -0,0 +1,80 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.osw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; + +import java.util.ArrayList; +import java.util.List; + +/** + * Call End Message. + */ +public class OswCallEnd extends LtrNetOswMessage +{ + private List mIdentifiers; + + /** + * Constructs a message + */ + public OswCallEnd(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.OSW_CALL_END; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("CALL END TALKGROUP:").append(getTalkgroup().formatted()); + sb.append(" AREA:").append(getArea(getMessage())); + sb.append(" LCN:").append(getChannel(getMessage())); + sb.append(" FREE:").append(getFree(getMessage())); + return sb.toString(); + } + + /** + * LCN for the call + */ + public int getChannel() + { + return getChannel(getMessage()); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTalkgroup()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/OswCallStart.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/OswCallStart.java new file mode 100644 index 00000000..6ddf3f43 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/OswCallStart.java @@ -0,0 +1,80 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.osw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; + +import java.util.ArrayList; +import java.util.List; + +/** + * Call Start Message. + */ +public class OswCallStart extends LtrNetOswMessage +{ + private List mIdentifiers; + + /** + * Constructs a message + */ + public OswCallStart(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.OSW_CALL_START; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("CALL START TALKGROUP:").append(getTalkgroup().formatted()); + sb.append(" AREA:").append(getArea(getMessage())); + sb.append(" LCN:").append(getChannel(getMessage())); + sb.append(" FREE:").append(getFree(getMessage())); + return sb.toString(); + } + + /** + * LCN for the call + */ + public int getChannel() + { + return getChannel(getMessage()); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTalkgroup()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/OswUnknown.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/OswUnknown.java new file mode 100644 index 00000000..66c662ae --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/OswUnknown.java @@ -0,0 +1,65 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.osw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; + +import java.util.Collections; +import java.util.List; + +/** + * OSW Unknown Message. + */ +public class OswUnknown extends LtrNetOswMessage +{ + /** + * Constructs a message + */ + public OswUnknown(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.OSW_UNKNOWN; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("OSW UNKNOWN MESSAGE - AREA:").append(getArea(getMessage())); + sb.append(" LCN:").append(getChannel(getMessage())); + sb.append(" HOME:").append(getHomeRepeater(getMessage())); + sb.append(" GROUP").append(getGroup(getMessage())); + sb.append(" FREE:").append(getFree(getMessage())); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/ReceiveFrequencyHigh.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/ReceiveFrequencyHigh.java new file mode 100644 index 00000000..e79a8624 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/ReceiveFrequencyHigh.java @@ -0,0 +1,63 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.osw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; + +import java.util.Collections; +import java.util.List; + +public class ReceiveFrequencyHigh extends FrequencyHigh +{ + /** + * Constructs a message + * + * @param message + * @param timestamp + */ + public ReceiveFrequencyHigh(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.OSW_RECEIVE_FREQUENCY_HIGH; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("LCN:").append(getChannel()); + sb.append(" RECEIVE FREQUENCY:").append(getFrequency()).append(" - HIGH"); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/ReceiveFrequencyLow.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/ReceiveFrequencyLow.java new file mode 100644 index 00000000..5ec5d75d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/ReceiveFrequencyLow.java @@ -0,0 +1,60 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.osw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; + +import java.util.Collections; +import java.util.List; + +public class ReceiveFrequencyLow extends FrequencyLow +{ + /** + * Constructs a message + */ + public ReceiveFrequencyLow(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.OSW_RECEIVE_FREQUENCY_LOW; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("LCN:").append(getChannel()); + sb.append(" RECEIVE FREQUENCY:").append(getFrequency()).append(" - LOW"); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + return Collections.emptyList(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/RegistrationAccept.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/RegistrationAccept.java new file mode 100644 index 00000000..b63b3afc --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/RegistrationAccept.java @@ -0,0 +1,82 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.osw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; +import io.github.dsheirer.module.decode.ltrnet.identifier.UniqueIdentifier; + +import java.util.ArrayList; +import java.util.List; + +/** + * Registration and assignment of Unique ID to a radio. + */ +public class RegistrationAccept extends LtrNetOswMessage +{ + private UniqueIdentifier mUniqueIdentifier; + private List mIdentifiers; + + /** + * Constructs an ESN-High message + */ + public RegistrationAccept(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.OSW_REGISTRATION_ACCEPT; + } + + public UniqueIdentifier getUniqueID() + { + if(mUniqueIdentifier == null) + { + mUniqueIdentifier = UniqueIdentifier.createTo(getMessage().getInt(SIXTEEN_BITS)); + } + + return mUniqueIdentifier; + } + + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("REGISTRATION ACCEPT - RADIO UNIQUE ID: ").append(getUniqueID()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getUniqueID()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/SiteId.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/SiteId.java new file mode 100644 index 00000000..eca264ba --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/SiteId.java @@ -0,0 +1,82 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.osw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; +import io.github.dsheirer.module.decode.ltrnet.identifier.SiteIdentifier; + +import java.util.ArrayList; +import java.util.List; + +/** + * Site Identifier. + */ +public class SiteId extends LtrNetOswMessage +{ + private SiteIdentifier mSiteIdentifier; + private List mIdentifiers; + + /** + * Constructs a message + */ + public SiteId(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.OSW_SITE_ID; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("SITE:").append(getSiteID()); + return sb.toString(); + } + + public SiteIdentifier getSiteID() + { + if(mSiteIdentifier == null) + { + mSiteIdentifier = SiteIdentifier.create(getMessage().getInt(23, 32)); + } + + return mSiteIdentifier; + } + + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSiteID()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/SystemIdle.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/SystemIdle.java new file mode 100644 index 00000000..2e080c36 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/SystemIdle.java @@ -0,0 +1,65 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.osw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; + +import java.util.Collections; +import java.util.List; + +public class SystemIdle extends LtrNetOswMessage +{ + public SystemIdle(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.OSW_SYSTEM_IDLE; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("IDLE - AREA:").append(getArea(getMessage())); + sb.append(" LCN:").append(getChannel(getMessage())); + sb.append(" HOME:").append(getHomeRepeater(getMessage())); + sb.append(" GROUP:").append(getGroup(getMessage())); + sb.append(" FREE:").append(getFree(getMessage())); + return sb.toString(); + } + + public int getChannel() + { + return getChannel(getMessage()); + } + + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/TransmitFrequencyHigh.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/TransmitFrequencyHigh.java new file mode 100644 index 00000000..f662f45e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/TransmitFrequencyHigh.java @@ -0,0 +1,60 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.osw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; + +import java.util.Collections; +import java.util.List; + +public class TransmitFrequencyHigh extends FrequencyHigh +{ + /** + * Constructs a message + */ + public TransmitFrequencyHigh(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.OSW_TRANSMIT_FREQUENCY_HIGH; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("LCN:").append(getChannel()); + sb.append(" TRANSMIT FREQUENCY:").append(getFrequency()).append(" - HIGH"); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/TransmitFrequencyLow.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/TransmitFrequencyLow.java new file mode 100644 index 00000000..e5904963 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/message/osw/TransmitFrequencyLow.java @@ -0,0 +1,60 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrnet.message.osw; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; + +import java.util.Collections; +import java.util.List; + +public class TransmitFrequencyLow extends FrequencyLow +{ + /** + * Constructs a message + */ + public TransmitFrequencyLow(CorrectedBinaryMessage message, long timestamp) + { + super(message, timestamp); + } + + @Override + public LtrNetMessageType getLtrNetMessageType() + { + return LtrNetMessageType.OSW_TRANSMIT_FREQUENCY_LOW; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("LCN:").append(getChannel()); + sb.append(" TRANSMIT FREQUENCY:").append(getFrequency()).append(" - LOW"); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoder.java b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoder.java index 212cf5c7..18e585f0 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoder.java @@ -53,7 +53,7 @@ public class LTRStandardDecoder extends Decoder implements IReusableBufferListen mLTRDecoder.setMessageFramer(mLTRMessageFramer); mLTRMessageFramer.setSyncDetectListener(mLTRDecoder); - mLTRMessageProcessor = new LTRStandardMessageProcessor(direction, aliasList); + mLTRMessageProcessor = new LTRStandardMessageProcessor(direction); mLTRMessageFramer.addMessageListener(mLTRMessageProcessor); mLTRMessageProcessor.setMessageListener(getMessageListener()); } diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoderState.java b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoderState.java index 32d7647c..8d3d6bf5 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoderState.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,28 +15,30 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.module.decode.ltrstandard; -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.alias.id.AliasIDType; -import io.github.dsheirer.channel.metadata.AliasedStringAttributeMonitor; -import io.github.dsheirer.channel.metadata.Attribute; -import io.github.dsheirer.channel.metadata.AttributeChangeRequest; import io.github.dsheirer.channel.state.DecoderState; import io.github.dsheirer.channel.state.DecoderStateEvent; import io.github.dsheirer.channel.state.DecoderStateEvent.Event; import io.github.dsheirer.channel.state.State; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier; +import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier; +import io.github.dsheirer.identifier.talkgroup.LTRTalkgroup; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.module.decode.event.CallEvent; -import io.github.dsheirer.module.decode.ltrnet.LTRCallEvent; -import io.github.dsheirer.module.decode.ltrstandard.message.CallEndMessage; -import io.github.dsheirer.module.decode.ltrstandard.message.CallMessage; -import io.github.dsheirer.module.decode.ltrstandard.message.IdleMessage; +import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.ltrstandard.channel.LtrChannel; +import io.github.dsheirer.module.decode.ltrstandard.message.Call; +import io.github.dsheirer.module.decode.ltrstandard.message.CallEnd; +import io.github.dsheirer.module.decode.ltrstandard.message.Idle; import io.github.dsheirer.module.decode.ltrstandard.message.LTRStandardMessage; +import io.github.dsheirer.protocol.Protocol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,23 +52,17 @@ import java.util.TreeSet; public class LTRStandardDecoderState extends DecoderState { - private final static Logger mLog = - LoggerFactory.getLogger(LTRStandardDecoderState.class); + private final static Logger mLog = LoggerFactory.getLogger(LTRStandardDecoderState.class); - private Map mActiveCalls = new HashMap<>(); - private Set mTalkgroupsFirstHeard = new HashSet<>(); - private Set mTalkgroups = new TreeSet<>(); - - private AliasedStringAttributeMonitor mTalkgroupAttribute; - private long mFrequency; + private Map mActiveCalls = new HashMap<>(); + private Set mTalkgroupsFirstHeard = new HashSet<>(); + private Set mTalkgroups = new TreeSet<>(); private LCNTracker mLCNTracker = new LCNTracker(); + private DecodeEvent mCurrentCallEvent; + private LTRTalkgroup mCurrentTalkgroup; - public LTRStandardDecoderState(AliasList aliasList) + public LTRStandardDecoderState() { - super(aliasList); - - mTalkgroupAttribute = new AliasedStringAttributeMonitor(Attribute.PRIMARY_ADDRESS_TO, - getAttributeChangeRequestListener(), getAliasList(), AliasIDType.TALKGROUP); } @Override @@ -75,116 +72,76 @@ public class LTRStandardDecoderState extends DecoderState } @Override - public void receive(Message message) + public void receive(IMessage message) { if(message.isValid() && message instanceof LTRStandardMessage) { - switch(((LTRStandardMessage) message).getMessageType()) + switch(((LTRStandardMessage)message).getMessageType()) { - case CA_STRT: - CallMessage start = (CallMessage) message; - - int channel = start.getChannel(); - - setChannelNumber(channel); - - mLCNTracker.processFreeChannel(start.getFree()); - - //Only process calls on this LCN, or call detects for - //talkgroups that are homed on this LCN - if(mLCNTracker.isValidChannel(channel) && - (mLCNTracker.isCurrentChannel(channel) || - mLCNTracker.isCurrentChannel(start.getHomeRepeater()))) + case CALL: + if(message instanceof Call) { - LTRCallEvent event = mActiveCalls.get(channel); + Call start = (Call)message; - if(event == null || !event.isMatchingTalkgroup(start.getToID())) + int channel = start.getChannel(); + setChannelNumber(channel); + mLCNTracker.processFreeChannel(start.getFree()); + + //Only process calls or call detects for valid channels + if(mLCNTracker.isValidChannel(channel) && mLCNTracker.isCurrentChannel(channel)) { - //Check for different talkgroup - if(event != null) + if(mCurrentTalkgroup == null || !mCurrentTalkgroup.equals(start.getTalkgroup())) { - event.end(); - mActiveCalls.remove(channel); + mCurrentTalkgroup = start.getTalkgroup(); + getIdentifierCollection().remove(IdentifierClass.USER); + getIdentifierCollection().update(start.getTalkgroup()); + mCurrentCallEvent = DecodeEvent.builder(start.getTimestamp()) + .protocol(Protocol.LTR_STANDARD) + .identifiers(getIdentifierCollection().copyOf()) + .channel(getCurrentChannel()) + .eventDescription("Call") + .build(); } - - boolean current = mLCNTracker.isCurrentChannel(channel); - - event = new LTRCallEvent.Builder( - DecoderType.LTR_STANDARD, - current ? CallEvent.CallEventType.CALL : CallEvent.CallEventType.CALL_DETECT) - .to(start.getToID()) - .aliasList(getAliasList()) - .channel(start.getChannelFormatted()) - .frequency(current ? mFrequency : 0) - .build(); - - mActiveCalls.put(channel, event); - - broadcast(event); - - if(mLCNTracker.isCurrentChannel(channel)) + else { - String talkgroup = start.getToID(); - mTalkgroupAttribute.process(talkgroup); - processTalkgroup(talkgroup); + mCurrentCallEvent.update(start.getTimestamp()); } + broadcast(mCurrentCallEvent); + broadcast(new DecoderStateEvent(this, Event.START, State.CALL)); } - - broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL)); } break; - case CA_ENDD: - CallEndMessage end = (CallEndMessage) message; - - //Home channel is 31 for call end -- use the free channel - //as the call end channel - int repeater = end.getFree(); - - setChannelNumber(repeater); - - if(mLCNTracker.isCurrentChannel(repeater)) + case CALL_END: + if(message instanceof CallEnd) { - String talkgroup = end.getToID(); - mTalkgroupAttribute.process(talkgroup); - processTalkgroup(talkgroup); + CallEnd end = (CallEnd)message; - LTRCallEvent event = mActiveCalls.remove(repeater); - - if(event != null) + //Home channel is 31 for call end -- use the free channel as the call end channel + int repeater = end.getFree(); + setChannelNumber(repeater); + if(mLCNTracker.isCurrentChannel(repeater)) { - event.end(); - broadcast(event); + if(mCurrentCallEvent != null) + { + mCurrentCallEvent.end(end.getTimestamp()); + } + broadcast(new DecoderStateEvent(this, Event.END, State.FADE)); } } break; - case SY_IDLE: - IdleMessage idle = (IdleMessage) message; - - int lcn = idle.getChannel(); - - mLCNTracker.processCallChannel(lcn); - + case IDLE: + if(message instanceof Idle) + { + mLCNTracker.processCallChannel(((Idle)message).getChannel()); + } break; - case UN_KNWN: default: break; } } } - private void processTalkgroup(String talkgroup) - { - if(mTalkgroupsFirstHeard.contains(talkgroup)) - { - mTalkgroups.add(talkgroup); - } - else - { - mTalkgroupsFirstHeard.add(talkgroup); - } - } - /** * Performs a full reset */ @@ -201,41 +158,25 @@ public class LTRStandardDecoderState extends DecoderState @Override public void start() { - } @Override public void stop() { - } @Override public void init() { - } /** * Performs a temporal reset following a call or other decode event */ - private void resetState() + protected void resetState() { - for(Integer key : mActiveCalls.keySet()) - { - LTRCallEvent event = mActiveCalls.get(key); - - if(event != null) - { - event.end(); - - broadcast(event); - } - } - + super.resetState(); mActiveCalls.clear(); - - mTalkgroupAttribute.reset(); } public boolean hasChannelNumber() @@ -256,8 +197,20 @@ public class LTRStandardDecoderState extends DecoderState if(mLCNTracker.getCurrentChannel() != original) { - broadcast(new AttributeChangeRequest(Attribute.CHANNEL_FREQUENCY_LABEL, - "LCN:" + mLCNTracker.getCurrentChannel())); + getIdentifierCollection().update(DecoderLogicalChannelNameIdentifier + .create(String.valueOf(mLCNTracker.getCurrentChannel()), Protocol.LTR_STANDARD)); + + LtrChannel ltrChannel = new LtrChannel(mLCNTracker.getCurrentChannel()); + + Identifier identifier = getIdentifierCollection().getIdentifier(IdentifierClass.CONFIGURATION, + Form.CHANNEL_FREQUENCY, Role.ANY); + + if(identifier instanceof FrequencyConfigurationIdentifier) + { + ltrChannel.setDownlink(((FrequencyConfigurationIdentifier)identifier).getValue()); + } + + setCurrentChannel(ltrChannel); } } @@ -269,11 +222,6 @@ public class LTRStandardDecoderState extends DecoderState case RESET: resetState(); break; - case SOURCE_FREQUENCY: - mFrequency = event.getFrequency(); - break; - default: - break; } } @@ -321,22 +269,10 @@ public class LTRStandardDecoderState extends DecoderState } else { - for(String talkgroup : mTalkgroups) + for(LTRTalkgroup talkgroup : mTalkgroups) { sb.append(" "); - sb.append(talkgroup); - sb.append(" "); - - if(hasAliasList()) - { - Alias alias = getAliasList().getTalkgroupAlias(talkgroup); - - if(alias != null) - { - sb.append(alias.getName()); - } - } - + sb.append(talkgroup.formatted()); sb.append("\n"); } @@ -425,7 +361,7 @@ public class LTRStandardDecoderState extends DecoderState int count = mFreeLCNCounts[channel]; - int threshold = (int) ((double) mFreeHighestCount * 0.2); + int threshold = (int)((double)mFreeHighestCount * 0.2); return count >= threshold; } diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardMessageFilter.java index c1d9c5ad..17530372 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardMessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardMessageFilter.java @@ -1,57 +1,73 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrstandard; import io.github.dsheirer.filter.Filter; import io.github.dsheirer.filter.FilterElement; -import io.github.dsheirer.message.Message; -import io.github.dsheirer.message.MessageType; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.ltrstandard.message.LTRStandardMessage; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; -public class LTRStandardMessageFilter extends Filter +public class LTRStandardMessageFilter extends Filter { - private HashMap> mElements = - new HashMap>(); - - public LTRStandardMessageFilter() - { - super( "LTR Message Filter" ); + private Map> mElements = new HashMap<>(); - mElements.put( MessageType.CA_STRT, - new FilterElement( MessageType.CA_STRT ) ); - mElements.put( MessageType.CA_ENDD, - new FilterElement( MessageType.CA_ENDD ) ); - mElements.put( MessageType.SY_IDLE, - new FilterElement( MessageType.SY_IDLE ) ); - } - - @Override - public boolean passes( Message message ) + public LTRStandardMessageFilter() { - if( mEnabled && canProcess( message ) ) - { - LTRStandardMessage ltr = (LTRStandardMessage)message; - - if( mElements.containsKey( ltr.getMessageType() ) ) - { - return mElements.get( ltr.getMessageType() ).isEnabled(); - } - } - - return false; + super("LTR Message Filter"); + + mElements.put(LtrStandardMessageType.CALL, new FilterElement<>(LtrStandardMessageType.CALL)); + mElements.put(LtrStandardMessageType.CALL_END, new FilterElement<>(LtrStandardMessageType.CALL_END)); + mElements.put(LtrStandardMessageType.IDLE, new FilterElement<>(LtrStandardMessageType.IDLE)); + mElements.put(LtrStandardMessageType.UNKNOWN, new FilterElement<>(LtrStandardMessageType.UNKNOWN)); } - @Override - public boolean canProcess( Message message ) + @Override + public boolean passes(IMessage message) { - return message instanceof LTRStandardMessage; + if(mEnabled && canProcess(message)) + { + LTRStandardMessage ltr = (LTRStandardMessage)message; + + if(mElements.containsKey(ltr.getMessageType())) + { + return mElements.get(ltr.getMessageType()).isEnabled(); + } + } + + return false; } - @Override + public boolean canProcess(IMessage message) + { + return message instanceof LTRStandardMessage; + } + + @Override public List> getFilterElements() { - return new ArrayList>( mElements.values() ); + return new ArrayList>(mElements.values()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardMessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardMessageProcessor.java index ccf7eb59..b04da8a6 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardMessageProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardMessageProcessor.java @@ -1,121 +1,114 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrstandard; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.edac.CRC; import io.github.dsheirer.edac.CRCLTR; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.MessageDirection; -import io.github.dsheirer.module.decode.ltrstandard.message.CallEndMessage; -import io.github.dsheirer.module.decode.ltrstandard.message.CallMessage; -import io.github.dsheirer.module.decode.ltrstandard.message.IdleMessage; +import io.github.dsheirer.module.decode.ltrstandard.message.Call; +import io.github.dsheirer.module.decode.ltrstandard.message.CallEnd; +import io.github.dsheirer.module.decode.ltrstandard.message.Idle; import io.github.dsheirer.module.decode.ltrstandard.message.LTRStandardMessage; import io.github.dsheirer.module.decode.ltrstandard.message.UnknownMessage; import io.github.dsheirer.sample.Listener; -public class LTRStandardMessageProcessor implements Listener +public class LTRStandardMessageProcessor implements Listener { - private MessageDirection mDirection; - private AliasList mAliasList; - private Listener mMessageListener; - - /** - * Processes raw binary messages and converts them to the correct message class - * - * @param direction - inbound (ISW) or outbound (OSW) - * @param list - alias list - */ - public LTRStandardMessageProcessor( MessageDirection direction, AliasList list ) - { - mDirection = direction; - mAliasList = list; - } - - @Override - public void receive( BinaryMessage binaryMessage ) + private MessageDirection mDirection; + private Listener mMessageListener; + + /** + * Processes raw binary messages and converts them to the correct message class + * + * @param direction - inbound (ISW) or outbound (OSW) + */ + public LTRStandardMessageProcessor(MessageDirection direction) { - if( mMessageListener != null ) - { - //Inbound Status Word (ISW) is a bit-flipped version of the Outbound - //(OSW), so flip the bits and process it as an OSW - if( mDirection == MessageDirection.ISW ) - { - binaryMessage.flip( 0, 40 ); - } - - CRC crc = CRCLTR.check( binaryMessage, mDirection ); - - if( crc != CRC.FAILED_CRC && - crc != CRC.FAILED_PARITY ) - { - LTRStandardMessage message; - - int channel = binaryMessage.getInt( LTRStandardMessage.CHANNEL ); - int home = binaryMessage.getInt( LTRStandardMessage.HOME_REPEATER ); - int free = binaryMessage.getInt( LTRStandardMessage.FREE ); - int group = binaryMessage.getInt( LTRStandardMessage.GROUP ); - - if( isValidChannel( channel ) && - isValidChannel( home ) && - isValidChannel( free ) ) - { - if( channel == free && group == 255 ) - { - message = new IdleMessage( binaryMessage, mDirection, mAliasList, crc ); - } - else - { - message = new CallMessage( binaryMessage, mDirection, mAliasList, crc ); - } - } - else if( channel == 31 && - isValidChannel( home ) && - isValidChannel( free ) ) - { - message = new CallEndMessage( binaryMessage, mDirection, mAliasList, crc ); - } - else - { - message = new UnknownMessage( binaryMessage, mDirection, mAliasList, crc ); - } - - mMessageListener.receive( message ); - } - } + mDirection = direction; } - - /** - * Checks the channel (LCN) number to ensure it is in the range 1 -20 - */ - private boolean isValidChannel( int channel ) - { - return ( 1 <= channel && channel <= 20 ); - } - - public void setMessageListener( Listener listener ) + + @Override + public void receive(CorrectedBinaryMessage binaryMessage) { - mMessageListener = listener; + if(mMessageListener != null) + { + //Inbound Status Word (ISW) is a bit-flipped version of the Outbound (OSW), so flip the bits and process it + // as an OSW + if(mDirection == MessageDirection.ISW) + { + binaryMessage.flip(0, 40); + } + + CRC crc = CRCLTR.check(binaryMessage, mDirection); + + if(crc.passes()) + { + LTRStandardMessage message; + + int channel = binaryMessage.getInt(LTRStandardMessage.CHANNEL); + int home = binaryMessage.getInt(LTRStandardMessage.HOME_REPEATER); + int free = binaryMessage.getInt(LTRStandardMessage.FREE); + int group = binaryMessage.getInt(LTRStandardMessage.GROUP); + + if(isValidChannel(channel) && isValidChannel(home) && isValidChannel(free)) + { + if(channel == free && group == 255) + { + message = new Idle(binaryMessage, mDirection, crc); + } + else + { + message = new Call(binaryMessage, mDirection, crc); + } + } + else if(channel == 31 && isValidChannel(home) && isValidChannel(free)) + { + message = new CallEnd(binaryMessage, mDirection, crc); + } + else + { + message = new UnknownMessage(binaryMessage, mDirection, crc); + } + + mMessageListener.receive(message); + } + } + } + + /** + * Checks the channel (LCN) number to ensure it is in the range 1 -20 + */ + private boolean isValidChannel(int channel) + { + return (1 <= channel && channel <= 20); + } + + public void setMessageListener(Listener listener) + { + mMessageListener = listener; } public void removeMessageListener() { - mMessageListener = null; + mMessageListener = null; } } \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LtrStandardMessageType.java b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LtrStandardMessageType.java new file mode 100644 index 00000000..e8c19822 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LtrStandardMessageType.java @@ -0,0 +1,29 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrstandard; + +public enum LtrStandardMessageType +{ + CALL, + CALL_END, + IDLE, + UNKNOWN; +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/channel/LtrChannel.java b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/channel/LtrChannel.java new file mode 100644 index 00000000..a6767d79 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/channel/LtrChannel.java @@ -0,0 +1,105 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrstandard.channel; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBand; +import io.github.dsheirer.protocol.Protocol; + +public class LtrChannel implements IChannelDescriptor, Comparable +{ + private long mDownlink; + private int mChannel; + + public LtrChannel(int channel) + { + mChannel = channel; + } + + public int getChannel() + { + return mChannel; + } + + public void setDownlink(long downlink) + { + mDownlink = downlink; + } + + @Override + public long getDownlinkFrequency() + { + return mDownlink; + } + + @Override + public long getUplinkFrequency() + { + return mDownlink; + } + + @Override + public int[] getFrequencyBandIdentifiers() + { + return new int[0]; + } + + @Override + public void setFrequencyBand(IFrequencyBand bandIdentifier) + { + //No-op + } + + @Override + public boolean isTDMAChannel() + { + return false; + } + + @Override + public int getTimeslotCount() + { + return 0; + } + + @Override + public Protocol getProtocol() + { + return Protocol.LTR_STANDARD; + } + + @Override + public String toString() + { + return String.valueOf(getChannel()); + } + + public String description() + { + return "TRANSMIT: " + getDownlinkFrequency(); + } + + @Override + public int compareTo(LtrChannel o) + { + return Integer.compare(getChannel(), o.getChannel()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/Call.java b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/Call.java new file mode 100644 index 00000000..316ed12d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/Call.java @@ -0,0 +1,67 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrstandard.message; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.CRC; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.message.MessageDirection; +import io.github.dsheirer.module.decode.ltrstandard.LtrStandardMessageType; + +import java.util.ArrayList; +import java.util.List; + +public class Call extends LTRStandardMessage +{ + private List mIdentifiers; + + public Call(CorrectedBinaryMessage message, MessageDirection direction, CRC crc) + { + super(message, direction, crc); + } + + @Override + public LtrStandardMessageType getMessageType() + { + return LtrStandardMessageType.CALL; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("CALL TO ").append(getTalkgroup().formatted()); + sb.append(" LCN:").append(getChannel()); + sb.append(" FREE:").append(getFree()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTalkgroup()); + } + + return mIdentifiers; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/CallEnd.java b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/CallEnd.java new file mode 100644 index 00000000..cf0577da --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/CallEnd.java @@ -0,0 +1,67 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrstandard.message; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.CRC; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.message.MessageDirection; +import io.github.dsheirer.module.decode.ltrstandard.LtrStandardMessageType; + +import java.util.ArrayList; +import java.util.List; + +public class CallEnd extends LTRStandardMessage +{ + private List mIdentifiers; + + public CallEnd(CorrectedBinaryMessage message, MessageDirection direction, CRC crc) + { + super(message, direction, crc); + } + + @Override + public LtrStandardMessageType getMessageType() + { + return LtrStandardMessageType.CALL_END; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("CALL END ").append(getTalkgroup().formatted()); + sb.append(" LCN:").append(getChannel()); + sb.append(" FREE:").append(getFree()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTalkgroup()); + } + + return mIdentifiers; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/CallEndMessage.java b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/CallEndMessage.java deleted file mode 100644 index 646f795b..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/CallEndMessage.java +++ /dev/null @@ -1,67 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 io.github.dsheirer.module.decode.ltrstandard.message; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRC; -import io.github.dsheirer.message.MessageDirection; -import io.github.dsheirer.message.MessageType; - -public class CallEndMessage extends LTRStandardMessage -{ - public CallEndMessage(BinaryMessage message, MessageDirection direction, - AliasList list, CRC crc ) - { - super( message, direction, list, crc ); - } - - @Override - public MessageType getMessageType() - { - return MessageType.CA_ENDD; - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "END* AREA:" ); - sb.append( getArea() ); - sb.append( " LCN:" ); - sb.append( getChannelFormatted() ); - sb.append( " TG [" ); - sb.append( getToID() ); - sb.append( "/" ); - Alias endAlias = getToIDAlias(); - if( endAlias != null ) - { - sb.append( getToIDAlias().getName() ); - } - else - { - sb.append( "UNKNOWN" ); - } - sb.append( "] FREE:" ); - sb.append( getFreeFormatted() ); - - return sb.toString(); - } -} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/CallMessage.java b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/CallMessage.java deleted file mode 100644 index 07f949b5..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/CallMessage.java +++ /dev/null @@ -1,67 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 io.github.dsheirer.module.decode.ltrstandard.message; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRC; -import io.github.dsheirer.message.MessageDirection; -import io.github.dsheirer.message.MessageType; - -public class CallMessage extends LTRStandardMessage -{ - public CallMessage(BinaryMessage message, MessageDirection direction, AliasList list, CRC crc ) - { - super( message, direction, list, crc ); - } - - @Override - public MessageType getMessageType() - { - return MessageType.CA_STRT; - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "CALL LCN:" ); - sb.append( getChannelFormatted() ); - sb.append( " TG [ " ); - sb.append( getToID() ); - sb.append( "/" ); - - Alias alias = getToIDAlias(); - - if( alias != null ) - { - sb.append( getToIDAlias().getName() ); - } - else - { - sb.append( "UNKNOWN" ); - } - - sb.append( " ] FREE:" ); - sb.append( getFreeFormatted() ); - - return sb.toString(); - } -} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/Idle.java b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/Idle.java new file mode 100644 index 00000000..7c9009ae --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/Idle.java @@ -0,0 +1,64 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrstandard.message; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.CRC; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.message.MessageDirection; +import io.github.dsheirer.module.decode.ltrstandard.LtrStandardMessageType; + +import java.util.Collections; +import java.util.List; + +/** + * Indicates the repeater is IDLE and available for use + */ +public class Idle extends LTRStandardMessage +{ + public Idle(CorrectedBinaryMessage message, MessageDirection direction, CRC crc) + { + super(message, direction, crc); + } + + @Override + public LtrStandardMessageType getMessageType() + { + return LtrStandardMessageType.IDLE; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("IDLE AREA:").append(getArea()); + sb.append(" LCN:").append(getChannel()); + sb.append(" HOME:").append(getHomeRepeater()); + sb.append(" GRP:").append(getGroup()); + sb.append(" FREE:").append(getFree()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/IdleMessage.java b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/IdleMessage.java deleted file mode 100644 index d6629e94..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/IdleMessage.java +++ /dev/null @@ -1,58 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 io.github.dsheirer.module.decode.ltrstandard.message; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRC; -import io.github.dsheirer.message.MessageDirection; -import io.github.dsheirer.message.MessageType; - -public class IdleMessage extends LTRStandardMessage -{ - public IdleMessage(BinaryMessage message, MessageDirection direction, - AliasList list, CRC crc ) - { - super( message, direction, list, crc ); - } - - @Override - public MessageType getMessageType() - { - return MessageType.SY_IDLE; - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "IDLE AREA:" ); - sb.append( getArea() ); - sb.append( " LCN:" ); - sb.append( getChannelFormatted() ); - sb.append( " HOME:" ); - sb.append( getHomeRepeaterFormatted() ); - sb.append( " GRP:" ); - sb.append( getGroupFormatted() ); - sb.append( " FREE:" ); - sb.append( getFreeFormatted() ); - - return sb.toString(); - } -} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/LTRStandardMessage.java b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/LTRStandardMessage.java index d977c3ea..412c13b2 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/LTRStandardMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/LTRStandardMessage.java @@ -1,211 +1,148 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrstandard.message; -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.edac.CRC; +import io.github.dsheirer.identifier.talkgroup.LTRTalkgroup; import io.github.dsheirer.message.Message; import io.github.dsheirer.message.MessageDirection; -import io.github.dsheirer.message.MessageType; -import io.github.dsheirer.module.decode.DecoderType; -import org.apache.commons.lang3.StringUtils; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; +import io.github.dsheirer.module.decode.ltrstandard.LtrStandardMessageType; +import io.github.dsheirer.protocol.Protocol; +/** + * LTR Standard Base Message + */ public abstract class LTRStandardMessage extends Message { - protected SimpleDateFormat mDatestampFormatter = - new SimpleDateFormat( "yyyyMMdd HHmmss" ); - - public static final int[] SYNC = { 0,1,2,3,4,5,6,7,8 }; - public static final int[] AREA = { 9 }; - public static final int[] CHANNEL = { 10,11,12,13,14 }; - public static final int[] HOME_REPEATER = { 15,16,17,18,19 }; - public static final int[] GROUP = { 20,21,22,23,24,25,26,27 }; - public static final int[] FREE = { 28,29,30,31,32 }; - public static final int[] CHECKSUM = { 33,34,35,36,37,38,39 }; - - protected BinaryMessage mMessage; - protected MessageDirection mMessageDirection; - protected AliasList mAliasList; - protected CRC mCRC; - - public LTRStandardMessage( BinaryMessage message, - MessageDirection direction, - AliasList list, - CRC crc ) + public static final int[] SYNC = {0, 1, 2, 3, 4, 5, 6, 7, 8}; + public static final int[] AREA = {9}; + public static final int[] CHANNEL = {10, 11, 12, 13, 14}; + public static final int[] HOME_REPEATER = {15, 16, 17, 18, 19}; + public static final int[] GROUP = {20, 21, 22, 23, 24, 25, 26, 27}; + public static final int[] FREE = {28, 29, 30, 31, 32}; + public static final int[] CHECKSUM = {33, 34, 35, 36, 37, 38, 39}; + + private CorrectedBinaryMessage mMessage; + private MessageDirection mMessageDirection; + private CRC mCRC; + private LTRTalkgroup mTalkgroup; + + /** + * Constructs the message + * @param message containing the raw bits + * @param direction of the messsage, ISW or OSW + * @param crc error check + */ + public LTRStandardMessage(CorrectedBinaryMessage message, MessageDirection direction, CRC crc) { mMessage = message; mMessageDirection = direction; - mAliasList = list; mCRC = crc; } - - public abstract MessageType getMessageType(); - public boolean isValid() + /** + * Identifies the type of message + */ + public abstract LtrStandardMessageType getMessageType(); + + /** + * Indicates if this message passes the CRC check + */ + public boolean isValid() { - return mCRC != CRC.FAILED_CRC && mCRC != CRC.FAILED_PARITY; - } - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append( mDatestampFormatter.format( - new Date( System.currentTimeMillis() ) ) ); - sb.append( " LTR " ); - sb.append( mMessageDirection.name() ); - sb.append( " [" ); - sb.append( mCRC.getAbbreviation() ); - sb.append( "] " ); - sb.append( getMessage() ); - - return sb.toString(); - } - - @Override - public String getBinaryMessage() - { - return mMessage.toString(); + return mCRC.passes(); } + /** + * Raw binary message + */ + public CorrectedBinaryMessage getMessage() + { + return mMessage; + } + + /** + * CRC error check stats + */ public CRC getCRC() { - return mCRC; - } - - public int getArea() - { - return mMessage.getInt( AREA ); - } - - public int getChannel() - { - return mMessage.getInt( CHANNEL ); - } - - public String getChannelFormatted() - { - return StringUtils.leftPad( String.valueOf( getChannel() ), 2, "0" ); - } - - public int getHomeRepeater() - { - return mMessage.getInt( HOME_REPEATER ); - } - - public String getHomeRepeaterFormatted() - { - return StringUtils.leftPad( String.valueOf( getHomeRepeater() ), 2, "0" ); - } - - public int getGroup() - { - return mMessage.getInt( GROUP ); - } - - public String getGroupFormatted() - { - return StringUtils.leftPad( String.valueOf( getGroup() ), 3, "0" ); - } - - public int getFree() - { - return mMessage.getInt( FREE ); - } - - public String getFreeFormatted() - { - return StringUtils.leftPad( String.valueOf( getFree() ), 2, "0" ); - } - - @Override - public String getFromID() - { - return null; + return mCRC; } - @Override - public Alias getFromIDAlias() + /** + * Area: 0 or 1 + */ + public int getArea() { - return null; + return mMessage.getInt(AREA); } - @Override - public String getToID() + /** + * Logical Channel Number (LCN) for the repeater + */ + public int getChannel() { - StringBuilder sb = new StringBuilder(); - - sb.append( getArea() ); - sb.append( "-" ); - sb.append( getHomeRepeaterFormatted() ); - sb.append( "-" ); - sb.append( getGroupFormatted() ); - - return sb.toString(); + return mMessage.getInt(CHANNEL); } - @Override - public Alias getToIDAlias() + /** + * Home repeater number for the talkgroup + */ + public int getHomeRepeater() { - return mAliasList.getTalkgroupAlias( getToID() ); + return mMessage.getInt(HOME_REPEATER); } - public List getAliases() - { - List aliases = new ArrayList<>(); - - Alias talkgroupAlias = getToIDAlias(); - - if( talkgroupAlias != null ) - { - aliases.add( talkgroupAlias ); - } - - return aliases; - } - - public int getCRCChecksum() - { - return mMessage.getInt( CHECKSUM ); - } - - @Override - public String getProtocol() + /** + * Talkgroup number + */ + public int getGroup() { - return DecoderType.LTR_STANDARD.getDisplayString(); + return mMessage.getInt(GROUP); } - @Override - public String getEventType() + /** + * Free or available repeater channel for other subscribers to use when this repeater channel is busy + */ + public int getFree() { - return getMessageType().getDisplayText(); + return mMessage.getInt(FREE); } - @Override - public String getErrorStatus() + /** + * Talkgroup identifier + */ + public LTRTalkgroup getTalkgroup() { - return mCRC.getDisplayText(); + if(mTalkgroup == null) + { + mTalkgroup = LTRTalkgroup.create((getHomeRepeater() << 8) + getGroup()); + } + + return mTalkgroup; + } + + @Override + public Protocol getProtocol() + { + return Protocol.LTR_STANDARD; } } \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/UnknownMessage.java b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/UnknownMessage.java index 41f13ccf..17bf3d50 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/UnknownMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/message/UnknownMessage.java @@ -1,57 +1,64 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.ltrstandard.message; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.edac.CRC; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.message.MessageDirection; -import io.github.dsheirer.message.MessageType; +import io.github.dsheirer.module.decode.ltrstandard.LtrStandardMessageType; +import java.util.Collections; +import java.util.List; + +/** + * Unknown message type + */ public class UnknownMessage extends LTRStandardMessage { - public UnknownMessage(BinaryMessage message, MessageDirection direction, AliasList list, CRC crc ) + public UnknownMessage(CorrectedBinaryMessage message, MessageDirection direction, CRC crc) { - super( message, direction, list, crc ); + super(message, direction, crc); } - @Override - public MessageType getMessageType() - { - return MessageType.UN_KNWN; - } - - @Override - public String getMessage() + @Override + public LtrStandardMessageType getMessageType() { - StringBuilder sb = new StringBuilder(); + return LtrStandardMessageType.UNKNOWN; + } - sb.append( "UNKNOWN AREA:" ); - sb.append( getArea() ); - sb.append( " LCN:" ); - sb.append( getChannelFormatted() ); - sb.append( " HOME:" ); - sb.append( getHomeRepeaterFormatted() ); - sb.append( " GRP:" ); - sb.append( getGroupFormatted() ); - sb.append( " FREE:" ); - sb.append( getFreeFormatted() ); + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("UNKNOWN AREA:").append(getArea()); + sb.append(" LCN:").append(getChannel()); + sb.append(" HOME:").append(getHomeRepeater()); + sb.append(" GRP:").append(getGroup()); + sb.append(" FREE:").append(getFree()); + return sb.toString(); + } - return sb.toString(); + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; } } \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCCallEvent.java b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCCallEvent.java deleted file mode 100644 index 05933a40..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCCallEvent.java +++ /dev/null @@ -1,161 +0,0 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ -package io.github.dsheirer.module.decode.mdc1200; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.module.decode.event.CallEvent; - -public class MDCCallEvent extends CallEvent -{ - public MDCCallEvent( CallEventType callEventType, - AliasList aliasList, - String fromID, - String toID, - String details ) - { - super( DecoderType.MDC1200, callEventType, - aliasList, fromID, toID, details ); - } - - private Alias getAlias( String ident ) - { - if( hasAliasList() ) - { - return getAliasList().getMDC1200Alias( ident ); - } - - return null; - } - - @Override - public Alias getFromIDAlias() - { - return getAlias( getFromID() ); - } - - @Override - public Alias getToIDAlias() - { - return getAlias( getToID() ); - } - - @Override - public String getChannel() - { - return null; - } - - @Override - public long getFrequency() - { - return 0; - } - - public static class Builder - { - /* Required parameters */ - private CallEventType mCallEventType; - - /* Optional parameters */ - private AliasList mAliasList; - private String mFromID; - private String mToID; - private String mDetails; - - public Builder( CallEventType callEventType ) - { - mCallEventType = callEventType; - } - - public Builder aliasList( AliasList aliasList ) - { - mAliasList = aliasList; - return this; - } - - public Builder from( String val ) - { - mFromID = val; - return this; - } - - public Builder details( String details ) - { - mDetails = details; - return this; - } - - public Builder to( String toID ) - { - mToID = toID; - return this; - } - - public MDCCallEvent build() - { - return new MDCCallEvent( this ); - } - } - - /** - * Private constructor for the builder - */ - private MDCCallEvent( Builder builder ) - { - this( builder.mCallEventType, - builder.mAliasList, - builder.mFromID, - builder.mToID, - builder.mDetails ); - } - - public static MDCCallEvent - getMDCCallEvent( MDCMessage message ) - { - CallEventType type = CallEventType.UNKNOWN; - StringBuilder sbDetails = new StringBuilder(); - sbDetails.append( "MDC-1200 OPCODE:" ); - sbDetails.append( String.valueOf( message.getOpcode() ) ); - - switch( message.getMessageType() ) - { - case ACKNOWLEDGE: - type = CallEventType.RESPONSE; - break; - case ANI: - type = CallEventType.ID_ANI; - break; - case EMERGENCY: - type = CallEventType.EMERGENCY; - break; - case PAGING: - type = CallEventType.PAGE; - break; - default: - break; - } - - return new MDCCallEvent.Builder( type ) - .details( sbDetails.toString() ) - .from( message.getFromID() ) - .to( message.getToID() ) - .build(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoder.java b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoder.java index f4910c4a..31c8eb66 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoder.java @@ -1,21 +1,24 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.module.decode.mdc1200; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.bits.MessageFramer; import io.github.dsheirer.bits.SyncPattern; import io.github.dsheirer.dsp.NRZDecoder; @@ -34,25 +37,25 @@ public class MDCDecoder extends AbstractAFSKDecoder private MessageFramer mMessageFramer; private MDCMessageProcessor mMessageProcessor; - public MDCDecoder(AliasList aliasList) + public MDCDecoder() { super(AFSK1200Decoder.Output.INVERTED); - init(aliasList); + init(); } - protected MDCDecoder(AFSK1200Decoder decoder, AliasList aliasList) + protected MDCDecoder(AFSK1200Decoder decoder) { super(decoder); - init(aliasList); + init(); } - private void init(AliasList aliasList) + private void init() { mNRZDecoder = new NRZDecoder(NRZDecoder.MODE_INVERTED); getDecoder().setSymbolProcessor(mNRZDecoder); mMessageFramer = new MessageFramer(SyncPattern.MDC1200.getPattern(), MESSAGE_LENGTH); mNRZDecoder.setListener(mMessageFramer); - mMessageProcessor = new MDCMessageProcessor(aliasList); + mMessageProcessor = new MDCMessageProcessor(); mMessageFramer.addMessageListener(mMessageProcessor); mMessageProcessor.addMessageListener(getMessageListener()); } diff --git a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderInstrumented.java index 526c5c3f..422c1745 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderInstrumented.java @@ -1,18 +1,22 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.module.decode.mdc1200; import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented; @@ -29,7 +33,7 @@ public class MDCDecoderInstrumented extends MDCDecoder implements IInstrumentedA public MDCDecoderInstrumented() { - super(new AFSK1200DecoderInstrumented(false), null); + super(new AFSK1200DecoderInstrumented(false)); } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderState.java b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderState.java index 8f8667fb..6997e0e9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderState.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,42 +15,32 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.module.decode.mdc1200; -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.alias.id.AliasIDType; -import io.github.dsheirer.channel.metadata.AliasedStringAttributeMonitor; -import io.github.dsheirer.channel.metadata.Attribute; -import io.github.dsheirer.channel.metadata.AttributeChangeRequest; import io.github.dsheirer.channel.state.DecoderState; import io.github.dsheirer.channel.state.DecoderStateEvent; import io.github.dsheirer.channel.state.DecoderStateEvent.Event; import io.github.dsheirer.channel.state.State; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.MutableIdentifierCollection; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.util.StringUtils; +import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.mdc1200.identifier.MDC1200Identifier; import java.util.Iterator; +import java.util.Set; import java.util.TreeSet; public class MDCDecoderState extends DecoderState { - private TreeSet mIdents = new TreeSet(); - private TreeSet mEmergencyIdents = new TreeSet(); + private Set mIdents = new TreeSet<>(); + private Set mEmergencyIdents = new TreeSet<>(); - private AliasedStringAttributeMonitor mFromAttribute; - private String mMessage; - private String mMessageType; - - public MDCDecoderState(AliasList aliasList) + public MDCDecoderState() { - super(aliasList); - - mFromAttribute = new AliasedStringAttributeMonitor(Attribute.SECONDARY_ADDRESS_FROM, - getAttributeChangeRequestListener(), getAliasList(), AliasIDType.MDC1200); } @Override @@ -85,37 +76,26 @@ public class MDCDecoderState extends DecoderState } - private void resetState() - { - mFromAttribute.reset(); - mMessage = null; - mMessageType = null; - } - @Override - public void receive(Message message) + public void receive(IMessage message) { if(message instanceof MDCMessage) { - MDCMessage mdc = (MDCMessage) message; + MDCMessage mdc = (MDCMessage)message; - mIdents.add(mdc.getUnitID()); + mIdents.add(mdc.getFromIdentifier()); if(mdc.isEmergency()) { - mEmergencyIdents.add(mdc.getUnitID()); + mEmergencyIdents.add(mdc.getFromIdentifier()); } - mFromAttribute.process(mdc.getFromID()); - MDCMessageType type = mdc.getMessageType(); - setMessageType(type.getLabel()); - StringBuilder sb = new StringBuilder(); sb.append("OPCODE "); - sb.append(String.valueOf(mdc.getOpcode())); + sb.append(mdc.getOpcode()); if(mdc.isBOT()) { @@ -127,11 +107,15 @@ public class MDCDecoderState extends DecoderState sb.append(" TYPE:EOT"); } - setMessage(sb.toString()); + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(message.getIdentifiers()); - MDCCallEvent event = MDCCallEvent.getMDCCallEvent(mdc); - event.setAliasList(getAliasList()); - broadcast(event); + broadcast(DecodeEvent.builder(mdc.getTimestamp()) + .eventDescription(type.getLabel()) + .details(mdc.toString()) + .identifiers(ic) + .build()); switch(type) { @@ -150,34 +134,6 @@ public class MDCDecoderState extends DecoderState } } - public String getMessage() - { - return mMessage; - } - - public void setMessage(String message) - { - if(!StringUtils.isEqual(mMessage, message)) - { - mMessage = message; - broadcast(new AttributeChangeRequest(Attribute.MESSAGE, mMessage)); - } - } - - public String getMessageType() - { - return mMessageType; - } - - public void setMessageType(String type) - { - if(!StringUtils.isEqual(mMessageType, type)) - { - mMessageType = type; - broadcast(new AttributeChangeRequest(Attribute.MESSAGE_TYPE, "MDC:" + mMessageType)); - } - } - @Override public String getActivitySummary() { @@ -193,27 +149,11 @@ public class MDCDecoderState extends DecoderState } else { - Iterator it = mIdents.iterator(); + Iterator it = mIdents.iterator(); while(it.hasNext()) { - String ident = it.next(); - - sb.append(" "); - sb.append(ident); - sb.append(" "); - - if(hasAliasList()) - { - Alias alias = getAliasList().getMDC1200Alias(ident); - - if(alias != null) - { - sb.append(alias.getName()); - } - } - - sb.append("\n"); + sb.append(" ").append(it.next()).append("\n"); } } @@ -225,27 +165,16 @@ public class MDCDecoderState extends DecoderState } else { - Iterator it = mEmergencyIdents.iterator(); + Iterator it = mEmergencyIdents.iterator(); while(it.hasNext()) { - String ident = it.next(); + Iterator it2 = mIdents.iterator(); - sb.append(" "); - sb.append(ident); - sb.append(" "); - - if(hasAliasList()) + while(it2.hasNext()) { - Alias alias = getAliasList().getMDC1200Alias(ident); - - if(alias != null) - { - sb.append(alias.getName()); - } + sb.append(" ").append(it2.next()).append("\n"); } - - sb.append("\n"); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCMessage.java b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCMessage.java index f73027df..1dfcaa88 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCMessage.java @@ -1,345 +1,261 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.mdc1200; -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.map.Plottable; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.message.Message; +import io.github.dsheirer.module.decode.mdc1200.identifier.MDC1200Identifier; +import io.github.dsheirer.protocol.Protocol; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Date; import java.util.List; public class MDCMessage extends Message { - private static SimpleDateFormat mSDF = new SimpleDateFormat( "yyyyMMdd HHmmss" ); + private static int[] SYNC1 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static int[] sSYNC1 = { 0,1,2,3,4,5,6,7,8,9, - 10,11,12,13,14,15,16,17,18,19, - 20,21,22,23,24,25,26,27,28,29, - 30,31,32,33,34,35,36,37,38,39 }; - - private static int[] sOPCODE = { 47,46,45,44,43,42,41,40 }; - private static int sANI_FLAG = 40; - private static int sDIRECTION_FLAG = 45; - private static int sACKNOWLEDGE_REQUIRED_FLAG = 46; - private static int sPACKET_TYPE_FLAG = 47; - private static int sEMERGENCY_FLAG = 48; - private static int[] sARGUMENT = { 49,50,51,52,53,54 }; - private static int sBOT_EOT_FLAG = 55; - private static int[] sDIGIT_2 = { 59,58,57,56 }; - private static int[] sDIGIT_1 = { 63,62,61,60 }; - private static int[] sDIGIT_4 = { 67,66,65,64 }; - private static int[] sDIGIT_3 = { 71,70,69,68 }; + private static int[] OPCODE = {47, 46, 45, 44, 43, 42, 41, 40}; + private static int ANI_FLAG = 40; + private static int DIRECTION_FLAG = 45; + private static int ACKNOWLEDGE_REQUIRED_FLAG = 46; + private static int PACKET_TYPE_FLAG = 47; + private static int EMERGENCY_FLAG = 48; + private static int[] ARGUMENT = {49, 50, 51, 52, 53, 54}; + private static int BOT_EOT_FLAG = 55; + private static int[] DIGIT_2 = {59, 58, 57, 56}; + private static int[] DIGIT_1 = {63, 62, 61, 60}; + private static int[] DIGIT_4 = {67, 66, 65, 64}; + private static int[] DIGIT_3 = {71, 70, 69, 68}; + private static int[] IDENTITY = {63, 62, 61, 60, 59, 58, 57, 56, 71, 70, 69, 68, 67, 66, 65, 64}; - private BinaryMessage mMessage; - private AliasList mAliasList; - - public MDCMessage( BinaryMessage message, AliasList list ) + private CorrectedBinaryMessage mMessage; + private MDC1200Identifier mFromIdentifier; + private List mIdentifiers; + + public MDCMessage(CorrectedBinaryMessage message) { mMessage = message; - mAliasList = list; } - + + public CorrectedBinaryMessage getMessage() + { + return mMessage; + } + + public MDC1200Identifier getFromIdentifier() + { + if(mFromIdentifier == null) + { + mFromIdentifier = MDC1200Identifier.createFrom(getMessage().getInt(IDENTITY)); + } + + return mFromIdentifier; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getFromIdentifier()); + } + + return mIdentifiers; + } + public boolean isValid() { - //TODO: add CRC and/or convolution decoding/repair + //TODO: add CRC and/or convolution decoding/repair return true; } public PacketType getPacketType() { - if( mMessage.get( sPACKET_TYPE_FLAG ) ) - { - return PacketType.DATA; - } - else - { - return PacketType.CMND; - } + if(mMessage.get(PACKET_TYPE_FLAG)) + { + return PacketType.DATA; + } + else + { + return PacketType.CMND; + } } - + public Acknowledge getResponse() { - if( mMessage.get( sACKNOWLEDGE_REQUIRED_FLAG ) ) - { - return Acknowledge.YES; - } - else - { - return Acknowledge.NO; - } + if(mMessage.get(ACKNOWLEDGE_REQUIRED_FLAG)) + { + return Acknowledge.YES; + } + else + { + return Acknowledge.NO; + } } - + public Direction getDirection() { - if( mMessage.get( sDIRECTION_FLAG ) ) - { - return Direction.OUT; - } - else - { - return Direction.IN; - } + if(mMessage.get(DIRECTION_FLAG)) + { + return Direction.OUT; + } + else + { + return Direction.IN; + } } - + public int getOpcode() { - return mMessage.getInt( sOPCODE ); + return mMessage.getInt(OPCODE); } - + public int getArgument() { - return mMessage.getInt( sARGUMENT ); + return mMessage.getInt(ARGUMENT); } - + public boolean isEmergency() { - return mMessage.get( sEMERGENCY_FLAG ); + return mMessage.get(EMERGENCY_FLAG); } - - + + public boolean isANI() { - return mMessage.get( sANI_FLAG ); + return mMessage.get(ANI_FLAG); } - + public boolean isBOT() { - return mMessage.get( sBOT_EOT_FLAG ); + return mMessage.get(BOT_EOT_FLAG); } - + public boolean isEOT() { - return !mMessage.get( sBOT_EOT_FLAG ); + return !mMessage.get(BOT_EOT_FLAG); } - - public String getUnitID() - { - StringBuilder sb = new StringBuilder(); - sb.append( Integer.toHexString( mMessage.getInt( sDIGIT_1 ) ).toUpperCase() ); - sb.append( Integer.toHexString( mMessage.getInt( sDIGIT_2 ) ).toUpperCase() ); - sb.append( Integer.toHexString( mMessage.getInt( sDIGIT_3 ) ).toUpperCase() ); - sb.append( Integer.toHexString( mMessage.getInt( sDIGIT_4 ) ).toUpperCase() ); - - return sb.toString(); - } - public MDCMessageType getMessageType() { - switch( getOpcode() ) - { - case 0: - if( isEmergency() ) - { - return MDCMessageType.EMERGENCY; - } - case 1: - return MDCMessageType.ANI; - default: - return MDCMessageType.UNKNOWN; - } - } - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(); - - sb.append( mSDF.format( new Date( System.currentTimeMillis() ) ) ); - - sb.append( getMessage() ); - - sb.append( " [" + mMessage.toString() + "]" ); - - return sb.toString(); + switch(getOpcode()) + { + case 0: + if(isEmergency()) + { + return MDCMessageType.EMERGENCY; + } + case 1: + return MDCMessageType.ANI; + default: + return MDCMessageType.UNKNOWN; + } } /** * Pads spaces onto the end of the value to make it 'places' long */ - public String pad( String value, int places, String padCharacter ) + public String pad(String value, int places, String padCharacter) { - StringBuilder sb = new StringBuilder(); - - sb.append( value ); - - while( sb.length() < places ) - { - sb.append( padCharacter ); - } - - return sb.toString(); + StringBuilder sb = new StringBuilder(); + + sb.append(value); + + while(sb.length() < places) + { + sb.append(padCharacter); + } + + return sb.toString(); } - + /** * Pads an integer value with additional zeroes to make it decimalPlaces long */ - public String format( int number, int decimalPlaces ) + public String format(int number, int decimalPlaces) { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); - int paddingRequired = decimalPlaces - ( String.valueOf( number ).length() ); + int paddingRequired = decimalPlaces - (String.valueOf(number).length()); - for( int x = 0; x < paddingRequired; x++) - { - sb.append( "0" ); - } - - sb.append( number ); - - return sb.toString(); - } - - @SuppressWarnings( "unused" ) - private int getInt( int[] bits ) - { - int retVal = 0; - - for( int x = 0; x < bits.length; x++ ) - { - if( mMessage.get( bits[ x ] ) ) - { - retVal += 1< getAliases() - { - List aliases = new ArrayList(); - - Alias from = getFromIDAlias(); - - if( from != null ) - { - aliases.add( from ); - } - - Alias to = getToIDAlias(); - - if( to != null ) - { - aliases.add( to ); - } - - return aliases; - } - - private enum PacketType { CMND, DATA }; - private enum Acknowledge { YES, NO }; - private enum Direction { IN, OUT }; } diff --git a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCMessageFilter.java index 1cae450d..8a49cb46 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCMessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCMessageFilter.java @@ -2,55 +2,56 @@ package io.github.dsheirer.module.decode.mdc1200; import io.github.dsheirer.filter.Filter; import io.github.dsheirer.filter.FilterElement; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.Message; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; -public class MDCMessageFilter extends Filter +public class MDCMessageFilter extends Filter { - private HashMap> mElements = - new HashMap>(); - - public MDCMessageFilter() - { - super( "MDC-1200 Message Filter" ); + private Map> mElements = new HashMap>(); - for( MDCMessageType type: MDCMessageType.values() ) - { - if( type != MDCMessageType.UNKNOWN ) - { - mElements.put( type, new FilterElement( type ) ); - } - } - } - - @Override - public boolean passes( Message message ) + public MDCMessageFilter() { - if( mEnabled && canProcess( message ) ) - { - MDCMessage mdc = (MDCMessage)message; - - if( mElements.containsKey( mdc.getMessageType() ) ) - { - return mElements.get( mdc.getMessageType() ).isEnabled(); - } - } - - return false; + super("MDC-1200 Message Filter"); + + for(MDCMessageType type : MDCMessageType.values()) + { + if(type != MDCMessageType.UNKNOWN) + { + mElements.put(type, new FilterElement(type)); + } + } } - @Override - public boolean canProcess( Message message ) + @Override + public boolean passes(IMessage message) { - return message instanceof MDCMessage; + if(mEnabled && canProcess(message)) + { + MDCMessage mdc = (MDCMessage) message; + + if(mElements.containsKey(mdc.getMessageType())) + { + return mElements.get(mdc.getMessageType()).isEnabled(); + } + } + + return false; } - @Override + @Override + public boolean canProcess(IMessage message) + { + return message instanceof MDCMessage; + } + + @Override public List> getFilterElements() { - return new ArrayList>( mElements.values() ); + return new ArrayList>(mElements.values()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCMessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCMessageProcessor.java index b4511e93..0265e971 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCMessageProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCMessageProcessor.java @@ -1,155 +1,155 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.mdc1200; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; -public class MDCMessageProcessor implements Listener +public class MDCMessageProcessor implements Listener { - private static int sMESSAGE_LENGTH = 112; - - private Broadcaster mBroadcaster = new Broadcaster(); - private AliasList mAliasList; - - public MDCMessageProcessor( AliasList aliasList ) - { - mAliasList = aliasList; - } - - public void dispose() - { - mBroadcaster.dispose(); - } - - @Override - public void receive( BinaryMessage buffer ) - { - /** - * De-interleave the 112 bits of message one, starting after the sync - * pattern - */ - deinterleave( buffer, 40 ); + private static int sMESSAGE_LENGTH = 112; - /** - * For simplicity, we also deinterleave message 2, which may or may not - * be there, starting after the initial sync ( 0 - 39 ) and the initial - * message ( 40 - 151 ) and the second sync ( 152 - 191 ). - */ - deinterleave( buffer, 192 ); + private Broadcaster mBroadcaster = new Broadcaster<>(); - /** - * Wrap the buffer in a message along with the designated alias list - * and send it on its merry way - */ - MDCMessage message = new MDCMessage( buffer, mAliasList ); - - mBroadcaster.receive( message ); - } - - public void addMessageListener( Listener listener ) + public MDCMessageProcessor() { - mBroadcaster.addListener( listener ); } - public void removeMessageListener( Listener listener ) + public void dispose() { - mBroadcaster.removeListener( listener ); + mBroadcaster.dispose(); } - - /** - * Removes NRZ-I encoding from the entire buffer. Assumes that the initial - * bit will be a 0 after decode, despite what is currently in the buffer, - * in order to maintain correct polarity of the decoded message. - */ - private void removeNRZIEncoding( BinaryMessage buffer ) - { - /** - * Clear bit position 0, the first bit of the sync pattern, to start - * the decode with the correct polarity - */ - buffer.clear( 0 ); - /** - * Set each bit according to the state of the previous bit using XOR - * as follows: - * - * 0 = same as previous state - * 1 = inverse of previous state - */ - for( int x = 1; x < buffer.size(); x++ ) - { - if( buffer.get( x - 1 ) ^ buffer.get( x ) ) - { - buffer.set( x ); - } - else - { - buffer.clear( x ); - } - } - } - - /** - * Deinterleaves a 112-bit packet, starting at the offset into the buffer - */ - private void deinterleave( BinaryMessage buffer, int offset ) - { - if( buffer.size() < sMESSAGE_LENGTH + offset ) - { - throw new IllegalArgumentException( "MDCMessageProcessor - " - + "cannot deinterleave message - message buffer too short" ); - } - - BinaryMessage deinterleaved = new BinaryMessage( 112 ); + @Override + public void receive(CorrectedBinaryMessage buffer) + { + /** + * De-interleave the 112 bits of message one, starting after the sync + * pattern + */ + deinterleave(buffer, 40); - int deinterleavedPointer = 0; - - for( int column = 0; column < 16; column++ ) - { - for( int row = 0; row < 7; row++ ) - { - if( buffer.get( ( row * 16 ) + column + offset ) ) - { - deinterleaved.set( deinterleavedPointer ); - } - - deinterleavedPointer++; - } - } + /** + * For simplicity, we also deinterleave message 2, which may or may not + * be there, starting after the initial sync ( 0 - 39 ) and the initial + * message ( 40 - 151 ) and the second sync ( 152 - 191 ). + */ + deinterleave(buffer, 192); - //Overlay the deinterleaved bits back onto the source message and - //return it - buffer.clear( offset, offset + sMESSAGE_LENGTH ); - - for( int x = 0; x < sMESSAGE_LENGTH; x++ ) - { - if( deinterleaved.get( x ) ) - { - buffer.set( x + offset ); - } - else - { - buffer.clear( x + offset ); - } - } - } + /** + * Wrap the buffer in a message along with the designated alias list + * and send it on its merry way + */ + MDCMessage message = new MDCMessage(buffer); + + mBroadcaster.receive(message); + } + + public void addMessageListener(Listener listener) + { + mBroadcaster.addListener(listener); + } + + public void removeMessageListener(Listener listener) + { + mBroadcaster.removeListener(listener); + } + + /** + * Removes NRZ-I encoding from the entire buffer. Assumes that the initial + * bit will be a 0 after decode, despite what is currently in the buffer, + * in order to maintain correct polarity of the decoded message. + */ + private void removeNRZIEncoding(BinaryMessage buffer) + { + /** + * Clear bit position 0, the first bit of the sync pattern, to start + * the decode with the correct polarity + */ + buffer.clear(0); + + /** + * Set each bit according to the state of the previous bit using XOR + * as follows: + * + * 0 = same as previous state + * 1 = inverse of previous state + */ + for(int x = 1; x < buffer.size(); x++) + { + if(buffer.get(x - 1) ^ buffer.get(x)) + { + buffer.set(x); + } + else + { + buffer.clear(x); + } + } + } + + /** + * Deinterleaves a 112-bit packet, starting at the offset into the buffer + */ + private void deinterleave(BinaryMessage buffer, int offset) + { + if(buffer.size() < sMESSAGE_LENGTH + offset) + { + throw new IllegalArgumentException("MDCMessageProcessor - " + + "cannot deinterleave message - message buffer too short"); + } + + BinaryMessage deinterleaved = new BinaryMessage(112); + + int deinterleavedPointer = 0; + + for(int column = 0; column < 16; column++) + { + for(int row = 0; row < 7; row++) + { + if(buffer.get((row * 16) + column + offset)) + { + deinterleaved.set(deinterleavedPointer); + } + + deinterleavedPointer++; + } + } + + //Overlay the deinterleaved bits back onto the source message and + //return it + buffer.clear(offset, offset + sMESSAGE_LENGTH); + + for(int x = 0; x < sMESSAGE_LENGTH; x++) + { + if(deinterleaved.get(x)) + { + buffer.set(x + offset); + } + else + { + buffer.clear(x + offset); + } + } + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/mdc1200/identifier/MDC1200Identifier.java b/src/main/java/io/github/dsheirer/module/decode/mdc1200/identifier/MDC1200Identifier.java new file mode 100644 index 00000000..1914bd09 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/mdc1200/identifier/MDC1200Identifier.java @@ -0,0 +1,62 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.mdc1200.identifier; + +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.protocol.Protocol; + +/** + * Identifier for an MDC1200 radio identity + */ +public class MDC1200Identifier extends TalkgroupIdentifier +{ + + /** + * Constructs an MDC1200 talkgroup Identifier + */ + public MDC1200Identifier(Integer value, Role role) + { + super(value, role, false); + } + + @Override + public Protocol getProtocol() + { + return Protocol.MDC1200; + } + + /** + * Creates an MDC1200 talkgroup identifier for the TO identity + */ + public static MDC1200Identifier createTo(int value) + { + return new MDC1200Identifier(value, Role.TO); + } + + /** + * Creates an MDC1200 talkgroup identifier for the FROM identity + */ + public static MDC1200Identifier createFrom(int value) + { + return new MDC1200Identifier(value, Role.FROM); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327CallEvent.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327CallEvent.java deleted file mode 100644 index 776a6d8c..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327CallEvent.java +++ /dev/null @@ -1,154 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.mpt1327; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.module.decode.event.CallEvent; - -public class MPT1327CallEvent extends CallEvent -{ - private String mChannel; - private long mFrequency; - - public MPT1327CallEvent( CallEventType callEventType, - AliasList aliasList, - String fromID, - String toID, - String channel, - long frequency, - String details ) - { - super( DecoderType.MPT1327, callEventType, - aliasList, fromID, toID, details ); - - mChannel = channel; - mFrequency = frequency; - } - - private Alias getAlias(String mpt1327Talkgroup ) - { - if( hasAliasList() ) - { - return getAliasList().getMPT1327Alias( mpt1327Talkgroup ); - } - - return null; - } - - @Override - public Alias getFromIDAlias() - { - return getAlias( getFromID() ); - } - - @Override - public Alias getToIDAlias() - { - return getAlias( getToID() ); - } - - @Override - public String getChannel() - { - return mChannel; - } - - @Override - public long getFrequency() - { - return mFrequency; - } - - public static class Builder - { - /* Required parameters */ - private CallEventType mCallEventType; - - /* Optional parameters */ - private AliasList mAliasList; - private String mFromID; - private String mToID; - private String mDetails; - private String mChannel; - private long mFrequency; - - public Builder( CallEventType callEventType ) - { - mCallEventType = callEventType; - } - - public Builder aliasList( AliasList aliasList ) - { - mAliasList = aliasList; - return this; - } - - public Builder from( String val ) - { - mFromID = val; - return this; - } - - public Builder channel( String channel ) - { - mChannel = channel; - return this; - } - - public Builder details( String details ) - { - mDetails = details; - return this; - } - - public Builder frequency( long frequency ) - { - mFrequency = frequency; - return this; - } - - public Builder to( String toID ) - { - mToID = toID; - return this; - } - - public MPT1327CallEvent build() - { - return new MPT1327CallEvent( this ); - } - - } - - /** - * Private constructor for the builder - */ - private MPT1327CallEvent( Builder builder ) - { - this( builder.mCallEventType, - builder.mAliasList, - builder.mFromID, - builder.mToID, - builder.mChannel, - builder.mFrequency, - builder.mDetails ); - } - -} diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327ChannelGrantEvent.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327ChannelGrantEvent.java new file mode 100644 index 00000000..18506fcc --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327ChannelGrantEvent.java @@ -0,0 +1,142 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.mpt1327; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.module.decode.p25.P25DecodeEvent; +import io.github.dsheirer.protocol.Protocol; + +public class MPT1327ChannelGrantEvent extends P25DecodeEvent +{ + public MPT1327ChannelGrantEvent(long timestamp) + { + super(timestamp); + } + + /** + * Creates a new decode event builder with the specified start timestamp. + * @param timeStart for the event + * @return builder + */ + public static MPT1327ChannelGrantDecodeEventBuilder mpt1327Builder(long timeStart) + { + return new MPT1327ChannelGrantDecodeEventBuilder(timeStart); + } + + /** + * Builder pattern for constructing decode events. + */ + public static class MPT1327ChannelGrantDecodeEventBuilder + { + protected long mTimeStart; + protected long mDuration; + protected String mEventDescription; + protected IdentifierCollection mIdentifierCollection; + protected IChannelDescriptor mChannelDescriptor; + protected String mDetails; + + /** + * Constructs a builder instance with the specified start time in milliseconds + * + * @param timeStart + */ + public MPT1327ChannelGrantDecodeEventBuilder(long timeStart) + { + mTimeStart = timeStart; + } + + /** + * Sets the duration value + * @param duration in milliseconds + */ + public MPT1327ChannelGrantDecodeEventBuilder duration(long duration) + { + mDuration = duration; + return this; + } + + /** + * Sets the duration value using the end - start timestamps + * @param timestamp for end of event + */ + public MPT1327ChannelGrantDecodeEventBuilder end(long timestamp) + { + mDuration = timestamp - mTimeStart; + return this; + } + + /** + * Sets the channel descriptor for this event + * @param channelDescriptor + */ + public MPT1327ChannelGrantDecodeEventBuilder channel(IChannelDescriptor channelDescriptor) + { + mChannelDescriptor = channelDescriptor; + return this; + } + + /** + * Sets the event description text + * @param description of the event + */ + public MPT1327ChannelGrantDecodeEventBuilder eventDescription(String description) + { + mEventDescription = description; + return this; + } + + /** + * Sets the identifier collection. + * @param identifierCollection containing optional identifiers like TO, FROM, frequency and + * alias list configuration name. + */ + public MPT1327ChannelGrantDecodeEventBuilder identifiers(IdentifierCollection identifierCollection) + { + mIdentifierCollection = identifierCollection; + return this; + } + + /** + * Sets the details for the event + * @param details + */ + public MPT1327ChannelGrantDecodeEventBuilder details(String details) + { + mDetails = details; + return this; + } + + /** + * Builds the decode event + */ + public MPT1327ChannelGrantEvent build() + { + MPT1327ChannelGrantEvent decodeEvent = new MPT1327ChannelGrantEvent(mTimeStart); + decodeEvent.setProtocol(Protocol.MPT1327); + decodeEvent.setChannelDescriptor(mChannelDescriptor); + decodeEvent.setDetails(mDetails); + decodeEvent.setDuration(mDuration); + decodeEvent.setEventDescription(mEventDescription); + decodeEvent.setIdentifierCollection(mIdentifierCollection); + return decodeEvent; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327Decoder.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327Decoder.java index 2c0ae2a2..b835f180 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327Decoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327Decoder.java @@ -1,33 +1,40 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.module.decode.mpt1327; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.bits.IBinarySymbolProcessor; import io.github.dsheirer.bits.MessageFramer; import io.github.dsheirer.dsp.afsk.AFSK1200Decoder; +import io.github.dsheirer.dsp.symbol.BinaryToByteBufferAssembler; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.afsk.AbstractAFSKDecoder; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider; +import io.github.dsheirer.sample.buffer.ReusableByteBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Instrumented version of the MPT1327 decoder. Exposes properties for instrumented manual decoding of a signal. */ -public class MPT1327Decoder extends AbstractAFSKDecoder implements IBinarySymbolProcessor +public class MPT1327Decoder extends AbstractAFSKDecoder implements IBinarySymbolProcessor, IReusableByteBufferProvider { private final static Logger mLog = LoggerFactory.getLogger(MPT1327Decoder.class); @@ -38,20 +45,21 @@ public class MPT1327Decoder extends AbstractAFSKDecoder implements IBinarySymbol private MessageFramer mControlMessageFramer; private MessageFramer mTrafficMessageFramer; private MPT1327MessageProcessor mMessageProcessor; + private BinaryToByteBufferAssembler mBinaryToByteBufferAssembler = new BinaryToByteBufferAssembler(512); - protected MPT1327Decoder(AFSK1200Decoder decoder, AliasList aliasList, Sync sync) + protected MPT1327Decoder(AFSK1200Decoder decoder, Sync sync) { super(decoder); - init(aliasList, sync); + init(sync); } - public MPT1327Decoder(AliasList aliasList, Sync sync) + public MPT1327Decoder(Sync sync) { super((sync == Sync.NORMAL ? AFSK1200Decoder.Output.NORMAL : AFSK1200Decoder.Output.INVERTED)); - init(aliasList, sync); + init(sync); } - private void init(AliasList aliasList, Sync sync) + private void init(Sync sync) { getDecoder().setSymbolProcessor(this); @@ -62,17 +70,18 @@ public class MPT1327Decoder extends AbstractAFSKDecoder implements IBinarySymbol mTrafficMessageFramer = new MessageFramer(sync.getTrafficSyncPattern().getPattern(), MESSAGE_LENGTH); //Fully decoded and framed messages processor - mMessageProcessor = new MPT1327MessageProcessor(aliasList); + mMessageProcessor = new MPT1327MessageProcessor(); mMessageProcessor.setMessageListener(getMessageListener()); mControlMessageFramer.addMessageListener(mMessageProcessor); mTrafficMessageFramer.addMessageListener(mMessageProcessor); } - public void receive(boolean symbol) + public void process(boolean symbol) { - mControlMessageFramer.receive(symbol); - mTrafficMessageFramer.receive(symbol); + mControlMessageFramer.process(symbol); + mTrafficMessageFramer.process(symbol); + mBinaryToByteBufferAssembler.process(symbol); } @Override @@ -84,6 +93,12 @@ public class MPT1327Decoder extends AbstractAFSKDecoder implements IBinarySymbol @Override public void reset() { + } + + @Override + public void start() + { + super.start(); mControlMessageFramer.reset(); mTrafficMessageFramer.reset(); } @@ -109,4 +124,22 @@ public class MPT1327Decoder extends AbstractAFSKDecoder implements IBinarySymbol { return mTrafficMessageFramer; } + + @Override + public void setBufferListener(Listener listener) + { + mBinaryToByteBufferAssembler.setBufferListener(listener); + } + + @Override + public void removeBufferListener(Listener listener) + { + mBinaryToByteBufferAssembler.removeBufferListener(listener); + } + + @Override + public boolean hasBufferListeners() + { + return mBinaryToByteBufferAssembler.hasBufferListeners(); + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderInstrumented.java index eb6d31ac..fa76b186 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderInstrumented.java @@ -1,18 +1,22 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.module.decode.mpt1327; import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented; @@ -29,7 +33,7 @@ public class MPT1327DecoderInstrumented extends MPT1327Decoder implements IInstr public MPT1327DecoderInstrumented() { - super(new AFSK1200DecoderInstrumented(true), null, Sync.FRENCH); + super(new AFSK1200DecoderInstrumented(true), Sync.NORMAL); } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderState.java index d09cf40d..7cd67fd3 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderState.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,28 +15,24 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.module.decode.mpt1327; -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.alias.id.AliasIDType; -import io.github.dsheirer.channel.metadata.AliasedStringAttributeMonitor; -import io.github.dsheirer.channel.metadata.Attribute; -import io.github.dsheirer.channel.metadata.AttributeChangeRequest; import io.github.dsheirer.channel.state.ChangeChannelTimeoutEvent; import io.github.dsheirer.channel.state.DecoderState; import io.github.dsheirer.channel.state.DecoderStateEvent; import io.github.dsheirer.channel.state.DecoderStateEvent.Event; import io.github.dsheirer.channel.state.State; -import io.github.dsheirer.channel.traffic.TrafficChannelAllocationEvent; import io.github.dsheirer.controller.channel.Channel.ChannelType; -import io.github.dsheirer.controller.channel.map.ChannelMap; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.MutableIdentifierCollection; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.config.DecodeConfiguration; -import io.github.dsheirer.module.decode.event.CallEvent; +import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.mpt1327.channel.MPT1327Channel; +import io.github.dsheirer.protocol.Protocol; import io.github.dsheirer.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,27 +52,31 @@ public class MPT1327DecoderState extends DecoderState private HashMap> mGroups = new HashMap>(); private String mSite; - private AliasedStringAttributeMonitor mFromAttribute; - private AliasedStringAttributeMonitor mToAttribute; private int mChannelNumber; private ChannelType mChannelType; - private ChannelMap mChannelMap; private long mFrequency = 0; private long mCallTimeout; + private MPT1327TrafficChannelManager mMPT1327TrafficChannelManager; - public MPT1327DecoderState(AliasList aliasList, ChannelMap channelMap, ChannelType channelType, long callTimeout) + /** + * Constructs an MPT-1327 decoder state with an optional traffic channel manager for allocating traffic channels + * when go to channel (GTC) messages are detected. + */ + public MPT1327DecoderState(MPT1327TrafficChannelManager trafficChannelManager, ChannelType channelType, long callTimeout) { - super(aliasList); - - mChannelMap = channelMap; + mMPT1327TrafficChannelManager = trafficChannelManager; mChannelType = channelType; mCallTimeout = callTimeout; - mFromAttribute = new AliasedStringAttributeMonitor(Attribute.PRIMARY_ADDRESS_FROM, - getAttributeChangeRequestListener(), getAliasList(), AliasIDType.MPT1327); - mToAttribute = new AliasedStringAttributeMonitor(Attribute.PRIMARY_ADDRESS_TO, - getAttributeChangeRequestListener(), getAliasList(), AliasIDType.MPT1327); + } + + /** + * Constructs an MPT1327 decoder state that does not allocate traffic channels + */ + public MPT1327DecoderState(ChannelType channelType, long callTimeout) + { + this(null, channelType, callTimeout); } @Override @@ -95,13 +96,13 @@ public class MPT1327DecoderState extends DecoderState } @Override - public void receive(Message message) + public void receive(IMessage message) { if(message.isValid()) { if(message instanceof MPT1327Message) { - MPT1327Message mpt = ((MPT1327Message) message); + MPT1327Message mpt = ((MPT1327Message)message); switch(mpt.getMessageType()) { @@ -112,166 +113,151 @@ public class MPT1327DecoderState extends DecoderState if(identType == MPT1327Message.IdentType.REGI) { - broadcast(new MPT1327CallEvent.Builder(CallEvent.CallEventType.REGISTER) - .aliasList(getAliasList()) - .channel(String.valueOf(mChannelNumber)) - .details("REGISTERED ON NETWORK") - .frequency(mFrequency) - .from(mpt.getToID()) - .to(mpt.getFromID()) + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(mpt.getIdentifiers()); + + broadcast(DecodeEvent.builder(message.getTimestamp()) + .protocol(Protocol.MPT1327) + .eventDescription("Register") + .identifiers(ic) .build()); } else { - broadcast(new MPT1327CallEvent.Builder(CallEvent.CallEventType.RESPONSE) - .aliasList(getAliasList()) - .channel(String.valueOf(mChannelNumber)) + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(mpt.getIdentifiers()); + + broadcast(DecodeEvent.builder(message.getTimestamp()) + .protocol(Protocol.MPT1327) + .eventDescription("Response") .details("ACK " + identType.getLabel()) - .frequency(mFrequency) - .from(mpt.getFromID()) - .to(mpt.getToID()) + .identifiers(ic) .build()); } broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); break; + case ACKB: + case ACKE: case ACKI: - mIdents.add(mpt.getFromID()); - mIdents.add(mpt.getToID()); + case ACKQ: + case ACKT: + case ACKV: + case ACKX: + MutableIdentifierCollection icACK = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icACK.remove(IdentifierClass.USER); + icACK.update(mpt.getIdentifiers()); + + broadcast(DecodeEvent.builder(message.getTimestamp()) + .protocol(Protocol.MPT1327) + .eventDescription("Acknowledge") + .details(mpt.getMessageType().getDescription()) + .identifiers(icACK) + .build()); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); break; case AHYC: - mIdents.add(mpt.getToID()); + MutableIdentifierCollection icAHY = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icAHY.remove(IdentifierClass.USER); + icAHY.update(mpt.getIdentifiers()); - broadcast(new MPT1327CallEvent.Builder(CallEvent.CallEventType.COMMAND) - .aliasList(getAliasList()) - .channel(String.valueOf(mChannelNumber)) - .details(((MPT1327Message) message).getRequestString()) - .frequency(mFrequency) - .from(mpt.getFromID()) - .to(mpt.getToID()) + broadcast(DecodeEvent.builder(message.getTimestamp()) + .protocol(Protocol.MPT1327) + .eventDescription("Command") + .details("Send Short Data Message") + .identifiers(icAHY) .build()); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); break; case AHYQ: - broadcast(new MPT1327CallEvent.Builder(CallEvent.CallEventType.STATUS) - .aliasList(getAliasList()) - .channel(String.valueOf(mChannelNumber)) - .details(mpt.getStatusMessage()) - .frequency(mFrequency) - .from(mpt.getFromID()) - .to(mpt.getToID()) + MutableIdentifierCollection icAHYQ = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icAHYQ.remove(IdentifierClass.USER); + icAHYQ.update(mpt.getIdentifiers()); + + broadcast(DecodeEvent.builder(message.getTimestamp()) + .protocol(Protocol.MPT1327) + .eventDescription("Command") + .details("Send Status Message") + .identifiers(icAHYQ) .build()); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); break; case ALH: + case ALHD: + case ALHE: + case ALHF: + case ALHR: + case ALHS: + case ALHX: setSite(mpt.getSiteID()); broadcast(new DecoderStateEvent(this, Event.START, State.CONTROL)); break; case GTC: - String from = mpt.getFromID(); - String to = mpt.getToID(); - - if(from != null) + if(mMPT1327TrafficChannelManager != null) { - mIdents.add(from); + mMPT1327TrafficChannelManager.processChannelGrant(mpt); } - - if(to != null) + else { - mIdents.add(to); + MPT1327Channel channel = MPT1327Channel.create(mpt.getChannel()); + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(mpt.getIdentifiers()); + + broadcast(DecodeEvent.builder(mpt.getTimestamp()) + .eventDescription("Call Detect") + .details(mpt.getMessage()) + .channel(channel) + .identifiers(ic) + .build()); } - - /* Capture the idents that talk to each group */ - if(from != null && to != null) - { - if(mGroups.containsKey(to)) - { - ArrayList talkgroups = mGroups.get(to); - - if(!talkgroups.contains(from)) - { - talkgroups.add(from); - } - } - else - { - ArrayList talkgroups = new ArrayList(); - talkgroups.add(from); - - mGroups.put(to, talkgroups); - } - } - - int channel = mpt.getChannel(); - - long frequency = 0; - - if(getChannelMap() != null) - { - frequency = getChannelMap().getFrequency(channel); - } - - CallEvent event = new MPT1327CallEvent.Builder(CallEvent.CallEventType.CALL) - .aliasList(getAliasList()) - .channel(String.valueOf(channel)) - .details("GTC") - .frequency(frequency) - .from(mpt.getFromID()) - .to(mpt.getToID()) - .build(); - - broadcast(new TrafficChannelAllocationEvent(this, event)); break; case HEAD_PLUS1: case HEAD_PLUS2: case HEAD_PLUS3: case HEAD_PLUS4: - broadcast(new MPT1327CallEvent.Builder(CallEvent.CallEventType.SDM) - .aliasList(getAliasList()) + MutableIdentifierCollection icHEAD = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icHEAD.remove(IdentifierClass.USER); + icHEAD.update(mpt.getIdentifiers()); + + broadcast(DecodeEvent.builder(message.getTimestamp()) + .protocol(Protocol.MPT1327) + .eventDescription("Short Data Message") .details(mpt.getMessage()) - .from(mpt.getFromID()) - .to(mpt.getToID()) + .identifiers(icHEAD) .build()); + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); break; - /* Traffic Channel Events */ + /* Traffic Channel Events */ case CLEAR: mChannelNumber = mpt.getChannel(); broadcast(new DecoderStateEvent(this, Event.END, State.FADE)); break; case MAINT: + getIdentifierCollection().update(mpt.getToIdentifier()); + if(mChannelType == ChannelType.STANDARD) { - /** - * When we receive a MAINT message and we're configured - * as a standard channel, we need to apply the call - * timeout specified by the user. Otherwise we'll - * be using the shorter default call timeout - */ - broadcast(new ChangeChannelTimeoutEvent(this, - mChannelType, mCallTimeout)); + //When we receive a MAINT message and we're configured as a standard channel, apply the call + // timeout specified by the user. Otherwise we'll be using the shorter default call timeout + broadcast(new ChangeChannelTimeoutEvent(this, mChannelType, mCallTimeout)); - if(mCurrentCallEvent == null) - { - mCurrentCallEvent = new MPT1327CallEvent.Builder(CallEvent.CallEventType.CALL) - .aliasList(getAliasList()) - .channel(String.valueOf(mChannelNumber)) - .details("MONITORED TRAFFIC CHANNEL") - .frequency(mFrequency) - .to(mpt.getToID()) - .build(); - - broadcast(mCurrentCallEvent); - } - - mToAttribute.process(mpt.getToID()); + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(mpt.getIdentifiers()); + broadcast(DecodeEvent.builder(mpt.getTimestamp()) + .identifiers(ic) + .eventDescription("Call In Progress") + .build()); broadcast(new DecoderStateEvent(this, Event.START, State.CALL)); } break; @@ -307,10 +293,9 @@ public class MPT1327DecoderState extends DecoderState } - private void resetState() + protected void resetState() { - mFromAttribute.reset(); - mToAttribute.reset(); + super.resetState(); /** * If this is a standard channel, reset the fade timeout to the default @@ -326,7 +311,6 @@ public class MPT1327DecoderState extends DecoderState if(mCurrentCallEvent != null) { mCurrentCallEvent.end(); - broadcast(mCurrentCallEvent); mCurrentCallEvent = null; } } @@ -345,31 +329,9 @@ public class MPT1327DecoderState extends DecoderState if(!StringUtils.isEqual(mSite, site)) { mSite = site; - broadcast(new AttributeChangeRequest(Attribute.NETWORK_ID_1, "SITE:" + getSite(), - getSiteAlias())); } } - public Alias getSiteAlias() - { - if(hasAliasList()) - { - return getAliasList().getSiteID(mSite); - } - - return null; - } - - public ChannelMap getChannelMap() - { - return mChannelMap; - } - - public void setChannelMap(ChannelMap channelMap) - { - mChannelMap = channelMap; - } - public int getChannelNumber() { return mChannelNumber; @@ -385,7 +347,6 @@ public class MPT1327DecoderState extends DecoderState if(mChannelNumber != channel) { mChannelNumber = channel; - broadcast(new AttributeChangeRequest(Attribute.CHANNEL_FREQUENCY_LABEL, String.valueOf(mChannelNumber))); } } @@ -400,17 +361,8 @@ public class MPT1327DecoderState extends DecoderState if(mSite != null) { - sb.append(mSite); + sb.append(mSite).append("\n"); - if(hasAliasList()) - { - Alias siteAlias = getAliasList().getSiteID(mSite); - - if(siteAlias != null) - { - sb.append(" ").append(siteAlias.getName()).append("\n"); - } - } } else { @@ -432,17 +384,6 @@ public class MPT1327DecoderState extends DecoderState { sb.append("\n ").append(talkgroup); - if(hasAliasList()) - { - Alias alias = getAliasList().getMPT1327Alias(talkgroup); - - if(alias != null) - { - sb.append(" "); - sb.append(alias.getName()); - } - } - sb.append("\n"); ArrayList members = mGroups.get(talkgroup); @@ -453,17 +394,6 @@ public class MPT1327DecoderState extends DecoderState sb.append(" >"); sb.append(member); - if(hasAliasList()) - { - Alias alias = getAliasList().getMPT1327Alias(member); - - if(alias != null) - { - sb.append(" "); - sb.append(alias.getName()); - } - } - sb.append("\n"); } } @@ -485,20 +415,7 @@ public class MPT1327DecoderState extends DecoderState { String ident = it.next(); - sb.append(""); sb.append(ident); - sb.append(" "); - - if(hasAliasList()) - { - Alias alias = getAliasList().getMPT1327Alias(ident); - - if(alias != null) - { - sb.append(alias.getName()); - } - } - sb.append("\n"); } } @@ -516,38 +433,6 @@ public class MPT1327DecoderState extends DecoderState break; case SOURCE_FREQUENCY: mFrequency = event.getFrequency(); - broadcast(new AttributeChangeRequest(Attribute.CHANNEL_FREQUENCY, event.getFrequency())); - break; - case TRAFFIC_CHANNEL_ALLOCATION: - if(event.getSource() != MPT1327DecoderState.this) - { - if(event instanceof TrafficChannelAllocationEvent) - { - TrafficChannelAllocationEvent allocationEvent = - (TrafficChannelAllocationEvent) event; - - String channel = allocationEvent.getCallEvent().getChannel(); - - if(channel != null) - { - try - { - setChannelNumber(Integer.valueOf(channel)); - } - catch(Exception e) - { - //Do nothing, we couldn't parse the channel number - } - } - - mFrequency = allocationEvent.getCallEvent().getFrequency(); - broadcast(new AttributeChangeRequest(Attribute.CHANNEL_FREQUENCY, - allocationEvent.getCallEvent().getFrequency())); - - mFromAttribute.process(allocationEvent.getCallEvent().getFromID()); - mToAttribute.process(allocationEvent.getCallEvent().getToID()); - } - } break; default: break; diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327IdentType.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327IdentType.java new file mode 100644 index 00000000..8d93313b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327IdentType.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.mpt1327; + +/** + * MPT-1327 Ident Value Types + */ +public enum MPT1327IdentType +{ + +} diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327Message.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327Message.java index 23cdf37b..6d3df6f9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327Message.java +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327Message.java @@ -1,293 +1,277 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.mpt1327; -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.edac.CRC; import io.github.dsheirer.edac.CRCFleetsync; -import io.github.dsheirer.map.Plottable; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.message.Message; +import io.github.dsheirer.module.decode.mpt1327.identifier.MPT1327Talkgroup; +import io.github.dsheirer.protocol.Protocol; -import java.text.DecimalFormat; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.BitSet; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; import java.util.List; public class MPT1327Message extends Message { - private static String[] TELEX_LETTERS = - { "A","B","C","D","E","F","G","H", - "I","J","K","L","M","N","O","P", - "Q","R","S","T","U","V","W","X", - "Y","Z","\n","\n","",""," "," " }; + private static String[] TELEX_LETTERS = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "\n", "\n", "", "", " ", " "}; + private static String[] TELEX_FIGURES = {"-", "?", ":", "WRU", "3", "{6}", "{7}", "{8}", "8", "{BEEP}", "(", ")", ".", ",", "9", "0", "1", "4", "'", "5", "7", "=", "2", "/", "6", "+", "\n", "\n", "", "", " ", " "}; + private static String[] BCD = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", " ", "*", "#"}; - private static String[] TELEX_FIGURES = - { "-","?",":","WRU","3","{6}","{7}","{8}", - "8","{BEEP}","(",")",".",",","9","0", - "1","4","'","5","7","=","2","/", - "6","+","\n","\n","",""," "," " }; - - private static String[] BCD = { "0","1","2","3","4","5","6", - "7","8","9"," ","*","#" }; - - private static int BLOCK_1_START = 20; - private static int BLOCK_2_START = 84; - private static int BLOCK_3_START = 148; - private static int BLOCK_4_START = 212; - private static int BLOCK_5_START = 276; - private static int BLOCK_6_START = 340; - private static int[] REVS = { 0,1,2,3 }; - private static int[] SYNC = { 4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 }; + private static int BLOCK_1_START = 20; + private static int BLOCK_2_START = 84; + private static int BLOCK_3_START = 148; + private static int BLOCK_4_START = 212; + private static int BLOCK_5_START = 276; + private static int BLOCK_6_START = 340; + private static int[] REVS = {0, 1, 2, 3}; + private static int[] SYNC = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; - /* Block 1 Fields */ - private static int[] B1_SYSDEF = { 21,22,23,24,25 }; - private static int[] B1_PREFIX = { 21,22,23,24,25,26,27 }; - private static int[] B1_TRAFFIC_CHANNEL = { 21,22,23,24,25,26,27,28,29,30 }; - private static int[] B1_SYSTEM_ID = { 26,27,28,29,30,31,32,33,34,35,36,37,38,39,40 }; - private static int[] B1_IDENT1 = { 28,29,30,31,32,33,34,35,36,37,38,39,40 }; - private static int[] B1_CONTROL_CHANNEL = { 31,32,33,34,35,36,37,38,39,40 }; - private static int[] B1_CHANNEL = { 35,36,37,38,39,40,41,42,43,44 }; - private static int[] B1_MESSAGE_TYPE = { 41,42,43,44,45,46,47,48,49 }; - private static int[] B1_GTC_CHAN = { 43,44,45,46,47,48,49,50,51,52 }; - private static int[] B1_PREFIX2 = { 48,49,50,51,52,53,54 }; - private static int[] B1_IDENT2 = { 50,51,52,53,54,55,56,57,58,59,60,61,62 }; - private static int[] B1_IDENT2_GTC = { 53,54,55,56,57,58,59,60,61,62,63,64,65 }; - private static int[] B1_IDENT2_HEAD = { 55,56,57,58,59,60,61,62,63,64,65,66,67 }; - private static int[] B1_ADJSITE = { 49,50,51,52 }; - private static int B1_PERIODIC_CALL_MAINT_MESSAGES = 50; - private static int[] B1_MAINT_MESSAGE_INTERVAL = { 51,52,53,54,55 }; - private static int[] B1_WAIT_TIME = { 54,55,56 }; - private static int B1_PRESSEL_ON_REQUIRED = 56; - private static int[] B1_ALOHA_RSVD = { 57,58 }; - private static int[] B1_ALOHA_M = { 59,60,61,62,63 }; - private static int[] B1_ALOHA_N = { 64,65,66,67 }; - private static int B1_IDENT1_ID_VALUE = 57; - private static int[] B1_SLOTS = { 63,64 }; - private static int[] B1_DESCRIPTOR = { 65,66,67 }; - private static int[] B1_STATUS_MESSAGE = { 63,64,65,66,67 }; - - /* Block 2 or First Data Codeword Fields */ - private static int[] B2_SYSTEM_ID = { 85,86,87,88,89,90,91,92,93,94,95,96,97,98,99 }; - private static int[] B2_PREFIX = { 112,113,114,115,116,117,118 }; - private static int[] B2_IDENT2 = { 119,120,121,122,123,124,125,126,127,128,129,130,131 }; + /* Block 1 Fields */ + private static int[] B1_SYSDEF = {21, 22, 23, 24, 25}; + private static int[] B1_PREFIX = {21, 22, 23, 24, 25, 26, 27}; + private static int[] B1_TRAFFIC_CHANNEL = {21, 22, 23, 24, 25, 26, 27, 28, 29, 30}; + private static int[] B1_SYSTEM_ID = {26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40}; + private static int[] B1_IDENT1 = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40}; + private static int[] B1_CONTROL_CHANNEL = {31, 32, 33, 34, 35, 36, 37, 38, 39, 40}; + private static int[] B1_CHANNEL = {35, 36, 37, 38, 39, 40, 41, 42, 43, 44}; + private static int[] B1_MESSAGE_TYPE = {41, 42, 43, 44, 45, 46, 47, 48, 49}; + private static int[] B1_GTC_CHAN = {43, 44, 45, 46, 47, 48, 49, 50, 51, 52}; + private static int[] B1_PREFIX2 = {48, 49, 50, 51, 52, 53, 54}; + private static int[] B1_IDENT2 = {50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62}; + private static int[] B1_IDENT2_GTC = {53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65}; + private static int[] B1_IDENT2_HEAD = {55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67}; + private static int[] B1_ADJSITE = {49, 50, 51, 52}; + private static int B1_PERIODIC_CALL_MAINT_MESSAGES = 50; + private static int[] B1_MAINT_MESSAGE_INTERVAL = {51, 52, 53, 54, 55}; + private static int[] B1_WAIT_TIME = {54, 55, 56}; + private static int B1_PRESSEL_ON_REQUIRED = 56; + private static int[] B1_ALOHA_RSVD = {57, 58}; + private static int[] B1_ALOHA_M = {59, 60, 61, 62, 63}; + private static int[] B1_ALOHA_N = {64, 65, 66, 67}; + private static int B1_IDENT1_ID_VALUE = 57; + private static int[] B1_SLOTS = {63, 64}; + private static int[] B1_DESCRIPTOR = {65, 66, 67}; + private static int[] B1_STATUS_MESSAGE = {63, 64, 65, 66, 67}; - private static int B2_SDM_SEGMENT_TRANSACTION_FLAG = 85; - private static int[] B2_SDM_GENERAL_FORMAT = { 86,87,88 }; - private static int B2_SDM_INITIAL_SEGMENT_FLAG = 89; - private static int B2_SDM_STF0_START = 86; - private static int B2_SDM_STF0_END = 131; - private static int B2_SDM_STF1_START = 90; - private static int B2_SDM_STF1_END = 131; + /* Block 2 or First Data Codeword Fields */ + private static int[] B2_SYSTEM_ID = {85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99}; + private static int[] B2_PREFIX = {112, 113, 114, 115, 116, 117, 118}; + private static int[] B2_IDENT2 = {119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131}; - /* Block 3 or Second Data Codeword Fields */ - private static int B3_SDM_RETURN_SLOT_ACCESS = 149; - private static int B3_SDM_START = 150; - private static int B3_SDM_END = 195; + private static int B2_SDM_SEGMENT_TRANSACTION_FLAG = 85; + private static int[] B2_SDM_GENERAL_FORMAT = {86, 87, 88}; + private static int B2_SDM_INITIAL_SEGMENT_FLAG = 89; + private static int B2_SDM_STF0_START = 86; + private static int B2_SDM_STF0_END = 131; + private static int B2_SDM_STF1_START = 90; + private static int B2_SDM_STF1_END = 131; - /* Block 4 or Third Data Codeword Fields */ - private static int B4_SDM_SEGMENT_TRANSACTION_FLAG = 213; - private static int[] B4_SDM_NUMBER_SEGMENTS = { 214,215 }; - private static int B4_SDM_CONTINUATION_SEGMENT_FLAG = 216; - private static int B4_SDM_RESERVED_FLAG = 217; - private static int B4_SDM_STF0_START = 214; - private static int B4_SDM_STF0_END = 259; - private static int B4_SDM_STF1_START = 218; - private static int B4_SDM_STF1_END = 259; - - /* Block 5 or Fourth Data Codeword Fields */ - private static int B5_SDM_RETURN_SLOT_ACCESS = 277; - private static int B5_SDM_START = 278; - private static int B5_SDM_END = 323; - - private static DecimalFormat mFrequencyFormatter = new DecimalFormat("#.#####"); - private static SimpleDateFormat mSDF = new SimpleDateFormat( "yyyyMMdd HHmmss" ); - Calendar mCalendar = new GregorianCalendar(); - - private BinaryMessage mMessage; - private CRC[] mCRC = new CRC[ 5 ]; - private AliasList mAliasList; + /* Block 3 or Second Data Codeword Fields */ + private static int B3_SDM_RETURN_SLOT_ACCESS = 149; + private static int B3_SDM_START = 150; + private static int B3_SDM_END = 195; + + /* Block 4 or Third Data Codeword Fields */ + private static int B4_SDM_SEGMENT_TRANSACTION_FLAG = 213; + private static int[] B4_SDM_NUMBER_SEGMENTS = {214, 215}; + private static int B4_SDM_CONTINUATION_SEGMENT_FLAG = 216; + private static int B4_SDM_RESERVED_FLAG = 217; + private static int B4_SDM_STF0_START = 214; + private static int B4_SDM_STF0_END = 259; + private static int B4_SDM_STF1_START = 218; + private static int B4_SDM_STF1_END = 259; + + /* Block 5 or Fourth Data Codeword Fields */ + private static int B5_SDM_RETURN_SLOT_ACCESS = 277; + private static int B5_SDM_START = 278; + private static int B5_SDM_END = 323; + + private BinaryMessage mMessage; + private CRC[] mCRC = new CRC[5]; private MPTMessageType mMessageType; - - public MPT1327Message( BinaryMessage message, AliasList list ) + + private MPT1327Talkgroup mFromIdentifier; + private MPT1327Talkgroup mToIdentifier; + private List mIdentifiers; + + public MPT1327Message(BinaryMessage message) { mMessage = message; - mAliasList = list; - checkParity( 0, BLOCK_1_START, BLOCK_2_START ); - - if( isValid() ) + checkParity(0, BLOCK_1_START, BLOCK_2_START); + + if(isValid()) { mMessageType = getMessageType(); - - switch( mMessageType ) + + switch(mMessageType) { - /* 1 data block messages */ - case AHYC: - case CLEAR: - case MAINT: - case MOVE: - break; - /* 2 data block messages */ - case AHYQ: - case ALH: - case HEAD_PLUS1: - checkParity( 1, BLOCK_2_START, BLOCK_3_START ); + /* 1 data block messages */ + case AHYC: + case CLEAR: + case MAINT: + case MOVE: break; - /* 3 data block messages */ - case HEAD_PLUS2: - checkParity( 1, BLOCK_2_START, BLOCK_3_START ); - checkParity( 2, BLOCK_3_START, BLOCK_4_START ); + /* 2 data block messages */ + case AHYQ: + case ALH: + case HEAD_PLUS1: + checkParity(1, BLOCK_2_START, BLOCK_3_START); break; - /* 4 data block messages */ - case ACKT: - case HEAD_PLUS3: - checkParity( 1, BLOCK_2_START, BLOCK_3_START ); - checkParity( 2, BLOCK_3_START, BLOCK_4_START ); - checkParity( 3, BLOCK_4_START, BLOCK_5_START ); + /* 3 data block messages */ + case HEAD_PLUS2: + checkParity(1, BLOCK_2_START, BLOCK_3_START); + checkParity(2, BLOCK_3_START, BLOCK_4_START); break; - /* 5 data block messages */ - case HEAD_PLUS4: - checkParity( 1, BLOCK_2_START, BLOCK_3_START ); - checkParity( 2, BLOCK_3_START, BLOCK_4_START ); - checkParity( 3, BLOCK_4_START, BLOCK_5_START ); - checkParity( 4, BLOCK_5_START, BLOCK_6_START ); + /* 4 data block messages */ + case ACKT: + case HEAD_PLUS3: + checkParity(1, BLOCK_2_START, BLOCK_3_START); + checkParity(2, BLOCK_3_START, BLOCK_4_START); + checkParity(3, BLOCK_4_START, BLOCK_5_START); + break; + /* 5 data block messages */ + case HEAD_PLUS4: + checkParity(1, BLOCK_2_START, BLOCK_3_START); + checkParity(2, BLOCK_3_START, BLOCK_4_START); + checkParity(3, BLOCK_4_START, BLOCK_5_START); + checkParity(4, BLOCK_5_START, BLOCK_6_START); + break; + case ACK: + case ACKB: + case ACKE: + case ACKI: + case ACKQ: + case ACKV: + case ACKX: + case AHOY: + case AHYP: + case AHYX: + case ALHD: + case ALHE: + case ALHF: + case ALHR: + case ALHS: + case ALHX: + case BCAST: + case GTC: + case MARK: + case DACKD: + case DACKZ: + case DACK_DAL: + case DACK_DALG: + case DACK_DALN: + case DACK_GO: + case DAHY: + case DAHYX: + case DAHYZ: + case DRQG: + case DRQX: + case DRQZ: + case GTT: + case RLA: + case SACK: + case SAMIS: + case SAMIU: + case SAMO: + case SITH: + case UNKN: + default: break; - case ACK: - case ACKB: - case ACKE: - case ACKI: - case ACKQ: - case ACKV: - case ACKX: - case AHOY: - case AHYP: - case AHYX: - case ALHD: - case ALHE: - case ALHF: - case ALHR: - case ALHS: - case ALHX: - case BCAST: - case GTC: - case MARK: - case DACKD: - case DACKZ: - case DACK_DAL: - case DACK_DALG: - case DACK_DALN: - case DACK_GO: - case DAHY: - case DAHYX: - case DAHYZ: - case DRQG: - case DRQX: - case DRQZ: - case GTT: - case RLA: - case SACK: - case SAMIS: - case SAMIU: - case SAMO: - case SITH: - case UNKN: - default: - break; } } else { - mMessageType = MPTMessageType.UNKN; + mMessageType = MPTMessageType.UNKN; } } - + /** - * Performs CRC check against the specified section/block, using the + * Performs CRC check against the specified section/block, using the * message bits between start and end. */ - private void checkParity( int section, int start, int end ) + private void checkParity(int section, int start, int end) { - mCRC[ section ] = detectAndCorrect( start, end ); + mCRC[section] = detectAndCorrect(start, end); } /** * Performs CRC check and corrects some bit errors. */ - - //TODO: move this to the CRC class - private CRC detectAndCorrect( int start, int end ) - { - BitSet original = mMessage.get( start, end ); - - CRC retVal = CRCFleetsync.check( original ); - - //Attempt to correct single-bit errors - if( retVal == CRC.FAILED_PARITY ) - { - int[] errorBitPositions = CRCFleetsync.findBitErrors( original ); - - if( errorBitPositions != null ) - { - for( int errorBitPosition: errorBitPositions ) - { - mMessage.flip( start + errorBitPosition ); - } - - retVal = CRC.CORRECTED; - } - } - - return retVal; - } - - /** - * String representing results of the parity check - * - * [P] = passes parity check - * [f] = fails parity check - * [C] = corrected message - * [-] = message section not present - */ - public String getParity() - { - return "[" + CRC.format( mCRC ) + "]"; - } - /** - * Indicates if Block 1 of the message has passed the CRC check. - */ + //TODO: move this to the CRC class + private CRC detectAndCorrect(int start, int end) + { + BitSet original = mMessage.get(start, end); + + CRC retVal = CRCFleetsync.check(original); + + //Attempt to correct single-bit errors + if(retVal == CRC.FAILED_PARITY) + { + int[] errorBitPositions = CRCFleetsync.findBitErrors(original); + + if(errorBitPositions != null) + { + for(int errorBitPosition : errorBitPositions) + { + mMessage.flip(start + errorBitPosition); + } + + retVal = CRC.CORRECTED; + } + } + + return retVal; + } + + /** + * String representing results of the parity check + * + * [P] = passes parity check + * [f] = fails parity check + * [C] = corrected message + * [-] = message section not present + */ + public String getParity() + { + return "[" + CRC.format(mCRC) + "]"; + } + + /** + * Indicates if Block 1 of the message has passed the CRC check. + */ public boolean isValid() { - return mCRC[ 0 ] == CRC.PASSED || - mCRC[ 0 ] == CRC.CORRECTED; + return mCRC[0] == CRC.PASSED || + mCRC[0] == CRC.CORRECTED; } - + public boolean isValidCall() { - return getPrefix() != 0 && - getIdent1() != 0 && - getIdent2() != 0; + return getPrefix() != 0 && + getIdent1() != 0 && + getIdent2() != 0; } /** @@ -296,23 +280,7 @@ public class MPT1327Message extends Message @Override public String toString() { - StringBuilder sb = new StringBuilder(); - - sb.append( mSDF.format( new Date() ) ); - - sb.append( " MPT1327 " ); - - sb.append( getParity() ); - - sb.append( " " ); - - sb.append( getMessage() ); - - sb.append( getFiller( sb, 100 ) ); - - sb.append( " [" + mMessage.toString() + "]" ); - - return sb.toString(); + return getMessage(); } /** @@ -320,9 +288,9 @@ public class MPT1327Message extends Message */ public MPTMessageType getMessageType() { - int value = mMessage.getInt( B1_MESSAGE_TYPE ); + int value = mMessage.getInt(B1_MESSAGE_TYPE); - return MPTMessageType.fromNumber( value ); + return MPTMessageType.fromNumber(value); } /** @@ -330,18 +298,18 @@ public class MPT1327Message extends Message */ public String getSiteID() { - if( mMessageType == MPTMessageType.BCAST ) - { - return String.valueOf( mMessage.getInt( B1_SYSTEM_ID ) ); - } - else if( mMessageType == MPTMessageType.ALH ) - { - return String.valueOf( mMessage.getInt( B2_SYSTEM_ID ) ); - } - else - { - return null; - } + if(mMessageType == MPTMessageType.BCAST) + { + return String.valueOf(mMessage.getInt(B1_SYSTEM_ID)); + } + else if(mMessageType == MPTMessageType.ALH) + { + return String.valueOf(mMessage.getInt(B2_SYSTEM_ID)); + } + else + { + return null; + } } /** @@ -349,1773 +317,1731 @@ public class MPT1327Message extends Message */ public boolean hasSystemID() { - return getSiteID() != null; + return getSiteID() != null; } - + public SystemDefinition getSystemDefinition() { - int sysdef = mMessage.getInt( B1_SYSDEF ); - - return SystemDefinition.fromNumber( sysdef ); + int sysdef = mMessage.getInt(B1_SYSDEF); + + return SystemDefinition.fromNumber(sysdef); } - + public boolean getPeriodicMaintenanceMessagesRequired() { - return mMessage.get( B1_PERIODIC_CALL_MAINT_MESSAGES ); + return mMessage.get(B1_PERIODIC_CALL_MAINT_MESSAGES); } - + public int getMaintenanceMessageInterval() { - return mMessage.getInt( B1_MAINT_MESSAGE_INTERVAL ); + return mMessage.getInt(B1_MAINT_MESSAGE_INTERVAL); } - + public boolean getPresselOnRequired() { - return mMessage.get( B1_PRESSEL_ON_REQUIRED ); + return mMessage.get(B1_PRESSEL_ON_REQUIRED); } - + public String getMaintMessageIDENT1Value() { - return ( mMessage.get( B1_IDENT1_ID_VALUE ) ? "GROUP ADDRESS" : - "INDIVIDUAL ADDRESS" ); + return (mMessage.get(B1_IDENT1_ID_VALUE) ? "GROUP ADDRESS" : + "INDIVIDUAL ADDRESS"); } - + public int getChannel() { - switch( mMessageType ) - { - case CLEAR: - return mMessage.getInt( B1_TRAFFIC_CHANNEL ); - case GTC: - return mMessage.getInt( B1_GTC_CHAN ); - default: - return mMessage.getInt( B1_CHANNEL ); - } + switch(mMessageType) + { + case CLEAR: + return mMessage.getInt(B1_TRAFFIC_CHANNEL); + case GTC: + return mMessage.getInt(B1_GTC_CHAN); + default: + return mMessage.getInt(B1_CHANNEL); + } } - + public int getReturnToChannel() { - if( mMessageType == MPTMessageType.CLEAR ) - { - return mMessage.getInt( B1_CONTROL_CHANNEL ); - } - else - { - return 0; - } + if(mMessageType == MPTMessageType.CLEAR) + { + return mMessage.getInt(B1_CONTROL_CHANNEL); + } + else + { + return 0; + } } - + public int getAdjacentSiteSerialNumber() { - return mMessage.getInt( B1_ADJSITE ); + return mMessage.getInt(B1_ADJSITE); } public int getPrefix() { - return mMessage.getInt( B1_PREFIX ); + return mMessage.getInt(B1_PREFIX); } - + public int getBlock2Prefix() { - return mMessage.getInt( B2_PREFIX ); + return mMessage.getInt(B2_PREFIX); } - + public int getIdent1() { - return mMessage.getInt( B1_IDENT1 ); + return mMessage.getInt(B1_IDENT1); } - + public IdentType getIdent1Type() { - return IdentType.fromIdent( getIdent1() ); + return IdentType.fromIdent(getIdent1()); } - + public int getIdent2() { - MPTMessageType type = getMessageType(); - - if( type == MPTMessageType.GTC ) - { - return mMessage.getInt( B1_IDENT2_GTC ); - } - else if( type == MPTMessageType.HEAD_PLUS1 || - type == MPTMessageType.HEAD_PLUS2 || - type == MPTMessageType.HEAD_PLUS3 || - type == MPTMessageType.HEAD_PLUS4 ) - { - return mMessage.getInt( B1_IDENT2_HEAD ); - } - else - { - return mMessage.getInt( B1_IDENT2 ); - } + MPTMessageType type = getMessageType(); + + if(type == MPTMessageType.GTC) + { + return mMessage.getInt(B1_IDENT2_GTC); + } + else if(type == MPTMessageType.HEAD_PLUS1 || + type == MPTMessageType.HEAD_PLUS2 || + type == MPTMessageType.HEAD_PLUS3 || + type == MPTMessageType.HEAD_PLUS4) + { + return mMessage.getInt(B1_IDENT2_HEAD); + } + else + { + return mMessage.getInt(B1_IDENT2); + } } - + public IdentType getIdent2Type() { - return IdentType.fromIdent( getIdent2() ); + return IdentType.fromIdent(getIdent2()); } - + public int getBlock2Ident2() { - return mMessage.getInt( B2_IDENT2 ); + return mMessage.getInt(B2_IDENT2); } - + public String getStatusMessage() { - int status = mMessage.getInt( B1_STATUS_MESSAGE ); - - switch( status ) - { - case 0: - return "STATUS: Request Speech Call"; - case 31: - return "STATUS: Cancel Request Speech Call"; - default: - return "STATUS: " + status; - } + int status = mMessage.getInt(B1_STATUS_MESSAGE); + + switch(status) + { + case 0: + return "STATUS: Request Speech Call"; + case 31: + return "STATUS: Cancel Request Speech Call"; + default: + return "STATUS: " + status; + } } - + /** * Returns spaces to the fill the string builder to ensure length is >= index */ - public String getFiller( StringBuilder sb, int index ) + public String getFiller(StringBuilder sb, int index) { - if( sb.length() < index ) - { - return String.format( "%" + ( index - sb.length() ) + "s", " " ); - } - else - { - return ""; - } + if(sb.length() < index) + { + return String.format("%" + (index - sb.length()) + "s", " "); + } + else + { + return ""; + } } - + /** * Pads spaces onto the end of the value to make it 'places' long */ - public String pad( String value, int places, String padCharacter ) + public String pad(String value, int places, String padCharacter) { - StringBuilder sb = new StringBuilder(); - - sb.append( value ); - - while( sb.length() < places ) - { - sb.append( padCharacter ); - } - - return sb.toString(); + StringBuilder sb = new StringBuilder(); + + sb.append(value); + + while(sb.length() < places) + { + sb.append(padCharacter); + } + + return sb.toString(); } - + /** * Pads an integer value with additional zeroes to make it decimalPlaces long */ - public String format( int number, int decimalPlaces ) + public String format(int number, int decimalPlaces) { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); - int paddingRequired = decimalPlaces - ( String.valueOf( number ).length() ); + int paddingRequired = decimalPlaces - (String.valueOf(number).length()); - for( int x = 0; x < paddingRequired; x++) - { - sb.append( "0" ); - } - - sb.append( number ); - - return sb.toString(); + for(int x = 0; x < paddingRequired; x++) + { + sb.append("0"); + } + + sb.append(number); + + return sb.toString(); } - - @Override + public String getMessage() { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); - sb.append( pad( mMessageType.toString(), 5, " " ) ); - - switch( mMessageType ) - { - case ACK: - sb.append( " ACKNOWLEDGE " ); + sb.append(pad(mMessageType.toString(), 5, " ")); - if( hasFromID() ) - { - sb.append( getFromID() ); - } + switch(mMessageType) + { + case ACK: + sb.append(" ACKNOWLEDGE "); - IdentType type = getIdent1Type(); - - switch( type ) - { - case ALLI: - case IPFIXI: - case PABXI: - case PSTNGI: - case PSTNSI1: - case PSTNSI2: - case PSTNSI3: - case PSTNSI4: - case PSTNSI5: - case PSTNSI6: - case PSTNSI7: - case PSTNSI8: - case PSTNSI9: - case PSTNSI10: - case PSTNSI11: - case PSTNSI12: - case PSTNSI13: - case PSTNSI14: - case PSTNSI15: - case USER: - sb.append( type.getLabel() ); - sb.append( " CALL REQUEST" ); - break; - case TSCI: - sb.append( " RQQ or RQC TRANSACTION" ); - break; - case DIVERTI: - sb.append( " CANCELLATION OF CALL DIVERSION REQUEST" ); - break; - default: - sb.append( " " ); - sb.append( type.getLabel() ); - sb.append( " REQUEST" ); - break; - } - break; - case ACKI: - sb.append( " MESSAGE ACKNOWLEDGED - MORE TO FOLLOW" ); + if(hasFromID()) + { + sb.append(getFromID()); + } - if( hasFromID() ) - { - sb.append( " FROM:" ); - sb.append( getFromID() ); - } - - if( hasToID() ) - { - sb.append( " TO:" ); - sb.append( getToID() ); - } - break; - case ACKT: - sb.append( " SITE:" ); - sb.append( getSiteID() ); + IdentType type = getIdent1Type(); - sb.append( " LONG ACK MESSAGE" ); + switch(type) + { + case ALLI: + case IPFIXI: + case PABXI: + case PSTNGI: + case PSTNSI1: + case PSTNSI2: + case PSTNSI3: + case PSTNSI4: + case PSTNSI5: + case PSTNSI6: + case PSTNSI7: + case PSTNSI8: + case PSTNSI9: + case PSTNSI10: + case PSTNSI11: + case PSTNSI12: + case PSTNSI13: + case PSTNSI14: + case PSTNSI15: + case USER: + sb.append(type.getLabel()); + sb.append(" CALL REQUEST"); + break; + case TSCI: + sb.append(" RQQ or RQC TRANSACTION"); + break; + case DIVERTI: + sb.append(" CANCELLATION OF CALL DIVERSION REQUEST"); + break; + default: + sb.append(" "); + sb.append(type.getLabel()); + sb.append(" REQUEST"); + break; + } + break; + case ACKI: + sb.append(" MESSAGE ACKNOWLEDGED - MORE TO FOLLOW"); - if( hasFromID() ) - { - sb.append( " FROM:" ); - sb.append( getFromID() ); - } - - if( hasToID() ) - { - sb.append( " TO:" ); - sb.append( getToID() ); - } - - sb.append( "**********************************" ); - break; - case ACKQ: - sb.append( " SYSTEM:" ); - sb.append( getSiteID() ); + if(hasFromID()) + { + sb.append(" FROM:"); + sb.append(getFromID()); + } - sb.append( " CALL QUEUED FROM:" ); + if(hasToID()) + { + sb.append(" TO:"); + sb.append(getToID()); + } + break; + case ACKT: + sb.append(" SITE:"); + sb.append(getSiteID()); - if( hasFromID() ) - { - sb.append( " FROM:" ); - sb.append( getFromID() ); - } - - if( hasToID() ) - { - sb.append( " TO:" ); - sb.append( getToID() ); - } - break; - case ACKX: - sb.append( " SYSTEM:" ); - sb.append( getSiteID() ); + sb.append(" LONG ACK MESSAGE"); - sb.append( " MESSAGE REJECTED FROM:" ); + if(hasFromID()) + { + sb.append(" FROM:"); + sb.append(getFromID()); + } - if( hasFromID() ) - { - sb.append( " FROM:" ); - sb.append( getFromID() ); - } - - if( hasToID() ) - { - sb.append( " TO:" ); - sb.append( getToID() ); - } - break; - case AHYC: - sb.append( " FM:" ); - sb.append( getFromID() ); - sb.append( " TO:" ); - sb.append( getToID() ); - sb.append( " " ); - sb.append( getRequestString() ); - break; - case AHYQ: - /* Status Message */ - sb.append( " STATUS MESSAGE" ); + if(hasToID()) + { + sb.append(" TO:"); + sb.append(getToID()); + } - if( hasFromID() ) - { - sb.append( " FROM:" ); - sb.append( getFromID() ); - } - - if( hasToID() ) - { - sb.append( " TO:" ); - sb.append( getToID() ); - } - sb.append( " " ); - sb.append( getStatusMessage() ); - break; - case ALH: - case ALHD: - case ALHS: - case ALHE: - case ALHR: - case ALHX: - case ALHF: - sb.append( " SYSTEM:" ); - sb.append( getSiteID() ); + sb.append("**********************************"); + break; + case ACKQ: + sb.append(" SYSTEM:"); + sb.append(getSiteID()); - if( hasToID() ) - { - sb.append( " ID:" ); - sb.append( getToID() ); - } - - sb.append( " WAIT:" ); - sb.append( getWaitTime() ); - sb.append( " RSVD:" ); - sb.append( getAlohaReserved() ); - sb.append( " M:" ); - sb.append( getAlohaM() ); - sb.append( " N:" ); - sb.append( getAlohaN() ); - break; - case BCAST: - sb.append( " SYSTEM:" ); - sb.append( getSiteID() ); + sb.append(" CALL QUEUED FROM:"); - SystemDefinition sysdef = getSystemDefinition(); + if(hasFromID()) + { + sb.append(" FROM:"); + sb.append(getFromID()); + } - sb.append( " " ); - sb.append( sysdef.getLabel() ); - - switch( sysdef ) - { - case ANNOUNCE_CONTROL_CHANNEL: - case WITHDRAW_CONTROL_CHANNEL: - sb.append( " CHAN:" ); - sb.append( getChannel() ); - break; - case BROADCAST_ADJACENT_SITE_CONTROL_CHANNEL_NUMBER: - sb.append( " CHAN:" ); - sb.append( getChannel() ); - sb.append( " SER:" ); - sb.append( getAdjacentSiteSerialNumber() ); - break; - case SPECIFY_CALL_MAINTENANCE_PARAMETERS: - sb.append( " PERIODIC MAINT MSG REQD:" ); - sb.append( getPeriodicMaintenanceMessagesRequired() ); - sb.append( " INTERVAL:" ); - sb.append( getMaintenanceMessageInterval() ); - sb.append( "(sec) PRESSEL ON REQD:" ); - sb.append( getPresselOnRequired() ); - sb.append( " IDENT1 VALUE:" ); - sb.append( getMaintMessageIDENT1Value() ); - default: - break; - } - break; - case CLEAR: - sb.append( " TRAFFIC CHANNEL:" ); - sb.append( getChannel() ); - sb.append( " RETURN TO CONTROL CHANNEL:" ); - sb.append( getReturnToChannel() ); - break; - case GTC: - if( hasFromID() ) - { - sb.append( " FROM:" ); - sb.append( getFromID() ); - } + if(hasToID()) + { + sb.append(" TO:"); + sb.append(getToID()); + } + break; + case ACKX: + sb.append(" SYSTEM:"); + sb.append(getSiteID()); - if( hasToID() ) - { - sb.append( " TO:" ); - sb.append( getToID() ); - } - - sb.append( " CHAN:" ); - sb.append( getChannel() ); - break; - case MAINT: - if( hasToID() ) - { - sb.append( " ID:" ); - sb.append( getToID() ); - } - - break; - case HEAD_PLUS1: - case HEAD_PLUS2: - case HEAD_PLUS3: - case HEAD_PLUS4: - sb.append( " " ); - sb.append( getSDM() ); - break; - default: - break; - } - - return sb.toString(); + sb.append(" MESSAGE REJECTED FROM:"); + + if(hasFromID()) + { + sb.append(" FROM:"); + sb.append(getFromID()); + } + + if(hasToID()) + { + sb.append(" TO:"); + sb.append(getToID()); + } + break; + case AHYC: + sb.append(" FM:"); + sb.append(getFromID()); + sb.append(" TO:"); + sb.append(getToID()); + sb.append(" "); + sb.append(getRequestString()); + break; + case AHYQ: + /* Status Message */ + sb.append(" STATUS MESSAGE"); + + if(hasFromID()) + { + sb.append(" FROM:"); + sb.append(getFromID()); + } + + if(hasToID()) + { + sb.append(" TO:"); + sb.append(getToID()); + } + sb.append(" "); + sb.append(getStatusMessage()); + break; + case ALH: + case ALHD: + case ALHS: + case ALHE: + case ALHR: + case ALHX: + case ALHF: + sb.append(" SYSTEM:"); + sb.append(getSiteID()); + + if(hasToID()) + { + sb.append(" ID:"); + sb.append(getToID()); + } + + sb.append(" WAIT:"); + sb.append(getWaitTime()); + sb.append(" RSVD:"); + sb.append(getAlohaReserved()); + sb.append(" M:"); + sb.append(getAlohaM()); + sb.append(" N:"); + sb.append(getAlohaN()); + break; + case BCAST: + sb.append(" SYSTEM:"); + sb.append(getSiteID()); + + SystemDefinition sysdef = getSystemDefinition(); + + sb.append(" "); + sb.append(sysdef.getLabel()); + + switch(sysdef) + { + case ANNOUNCE_CONTROL_CHANNEL: + case WITHDRAW_CONTROL_CHANNEL: + sb.append(" CHAN:"); + sb.append(getChannel()); + break; + case BROADCAST_ADJACENT_SITE_CONTROL_CHANNEL_NUMBER: + sb.append(" CHAN:"); + sb.append(getChannel()); + sb.append(" SER:"); + sb.append(getAdjacentSiteSerialNumber()); + break; + case SPECIFY_CALL_MAINTENANCE_PARAMETERS: + sb.append(" PERIODIC MAINT MSG REQD:"); + sb.append(getPeriodicMaintenanceMessagesRequired()); + sb.append(" INTERVAL:"); + sb.append(getMaintenanceMessageInterval()); + sb.append("(sec) PRESSEL ON REQD:"); + sb.append(getPresselOnRequired()); + sb.append(" IDENT1 VALUE:"); + sb.append(getMaintMessageIDENT1Value()); + default: + break; + } + break; + case CLEAR: + sb.append(" TRAFFIC CHANNEL:"); + sb.append(getChannel()); + sb.append(" RETURN TO CONTROL CHANNEL:"); + sb.append(getReturnToChannel()); + break; + case GTC: + if(hasFromID()) + { + sb.append(" FROM:"); + sb.append(getFromID()); + } + + if(hasToID()) + { + sb.append(" TO:"); + sb.append(getToID()); + } + + sb.append(" CHAN:"); + sb.append(getChannel()); + break; + case MAINT: + if(hasToID()) + { + sb.append(" ID:"); + sb.append(getToID()); + } + + break; + case HEAD_PLUS1: + case HEAD_PLUS2: + case HEAD_PLUS3: + case HEAD_PLUS4: + sb.append(" "); + sb.append(getSDM()); + break; + default: + break; + } + + return sb.toString(); } - @Override - public String getBinaryMessage() + @Override + public Protocol getProtocol() { - return mMessage.toString(); + return Protocol.MPT1327; } - @Override - public String getProtocol() + /** + * Translates the AHYC request from the values in ident1 and ident2 + */ + public String getRequestString() { - return "MPT-1327"; + StringBuilder sb = new StringBuilder(); + + IdentType type2 = getIdent2Type(); + + if(type2 == IdentType.USER) + { + IdentType type1 = getIdent1Type(); + + Slots slots = getSlots(); + + switch(type1) + { + case DIVERTI: + sb.append("SEND BLOCKED ADDRESS FOR THIRD-PARTY CALL DIVERSION USING "); + sb.append(slots.getLabel()); + break; + case IPFIXI: + sb.append("SEND INTER-PREFIX CALL EXTENDED ADDRESSING INFORMATION USING "); + sb.append(slots.getLabel()); + break; + case PABXI: + sb.append("SEND PABX EXTENSION USING"); + sb.append(slots.getLabel()); + break; + case PSTNGI: + if(slots == Slots.SLOTS_1) + { + sb.append("SEND UP TO 9 PSTN DIALED DIGITS"); + } + else if(slots == Slots.SLOTS_2) + { + sb.append("SEND 10 TO 31 PSTN DIALED DIGITS"); + } + else + { + sb.append("SEND PSTN DIALED DIGITS USING "); + sb.append(slots.getLabel()); + } + break; + case SDMI: + sb.append("SEND SHORT DATA MESSAGE USING "); + sb.append(slots.getLabel()); + break; + default: + break; + } + } + else + { + /* MODE 2 */ + sb.append("SEND "); + sb.append(getDescriptor().getMode2Label()); + } + + return sb.toString(); } - @Override - public String getEventType() - { - // TODO Auto-generated method stub - return null; - } - - /** - * Translates the AHYC request from the values in ident1 and ident2 - */ - public String getRequestString() - { - StringBuilder sb = new StringBuilder(); - - IdentType type2 = getIdent2Type(); - - if( type2 == IdentType.USER ) - { - IdentType type1 = getIdent1Type(); - - Slots slots = getSlots(); - - switch( type1 ) - { - case DIVERTI: - sb.append( "SEND BLOCKED ADDRESS FOR THIRD-PARTY CALL DIVERSION USING " ); - sb.append( slots.getLabel() ); - break; - case IPFIXI: - sb.append( "SEND INTER-PREFIX CALL EXTENDED ADDRESSING INFORMATION USING " ); - sb.append( slots.getLabel() ); - break; - case PABXI: - sb.append( "SEND PABX EXTENSION USING" ); - sb.append( slots.getLabel() ); - break; - case PSTNGI: - if( slots == Slots.SLOTS_1 ) - { - sb.append( "SEND UP TO 9 PSTN DIALED DIGITS" ); - } - else if( slots == Slots.SLOTS_2 ) - { - sb.append( "SEND 10 TO 31 PSTN DIALED DIGITS" ); - } - else - { - sb.append( "SEND PSTN DIALED DIGITS USING " ); - sb.append( slots.getLabel() ); - } - break; - case SDMI: - sb.append( "SEND SHORT DATA MESSAGE USING " ); - sb.append( slots.getLabel() ); - break; - default: - break; - } - } - else - { - /* MODE 2 */ - sb.append( "SEND " ); - sb.append( getDescriptor().getMode2Label() ); - } - - return sb.toString(); - } - - /** - * Constructs a FROM identifier from the Prefix2 and Ident2 values - */ - @Override + /** + * Constructs a FROM identifier from the Prefix2 and Ident2 values + */ public String getFromID() { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); - int ident2 = getIdent2(); - - IdentType type = IdentType.fromIdent( ident2 ); + int ident2 = getIdent2(); - /* Inter-Prefix - the from and to idents are different prefixes */ - if( type == IdentType.IPFIXI ) - { - sb.append( format( getBlock2Prefix(), 3 ) ); - sb.append( "-" ); - sb.append( format( getBlock2Ident2(), 4) ); - } - else - { - sb.append( format( getPrefix(), 3 ) ); - sb.append( "-" ); - sb.append( format( ident2, 4) ); - } - - return sb.toString(); + IdentType type = IdentType.fromIdent(ident2); + + /* Inter-Prefix - the from and to idents are different prefixes */ + if(type == IdentType.IPFIXI) + { + sb.append(format(getBlock2Prefix(), 3)); + sb.append("-"); + sb.append(format(getBlock2Ident2(), 4)); + } + else + { + sb.append(format(getPrefix(), 3)); + sb.append("-"); + sb.append(format(ident2, 4)); + } + + return sb.toString(); } - - public boolean hasFromID() - { - return getFromID() != null && !getFromID().isEmpty(); - } - @Override - public Alias getFromIDAlias() + public boolean hasFromID() { - if( mAliasList != null && hasFromID() ) - { - return mAliasList.getMPT1327Alias( getFromID() ); - } - - return null; + return getFromID() != null && !getFromID().isEmpty(); } - /** - * Constructs a TO identifier from the Prefix1 and Ident1 fields - */ - @Override + /** + * Constructs a TO identifier from the Prefix1 and Ident1 fields + */ public String getToID() { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); - int prefix = getPrefix(); - int ident = getIdent1(); + int prefix = getPrefix(); + int ident = getIdent1(); - switch( IdentType.fromIdent( ident ) ) - { - case IPFIXI: - sb.append( "INTER-PREFIX" ); - break; - case ALLI: - sb.append( "ALL RADIOS" ); - break; - case PABXI: - sb.append( "PABX EXT" ); - break; - case PSTNSI1: - case PSTNSI2: - case PSTNSI3: - case PSTNSI4: - case PSTNSI5: - case PSTNSI6: - case PSTNSI7: - case PSTNSI8: - case PSTNSI9: - case PSTNSI10: - case PSTNSI11: - case PSTNSI12: - case PSTNSI13: - case PSTNSI14: - case PSTNSI15: - sb.append( "PRE-DEFINED PSTN" ); - break; - case PSTNGI: - sb.append( "PSTN GATEWAY" ); - break; - case TSCI: - sb.append( "SYSTEM CONTROLLER" ); - break; - case DIVERTI: - sb.append( "CALL DIVERT" ); - break; - case USER: - default: - if( prefix != 0 || ident != 0 ) - { - sb.append( format( prefix, 3 ) ); - sb.append( "-" ); - sb.append( format( ident, 4) ); - } - break; - } + switch(IdentType.fromIdent(ident)) + { + case IPFIXI: + sb.append("INTER-PREFIX"); + break; + case ALLI: + sb.append("ALL RADIOS"); + break; + case PABXI: + sb.append("PABX EXT"); + break; + case PSTNSI1: + case PSTNSI2: + case PSTNSI3: + case PSTNSI4: + case PSTNSI5: + case PSTNSI6: + case PSTNSI7: + case PSTNSI8: + case PSTNSI9: + case PSTNSI10: + case PSTNSI11: + case PSTNSI12: + case PSTNSI13: + case PSTNSI14: + case PSTNSI15: + sb.append("PRE-DEFINED PSTN"); + break; + case PSTNGI: + sb.append("PSTN GATEWAY"); + break; + case TSCI: + sb.append("SYSTEM CONTROLLER"); + break; + case DIVERTI: + sb.append("CALL DIVERT"); + break; + case USER: + default: + if(prefix != 0 || ident != 0) + { + sb.append(format(prefix, 3)); + sb.append("-"); + sb.append(format(ident, 4)); + } + break; + } - return sb.toString(); + return sb.toString(); } - - public boolean hasToID() - { - return getToID() != null && !getToID().isEmpty(); - } - /** - * Data message codeword descriptor. Indicates the type of data message - * that the radio unit shall respond with. - */ - public Descriptor getDescriptor() - { - return Descriptor.fromNumber( mMessage.getInt( B1_DESCRIPTOR ) ); - } - - /** - * Indicates the number of data codeword slots that are appended to a HEAD - * message - */ - public Slots getSlots() - { - return Slots.fromNumber( mMessage.getInt( B1_SLOTS ) ); - } - - @Override - public Alias getToIDAlias() + public boolean hasToID() { - if( hasToID() ) - { - return mAliasList.getMPT1327Alias( getToID() ); - } - else - { - return null; - } + return getToID() != null && !getToID().isEmpty(); } - - /** - * Aloha wait time - random access protocol - */ - public int getWaitTime() - { - return mMessage.getInt( B1_WAIT_TIME ); - } - /** - * Aloha reserved field - */ - public int getAlohaReserved() - { - return mMessage.getInt( B1_ALOHA_RSVD ); - } + /** + * Data message codeword descriptor. Indicates the type of data message + * that the radio unit shall respond with. + */ + public Descriptor getDescriptor() + { + return Descriptor.fromNumber(mMessage.getInt(B1_DESCRIPTOR)); + } - /** - * Aloha M field - */ - public int getAlohaM() - { - return mMessage.getInt( B1_ALOHA_M ); - } + /** + * Indicates the number of data codeword slots that are appended to a HEAD + * message + */ + public Slots getSlots() + { + return Slots.fromNumber(mMessage.getInt(B1_SLOTS)); + } - /** - * Aloha N field - */ - public int getAlohaN() - { - return mMessage.getInt( B1_ALOHA_N ); - } + /** + * Aloha wait time - random access protocol + */ + public int getWaitTime() + { + return mMessage.getInt(B1_WAIT_TIME); + } - /** - * String representation of the CRC check for each 64-bit message block in - * this message. - */ - @Override + /** + * Aloha reserved field + */ + public int getAlohaReserved() + { + return mMessage.getInt(B1_ALOHA_RSVD); + } + + /** + * Aloha M field + */ + public int getAlohaM() + { + return mMessage.getInt(B1_ALOHA_M); + } + + /** + * Aloha N field + */ + public int getAlohaN() + { + return mMessage.getInt(B1_ALOHA_N); + } + + /** + * String representation of the CRC check for each 64-bit message block in + * this message. + */ public String getErrorStatus() { - return getParity(); + return getParity(); } - /** - * Used by the map to get a plottable version of this event - */ - @Override - public Plottable getPlottable() + /** + * Indicates if the received SDM is formatted according to the MPT-1327 or + * the MPT-1343 ICD. + */ + public SDMFormat getSDMFormat() { - return null; + return SDMFormat.valueOf(mMessage.get(B2_SDM_SEGMENT_TRANSACTION_FLAG), + mMessage.getInt(B2_SDM_GENERAL_FORMAT)); } - - /** - * Indicates if the received SDM is formatted according to the MPT-1327 or - * the MPT-1343 ICD. - */ - public SDMFormat getSDMFormat() - { - return SDMFormat.valueOf( mMessage.get( B2_SDM_SEGMENT_TRANSACTION_FLAG ), - mMessage.getInt( B2_SDM_GENERAL_FORMAT ) ); - } - - public String getSDM() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "SDM:" ); - - MPTMessageType type = getMessageType(); - switch( type ) - { - case HEAD_PLUS1: - case HEAD_PLUS2: - case HEAD_PLUS3: - case HEAD_PLUS4: - SDMFormat format = getSDMFormat(); - - switch( format ) - { - case MPT1327: - sb.append( "MPT1327 " ); - sb.append( getSDMBinary( type, format ) ); - break; - case MPT1343_BINARY: - sb.append( "BINARY " ); - sb.append( getSDMBinary( type, format ) ); - break; - case MPT1343_COMMAND: - sb.append( "COMMAND " ); - sb.append( getSDMBinary( type, format ) ); - break; - case MPT1343_MAP27: - sb.append( "MAP27 " ); - sb.append( getSDMBinary( type, format ) ); - break; - case MPT1343_RESERVED: - sb.append( "RESERVED " ); - sb.append( getSDMBinary( type, format ) ); - break; - case MPT1343_SPARE: - sb.append( "SPARE " ); - sb.append( getSDMBinary( type, format ) ); - break; - case MPT1343_ASCII: - sb.append( "ASCII " ); - sb.append( getSDMASCII( type ) ); - break; - case MPT1343_BCD: - sb.append( "BCD " ); - sb.append( getSDMBCD( type ) ); - break; - case MPT1343_TELEX: - sb.append( "TELEX " ); - sb.append( getSDMTelex( type ) ); - break; - case UNKNOWN: - default: - sb.append( "UNKNOWN " ); - break; - } - - break; - case SAMO: - sb.append( "SAMO " ); - break; - case SAMIS: - sb.append( "SAMIS " ); - break; - case SAMIU: - sb.append( "SAMIU " ); - break; - default: - break; - - } - - return sb.toString(); - } - - public String getSDMBinary( MPTMessageType type, SDMFormat format ) - { - StringBuilder sb = new StringBuilder(); - - switch( type ) - { - case HEAD_PLUS1: - if( format == SDMFormat.MPT1327 ) - { - sb.append( mMessage.getHex( B2_SDM_STF0_START, - B2_SDM_STF0_END, - 12 ) ); - } - else - { - sb.append( mMessage.getHex( B2_SDM_STF1_START, - B2_SDM_STF1_END, - 12 ) ); - } - break; - case HEAD_PLUS2: - if( format == SDMFormat.MPT1327 ) - { - sb.append( mMessage.getHex( B2_SDM_STF0_START, - B2_SDM_STF0_END, - 12 ) ); - sb.append( " " ); - sb.append( mMessage.getHex( B3_SDM_START, - B3_SDM_END, - 12 ) ); - } - else - { - sb.append( mMessage.getHex( B2_SDM_STF1_START, - B2_SDM_STF1_END, - 12 ) ); - sb.append( " " ); - sb.append( mMessage.getHex( B3_SDM_START, - B3_SDM_END, - 12 ) ); - } - break; - case HEAD_PLUS3: - if( format == SDMFormat.MPT1327 ) - { - sb.append( mMessage.getHex( B2_SDM_STF0_START, - B2_SDM_STF0_END, - 12 ) ); - sb.append( " " ); - sb.append( mMessage.getHex( B3_SDM_START, - B3_SDM_END, - 12 ) ); - sb.append( " " ); - sb.append( mMessage.getHex( B4_SDM_STF0_START, - B4_SDM_STF0_END, - 12 ) ); - } - else - { - sb.append( mMessage.getHex( B2_SDM_STF1_START, - B2_SDM_STF1_END, - 12 ) ); - sb.append( " " ); - sb.append( mMessage.getHex( B3_SDM_START, - B3_SDM_END, - 12 ) ); - sb.append( " " ); - sb.append( mMessage.getHex( B4_SDM_STF1_START, - B4_SDM_STF1_END, - 12 ) ); - } - break; - case HEAD_PLUS4: - if( format == SDMFormat.MPT1327 ) - { - sb.append( mMessage.getHex( B2_SDM_STF0_START, - B2_SDM_STF0_END, - 12 ) ); - sb.append( " " ); - sb.append( mMessage.getHex( B3_SDM_START, - B3_SDM_END, - 12 ) ); - sb.append( " " ); - sb.append( mMessage.getHex( B4_SDM_STF0_START, - B4_SDM_STF0_END, - 12 ) ); - sb.append( " " ); - sb.append( mMessage.getHex( B5_SDM_START, - B5_SDM_END, - 12 ) ); - } - else - { - sb.append( mMessage.getHex( B2_SDM_STF1_START, - B2_SDM_STF1_END, - 12 ) ); - sb.append( " " ); - sb.append( mMessage.getHex( B3_SDM_START, - B3_SDM_END, - 12 ) ); - sb.append( " " ); - sb.append( mMessage.getHex( B4_SDM_STF1_START, - B4_SDM_STF1_END, - 12 ) ); - sb.append( " " ); - sb.append( mMessage.getHex( B5_SDM_START, - B5_SDM_END, - 12 ) ); - } - break; - default: - break; - } - - return sb.toString(); - } + public String getSDM() + { + StringBuilder sb = new StringBuilder(); - public String getSDMASCII( MPTMessageType type ) - { - StringBuilder sb = new StringBuilder(); - - int blocks = 0; - - switch( type ) - { - case HEAD_PLUS1: - blocks = 1; - break; - case HEAD_PLUS2: - blocks = 2; - break; - case HEAD_PLUS3: - blocks = 3; - break; - case HEAD_PLUS4: - blocks = 4; - break; - default: - break; - } - - if( blocks >= 1 ) - { - sb.append( (char)mMessage.getInt( 91, 97 ) ); - sb.append( (char)mMessage.getInt( 98, 104 ) ); - sb.append( (char)mMessage.getInt( 105, 111 ) ); - sb.append( (char)mMessage.getInt( 112, 118 ) ); - sb.append( (char)mMessage.getInt( 119, 125 ) ); - } - - if( blocks >= 2 ) - { - sb.append( (char)mMessage.getInt( - new int[] { 126,127,128,129,130,131,150 } ) ); - sb.append( (char)mMessage.getInt( 151, 157 ) ); - sb.append( (char)mMessage.getInt( 158, 164 ) ); - sb.append( (char)mMessage.getInt( 165, 171 ) ); - sb.append( (char)mMessage.getInt( 172, 178 ) ); - sb.append( (char)mMessage.getInt( 179, 185 ) ); - sb.append( (char)mMessage.getInt( 186, 192 ) ); - } - - if( blocks >= 3 ) - { - sb.append( (char)mMessage.getInt( - new int[] { 193,194,195,218,219,220,221 } ) ); - sb.append( (char)mMessage.getInt( 222, 228 ) ); - sb.append( (char)mMessage.getInt( 229, 235 ) ); - sb.append( (char)mMessage.getInt( 236, 242 ) ); - sb.append( (char)mMessage.getInt( 243, 249 ) ); - sb.append( (char)mMessage.getInt( 250, 256 ) ); - } + sb.append("SDM:"); - if( blocks == 4 ) - { - sb.append( (char)mMessage.getInt( - new int[] { 257,258,259,278,279,280,281 } ) ); - sb.append( (char)mMessage.getInt( 282, 288 ) ); - sb.append( (char)mMessage.getInt( 289, 295 ) ); - sb.append( (char)mMessage.getInt( 296, 302 ) ); - sb.append( (char)mMessage.getInt( 303, 309 ) ); - sb.append( (char)mMessage.getInt( 310, 316 ) ); - sb.append( (char)mMessage.getInt( 317, 323 ) ); - } + MPTMessageType type = getMessageType(); - return sb.toString(); - } + switch(type) + { + case HEAD_PLUS1: + case HEAD_PLUS2: + case HEAD_PLUS3: + case HEAD_PLUS4: + SDMFormat format = getSDMFormat(); - public String getSDMBCD( MPTMessageType type ) - { - StringBuilder sb = new StringBuilder(); - - int blocks = 0; - - switch( type ) - { - case HEAD_PLUS1: - blocks = 1; - break; - case HEAD_PLUS2: - blocks = 2; - break; - case HEAD_PLUS3: - blocks = 3; - break; - case HEAD_PLUS4: - blocks = 4; - break; - default: - break; - } - - if( blocks >= 1 ) - { - sb.append( getBCD( mMessage.getInt( 90, 93 ) ) ); - sb.append( getBCD( mMessage.getInt( 94, 97 ) ) ); - sb.append( getBCD( mMessage.getInt( 98, 101 ) ) ); - sb.append( getBCD( mMessage.getInt( 102, 105 ) ) ); - sb.append( getBCD( mMessage.getInt( 106, 109 ) ) ); - sb.append( getBCD( mMessage.getInt( 110, 113 ) ) ); - sb.append( getBCD( mMessage.getInt( 114, 117 ) ) ); - sb.append( getBCD( mMessage.getInt( 118, 121 ) ) ); - sb.append( getBCD( mMessage.getInt( 122, 125 ) ) ); - sb.append( getBCD( mMessage.getInt( 126, 129 ) ) ); - } - - if( blocks >= 2 ) - { - sb.append( getBCD( mMessage.getInt( - new int[] { 130,131,150,151 } ) ) ); - sb.append( getBCD( mMessage.getInt( 152, 155 ) ) ); - sb.append( getBCD( mMessage.getInt( 156, 159 ) ) ); - sb.append( getBCD( mMessage.getInt( 160, 163 ) ) ); - sb.append( getBCD( mMessage.getInt( 164, 167 ) ) ); - sb.append( getBCD( mMessage.getInt( 168, 171 ) ) ); - sb.append( getBCD( mMessage.getInt( 172, 175 ) ) ); - sb.append( getBCD( mMessage.getInt( 176, 179 ) ) ); - sb.append( getBCD( mMessage.getInt( 180, 183 ) ) ); - sb.append( getBCD( mMessage.getInt( 184, 187 ) ) ); - sb.append( getBCD( mMessage.getInt( 188, 191 ) ) ); - sb.append( getBCD( mMessage.getInt( 192, 195 ) ) ); - } - - if( blocks >= 3 ) - { - sb.append( getBCD( mMessage.getInt( 218, 221 ) ) ); - sb.append( getBCD( mMessage.getInt( 222, 225 ) ) ); - sb.append( getBCD( mMessage.getInt( 226, 229 ) ) ); - sb.append( getBCD( mMessage.getInt( 230, 233 ) ) ); - sb.append( getBCD( mMessage.getInt( 234, 237 ) ) ); - sb.append( getBCD( mMessage.getInt( 238, 241 ) ) ); - sb.append( getBCD( mMessage.getInt( 242, 245 ) ) ); - sb.append( getBCD( mMessage.getInt( 246, 249 ) ) ); - sb.append( getBCD( mMessage.getInt( 250, 253 ) ) ); - sb.append( getBCD( mMessage.getInt( 254, 257 ) ) ); - } + switch(format) + { + case MPT1327: + sb.append("MPT1327 "); + sb.append(getSDMBinary(type, format)); + break; + case MPT1343_BINARY: + sb.append("BINARY "); + sb.append(getSDMBinary(type, format)); + break; + case MPT1343_COMMAND: + sb.append("COMMAND "); + sb.append(getSDMBinary(type, format)); + break; + case MPT1343_MAP27: + sb.append("MAP27 "); + sb.append(getSDMBinary(type, format)); + break; + case MPT1343_RESERVED: + sb.append("RESERVED "); + sb.append(getSDMBinary(type, format)); + break; + case MPT1343_SPARE: + sb.append("SPARE "); + sb.append(getSDMBinary(type, format)); + break; + case MPT1343_ASCII: + sb.append("ASCII "); + sb.append(getSDMASCII(type)); + break; + case MPT1343_BCD: + sb.append("BCD "); + sb.append(getSDMBCD(type)); + break; + case MPT1343_TELEX: + sb.append("TELEX "); + sb.append(getSDMTelex(type)); + break; + case UNKNOWN: + default: + sb.append("UNKNOWN "); + break; + } - if( blocks == 4 ) - { - sb.append( getBCD( mMessage.getInt( - new int[] { 258,259,278,279 } ) ) ); - sb.append( getBCD( mMessage.getInt( 280, 283 ) ) ); - sb.append( getBCD( mMessage.getInt( 284, 287 ) ) ); - sb.append( getBCD( mMessage.getInt( 288, 291 ) ) ); - sb.append( getBCD( mMessage.getInt( 292, 295 ) ) ); - sb.append( getBCD( mMessage.getInt( 296, 299 ) ) ); - sb.append( getBCD( mMessage.getInt( 300, 303 ) ) ); - sb.append( getBCD( mMessage.getInt( 304, 307 ) ) ); - sb.append( getBCD( mMessage.getInt( 308, 311 ) ) ); - sb.append( getBCD( mMessage.getInt( 312, 315 ) ) ); - sb.append( getBCD( mMessage.getInt( 316, 319 ) ) ); - sb.append( getBCD( mMessage.getInt( 320, 232 ) ) ); - } + break; + case SAMO: + sb.append("SAMO "); + break; + case SAMIS: + sb.append("SAMIS "); + break; + case SAMIU: + sb.append("SAMIU "); + break; + default: + break; - return sb.toString(); - } - - private String getBCD( int value ) - { - if( 0 <= value && value <= 12 ) - { - return BCD[ value ]; - } - else - { - return "?"; - } - } + } - public String getSDMTelex( MPTMessageType type ) - { - ArrayList values = new ArrayList(); - - int blocks = 0; - - switch( type ) - { - case HEAD_PLUS1: - blocks = 1; - break; - case HEAD_PLUS2: - blocks = 2; - break; - case HEAD_PLUS3: - blocks = 3; - break; - case HEAD_PLUS4: - blocks = 4; - break; - default: - break; - } - - if( blocks >= 1 ) - { - values.add( mMessage.getInt( 91, 95 ) ); - values.add( mMessage.getInt( 96, 100 ) ); - values.add( mMessage.getInt( 101, 105 ) ); - values.add( mMessage.getInt( 106, 110 ) ); - values.add( mMessage.getInt( 111, 115 ) ); - values.add( mMessage.getInt( 116, 120 ) ); - values.add( mMessage.getInt( 121, 125 ) ); - values.add( mMessage.getInt( 126, 130 ) ); - } - - if( blocks >= 2 ) - { - values.add( mMessage.getInt( - new int[] { 131,150,151,152,153 } ) ); - values.add( mMessage.getInt( 154, 158 ) ); - values.add( mMessage.getInt( 159, 163 ) ); - values.add( mMessage.getInt( 164, 168 ) ); - values.add( mMessage.getInt( 169, 173 ) ); - values.add( mMessage.getInt( 174, 178 ) ); - values.add( mMessage.getInt( 179, 183 ) ); - values.add( mMessage.getInt( 184, 188 ) ); - values.add( mMessage.getInt( 189, 193 ) ); - } - - if( blocks >= 3 ) - { - values.add( mMessage.getInt( - new int[] { 194,195,218,219,220 } ) ); - values.add( mMessage.getInt( 221, 225 ) ); - values.add( mMessage.getInt( 226, 230 ) ); - values.add( mMessage.getInt( 231, 235 ) ); - values.add( mMessage.getInt( 236, 240 ) ); - values.add( mMessage.getInt( 241, 245 ) ); - values.add( mMessage.getInt( 246, 250 ) ); - } + return sb.toString(); + } - if( blocks == 4 ) - { - values.add( mMessage.getInt( - new int[] { 251,252,253,254,278 } ) ); - values.add( mMessage.getInt( 279, 283 ) ); - values.add( mMessage.getInt( 284, 288 ) ); - values.add( mMessage.getInt( 289, 293 ) ); - values.add( mMessage.getInt( 294, 298 ) ); - values.add( mMessage.getInt( 299, 303 ) ); - values.add( mMessage.getInt( 304, 308 ) ); - values.add( mMessage.getInt( 309, 313 ) ); - values.add( mMessage.getInt( 314, 318 ) ); - values.add( mMessage.getInt( 319, 323 ) ); - } + public String getSDMBinary(MPTMessageType type, SDMFormat format) + { + StringBuilder sb = new StringBuilder(); - StringBuilder sb = new StringBuilder(); + switch(type) + { + case HEAD_PLUS1: + if(format == SDMFormat.MPT1327) + { + sb.append(mMessage.getHex(B2_SDM_STF0_START, + B2_SDM_STF0_END, + 12)); + } + else + { + sb.append(mMessage.getHex(B2_SDM_STF1_START, + B2_SDM_STF1_END, + 12)); + } + break; + case HEAD_PLUS2: + if(format == SDMFormat.MPT1327) + { + sb.append(mMessage.getHex(B2_SDM_STF0_START, + B2_SDM_STF0_END, + 12)); + sb.append(" "); + sb.append(mMessage.getHex(B3_SDM_START, + B3_SDM_END, + 12)); + } + else + { + sb.append(mMessage.getHex(B2_SDM_STF1_START, + B2_SDM_STF1_END, + 12)); + sb.append(" "); + sb.append(mMessage.getHex(B3_SDM_START, + B3_SDM_END, + 12)); + } + break; + case HEAD_PLUS3: + if(format == SDMFormat.MPT1327) + { + sb.append(mMessage.getHex(B2_SDM_STF0_START, + B2_SDM_STF0_END, + 12)); + sb.append(" "); + sb.append(mMessage.getHex(B3_SDM_START, + B3_SDM_END, + 12)); + sb.append(" "); + sb.append(mMessage.getHex(B4_SDM_STF0_START, + B4_SDM_STF0_END, + 12)); + } + else + { + sb.append(mMessage.getHex(B2_SDM_STF1_START, + B2_SDM_STF1_END, + 12)); + sb.append(" "); + sb.append(mMessage.getHex(B3_SDM_START, + B3_SDM_END, + 12)); + sb.append(" "); + sb.append(mMessage.getHex(B4_SDM_STF1_START, + B4_SDM_STF1_END, + 12)); + } + break; + case HEAD_PLUS4: + if(format == SDMFormat.MPT1327) + { + sb.append(mMessage.getHex(B2_SDM_STF0_START, + B2_SDM_STF0_END, + 12)); + sb.append(" "); + sb.append(mMessage.getHex(B3_SDM_START, + B3_SDM_END, + 12)); + sb.append(" "); + sb.append(mMessage.getHex(B4_SDM_STF0_START, + B4_SDM_STF0_END, + 12)); + sb.append(" "); + sb.append(mMessage.getHex(B5_SDM_START, + B5_SDM_END, + 12)); + } + else + { + sb.append(mMessage.getHex(B2_SDM_STF1_START, + B2_SDM_STF1_END, + 12)); + sb.append(" "); + sb.append(mMessage.getHex(B3_SDM_START, + B3_SDM_END, + 12)); + sb.append(" "); + sb.append(mMessage.getHex(B4_SDM_STF1_START, + B4_SDM_STF1_END, + 12)); + sb.append(" "); + sb.append(mMessage.getHex(B5_SDM_START, + B5_SDM_END, + 12)); + } + break; + default: + break; + } - /* Decode the numeric values. 28 and 29 are used to shift the decoder - * to use the figures alphabet or the letters alphabet */ - boolean figure = false; - - for( Integer value: values ) - { - if( 0 <= value && value < 32 ) - { - if( value == 28 ) - { - figure = false; - } - else if( value == 29 ) - { - figure = true; - } - else - { - if( figure ) - { - sb.append( TELEX_FIGURES[ value ] ); - } - else - { - sb.append( TELEX_LETTERS[ value ] ); - } - } - } - } - - return sb.toString(); - } - - /** - * Provides a listing of aliases contained in the message. - */ - public List getAliases() - { - List aliases = new ArrayList(); - - Alias from = getFromIDAlias(); - - if( from != null ) - { - aliases.add( from ); - } + return sb.toString(); + } - Alias to = getToIDAlias(); - - if( to != null ) - { - aliases.add( to ); - } - - return aliases; - } - - - /** - * SDM Message format ICDs - */ - public enum SDMFormat - { - MPT1327, - MPT1343_BINARY, - MPT1343_BCD, - MPT1343_TELEX, - MPT1343_ASCII, - MPT1343_RESERVED, - MPT1343_SPARE, - MPT1343_COMMAND, - MPT1343_MAP27, - UNKNOWN; - - public static SDMFormat valueOf( boolean stf, int gfi ) - { - if( stf ) //MPT1343 - { - switch( gfi ) - { - case 0: - return MPT1343_BINARY; - case 1: - return MPT1343_BCD; - case 2: - return MPT1343_TELEX; - case 3: - return MPT1343_ASCII; - case 4: - return MPT1343_RESERVED; - case 5: - return MPT1343_SPARE; - case 6: - return MPT1343_COMMAND; - case 7: - return MPT1343_MAP27; - default: - return UNKNOWN; - } - } - else - { - return MPT1327; - } - } - } - - - public enum IdentType - { - ALLI( "SYSTEM-WIDE" ), - DIVERTI( "DIVERT" ), - DNI( "DATA NETWORK GATEWAY" ), - DUMMYI( "DUMMY IDENT" ), - INCI( "INCLUDE IN CALL" ), - IPFIXI( "INTER-PREFIX" ), - PABXI( "PABX GATEWAY" ), - PSTNGI( "PSTN GATEWAY" ), - PSTNSI1( "PSTN OR NETWORK 1" ), - PSTNSI2( "PSTN OR NETWORK 2" ), - PSTNSI3( "PSTN OR NETWORK 3" ), - PSTNSI4( "PSTN OR NETWORK 4" ), - PSTNSI5( "PSTN OR NETWORK 5" ), - PSTNSI6( "PSTN OR NETWORK 6" ), - PSTNSI7( "PSTN OR NETWORK 7" ), - PSTNSI8( "PSTN OR NETWORK 8" ), - PSTNSI9( "PSTN OR NETWORK 9" ), - PSTNSI10( "PSTN OR NETWORK 10" ), - PSTNSI11( "PSTN OR NETWORK 11" ), - PSTNSI12( "PSTN OR NETWORK 12" ), - PSTNSI13( "PSTN OR NETWORK 13" ), - PSTNSI14( "PSTN OR NETWORK 14" ), - PSTNSI15( "PSTN OR NETWORK 15" ), - REGI( "REGISTRATION" ), - RESERVED( "RESERVED" ), - SDMI( "SHORT DATA MESSAGE" ), - SPARE( "SPARE" ), - TSCI( "SYSTEM CONTROLLER" ), - USER( "COMMON-PREFIX IDENT" ), - UNKNOWN( "UNKNOWN" ); - - private String mLabel; - - private IdentType( String label ) - { - mLabel = label; - } - - public String getLabel() - { - return mLabel; - } - - public static IdentType fromIdent( int ident ) - { - switch( ident ) - { - case 0: - return DUMMYI; - case 8101: - return PSTNGI; - case 8102: - return PABXI; - case 8103: - return DNI; - case 8121: - return PSTNSI1; - case 8122: - return PSTNSI2; - case 8123: - return PSTNSI3; - case 8124: - return PSTNSI4; - case 8125: - return PSTNSI5; - case 8126: - return PSTNSI6; - case 8127: - return PSTNSI7; - case 8128: - return PSTNSI8; - case 8129: - return PSTNSI9; - case 8130: - return PSTNSI10; - case 8131: - return PSTNSI11; - case 8132: - return PSTNSI12; - case 8133: - return PSTNSI13; - case 8134: - return PSTNSI14; - case 8135: - return PSTNSI15; - case 8181: - case 8182: - case 8183: - case 8184: - return RESERVED; - case 8185: - return REGI; - case 8186: - return INCI; - case 8187: - return DIVERTI; - case 8188: - return SDMI; - case 8189: - return IPFIXI; - case 8190: - return TSCI; - case 8191: - return ALLI; - default: - if( 1 <= ident && ident <= 8100 ) - { - return USER; - } - else if( ( 8104 <= ident && ident <= 8120 ) || - ( 8136 <= ident && ident <= 8180 ) ) - { - return SPARE; - } - else - { - return UNKNOWN; - } - } - } - } - - public enum Descriptor - { - DESC0( "EXTENDED ADDRESSING INFORMATION", - "ESN", - "SINGLE SEGMENT TRANSACTIONS (SST) ONLY" ), - DESC1( "PSTN DIALED DIGITS", "RESERVED", "N/A" ), - DESC2( "PABX EXTENSION", "RESERVED", "N/A" ), - DESC3( "N/A", "N/A", "N/A" ), - DESC4( "N/A", "N/A", "FIRST MST SEGMENT OR SINGLE SST" ), - DESC5( "N/A", "N/A", "SECOND MST SEGMENT OR SINGLE SST" ), - DESC6( "N/A", "N/A", "THIRD MST SEGMENT OR SINGLE SST" ), - DESC7( "RESERVED", "RESERVED", "FOURTH MST SEGMENT OR SINGLE SST" ), - UNKNOWN( "UNKNOWN", "UNKNOWN", "UNKNOWN" ); - - private String mMode1Label; - private String mMode2Label; - private String mSDMLabel; - - private Descriptor( String mode1Label, String mode2Label, String sdmLabel ) - { - mMode1Label = mode1Label; - mMode2Label = mode2Label; - mSDMLabel = sdmLabel; - } - - /* Label that applies to AHYC Mode 1 responses */ - public String getMode1Label() - { - return mMode1Label; - } + public String getSDMASCII(MPTMessageType type) + { + StringBuilder sb = new StringBuilder(); - /* Label that applies to AHYC Mode 2 responses */ - public String getMode2Label() - { - return mMode2Label; - } + int blocks = 0; - /* Label that applies to Short Data Messages (SDM) */ - public String getSDMLabel() - { - return mSDMLabel; - } - - public static Descriptor fromNumber( int number ) - { - switch( number ) - { - case 0: - return DESC0; - case 1: - return DESC1; - case 2: - return DESC2; - case 3: - return DESC3; - case 4: - return DESC4; - case 5: - return DESC5; - case 6: - return DESC6; - case 7: - return DESC7; - default: - return UNKNOWN; - } - } - } - - /** - * Indicates the number of data codeword slots appended to a HEAD message - */ - public enum Slots - { - SLOTS_0( "RESERVED" ), - SLOTS_1( "ADDRESS CODEWORD ONLY" ), - SLOTS_2( "ADDRESS CODEWORD & 1-2 DATA CODEWORDS" ), - SLOTS_3( "ADDRESS CODEWORD & 3-4 DATA CODEWORDS" ), - UNKNOWN( "UNKNOWN" ); + switch(type) + { + case HEAD_PLUS1: + blocks = 1; + break; + case HEAD_PLUS2: + blocks = 2; + break; + case HEAD_PLUS3: + blocks = 3; + break; + case HEAD_PLUS4: + blocks = 4; + break; + default: + break; + } - private String mLabel; - - private Slots( String label ) - { - mLabel = label; - } - - public String getLabel() - { - return mLabel; - } - - public static Slots fromNumber( int number ) - { - switch( number ) - { - case 0: - return SLOTS_0; - case 1: - return SLOTS_1; - case 2: - return SLOTS_2; - case 3: - return SLOTS_3; - default: - return UNKNOWN; - } - } - } - + if(blocks >= 1) + { + sb.append((char)mMessage.getInt(91, 97)); + sb.append((char)mMessage.getInt(98, 104)); + sb.append((char)mMessage.getInt(105, 111)); + sb.append((char)mMessage.getInt(112, 118)); + sb.append((char)mMessage.getInt(119, 125)); + } - public enum SystemDefinition - { - UNKNOWN( "UNKNOWN" ), - ANNOUNCE_CONTROL_CHANNEL( "ANNOUNCE CONTROL CHANNEL" ), - WITHDRAW_CONTROL_CHANNEL( "WITHDRAW CONTROL CHANNEL" ), - SPECIFY_CALL_MAINTENANCE_PARAMETERS( "CALL MAINT PARAMETERS" ), - SPECIFY_REGISTRATION_PARAMETERS( "REGISTRATION PARAMETERS" ), - BROADCAST_ADJACENT_SITE_CONTROL_CHANNEL_NUMBER( "NEIGHBOR" ), - VOTE_NOW_ADVICE( "VOTE NOW" ); + if(blocks >= 2) + { + sb.append((char)mMessage.getInt( + new int[]{126, 127, 128, 129, 130, 131, 150})); + sb.append((char)mMessage.getInt(151, 157)); + sb.append((char)mMessage.getInt(158, 164)); + sb.append((char)mMessage.getInt(165, 171)); + sb.append((char)mMessage.getInt(172, 178)); + sb.append((char)mMessage.getInt(179, 185)); + sb.append((char)mMessage.getInt(186, 192)); + } - private String mLabel; - - private SystemDefinition( String label ) - { - mLabel = label; - } - - public String getLabel() - { - return mLabel; - } - - public static SystemDefinition fromNumber( int number ) - { - switch( number ) - { - case 0: - return ANNOUNCE_CONTROL_CHANNEL; - case 1: - return WITHDRAW_CONTROL_CHANNEL; - case 2: - return SPECIFY_CALL_MAINTENANCE_PARAMETERS; - case 3: - return SPECIFY_REGISTRATION_PARAMETERS; - case 4: - return BROADCAST_ADJACENT_SITE_CONTROL_CHANNEL_NUMBER; - case 5: - return VOTE_NOW_ADVICE; - default: - return UNKNOWN; - } - } - } - - public enum MPTMessageType - { - UNKN( "Unknown" ), - GTC( "GTC - Goto Channel" ), //0 - ALH( "ALH - Aloha" ), //256 - ALHS( "ALHS - Standard Data Excluded" ), //257 - ALHD( "ALHD - Simple Calls Excluded" ), //258 - ALHE( "ALHE - Emergency Calls Only" ), //259 - ALHR( "ALHR - Emergency or Registration" ), //260 - ALHX( "ALHX - Registration Excluded" ), //261 - ALHF( "ALHF - Fallback Mode" ), //262 - //263 - ACK( "ACK - Acknowledge" ), //264 - ACKI( "ACKI - More To Follow" ), //265 - ACKQ( "ACKQ - Call Queued" ), //266 - ACKX( "ACKX - Message Rejected" ), //267 - ACKV( "ACKV - Called Unit Unavailable" ), //268 - ACKE( "ACKE - Emergency" ), //269 - ACKT( "ACKT - Try On Given Address" ), //270 - ACKB( "ACKB - Call Back/Negative Ack" ), //271 - AHOY( "AHOY - General Availability Check" ), //272 - AHYX( "AHYX - Cancel Alert/Waiting Status" ), //274 - AHYP( "AHYP - Called Unit Presence Monitoring" ),//277 - AHYQ( "AHYQ - Status Message" ), //278 - AHYC( "AHYC - Short Data Message" ), //279 - MARK( "MARK - Control Channel Marker" ), //280 - MAINT( "MAINT - Call Maintenance Message"), //281 - CLEAR( "CLEAR - Down From Allocated Channel" ), //282 - MOVE( "MOVE - To Specified Channel" ), //283 - BCAST( "BCAST - System Parameters" ), //284 - //285 - 287 - SAMO( "SAMO - Outbound Single Address" ), //288 - 303 - SAMIS( "SAMIS - Inbound Solicited Single Address" ), //288 - 295 - SAMIU( "SAMIU - Inbound Unsolicited Single Address" ), //296 - 303 - HEAD_PLUS1( "HEAD - 1 DATA CODEWORD" ), //304 - 307 - HEAD_PLUS2( "HEAD - 2 DATA CODEWORDS" ), //308 - 311 - HEAD_PLUS3( "HEAD - 3 DATA CODEWORDS" ), //312 - 315 - HEAD_PLUS4( "HEAD - 4 DATA CODEWORDS" ), //316 - 319 - GTT( "GTT - Go To Transaction" ), //320 - 335 - SACK( "SACK - Standard Data Selective Ack Header" ), //416 - 423 - DACK_DAL( "DACK - Data Ack + DAL" ), //416 - DACK_DALG( "DACK - Data Ack + DALG" ), //417 - DACK_DALN( "DACK - Data Ack + DALN" ), //418 - DACK_GO( "DACK - GO Fragment Transmit Invitation" ), //419 - DACKZ( "DACKZ - Data Ack For Expedited Data"), //420 - DACKD( "DACKD - Data Ack For Standard Data" ), //421 - DAHY( "DAHY - Standard Data Ahoy" ), //424 - DRQG( "DRQG - Repeat Group Message" ), //426 - DRQZ( "DRQZ - Request Containing Expedited Data" ), //428 - DAHYZ( "DAHYZ - Expedited Data Ahoy" ), //428 - DAHYX( "DAHYX - Standard Data For Closing TRANS" ),//430 - DRQX( "DRQX - Request To Close A Transaction" ), //430 - RLA( "RLA - Repeat Last ACK" ), //431 - SITH( "SITH - Standard Data Address Codeword Data Item" ); //440 - 443 - - private String mDescription; - - private MPTMessageType( String description ) - { - mDescription = description; - } - - public String getDescription() - { - return mDescription; - } - - public String toString() - { - return getDescription(); - } + if(blocks >= 3) + { + sb.append((char)mMessage.getInt( + new int[]{193, 194, 195, 218, 219, 220, 221})); + sb.append((char)mMessage.getInt(222, 228)); + sb.append((char)mMessage.getInt(229, 235)); + sb.append((char)mMessage.getInt(236, 242)); + sb.append((char)mMessage.getInt(243, 249)); + sb.append((char)mMessage.getInt(250, 256)); + } - public static MPTMessageType fromNumber( int number ) - { - if( number < 256 ) - { - return GTC; - } - - switch( number ) - { - case 256: - return ALH; - case 257: - return ALHS; - case 258: - return ALHD; - case 259: - return ALHE; - case 260: - return ALHR; - case 261: - return ALHX; - case 262: - return ALHF; - case 264: - return ACK; - case 265: - return ACKI; - case 266: - return ACKQ; - case 267: - return ACKX; - case 268: - return ACKV; - case 269: - return ACKE; - case 270: - return ACKT; - case 271: - return ACKB; - case 272: - return AHOY; - case 274: - return AHYX; - case 277: - return AHYP; - case 278: - return AHYQ; - case 279: - return AHYC; - case 280: - return MARK; - case 281: - return MAINT; - case 282: - return CLEAR; - case 283: - return MOVE; - case 284: - return BCAST; - case 288: - case 289: - case 290: - case 291: - case 292: - case 293: - case 294: - case 295: - case 296: - case 297: - case 298: - case 299: - case 300: - case 301: - case 302: - case 303: - return SAMO; - case 304: - case 305: - case 306: - case 307: - return HEAD_PLUS1; - case 308: - case 309: - case 310: - case 311: - return HEAD_PLUS2; - case 312: - case 313: - case 314: - case 315: - return HEAD_PLUS3; - case 316: - case 317: - case 318: - case 319: - return HEAD_PLUS4; - case 320: - case 321: - case 322: - case 323: - case 324: - case 325: - case 326: - case 327: - case 328: - case 329: - case 330: - case 331: - case 332: - case 333: - case 334: - case 335: - return GTT; - case 416: - return DACK_DAL; - case 417: - return DACK_DALG; - case 418: - return DACK_DALN; - case 419: - return DACK_GO; - case 420: - return DACKZ; - case 421: - return DACKD; - case 424: - return DAHY; - case 426: - return DRQG; - case 428: - return DAHYZ; - case 430: - return DAHYX; - case 440: - case 441: - case 442: - case 443: - return SITH; - default: - return UNKN; - } - } - } + if(blocks == 4) + { + sb.append((char)mMessage.getInt( + new int[]{257, 258, 259, 278, 279, 280, 281})); + sb.append((char)mMessage.getInt(282, 288)); + sb.append((char)mMessage.getInt(289, 295)); + sb.append((char)mMessage.getInt(296, 302)); + sb.append((char)mMessage.getInt(303, 309)); + sb.append((char)mMessage.getInt(310, 316)); + sb.append((char)mMessage.getInt(317, 323)); + } + + return sb.toString(); + } + + public String getSDMBCD(MPTMessageType type) + { + StringBuilder sb = new StringBuilder(); + + int blocks = 0; + + switch(type) + { + case HEAD_PLUS1: + blocks = 1; + break; + case HEAD_PLUS2: + blocks = 2; + break; + case HEAD_PLUS3: + blocks = 3; + break; + case HEAD_PLUS4: + blocks = 4; + break; + default: + break; + } + + if(blocks >= 1) + { + sb.append(getBCD(mMessage.getInt(90, 93))); + sb.append(getBCD(mMessage.getInt(94, 97))); + sb.append(getBCD(mMessage.getInt(98, 101))); + sb.append(getBCD(mMessage.getInt(102, 105))); + sb.append(getBCD(mMessage.getInt(106, 109))); + sb.append(getBCD(mMessage.getInt(110, 113))); + sb.append(getBCD(mMessage.getInt(114, 117))); + sb.append(getBCD(mMessage.getInt(118, 121))); + sb.append(getBCD(mMessage.getInt(122, 125))); + sb.append(getBCD(mMessage.getInt(126, 129))); + } + + if(blocks >= 2) + { + sb.append(getBCD(mMessage.getInt( + new int[]{130, 131, 150, 151}))); + sb.append(getBCD(mMessage.getInt(152, 155))); + sb.append(getBCD(mMessage.getInt(156, 159))); + sb.append(getBCD(mMessage.getInt(160, 163))); + sb.append(getBCD(mMessage.getInt(164, 167))); + sb.append(getBCD(mMessage.getInt(168, 171))); + sb.append(getBCD(mMessage.getInt(172, 175))); + sb.append(getBCD(mMessage.getInt(176, 179))); + sb.append(getBCD(mMessage.getInt(180, 183))); + sb.append(getBCD(mMessage.getInt(184, 187))); + sb.append(getBCD(mMessage.getInt(188, 191))); + sb.append(getBCD(mMessage.getInt(192, 195))); + } + + if(blocks >= 3) + { + sb.append(getBCD(mMessage.getInt(218, 221))); + sb.append(getBCD(mMessage.getInt(222, 225))); + sb.append(getBCD(mMessage.getInt(226, 229))); + sb.append(getBCD(mMessage.getInt(230, 233))); + sb.append(getBCD(mMessage.getInt(234, 237))); + sb.append(getBCD(mMessage.getInt(238, 241))); + sb.append(getBCD(mMessage.getInt(242, 245))); + sb.append(getBCD(mMessage.getInt(246, 249))); + sb.append(getBCD(mMessage.getInt(250, 253))); + sb.append(getBCD(mMessage.getInt(254, 257))); + } + + if(blocks == 4) + { + sb.append(getBCD(mMessage.getInt( + new int[]{258, 259, 278, 279}))); + sb.append(getBCD(mMessage.getInt(280, 283))); + sb.append(getBCD(mMessage.getInt(284, 287))); + sb.append(getBCD(mMessage.getInt(288, 291))); + sb.append(getBCD(mMessage.getInt(292, 295))); + sb.append(getBCD(mMessage.getInt(296, 299))); + sb.append(getBCD(mMessage.getInt(300, 303))); + sb.append(getBCD(mMessage.getInt(304, 307))); + sb.append(getBCD(mMessage.getInt(308, 311))); + sb.append(getBCD(mMessage.getInt(312, 315))); + sb.append(getBCD(mMessage.getInt(316, 319))); + sb.append(getBCD(mMessage.getInt(320, 232))); + } + + return sb.toString(); + } + + private String getBCD(int value) + { + if(0 <= value && value <= 12) + { + return BCD[value]; + } + else + { + return "?"; + } + } + + public String getSDMTelex(MPTMessageType type) + { + ArrayList values = new ArrayList(); + + int blocks = 0; + + switch(type) + { + case HEAD_PLUS1: + blocks = 1; + break; + case HEAD_PLUS2: + blocks = 2; + break; + case HEAD_PLUS3: + blocks = 3; + break; + case HEAD_PLUS4: + blocks = 4; + break; + default: + break; + } + + if(blocks >= 1) + { + values.add(mMessage.getInt(91, 95)); + values.add(mMessage.getInt(96, 100)); + values.add(mMessage.getInt(101, 105)); + values.add(mMessage.getInt(106, 110)); + values.add(mMessage.getInt(111, 115)); + values.add(mMessage.getInt(116, 120)); + values.add(mMessage.getInt(121, 125)); + values.add(mMessage.getInt(126, 130)); + } + + if(blocks >= 2) + { + values.add(mMessage.getInt( + new int[]{131, 150, 151, 152, 153})); + values.add(mMessage.getInt(154, 158)); + values.add(mMessage.getInt(159, 163)); + values.add(mMessage.getInt(164, 168)); + values.add(mMessage.getInt(169, 173)); + values.add(mMessage.getInt(174, 178)); + values.add(mMessage.getInt(179, 183)); + values.add(mMessage.getInt(184, 188)); + values.add(mMessage.getInt(189, 193)); + } + + if(blocks >= 3) + { + values.add(mMessage.getInt( + new int[]{194, 195, 218, 219, 220})); + values.add(mMessage.getInt(221, 225)); + values.add(mMessage.getInt(226, 230)); + values.add(mMessage.getInt(231, 235)); + values.add(mMessage.getInt(236, 240)); + values.add(mMessage.getInt(241, 245)); + values.add(mMessage.getInt(246, 250)); + } + + if(blocks == 4) + { + values.add(mMessage.getInt( + new int[]{251, 252, 253, 254, 278})); + values.add(mMessage.getInt(279, 283)); + values.add(mMessage.getInt(284, 288)); + values.add(mMessage.getInt(289, 293)); + values.add(mMessage.getInt(294, 298)); + values.add(mMessage.getInt(299, 303)); + values.add(mMessage.getInt(304, 308)); + values.add(mMessage.getInt(309, 313)); + values.add(mMessage.getInt(314, 318)); + values.add(mMessage.getInt(319, 323)); + } + + StringBuilder sb = new StringBuilder(); + + /* Decode the numeric values. 28 and 29 are used to shift the decoder + * to use the figures alphabet or the letters alphabet */ + boolean figure = false; + + for(Integer value : values) + { + if(0 <= value && value < 32) + { + if(value == 28) + { + figure = false; + } + else if(value == 29) + { + figure = true; + } + else + { + if(figure) + { + sb.append(TELEX_FIGURES[value]); + } + else + { + sb.append(TELEX_LETTERS[value]); + } + } + } + } + + return sb.toString(); + } + + /** + * SDM Message format ICDs + */ + public enum SDMFormat + { + MPT1327, + MPT1343_BINARY, + MPT1343_BCD, + MPT1343_TELEX, + MPT1343_ASCII, + MPT1343_RESERVED, + MPT1343_SPARE, + MPT1343_COMMAND, + MPT1343_MAP27, + UNKNOWN; + + public static SDMFormat valueOf(boolean stf, int gfi) + { + if(stf) //MPT1343 + { + switch(gfi) + { + case 0: + return MPT1343_BINARY; + case 1: + return MPT1343_BCD; + case 2: + return MPT1343_TELEX; + case 3: + return MPT1343_ASCII; + case 4: + return MPT1343_RESERVED; + case 5: + return MPT1343_SPARE; + case 6: + return MPT1343_COMMAND; + case 7: + return MPT1343_MAP27; + default: + return UNKNOWN; + } + } + else + { + return MPT1327; + } + } + } + + + public enum IdentType + { + ALLI("SYSTEM-WIDE"), + DIVERTI("DIVERT"), + DNI("DATA NETWORK GATEWAY"), + DUMMYI("DUMMY IDENT"), + INCI("INCLUDE IN CALL"), + IPFIXI("INTER-PREFIX"), + PABXI("PABX GATEWAY"), + PSTNGI("PSTN GATEWAY"), + PSTNSI1("PSTN OR NETWORK 1"), + PSTNSI2("PSTN OR NETWORK 2"), + PSTNSI3("PSTN OR NETWORK 3"), + PSTNSI4("PSTN OR NETWORK 4"), + PSTNSI5("PSTN OR NETWORK 5"), + PSTNSI6("PSTN OR NETWORK 6"), + PSTNSI7("PSTN OR NETWORK 7"), + PSTNSI8("PSTN OR NETWORK 8"), + PSTNSI9("PSTN OR NETWORK 9"), + PSTNSI10("PSTN OR NETWORK 10"), + PSTNSI11("PSTN OR NETWORK 11"), + PSTNSI12("PSTN OR NETWORK 12"), + PSTNSI13("PSTN OR NETWORK 13"), + PSTNSI14("PSTN OR NETWORK 14"), + PSTNSI15("PSTN OR NETWORK 15"), + REGI("REGISTRATION"), + RESERVED("RESERVED"), + SDMI("SHORT DATA MESSAGE"), + SPARE("SPARE"), + TSCI("SYSTEM CONTROLLER"), + USER("COMMON-PREFIX IDENT"), + UNKNOWN("UNKNOWN"); + + private String mLabel; + + IdentType(String label) + { + mLabel = label; + } + + public String getLabel() + { + return mLabel; + } + + public static IdentType fromIdent(int ident) + { + switch(ident) + { + case 0: + return DUMMYI; + case 8101: + return PSTNGI; + case 8102: + return PABXI; + case 8103: + return DNI; + case 8121: + return PSTNSI1; + case 8122: + return PSTNSI2; + case 8123: + return PSTNSI3; + case 8124: + return PSTNSI4; + case 8125: + return PSTNSI5; + case 8126: + return PSTNSI6; + case 8127: + return PSTNSI7; + case 8128: + return PSTNSI8; + case 8129: + return PSTNSI9; + case 8130: + return PSTNSI10; + case 8131: + return PSTNSI11; + case 8132: + return PSTNSI12; + case 8133: + return PSTNSI13; + case 8134: + return PSTNSI14; + case 8135: + return PSTNSI15; + case 8181: + case 8182: + case 8183: + case 8184: + return RESERVED; + case 8185: + return REGI; + case 8186: + return INCI; + case 8187: + return DIVERTI; + case 8188: + return SDMI; + case 8189: + return IPFIXI; + case 8190: + return TSCI; + case 8191: + return ALLI; + default: + if(1 <= ident && ident <= 8100) + { + return USER; + } + else if((8104 <= ident && ident <= 8120) || + (8136 <= ident && ident <= 8180)) + { + return SPARE; + } + else + { + return UNKNOWN; + } + } + } + } + + public enum Descriptor + { + DESC0("EXTENDED ADDRESSING INFORMATION", + "ESN", + "SINGLE SEGMENT TRANSACTIONS (SST) ONLY"), + DESC1("PSTN DIALED DIGITS", "RESERVED", "N/A"), + DESC2("PABX EXTENSION", "RESERVED", "N/A"), + DESC3("N/A", "N/A", "N/A"), + DESC4("N/A", "N/A", "FIRST MST SEGMENT OR SINGLE SST"), + DESC5("N/A", "N/A", "SECOND MST SEGMENT OR SINGLE SST"), + DESC6("N/A", "N/A", "THIRD MST SEGMENT OR SINGLE SST"), + DESC7("RESERVED", "RESERVED", "FOURTH MST SEGMENT OR SINGLE SST"), + UNKNOWN("UNKNOWN", "UNKNOWN", "UNKNOWN"); + + private String mMode1Label; + private String mMode2Label; + private String mSDMLabel; + + Descriptor(String mode1Label, String mode2Label, String sdmLabel) + { + mMode1Label = mode1Label; + mMode2Label = mode2Label; + mSDMLabel = sdmLabel; + } + + /* Label that applies to AHYC Mode 1 responses */ + public String getMode1Label() + { + return mMode1Label; + } + + /* Label that applies to AHYC Mode 2 responses */ + public String getMode2Label() + { + return mMode2Label; + } + + /* Label that applies to Short Data Messages (SDM) */ + public String getSDMLabel() + { + return mSDMLabel; + } + + public static Descriptor fromNumber(int number) + { + switch(number) + { + case 0: + return DESC0; + case 1: + return DESC1; + case 2: + return DESC2; + case 3: + return DESC3; + case 4: + return DESC4; + case 5: + return DESC5; + case 6: + return DESC6; + case 7: + return DESC7; + default: + return UNKNOWN; + } + } + } + + /** + * Indicates the number of data codeword slots appended to a HEAD message + */ + public enum Slots + { + SLOTS_0("RESERVED"), + SLOTS_1("ADDRESS CODEWORD ONLY"), + SLOTS_2("ADDRESS CODEWORD & 1-2 DATA CODEWORDS"), + SLOTS_3("ADDRESS CODEWORD & 3-4 DATA CODEWORDS"), + UNKNOWN("UNKNOWN"); + + private String mLabel; + + Slots(String label) + { + mLabel = label; + } + + public String getLabel() + { + return mLabel; + } + + public static Slots fromNumber(int number) + { + switch(number) + { + case 0: + return SLOTS_0; + case 1: + return SLOTS_1; + case 2: + return SLOTS_2; + case 3: + return SLOTS_3; + default: + return UNKNOWN; + } + } + } + + + public enum SystemDefinition + { + UNKNOWN("UNKNOWN"), + ANNOUNCE_CONTROL_CHANNEL("ANNOUNCE CONTROL CHANNEL"), + WITHDRAW_CONTROL_CHANNEL("WITHDRAW CONTROL CHANNEL"), + SPECIFY_CALL_MAINTENANCE_PARAMETERS("CALL MAINT PARAMETERS"), + SPECIFY_REGISTRATION_PARAMETERS("REGISTRATION PARAMETERS"), + BROADCAST_ADJACENT_SITE_CONTROL_CHANNEL_NUMBER("NEIGHBOR"), + VOTE_NOW_ADVICE("VOTE NOW"); + + private String mLabel; + + SystemDefinition(String label) + { + mLabel = label; + } + + public String getLabel() + { + return mLabel; + } + + public static SystemDefinition fromNumber(int number) + { + switch(number) + { + case 0: + return ANNOUNCE_CONTROL_CHANNEL; + case 1: + return WITHDRAW_CONTROL_CHANNEL; + case 2: + return SPECIFY_CALL_MAINTENANCE_PARAMETERS; + case 3: + return SPECIFY_REGISTRATION_PARAMETERS; + case 4: + return BROADCAST_ADJACENT_SITE_CONTROL_CHANNEL_NUMBER; + case 5: + return VOTE_NOW_ADVICE; + default: + return UNKNOWN; + } + } + } + + public enum MPTMessageType + { + UNKN("Unknown"), + GTC("GTC - Goto Channel"), //0 + ALH("ALH - Aloha"), //256 + ALHS("ALHS - Standard Data Excluded"), //257 + ALHD("ALHD - Simple Calls Excluded"), //258 + ALHE("ALHE - Emergency Calls Only"), //259 + ALHR("ALHR - Emergency or Registration"), //260 + ALHX("ALHX - Registration Excluded"), //261 + ALHF("ALHF - Fallback Mode"), //262 + //263 + ACK("ACK - Acknowledge"), //264 + ACKI("ACKI - More To Follow"), //265 + ACKQ("ACKQ - Call Queued"), //266 + ACKX("ACKX - Message Rejected"), //267 + ACKV("ACKV - Called Unit Unavailable"), //268 + ACKE("ACKE - Emergency"), //269 + ACKT("ACKT - Try On Given Address"), //270 + ACKB("ACKB - Call Back/Negative Ack"), //271 + AHOY("AHOY - General Availability Check"), //272 + AHYX("AHYX - Cancel Alert/Waiting Status"), //274 + AHYP("AHYP - Called Unit Presence Monitoring"),//277 + AHYQ("AHYQ - Status Message"), //278 + AHYC("AHYC - Short Data Message"), //279 + MARK("MARK - Control Channel Marker"), //280 + MAINT("MAINT - Call Maintenance Message"), //281 + CLEAR("CLEAR - Down From Allocated Channel"), //282 + MOVE("MOVE - To Specified Channel"), //283 + BCAST("BCAST - System Parameters"), //284 + //285 - 287 + SAMO("SAMO - Outbound Single Address"), //288 - 303 + SAMIS("SAMIS - Inbound Solicited Single Address"), //288 - 295 + SAMIU("SAMIU - Inbound Unsolicited Single Address"), //296 - 303 + HEAD_PLUS1("HEAD - 1 DATA CODEWORD"), //304 - 307 + HEAD_PLUS2("HEAD - 2 DATA CODEWORDS"), //308 - 311 + HEAD_PLUS3("HEAD - 3 DATA CODEWORDS"), //312 - 315 + HEAD_PLUS4("HEAD - 4 DATA CODEWORDS"), //316 - 319 + GTT("GTT - Go To Transaction"), //320 - 335 + SACK("SACK - Standard Data Selective Ack Header"), //416 - 423 + DACK_DAL("DACK - Data Ack + DAL"), //416 + DACK_DALG("DACK - Data Ack + DALG"), //417 + DACK_DALN("DACK - Data Ack + DALN"), //418 + DACK_GO("DACK - GO Fragment Transmit Invitation"), //419 + DACKZ("DACKZ - Data Ack For Expedited Data"), //420 + DACKD("DACKD - Data Ack For Standard Data"), //421 + DAHY("DAHY - Standard Data Ahoy"), //424 + DRQG("DRQG - Repeat Group Message"), //426 + DRQZ("DRQZ - Request Containing Expedited Data"), //428 + DAHYZ("DAHYZ - Expedited Data Ahoy"), //428 + DAHYX("DAHYX - Standard Data For Closing TRANS"),//430 + DRQX("DRQX - Request To Close A Transaction"), //430 + RLA("RLA - Repeat Last ACK"), //431 + SITH("SITH - Standard Data Address Codeword Data Item"); //440 - 443 + + private String mDescription; + + MPTMessageType(String description) + { + mDescription = description; + } + + public String getDescription() + { + return mDescription; + } + + public String toString() + { + return getDescription(); + } + + public static MPTMessageType fromNumber(int number) + { + if(number < 256) + { + return GTC; + } + + switch(number) + { + case 256: + return ALH; + case 257: + return ALHS; + case 258: + return ALHD; + case 259: + return ALHE; + case 260: + return ALHR; + case 261: + return ALHX; + case 262: + return ALHF; + case 264: + return ACK; + case 265: + return ACKI; + case 266: + return ACKQ; + case 267: + return ACKX; + case 268: + return ACKV; + case 269: + return ACKE; + case 270: + return ACKT; + case 271: + return ACKB; + case 272: + return AHOY; + case 274: + return AHYX; + case 277: + return AHYP; + case 278: + return AHYQ; + case 279: + return AHYC; + case 280: + return MARK; + case 281: + return MAINT; + case 282: + return CLEAR; + case 283: + return MOVE; + case 284: + return BCAST; + case 288: + case 289: + case 290: + case 291: + case 292: + case 293: + case 294: + case 295: + case 296: + case 297: + case 298: + case 299: + case 300: + case 301: + case 302: + case 303: + return SAMO; + case 304: + case 305: + case 306: + case 307: + return HEAD_PLUS1; + case 308: + case 309: + case 310: + case 311: + return HEAD_PLUS2; + case 312: + case 313: + case 314: + case 315: + return HEAD_PLUS3; + case 316: + case 317: + case 318: + case 319: + return HEAD_PLUS4; + case 320: + case 321: + case 322: + case 323: + case 324: + case 325: + case 326: + case 327: + case 328: + case 329: + case 330: + case 331: + case 332: + case 333: + case 334: + case 335: + return GTT; + case 416: + return DACK_DAL; + case 417: + return DACK_DALG; + case 418: + return DACK_DALN; + case 419: + return DACK_GO; + case 420: + return DACKZ; + case 421: + return DACKD; + case 424: + return DAHY; + case 426: + return DRQG; + case 428: + return DAHYZ; + case 430: + return DAHYX; + case 440: + case 441: + case 442: + case 443: + return SITH; + default: + return UNKN; + } + } + } + + public MPT1327Talkgroup getFromIdentifier() + { + if(mFromIdentifier == null) + { + mFromIdentifier = MPT1327Talkgroup.createFrom(getPrefix(), getIdent2()); + } + + return mFromIdentifier; + } + + public MPT1327Talkgroup getToIdentifier() + { + if(mToIdentifier == null) + { + mToIdentifier = MPT1327Talkgroup.createTo(getPrefix(), getIdent1()); + } + + return mToIdentifier; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getToIdentifier()); + mIdentifiers.add(getFromIdentifier()); + } + + return mIdentifiers; + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327MessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327MessageFilter.java index 21f0c9f4..c5f2c2c0 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327MessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327MessageFilter.java @@ -1,62 +1,80 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.mpt1327; import io.github.dsheirer.filter.Filter; import io.github.dsheirer.filter.FilterElement; -import io.github.dsheirer.message.Message; -import io.github.dsheirer.module.decode.mpt1327.MPT1327Message.MPTMessageType; +import io.github.dsheirer.message.IMessage; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; -public class MPT1327MessageFilter extends Filter +public class MPT1327MessageFilter extends Filter { - private HashMap> mFilterElements = - new HashMap>(); + private Map> mFilterElements = new HashMap<>(); - public MPT1327MessageFilter() - { - super( "MPT1327 Message Type Filter" ); - - for( MPTMessageType type: MPT1327Message.MPTMessageType.values() ) - { - if( type != MPTMessageType.UNKN ) - { - mFilterElements.put( type, - new FilterElement( type ) ); - } - } - } - - @Override - public boolean passes( Message message ) + public MPT1327MessageFilter() { - if( mEnabled && canProcess( message ) ) - { - MPT1327Message mpt = (MPT1327Message)message; + super("MPT1327 Message Type Filter"); + + for(MPT1327Message.MPTMessageType type : MPT1327Message.MPTMessageType.values()) + { + if(type != MPT1327Message.MPTMessageType.UNKN) + { + mFilterElements.put(type, new FilterElement(type)); + } + } + } + + @Override + public boolean passes(IMessage message) + { + if(mEnabled && canProcess(message)) + { + MPT1327Message mpt = (MPT1327Message) message; + + FilterElement element = + mFilterElements.get(mpt.getMessageType()); + + if(element != null) + { + return element.isEnabled(); + } + } - FilterElement element = - mFilterElements.get( mpt.getMessageType() ); - - if( element != null ) - { - return element.isEnabled(); - } - } - return false; } - @Override - public boolean canProcess( Message message ) + @Override + public boolean canProcess(IMessage message) { return message instanceof MPT1327Message; } - @Override + @Override public List> getFilterElements() { - return new ArrayList>( mFilterElements.values() ); + return new ArrayList>(mFilterElements.values()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327MessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327MessageProcessor.java index d8a145e3..67737e4e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327MessageProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327MessageProcessor.java @@ -1,65 +1,61 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.mpt1327; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.sample.Listener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class MPT1327MessageProcessor implements Listener +public class MPT1327MessageProcessor implements Listener { - private final static Logger mLog = LoggerFactory.getLogger( MPT1327MessageProcessor.class ); + private final static Logger mLog = LoggerFactory.getLogger(MPT1327MessageProcessor.class); - private Listener mMessageListener; - - private AliasList mAliasList; - - public MPT1327MessageProcessor( AliasList list ) - { - mAliasList = list; - } - - public void dispose() - { - mMessageListener = null; - } - - @Override - public void receive( BinaryMessage buffer ) + private Listener mMessageListener; + + public MPT1327MessageProcessor() { - if( mMessageListener != null ) - { - MPT1327Message message = new MPT1327Message( buffer, mAliasList ); - - mMessageListener.receive( message ); - } } - - public void setMessageListener( Listener listener ) + + public void dispose() { - mMessageListener = listener; + mMessageListener = null; + } + + @Override + public void receive(CorrectedBinaryMessage message) + { + if(mMessageListener != null) + { + mMessageListener.receive(new MPT1327Message(message)); + } + } + + public void setMessageListener(Listener listener) + { + mMessageListener = listener; } public void removeMessageListener() { - mMessageListener = null; + mMessageListener = null; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327TrafficChannelManager.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327TrafficChannelManager.java new file mode 100644 index 00000000..661ada65 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327TrafficChannelManager.java @@ -0,0 +1,390 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.mpt1327; + +import io.github.dsheirer.controller.channel.Channel; +import io.github.dsheirer.controller.channel.ChannelEvent; +import io.github.dsheirer.controller.channel.ChannelGrantEvent; +import io.github.dsheirer.controller.channel.IChannelEventListener; +import io.github.dsheirer.controller.channel.IChannelEventProvider; +import io.github.dsheirer.controller.channel.map.ChannelMap; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.module.Module; +import io.github.dsheirer.module.decode.config.DecodeConfiguration; +import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.IDecodeEvent; +import io.github.dsheirer.module.decode.event.IDecodeEventProvider; +import io.github.dsheirer.module.decode.mpt1327.channel.MPT1327Channel; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.source.config.SourceConfigTuner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class MPT1327TrafficChannelManager extends Module implements IDecodeEventProvider, IChannelEventListener, + IChannelEventProvider +{ + private final static Logger mLog = LoggerFactory.getLogger(MPT1327TrafficChannelManager.class); + + public static final String CHANNEL_START_REJECTED = "CHANNEL START REJECTED"; + public static final String MAX_TRAFFIC_CHANNELS_EXCEEDED = "MAX TRAFFIC CHANNELS EXCEEDED"; + + private Queue mAvailableTrafficChannelQueue = new ConcurrentLinkedQueue<>(); + private List mManagedTrafficChannels; + private Map mAllocatedTrafficChannelMap = new ConcurrentHashMap<>(); + private Map mChannelGrantEventMap = new ConcurrentHashMap<>(); + private TrafficChannelTeardownMonitor mTrafficChannelTeardownMonitor = new TrafficChannelTeardownMonitor(); + private Listener mChannelEventListener; + private Listener mDecodeEventListener; + private ChannelMap mChannelMap; + + /** + * Constructs an MPT1327 traffic channel manage. + * + * @param parentChannel containing configuration items for traffic channels to inherit + */ + public MPT1327TrafficChannelManager(Channel parentChannel, ChannelMap channelMap) + { + createTrafficChannels(parentChannel); + mChannelMap = channelMap; + } + + /** + * Processes channel grants to allocate traffic channels and track overall channel usage. Generates + * decode events for each new channel that is allocated. + */ + public void processChannelGrant(MPT1327Message mpt1327Message) + { + if(mpt1327Message.getMessageType() == MPT1327Message.MPTMessageType.GTC) + { + IdentifierCollection identifierCollection = new IdentifierCollection(mpt1327Message.getIdentifiers()); + MPT1327Channel mpt1327Channel = MPT1327Channel.create(mpt1327Message.getChannel()); + mpt1327Channel.setChannelMap(mChannelMap); + + MPT1327ChannelGrantEvent event = mChannelGrantEventMap.get(mpt1327Channel); + + if(event != null) + { + if(isSameTalkgroup(identifierCollection, event.getIdentifierCollection())) + { + //update the ending timestamp so that the duration value is correctly calculated + event.end(mpt1327Message.getTimestamp()); + return; + } + else if(mAllocatedTrafficChannelMap.containsKey(mpt1327Channel)) + { + Channel trafficChannel = mAllocatedTrafficChannelMap.get(mpt1327Channel); + broadcast(new ChannelEvent(trafficChannel, ChannelEvent.Event.REQUEST_DISABLE)); + } + } + + MPT1327ChannelGrantEvent channelGrantEvent = MPT1327ChannelGrantEvent.mpt1327Builder(mpt1327Message.getTimestamp()) + .channel(mpt1327Channel) + .eventDescription("Call") + .details("Traffic Channel Grant") + .identifiers(identifierCollection) + .build(); + + mChannelGrantEventMap.put(mpt1327Channel, channelGrantEvent); + + if(mpt1327Channel.getDownlinkFrequency() == 0) + { + channelGrantEvent.setDetails("Invalid Channel Map - No Frequency For Channel " + mpt1327Channel.getChannelNumber()); + } + else + { + Channel trafficChannel = mAvailableTrafficChannelQueue.poll(); + + if(trafficChannel == null) + { + channelGrantEvent.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED); + channelGrantEvent.setEventDescription("Detect:" + channelGrantEvent.getEventDescription()); + return; + } + + SourceConfigTuner sourceConfig = new SourceConfigTuner(); + sourceConfig.setFrequency(mpt1327Channel.getDownlinkFrequency()); + trafficChannel.setSourceConfiguration(sourceConfig); + mAllocatedTrafficChannelMap.put(mpt1327Channel, trafficChannel); + broadcast(new ChannelGrantEvent(trafficChannel, ChannelEvent.Event.REQUEST_ENABLE, mpt1327Channel, identifierCollection)); + } + + broadcast(channelGrantEvent); + } + } + + + /** + * Creates up to the maximum number of traffic channels for use in allocating traffic channels. + * + * @param parentChannel for inheriting naming, alias list and traffic channel pool size properties + */ + private void createTrafficChannels(Channel parentChannel) + { + DecodeConfiguration decodeConfiguration = parentChannel.getDecodeConfiguration(); + List trafficChannelList = new ArrayList<>(); + + if(decodeConfiguration instanceof DecodeConfigMPT1327) + { + DecodeConfigMPT1327 decodeConfigMPT1327 = (DecodeConfigMPT1327)decodeConfiguration; + + int maxTrafficChannels = decodeConfigMPT1327.getTrafficChannelPoolSize(); + + for(int x = 0; x < maxTrafficChannels; x++) + { + Channel trafficChannel = new Channel("TRAFFIC", Channel.ChannelType.TRAFFIC); + trafficChannel.setAliasListName(parentChannel.getAliasListName()); + trafficChannel.setSystem(parentChannel.getSystem()); + trafficChannel.setSite(parentChannel.getSite()); + trafficChannel.setDecodeConfiguration(decodeConfigMPT1327); + trafficChannel.setEventLogConfiguration(parentChannel.getEventLogConfiguration()); + trafficChannel.setRecordConfiguration(parentChannel.getRecordConfiguration()); + trafficChannelList.add(trafficChannel); + } + } + + mAvailableTrafficChannelQueue.addAll(trafficChannelList); + mManagedTrafficChannels = Collections.unmodifiableList(trafficChannelList); + } + + /** + * Broadcasts an initial or update decode event to any registered listener. + */ + public void broadcast(DecodeEvent decodeEvent) + { + if(mDecodeEventListener != null) + { + mDecodeEventListener.receive(decodeEvent); + } + } + + /** + * Channel event listener to receive notifications that a traffic channel has ended processing and we + * can reclaim the traffic channel for reuse. + * + * @return listener for processing channel events. + */ + @Override + public Listener getChannelEventListener() + { + return mTrafficChannelTeardownMonitor; + } + + /** + * Broadcasts a channel event to a registered external listener (for action). + */ + private void broadcast(ChannelEvent channelEvent) + { + if(mChannelEventListener != null) + { + mChannelEventListener.receive(channelEvent); + } + } + + /** + * Sets the external channel event listener to receive channel events from this traffic channel manager + */ + @Override + public void setChannelEventListener(Listener listener) + { + mChannelEventListener = listener; + } + + /** + * Removes the channel event listener + */ + @Override + public void removeChannelEventListener() + { + mChannelEventListener = null; + } + + @Override + public void reset() + { + + } + + @Override + public void start() + { + + } + + @Override + public void stop() + { + List channels = new ArrayList<>(mAllocatedTrafficChannelMap.values()); + + //Issue a disable request for each traffic channel + for(Channel channel : channels) + { + mLog.debug("Stopping traffic channel: " + channel); + broadcast(new ChannelEvent(channel, ChannelEvent.Event.REQUEST_DISABLE)); + } + } + + + /** + * Compares the TO role identifier(s) from each collection for equality + * + * @param collection1 containing a TO identifier + * @param collection2 containing a TO identifier + * @return true if both collections contain a TO identifier and the TO identifiers are the same value + */ + private boolean isSameTalkgroup(IdentifierCollection collection1, IdentifierCollection collection2) + { + Identifier toIdentifier1 = getToIdentifier(collection1); + Identifier toIdentifier2 = getToIdentifier(collection2); + return Objects.equals(toIdentifier1, toIdentifier2); + } + + /** + * Retrieves the first identifier with a TO role. + * + * @param collection containing a TO identifier + * @return TO identifier or null + */ + private Identifier getToIdentifier(IdentifierCollection collection) + { + List identifiers = collection.getIdentifiers(Role.TO); + + if(identifiers.size() >= 1) + { + return identifiers.get(0); + } + + return null; + } + + @Override + public void dispose() + { + for(Channel trafficChannel : mAvailableTrafficChannelQueue) + { + broadcast(new ChannelEvent(trafficChannel, ChannelEvent.Event.REQUEST_DISABLE)); + } + + mAvailableTrafficChannelQueue.clear(); + } + + + @Override + public void addDecodeEventListener(Listener listener) + { + mDecodeEventListener = listener; + } + + @Override + public void removeDecodeEventListener(Listener listener) + { + mDecodeEventListener = null; + } + + /** + * Monitors channel teardown events to detect when traffic channel processing has ended. Reclaims the + * channel instance for reuse by future traffic channel grants. + */ + public class TrafficChannelTeardownMonitor implements Listener + { + @Override + public synchronized void receive(ChannelEvent channelEvent) + { + Channel channel = channelEvent.getChannel(); + + if(channel.isTrafficChannel() && mManagedTrafficChannels.contains(channel)) + { + switch(channelEvent.getEvent()) + { + case NOTIFICATION_PROCESSING_STOP: + MPT1327Channel toRemove = null; + + for(Map.Entry entry : mAllocatedTrafficChannelMap.entrySet()) + { + if(entry.getValue() == channel) + { + toRemove = entry.getKey(); + continue; + } + } + + if(toRemove != null) + { + mAllocatedTrafficChannelMap.remove(toRemove); + mAvailableTrafficChannelQueue.add(channel); + + MPT1327ChannelGrantEvent event = mChannelGrantEventMap.get(toRemove); + + if(event != null) + { + event.end(System.currentTimeMillis()); + broadcast(event); + } + } + break; + case NOTIFICATION_PROCESSING_START_REJECTED: + MPT1327Channel rejected = null; + + for(Map.Entry entry : mAllocatedTrafficChannelMap.entrySet()) + { + if(entry.getValue() == channel) + { + rejected = entry.getKey(); + continue; + } + } + + if(rejected != null) + { + mAllocatedTrafficChannelMap.remove(rejected); + mAvailableTrafficChannelQueue.add(channel); + + MPT1327ChannelGrantEvent event = mChannelGrantEventMap.get(rejected); + + if(event != null) + { + if(channelEvent.getDescription() != null) + { + event.setDetails(channelEvent.getDescription() + " - " + event.getDetails()); + } + else + { + event.setDetails(channelEvent.getDescription() + " - " + CHANNEL_START_REJECTED); + } + broadcast(event); + } + } + break; + } + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/channel/MPT1327Channel.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/channel/MPT1327Channel.java new file mode 100644 index 00000000..36ac3c44 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/channel/MPT1327Channel.java @@ -0,0 +1,142 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.mpt1327.channel; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.controller.channel.map.ChannelMap; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBand; +import io.github.dsheirer.protocol.Protocol; + +import java.util.Objects; + +/** + * MPT-1327 Channel + */ +public class MPT1327Channel implements IChannelDescriptor +{ + private int mChannelNumber; + private ChannelMap mChannelMap; + + /** + * Constructs a channel instance + */ + public MPT1327Channel(int channelNumber) + { + mChannelNumber = channelNumber; + } + + public String toString() + { + return String.valueOf(mChannelNumber); + } + + /** + * Channel number for this channel. + */ + public int getChannelNumber() + { + return mChannelNumber; + } + + /** + * Assigns a channel map to this channel so that the frequency can be calculated. + */ + public void setChannelMap(ChannelMap channelMap) + { + mChannelMap = channelMap; + } + + @Override + public long getDownlinkFrequency() + { + if(mChannelMap != null) + { + return mChannelMap.getFrequency(mChannelNumber); + } + + return 0; + } + + @Override + public long getUplinkFrequency() + { + return 0; + } + + @Override + public int[] getFrequencyBandIdentifiers() + { + return new int[0]; + } + + @Override + public void setFrequencyBand(IFrequencyBand bandIdentifier) + { + //no-op + } + + @Override + public boolean isTDMAChannel() + { + return false; + } + + @Override + public int getTimeslotCount() + { + return 0; + } + + @Override + public Protocol getProtocol() + { + return Protocol.MPT1327; + } + + @Override + public boolean equals(Object o) + { + if(this == o) + { + return true; + } + if(o == null || getClass() != o.getClass()) + { + return false; + } + MPT1327Channel that = (MPT1327Channel)o; + return getChannelNumber() == that.getChannelNumber(); + } + + @Override + public int hashCode() + { + return Objects.hash(getChannelNumber()); + } + + /** + * Creates a new MPT1327 channel + */ + public static MPT1327Channel create(int channelNumber) + { + return new MPT1327Channel(channelNumber); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/identifier/IdentType.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/identifier/IdentType.java new file mode 100644 index 00000000..56a60205 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/identifier/IdentType.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.mpt1327.identifier; + +/** + * MPT-1327 Ident Types enumeration + */ +public enum IdentType +{ + ALLI_ALL_CALL, + CUSTOM_IDENT, + DIVERTI_DIVERT_IDENT, + DNI_DATA_NETWORK_GATEWAY_IDENT, + DUMMI, + INCI_INCLUDE_IDENT, + IPFIXI_INTER_PREFIX_IDENT, + PABXI_PABX_GATEWAY_IDENT, + PSTNGI_PSTN_GATEWAY_IDENT, + PSTNSI_AND_NETSI_SHORT_FORM_IDENT, + REGI_REGISTRATION_IDENT, + RESERVED, + SDMI_SHORT_DATA_MESSAGE, + TSC_TRUNKING_SYSTEM_CONTROLLER, + USER_IDENT, + UNKNOWN; + +// User idents 1 - 8100(individual and group idents) + + public static IdentType fromIdent(int ident) + { + if(1 <= ident && ident <= 8100) + { + return USER_IDENT; + } + + switch(ident) + { + case 0: + return DUMMI; + case 8101: + return PSTNGI_PSTN_GATEWAY_IDENT; + case 8102: + return PABXI_PABX_GATEWAY_IDENT; + case 8103: + return DNI_DATA_NETWORK_GATEWAY_IDENT; + case 8185: + return REGI_REGISTRATION_IDENT; + case 8186: + return INCI_INCLUDE_IDENT; + case 8187: + return DIVERTI_DIVERT_IDENT; + case 8188: + return SDMI_SHORT_DATA_MESSAGE; + case 8189: + return IPFIXI_INTER_PREFIX_IDENT; + case 8190: + return TSC_TRUNKING_SYSTEM_CONTROLLER; + case 8191: + return ALLI_ALL_CALL; + } + + if(8136 <= ident && ident <= 8180) + { + return CUSTOM_IDENT; + } + else if(8121 <= ident && ident <= 8135) + { + return PSTNSI_AND_NETSI_SHORT_FORM_IDENT; + } + else if((8181 <= ident && ident <= 8184) || (8104 <= ident && ident <= 8120)) + { + return RESERVED; + } + + return UNKNOWN; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/identifier/MPT1327SiteIdentifier.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/identifier/MPT1327SiteIdentifier.java new file mode 100644 index 00000000..3f861765 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/identifier/MPT1327SiteIdentifier.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.mpt1327.identifier; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.integer.IntegerIdentifier; +import io.github.dsheirer.protocol.Protocol; + +public class MPT1327SiteIdentifier extends IntegerIdentifier +{ + public MPT1327SiteIdentifier(int site) + { + super(site, IdentifierClass.NETWORK, Form.SITE, Role.BROADCAST); + } + + @Override + public Protocol getProtocol() + { + return Protocol.MPT1327; + } + + public static MPT1327SiteIdentifier create(int site) + { + return new MPT1327SiteIdentifier(site); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/identifier/MPT1327Talkgroup.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/identifier/MPT1327Talkgroup.java new file mode 100644 index 00000000..4294112b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/identifier/MPT1327Talkgroup.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.mpt1327.identifier; + +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.protocol.Protocol; + +/** + * MPT-1327 Talkgroup Identifier. + * + * Note: the isGroup() method of the talkgroup identifier parent class does not apply here since we cannot determine + * if a talkgroup is a group or a unit identifier. + */ +public class MPT1327Talkgroup extends TalkgroupIdentifier +{ + public MPT1327Talkgroup(Integer value, Role role) + { + super(value, role, false); + } + + @Override + public Protocol getProtocol() + { + return Protocol.MPT1327; + } + + /** + * This identifier formatted as PPP-IIII where P=prefix and I=ident + */ + public String formatted() + { + return String.format("%03d-%04d", getPrefix(), getIdent()); + } + + /** + * Prefix for the talkgroup + */ + public int getPrefix() + { + return (getValue() >> 13) & 0x7F; + } + + /** + * Ident for the talkgroup + */ + public int getIdent() + { + return getValue() & 0x1FFF; + } + + /** + * Indicates the type of ident value + */ + public IdentType getIdentType() + { + return IdentType.fromIdent(getIdent()); + } + + /** + * Creates a FROM role talkgroup identifier + */ + public static MPT1327Talkgroup createFrom(int prefix, int ident) + { + if(0 < ident && ident <= 8100) + { + return new MPT1327Talkgroup(((prefix << 13) + ident), Role.FROM); + } + else + { + //Mask the prefix for ident values of 0, 8101-8192 + return new MPT1327Talkgroup(ident, Role.FROM); + } + } + + /** + * Creates a TO role talkgroup identifier + */ + public static MPT1327Talkgroup createTo(int prefix, int ident) + { + if(0 < ident && ident <= 8100) + { + return new MPT1327Talkgroup(((prefix << 13) + ident), Role.TO); + } + else + { + //Mask the prefix for ident values of 0, 8101-8192 + return new MPT1327Talkgroup(ident, Role.TO); + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/IDataUnitDetectListener.java b/src/main/java/io/github/dsheirer/module/decode/p25/IDataUnitDetectListener.java new file mode 100644 index 00000000..1af23a80 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/IDataUnitDetectListener.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25; + +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +/** + * Listener interface to be notified each time a P25 sync pattern and data unit has been detected + * and the data unit is correct after error detection and correction and/or when the sync has been lost. + */ +public interface IDataUnitDetectListener +{ + /** + * Indicates that a P25 sync has been detected and a P25 data unit was successfully decoded. + * @param dataUnitID that was contained in the detected NID + * @param nac or Network Access Code that was contained in the detected NID + * @param discardedDibits prior to detecting the P25 sync pattern + * @param bitErrors detected and corrected from both the sync pattern and the NID. + * @param correctedNid bits corrected by the BCH error correction code (temporary until message parsers are updated + */ + void dataUnitDetected(DataUnitID dataUnitID, int nac, int bitErrors, int discardedDibits, int[] correctedNid); + + /** + * Indicates that sync has been lost on the dibit stream + * + * @param bitsProcessed since the last sync detect + */ + void syncLost(int bitsProcessed); +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25CallEvent.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25CallEvent.java deleted file mode 100644 index 99d80555..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25CallEvent.java +++ /dev/null @@ -1,170 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.module.decode.event.CallEvent; - -public class P25CallEvent extends CallEvent -{ - private String mChannel; - private long mFrequency; - - public P25CallEvent( CallEventType callEventType, - AliasList aliasList, - String fromID, - String toID, - String channel, - long frequency, - String details ) - { - super( DecoderType.P25_PHASE1, callEventType, - aliasList, fromID, toID, details ); - - mChannel = channel; - mFrequency = frequency; - } - - private Alias getAlias( String talkgroup ) - { - if( hasAliasList() ) - { - return getAliasList().getTalkgroupAlias( talkgroup ); - } - - return null; - } - - @Override - public Alias getFromIDAlias() - { - return getAlias( getFromID() ); - } - - @Override - public Alias getToIDAlias() - { - return getAlias( getToID() ); - } - - @Override - public String getChannel() - { - return mChannel; - } - - @Override - public long getFrequency() - { - return mFrequency; - } - - public String toString() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "P25 " ); - sb.append( getCallEventType().name() ); - sb.append( " CHAN[" ); - sb.append( getChannel() ); - sb.append( "] TO[" ); - sb.append( getToID() ); - sb.append( "] FROM[" ); - sb.append( getFromID() ); - - return sb.toString(); - } - - public static class Builder - { - /* Required parameters */ - private CallEventType mCallEventType; - - /* Optional parameters */ - private AliasList mAliasList; - private String mFromID; - private String mToID; - private String mDetails; - private String mChannel; - private long mFrequency; - - public Builder( CallEventType callEventType ) - { - mCallEventType = callEventType; - } - - public Builder aliasList( AliasList aliasList ) - { - mAliasList = aliasList; - return this; - } - - public Builder from( String val ) - { - mFromID = val; - return this; - } - - public Builder channel( String channel ) - { - mChannel = channel; - return this; - } - - public Builder details( String details ) - { - mDetails = details; - return this; - } - - public Builder frequency( long frequency ) - { - mFrequency = frequency; - return this; - } - - public Builder to( String toID ) - { - mToID = toID; - return this; - } - - public P25CallEvent build() - { - return new P25CallEvent( this ); - } - - } - - /** - * Private constructor for the builder - */ - private P25CallEvent( Builder builder ) - { - this( builder.mCallEventType, - builder.mAliasList, - builder.mFromID, - builder.mToID, - builder.mChannel, - builder.mFrequency, - builder.mDetails ); - } - -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25ChannelGrantEvent.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25ChannelGrantEvent.java new file mode 100644 index 00000000..377fdcd2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25ChannelGrantEvent.java @@ -0,0 +1,172 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.module.decode.p25.reference.ServiceOptions; +import io.github.dsheirer.protocol.Protocol; + +public class P25ChannelGrantEvent extends P25DecodeEvent +{ + private ServiceOptions mServiceOptions; + + public P25ChannelGrantEvent(long timestamp) + { + super(timestamp); + } + + /** + * Creates a new decode event builder with the specified start timestamp. + * @param timeStart for the event + * @return builder + */ + public static P25ChannelGrantDecodeEventBuilder builder(long timeStart, ServiceOptions serviceOptions) + { + return new P25ChannelGrantDecodeEventBuilder(timeStart, serviceOptions); + } + + /** + * Service options for the channel grant + */ + public ServiceOptions getServiceOptions() + { + return mServiceOptions; + } + + + /** + * Service options for the channel grant + */ + public void setServiceOptions(ServiceOptions serviceOptions) + { + mServiceOptions = serviceOptions; + } + + /** + * Indicates if this channel grant event has non-null service options + */ + public boolean hasServiceOptions() + { + return mServiceOptions != null; + } + + /** + * Builder pattern for constructing decode events. + */ + public static class P25ChannelGrantDecodeEventBuilder + { + protected long mTimeStart; + protected long mDuration; + protected String mEventDescription; + protected IdentifierCollection mIdentifierCollection; + protected IChannelDescriptor mChannelDescriptor; + protected String mDetails; + private ServiceOptions mServiceOptions; + + /** + * Constructs a builder instance with the specified start time in milliseconds + * + * @param timeStart + */ + public P25ChannelGrantDecodeEventBuilder(long timeStart, ServiceOptions serviceOptions) + { + mTimeStart = timeStart; + mServiceOptions = serviceOptions; + } + + /** + * Sets the duration value + * @param duration in milliseconds + */ + public P25ChannelGrantDecodeEventBuilder duration(long duration) + { + mDuration = duration; + return this; + } + + /** + * Sets the duration value using the end - start timestamps + * @param timestamp for end of event + */ + public P25ChannelGrantDecodeEventBuilder end(long timestamp) + { + mDuration = timestamp - mTimeStart; + return this; + } + + /** + * Sets the channel descriptor for this event + * @param channelDescriptor + */ + public P25ChannelGrantDecodeEventBuilder channel(IChannelDescriptor channelDescriptor) + { + mChannelDescriptor = channelDescriptor; + return this; + } + + /** + * Sets the event description text + * @param description of the event + */ + public P25ChannelGrantDecodeEventBuilder eventDescription(String description) + { + mEventDescription = description; + return this; + } + + /** + * Sets the identifier collection. + * @param identifierCollection containing optional identifiers like TO, FROM, frequency and + * alias list configuration name. + */ + public P25ChannelGrantDecodeEventBuilder identifiers(IdentifierCollection identifierCollection) + { + mIdentifierCollection = identifierCollection; + return this; + } + + /** + * Sets the details for the event + * @param details + */ + public P25ChannelGrantDecodeEventBuilder details(String details) + { + mDetails = details; + return this; + } + + /** + * Builds the decode event + */ + public P25ChannelGrantEvent build() + { + P25ChannelGrantEvent decodeEvent = new P25ChannelGrantEvent(mTimeStart); + decodeEvent.setProtocol(Protocol.APCO25); + decodeEvent.setChannelDescriptor(mChannelDescriptor); + decodeEvent.setDetails(mDetails); + decodeEvent.setDuration(mDuration); + decodeEvent.setEventDescription(mEventDescription); + decodeEvent.setIdentifierCollection(mIdentifierCollection); + decodeEvent.setServiceOptions(mServiceOptions); + return decodeEvent; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25ChannelStatusProcessor.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25ChannelStatusProcessor.java new file mode 100644 index 00000000..8fdfb8fa --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25ChannelStatusProcessor.java @@ -0,0 +1,85 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25; + +import io.github.dsheirer.dsp.symbol.Dibit; +import io.github.dsheirer.module.decode.p25.reference.Direction; +import io.github.dsheirer.sample.Listener; + +/** + * Processes P25 status dibits to determine if the channel being monitored is the output of a repeater or if + * the channel is from a mobile subscriber. + * + * Dibit 00 is used by the subscriber + * Dibits 01 and 11 are used by the repeater. + * Dibit 10 is used by both and is ignored by this processor. + */ +public class P25ChannelStatusProcessor implements Listener +{ + private int mSubscriberCount = 0; + private int mRepeaterCount = 0; + private Direction mDirection = Direction.OUTBOUND; + + public void receive(Dibit status) + { + switch(status) + { + case D00_PLUS_1: + mSubscriberCount++; + break; + case D01_PLUS_3: + case D11_MINUS_3: + mRepeaterCount++; + break; + } + + update(); + } + + private void update() + { + if(mRepeaterCount > mSubscriberCount) + { + mDirection = Direction.OUTBOUND; + + if(mRepeaterCount == Integer.MAX_VALUE) + { + mRepeaterCount = 1000; + mSubscriberCount = 0; + } + } + else + { + mDirection = Direction.INBOUND; + + if(mSubscriberCount == Integer.MAX_VALUE) + { + mSubscriberCount = 1000; + mRepeaterCount = 0; + } + } + } + + public Direction getDirection() + { + return mDirection; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25DataUnitDetector.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25DataUnitDetector.java new file mode 100644 index 00000000..3a2e59ec --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25DataUnitDetector.java @@ -0,0 +1,557 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25; + +import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop; +import io.github.dsheirer.dsp.symbol.Dibit; +import io.github.dsheirer.dsp.symbol.ISyncDetectListener; +import io.github.dsheirer.edac.BCH_63_16_11; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.sample.Listener; +import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class P25DataUnitDetector implements Listener, ISyncDetectListener +{ + private final static Logger mLog = LoggerFactory.getLogger(P25DataUnitDetector.class); + private static final int DATA_UNIT_DIBIT_LENGTH = 57; //56 dibits plus 1 status symbol + private static final int SYNC_DIBIT_LENGTH = 24; + private static final int MAXIMUM_SYNC_MATCH_BIT_ERRORS = 9; + private P25SyncDetector mSyncDetector; + private NIDDelayBuffer mDataUnitBuffer = new NIDDelayBuffer(); + private DibitDelayBuffer mSyncDelayBuffer = new DibitDelayBuffer(DATA_UNIT_DIBIT_LENGTH - SYNC_DIBIT_LENGTH); + private IDataUnitDetectListener mDataUnitDetectListener; + private boolean mInitialSyncTestProcessed = false; + private int mDibitsProcessed = 0; + private BCH_63_16_11 mNIDDecoder = new BCH_63_16_11(); + private DataUnitID mPreviousDataUnitId = DataUnitID.TERMINATOR_DATA_UNIT; + private int mNIDDetectionCount; + + public P25DataUnitDetector(IDataUnitDetectListener dataUnitDetectListener, IPhaseLockedLoop phaseLockedLoop) + { + mDataUnitDetectListener = dataUnitDetectListener; + mSyncDetector = new P25SyncDetector(this, phaseLockedLoop); + } + + /** + * Sets the sample rate for the phase inversion sync detector + */ + public void setSampleRate(double sampleRate) + { + mSyncDetector.setSampleRate(sampleRate); + } + + public void reset() + { + mDibitsProcessed = 0; + mInitialSyncTestProcessed = false; + } + + @Override + public void syncDetected(int bitErrors) + { + mInitialSyncTestProcessed = true; + checkForNid(bitErrors, false); + } + + @Override + public void syncLost() + { + dispatchSyncLoss(0); + } + + private void dispatchSyncLoss(int bitsProcessed) + { + if(mDataUnitDetectListener != null) + { + mDataUnitDetectListener.syncLost(bitsProcessed); + } + } + + @Override + public void receive(Dibit dibit) + { + mDibitsProcessed++; + + //Broadcast a sync loss every 4800 dibits/9600 bits ... or 1x per second for phase 1 + if(mDibitsProcessed > 4864) + { + dispatchSyncLoss(9600); + mDibitsProcessed -= 4800; + } + + mDataUnitBuffer.put(dibit); + + //Feed the sync detect with a 32 dibit delay from the data unit buffer so that if/when + //a sync detect occurs, the data unit buffer is already filled with both the sync dibits + //and the NID dibits and we can test for a valid NID + mSyncDetector.receive(mSyncDelayBuffer.getAndPut(dibit)); + + //If the sync detector doesn't fire and we've processed enough dibits for a sync/nid sequence + //immediately following a valid message, then test for a NID anyway ... maybe the sync was corrupted + if(!mInitialSyncTestProcessed && mDibitsProcessed == DATA_UNIT_DIBIT_LENGTH) + { + mInitialSyncTestProcessed = true; + checkForNid(mSyncDetector.getPrimarySyncMatchErrorCount(), true); + } + } + + /** + * Chects/tests the contents of the data unit buffer for a valid NID when a sync pattern is detected + * or when commanded following a valid message sequence + * + * @param bitErrorCount when comparing the sync pattern to the received bit sequence + * @param forcedCheck indicates if the NID check was forced, meaning that the primary sync detector + * did not detect a sync, however we expect there to be a message sync following a successfully + * decoded and framed preceeding messsage. In these instances, the framer will allow for a less + * than perfect match on the sync pattern, as long as the NID passes error check. + */ + private void checkForNid(int bitErrorCount, boolean forcedCheck) + { + if(bitErrorCount <= MAXIMUM_SYNC_MATCH_BIT_ERRORS) + { + int[] nid = mSyncDelayBuffer.getNID(); + int[] correctedNid = new int[63]; + + //If decoder indicates there are no unrecoverable errors .... + if(!mNIDDecoder.decode(nid, correctedNid)) + { + mNIDDetectionCount++; + + int nidBitErrorCount = getBitErrorCount(nid, correctedNid); + + if(mDataUnitDetectListener != null) + { + mPreviousDataUnitId = getDataUnitID(correctedNid); + + mDataUnitDetectListener.dataUnitDetected(mPreviousDataUnitId, getNAC(correctedNid), + (bitErrorCount + nidBitErrorCount), (mDibitsProcessed - DATA_UNIT_DIBIT_LENGTH), correctedNid); + } + } + else if(mPreviousDataUnitId == DataUnitID.LOGICAL_LINK_DATA_UNIT_1) + { + //We have a good sync match, but the NID didn't pass error control and we're in the middle + //of voice call, so treat this message as voice message, but set the previous duid to + //terminator so we can end if there isn't a subsequent voice message + mDataUnitDetectListener.dataUnitDetected(DataUnitID.LOGICAL_LINK_DATA_UNIT_2, -1, + (bitErrorCount + 64), (mDibitsProcessed - DATA_UNIT_DIBIT_LENGTH), new int[63]); + + mPreviousDataUnitId = DataUnitID.TERMINATOR_DATA_UNIT; + } + else if(mPreviousDataUnitId == DataUnitID.LOGICAL_LINK_DATA_UNIT_2) + { + //We have a good sync match, but the NID didn't pass error control and we're in the middle + //of voice call, so treat this message as voice message, but set the previous duid to + //terminator so we can end if there isn't a subsequent voice message + mDataUnitDetectListener.dataUnitDetected(DataUnitID.LOGICAL_LINK_DATA_UNIT_1, -1, + (bitErrorCount + 64), (mDibitsProcessed - DATA_UNIT_DIBIT_LENGTH), new int[63]); + + mPreviousDataUnitId = DataUnitID.TERMINATOR_DATA_UNIT; + } + } + } + + /** + * Determines the data unit ID present in the nid value. + * @param nid in reverse bit order + * @return + */ + public DataUnitID getDataUnitID(int[] nid) + { + int duid = 0; + + if(nid[47] == 1) + { + duid ^= 1; + } + + if(nid[48] == 1) + { + duid ^= 2; + } + + if(nid[49] == 1) + { + duid ^= 4; + } + + if(nid[50] == 1) + { + duid ^= 8; + } + + return DataUnitID.fromValue(duid); + } + + /** + * Determines the Network Access Code (NAC) present in the nid value. + * @param nid in reverse bit order + * @return nac + */ + public int getNAC(int[] nid) + { + int nac = 0; + + if(nid[51] == 1) + { + nac ^= 1; + } + + if(nid[52] == 1) + { + nac ^= 2; + } + + if(nid[53] == 1) + { + nac ^= 4; + } + + if(nid[54] == 1) + { + nac ^= 8; + } + + if(nid[55] == 1) + { + nac ^= 16; + } + + if(nid[56] == 1) + { + nac ^= 32; + } + + if(nid[57] == 1) + { + nac ^= 64; + } + + if(nid[58] == 1) + { + nac ^= 128; + } + + if(nid[59] == 1) + { + nac ^= 256; + } + + if(nid[60] == 1) + { + nac ^= 512; + } + + if(nid[61] == 1) + { + nac ^= 1024; + } + + if(nid[62] == 1) + { + nac ^= 2048; + } + + return nac; + } + + public int getNIDDetectionCount() + { + return mNIDDetectionCount; + } + + public class NIDDelayBuffer extends DibitDelayBuffer + { + public NIDDelayBuffer() + { + super(DATA_UNIT_DIBIT_LENGTH); + } + + public int[] getNID() + { + int[] nid = new int[64]; + + int nidPointer = 0; + int bufferPointer = mPointer; + + while(nidPointer < 64) + { + if(mBuffer[bufferPointer].getBit2()) + { + nid[nidPointer] = 1; + } + + nidPointer++; + + if(mBuffer[bufferPointer].getBit1()) + { + nid[nidPointer] = 1; + } + + nidPointer++; + + bufferPointer--; + + if(bufferPointer < 0) + { + bufferPointer += mBuffer.length; + } + + //Check for the status symbol + if(nidPointer == 70) + { + nidPointer += 2; + } + } + + return nid; + } + } + + + /** + * Circular buffer for storing and accessing dibits. + */ + public class DibitDelayBuffer + { + protected Dibit[] mBuffer; + protected int mPointer; + + /** + * Constructs a dibit delay buffer of the specified length + */ + public DibitDelayBuffer(int length) + { + mBuffer = new Dibit[length]; + + //Preload the buffer to avoid null pointers + for(int x = 0; x < length; x++) + { + mBuffer[x] = Dibit.D00_PLUS_1; + } + } + + /** + * Fetches the NID in reverse order format required for the ECC code + */ + public int[] getNID() + { + int[] nid = new int[63]; + + int nidPointer = 0; + int bufferPointer = mPointer - 1; + + if(bufferPointer < 0) + { + bufferPointer += mBuffer.length; + } + + //Skip bit 2 of the starting (ie last) dibit ... we only want 63 of the 64 bits from the dibit buffer + if(mBuffer[bufferPointer].getBit1()) + { + nid[nidPointer] = 1; + } + + nidPointer++; + + bufferPointer--; + + if(bufferPointer < 0) + { + bufferPointer += mBuffer.length; + } + + while(nidPointer < 63) + { + if(mBuffer[bufferPointer].getBit2()) + { + nid[nidPointer] = 1; + } + + nidPointer++; + + if(mBuffer[bufferPointer].getBit1()) + { + nid[nidPointer] = 1; + } + + nidPointer++; + + bufferPointer--; + + //Check for and skip the status symbol that gets inserted after 70 bits, meaning + //it will be located in the buffer after 22 NID bits + if(nidPointer == 41) + { + bufferPointer--; + } + + if(bufferPointer < 0) + { + bufferPointer += mBuffer.length; + } + } + + return nid; + } + + public void log() + { + StringBuilder sb = new StringBuilder(); + + int counter = 0; + int pointer = mPointer; + + while(counter < mBuffer.length) + { + sb.append(mBuffer[pointer].getBit1() ? "1" : "0"); + sb.append(mBuffer[pointer++].getBit2() ? "1" : "0"); + + if(pointer >= mBuffer.length) + { + pointer = 0; + } + + counter++; + } + + if(this instanceof NIDDelayBuffer) + { + mLog.debug("NIDBUF: " + sb.toString() + " Length:" + mBuffer.length); + } + else + { + mLog.debug("SYNBUF: " + sb.toString() + " Length:" + mBuffer.length); + } + } + + /** + * Returns an ordered buffer of the internal circular buffer contents. + */ + public Dibit[] getBuffer() + { + Dibit[] transferBuffer = new Dibit[mBuffer.length]; + + int transferBufferPointer = 0; + int bufferPointer = mPointer; + + while(transferBufferPointer < transferBuffer.length) + { + transferBuffer[transferBufferPointer++] = mBuffer[bufferPointer++]; + + if(bufferPointer >= mBuffer.length) + { + bufferPointer = 0; + } + } + + return transferBuffer; + } + + public int[] getBufferAsArray() + { + Dibit[] dibits = getBuffer(); + + int[] bits = new int[dibits.length * 2]; + + for(int x = 0; x < dibits.length; x++) + { + if(dibits[x].getBit1()) + { + bits[x * 2] = 1; + } + if(dibits[x].getBit2()) + { + bits[x * 2 + 1] = 1; + } + } + + return bits; + } + + /** + * Places the dibit into the internal circular buffer, overwriting the oldest dibit. + */ + public void put(Dibit dibit) + { + mBuffer[mPointer++] = dibit; + + if(mPointer >= mBuffer.length) + { + mPointer = 0; + } + } + + /** + * Places the dibit into the internal circular buffer, overwriting and returning the + * oldest dibit. + */ + public Dibit getAndPut(Dibit dibit) + { + Dibit toReturn = mBuffer[mPointer]; + put(dibit); + return toReturn; + } + } + + public static int[] reverse(int[] values) + { + int[] reversed = new int[values.length]; + + for(int x = 0; x < values.length; x++) + { + reversed[values.length - x - 1] = values[x]; + } + + return reversed; + } + + public static void logNID(int[] nid, boolean corrected) + { + StringBuilder sb = new StringBuilder(); + for(int value: nid) + { + sb.append(value); + } + + if(corrected) + { + mLog.debug("C* NID: " + sb.toString()); + } + else + { + mLog.debug(" NID: " + sb.toString()); + } + } + + public static int getBitErrorCount(int[] a, int[] b) + { + Validate.isTrue(a.length == b.length, "Array lengths must be the same"); + + int count = 0; + + for(int x = 0; x < a.length; x++) + { + if(a[x] != b[x]) + { + count++; + } + } + + return count; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecodeEvent.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecodeEvent.java new file mode 100644 index 00000000..8189e25d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecodeEvent.java @@ -0,0 +1,49 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25; + +import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.protocol.Protocol; + +public class P25DecodeEvent extends DecodeEvent +{ + /** + * Constucts a P25 decode event + * @param start + */ + public P25DecodeEvent(long start) + { + super(start); + setProtocol(Protocol.APCO25); + } + + /** + * Creates a new decode event builder with the specified start timestamp. + * @param timeStart for the event + * @return builder + */ + public static DecodeEventBuilder builder(long timeStart) + { + DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(timeStart); + decodeEventBuilder.protocol(Protocol.APCO25); + return decodeEventBuilder; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25Decoder.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25Decoder.java index 786bf482..35dc9068 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25Decoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25Decoder.java @@ -15,7 +15,6 @@ ******************************************************************************/ package io.github.dsheirer.module.decode.p25; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.dsp.symbol.Dibit; import io.github.dsheirer.dsp.symbol.DibitToByteBufferAssembler; import io.github.dsheirer.module.decode.Decoder; @@ -37,15 +36,13 @@ public abstract class P25Decoder extends Decoder implements ISourceEventListener private Broadcaster mDibitBroadcaster = new Broadcaster<>(); private DibitToByteBufferAssembler mByteBufferAssembler = new DibitToByteBufferAssembler(300); private P25MessageProcessor mMessageProcessor; - private AliasList mAliasList; private Listener mSourceEventListener; private double mSymbolRate; - public P25Decoder(double symbolRate, AliasList aliasList) + public P25Decoder(double symbolRate) { mSymbolRate = symbolRate; - mAliasList = aliasList; - mMessageProcessor = new P25MessageProcessor(mAliasList); + mMessageProcessor = new P25MessageProcessor(); mMessageProcessor.setMessageListener(getMessageListener()); mDibitBroadcaster.addListener(mByteBufferAssembler); } @@ -90,11 +87,6 @@ public abstract class P25Decoder extends Decoder implements ISourceEventListener return mSymbolRate; } - protected AliasList getAliasList() - { - return mAliasList; - } - /** * Current sample rate for this decoder */ diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FM.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FM.java index b9a9cb4d..79e3be91 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FM.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FM.java @@ -1,21 +1,24 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.module.decode.p25; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.dsp.filter.FilterFactory; import io.github.dsheirer.dsp.filter.design.FilterDesignException; import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification; @@ -25,6 +28,7 @@ import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulator; import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer; import io.github.dsheirer.dsp.psk.pll.AdaptivePLLGainMonitor; import io.github.dsheirer.dsp.psk.pll.CostasLoop; +import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.source.SourceEvent; import org.slf4j.Logger; @@ -42,7 +46,7 @@ public class P25DecoderC4FM extends P25Decoder protected DQPSKDecisionDirectedDemodulator mQPSKDemodulator; protected CostasLoop mCostasLoop; protected AdaptivePLLGainMonitor mPLLGainMonitor; - protected P25MessageFramer mMessageFramer; + protected P25MessageFramer2 mMessageFramer; private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl(32); private Map mBasebandFilters = new HashMap<>(); private ComplexFIRFilter2 mBasebandFilter; @@ -50,12 +54,10 @@ public class P25DecoderC4FM extends P25Decoder /** * P25 Phase 1 - standard C4FM modulation decoder. Uses Differential QPSK decoding with a Costas PLL and a * decision-directed phase and timing error detector. - * - * @param aliasList */ - public P25DecoderC4FM(AliasList aliasList) + public P25DecoderC4FM() { - super(4800.0, aliasList); + super(4800.0); setSampleRate(25000.0); } @@ -74,12 +76,12 @@ public class P25DecoderC4FM extends P25Decoder if(mMessageFramer != null) { getDibitBroadcaster().removeListener(mMessageFramer); - mMessageFramer.dispose(); } //The Costas Loop receives symbol-inversion correction requests when detected. //The PLL gain monitor receives sync detect/loss signals from the message framer - mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop, mPLLGainMonitor); + mMessageFramer = new P25MessageFramer2(mCostasLoop, DecoderType.P25_PHASE1.getProtocol().getBitRate()); + mMessageFramer.setSyncDetectListener(mPLLGainMonitor); mMessageFramer.setListener(getMessageProcessor()); mMessageFramer.setSampleRate(sampleRate); @@ -100,6 +102,8 @@ public class P25DecoderC4FM extends P25Decoder //User accounting of the incoming buffer is handled by the gain filter ReusableComplexBuffer gainApplied = mAGC.filter(basebandFiltered); + mMessageFramer.setCurrentTime(reusableComplexBuffer.getTimestamp()); + //User accounting of the filtered buffer is handled by the demodulator mQPSKDemodulator.receive(gainApplied); } @@ -164,7 +168,6 @@ public class P25DecoderC4FM extends P25Decoder mBasebandFilter.dispose(); mBasebandFilter = null; - mMessageFramer.dispose(); mMessageFramer = null; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FMInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FMInstrumented.java index ea3ff3d8..6c92fc60 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FMInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FMInstrumented.java @@ -15,7 +15,6 @@ ******************************************************************************/ package io.github.dsheirer.module.decode.p25; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulatorInstrumented; import io.github.dsheirer.dsp.psk.InterpolatingSampleBufferInstrumented; import io.github.dsheirer.dsp.psk.SymbolDecisionData; @@ -35,12 +34,9 @@ public class P25DecoderC4FMInstrumented extends P25DecoderC4FM /** * Instrumented version of the P25 C4FM decoder that supports registering listeners to provide access to data as * it is being processed by the decoder. - * - * @param aliasList */ - public P25DecoderC4FMInstrumented(AliasList aliasList) + public P25DecoderC4FMInstrumented() { - super(aliasList); } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSM.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSM.java index be99d126..101971d6 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSM.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSM.java @@ -1,21 +1,24 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.module.decode.p25; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.dsp.filter.FilterFactory; import io.github.dsheirer.dsp.filter.Window.WindowType; import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2; @@ -24,6 +27,7 @@ import io.github.dsheirer.dsp.psk.DQPSKGardnerDemodulator; import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer; import io.github.dsheirer.dsp.psk.pll.AdaptivePLLGainMonitor; import io.github.dsheirer.dsp.psk.pll.CostasLoop; +import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.source.SourceEvent; @@ -32,13 +36,15 @@ import java.util.Map; public class P25DecoderLSM extends P25Decoder { +// private final static Logger mLog = LoggerFactory.getLogger(P25DecoderLSM.class); + protected static final float SAMPLE_COUNTER_GAIN = 0.3f; private Map mBasebandFilters = new HashMap<>(); private ComplexFIRFilter2 mBasebandFilter; private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl(32); protected DQPSKGardnerDemodulator mQPSKDemodulator; - protected P25MessageFramer mMessageFramer; + protected P25MessageFramer2 mMessageFramer; protected CostasLoop mCostasLoop; protected AdaptivePLLGainMonitor mPLLGainMonitor; protected InterpolatingSampleBuffer mInterpolatingSampleBuffer; @@ -46,12 +52,10 @@ public class P25DecoderLSM extends P25Decoder /** * P25 Phase 1 - linear simulcast modulation (LSM) decoder. Uses Differential QPSK decoding with a Costas PLL and * a gardner timing error detector. - * - * @param aliasList */ - public P25DecoderLSM(AliasList aliasList) + public P25DecoderLSM() { - super(4800.0, aliasList); + super(4800.0); setSampleRate(25000.0); } @@ -78,10 +82,10 @@ public class P25DecoderLSM extends P25Decoder if(mMessageFramer != null) { getDibitBroadcaster().removeListener(mMessageFramer); - mMessageFramer.dispose(); } - mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop, mPLLGainMonitor); + mMessageFramer = new P25MessageFramer2(mCostasLoop, DecoderType.P25_PHASE1.getProtocol().getBitRate()); + mMessageFramer.setSyncDetectListener(mPLLGainMonitor); mMessageFramer.setListener(getMessageProcessor()); mMessageFramer.setSampleRate(sampleRate); mQPSKDemodulator.setSymbolListener(getDibitBroadcaster()); @@ -100,6 +104,8 @@ public class P25DecoderLSM extends P25Decoder //AGC will decrement the user count when finished ReusableComplexBuffer gainApplied = mAGC.filter(basebandFiltered); + mMessageFramer.setCurrentTime(reusableComplexBuffer.getTimestamp()); + //Decoder will decrement the user count when finished mQPSKDemodulator.receive(gainApplied); } @@ -163,7 +169,6 @@ public class P25DecoderLSM extends P25Decoder mQPSKDemodulator.dispose(); mQPSKDemodulator = null; - mMessageFramer.dispose(); mMessageFramer = null; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSMInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSMInstrumented.java index 8099d31e..ea56c150 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSMInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSMInstrumented.java @@ -15,7 +15,6 @@ ******************************************************************************/ package io.github.dsheirer.module.decode.p25; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.dsp.psk.DQPSKGardnerDemodulatorInstrumented; import io.github.dsheirer.dsp.psk.InterpolatingSampleBufferInstrumented; import io.github.dsheirer.dsp.psk.SymbolDecisionData; @@ -37,12 +36,9 @@ public class P25DecoderLSMInstrumented extends P25DecoderLSM * a gardner timing error detector. * * Instrumented decoder instance. - * - * @param aliasList */ - public P25DecoderLSMInstrumented(AliasList aliasList) + public P25DecoderLSMInstrumented() { - super(aliasList); } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderState.java index 2fcdd2f3..8db5520f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderState.java @@ -1,227 +1,212 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,2015 Dennis Sheirer +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 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. + * 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 - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.module.decode.p25; -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.alias.id.AliasIDType; -import io.github.dsheirer.channel.metadata.AliasedStringAttributeMonitor; -import io.github.dsheirer.channel.metadata.Attribute; -import io.github.dsheirer.channel.metadata.AttributeChangeRequest; import io.github.dsheirer.channel.state.ChangeChannelTimeoutEvent; import io.github.dsheirer.channel.state.DecoderState; import io.github.dsheirer.channel.state.DecoderStateEvent; import io.github.dsheirer.channel.state.DecoderStateEvent.Event; import io.github.dsheirer.channel.state.State; -import io.github.dsheirer.channel.traffic.TrafficChannelAllocationEvent; +import io.github.dsheirer.controller.channel.Channel; import io.github.dsheirer.controller.channel.Channel.ChannelType; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.controller.channel.ChannelEvent; +import io.github.dsheirer.controller.channel.IChannelEventListener; +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.identifier.MutableIdentifierCollection; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.patch.PatchGroupManager; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.module.decode.event.CallEvent; -import io.github.dsheirer.module.decode.p25.message.IAdjacentSite; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; +import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; +import io.github.dsheirer.module.decode.ip.IPacket; +import io.github.dsheirer.module.decode.ip.ars.ARSPacket; +import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet; +import io.github.dsheirer.module.decode.ip.udp.UDPPacket; import io.github.dsheirer.module.decode.p25.message.P25Message; import io.github.dsheirer.module.decode.p25.message.hdu.HDUMessage; +import io.github.dsheirer.module.decode.p25.message.hdu.HeaderData; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCCallTermination; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCExtendedFunctionCommand; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCMessageUpdate; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCStatusUpdate; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCTelephoneInterconnectAnswerRequest; +import io.github.dsheirer.module.decode.p25.message.ldu.EncryptionSyncParameters; import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.message.ldu.LDUMessage; -import io.github.dsheirer.module.decode.p25.message.ldu.lc.CallTermination; +import io.github.dsheirer.module.decode.p25.message.ldu.LDU2Message; import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.message.pdu.confirmed.PDUConfirmedMessage; -import io.github.dsheirer.module.decode.p25.message.pdu.confirmed.PDUTypeUnknown; -import io.github.dsheirer.module.decode.p25.message.pdu.confirmed.PacketData; -import io.github.dsheirer.module.decode.p25.message.pdu.confirmed.SNDCPActivateTDSContextAccept; -import io.github.dsheirer.module.decode.p25.message.pdu.confirmed.SNDCPActivateTDSContextReject; -import io.github.dsheirer.module.decode.p25.message.pdu.confirmed.SNDCPActivateTDSContextRequest; -import io.github.dsheirer.module.decode.p25.message.pdu.confirmed.SNDCPDeactivateTDSContext; -import io.github.dsheirer.module.decode.p25.message.pdu.confirmed.SNDCPUserData; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.AdjacentStatusBroadcastExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.CallAlertExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.GroupAffiliationQueryExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.GroupAffiliationResponseExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.MessageUpdateExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.NetworkStatusBroadcastExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.ProtectionParameterBroadcast; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.RFSSStatusBroadcastExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.RoamingAddressUpdateExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.StatusQueryExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.StatusUpdateExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.UnitRegistrationResponseExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.data.GroupDataChannelGrantExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.data.IndividualDataChannelGrantExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.voice.GroupVoiceChannelGrantExplicit; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.voice.TelephoneInterconnectChannelGrantExplicit; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.voice.UnitToUnitAnswerRequestExplicit; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.voice.UnitToUnitVoiceChannelGrantExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.voice.UnitToUnitVoiceChannelGrantUpdateExtended; -import io.github.dsheirer.module.decode.p25.message.tdu.TDUMessage; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.AdjacentSiteStatusBroadcast; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.AdjacentSiteStatusBroadcastExplicit; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.CallAlert; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.ExtendedFunctionCommand; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.GroupAffiliationQuery; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.GroupVoiceChannelUpdate; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.GroupVoiceChannelUpdateExplicit; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.GroupVoiceChannelUser; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.MessageUpdate; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.NetworkStatusBroadcastExplicit; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.RFSSStatusBroadcastExplicit; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.SecondaryControlChannelBroadcastExplicit; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.StatusQuery; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.StatusUpdate; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.TDULinkControlMessage; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.TelephoneInterconnectAnswerRequest; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.TelephoneInterconnectVoiceChannelUser; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.UnitAuthenticationCommand; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.UnitRegistrationCommand; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.UnitToUnitAnswerRequest; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.UnitToUnitVoiceChannelUser; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCAuthenticationResponse; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCIndividualDataServiceRequest; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCLocationRegistrationRequest; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCMessageUpdateRequest; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCStatusQueryResponse; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCStatusUpdateRequest; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCUnitAcknowledgeResponse; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCUnitToUnitVoiceServiceAnswerResponse; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCUnitToUnitVoiceServiceRequest; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCGroupAffiliationResponse; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCGroupDataChannelGrant; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCGroupVoiceChannelGrant; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCIndividualDataChannelGrant; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCMessageUpdate; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCProtectionParameterBroadcast; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCStatusUpdate; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCTelephoneInterconnectChannelGrant; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCTelephoneInterconnectChannelGrantUpdate; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCUnitRegistrationResponse; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCUnitToUnitVoiceServiceChannelGrant; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCUnitToUnitVoiceServiceChannelGrantUpdate; +import io.github.dsheirer.module.decode.p25.message.pdu.packet.PacketMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.packet.sndcp.SNDCPMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.packet.sndcp.SNDCPPacketMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.umbtc.isp.UMBTCTelephoneInterconnectRequestExplicitDialing; +import io.github.dsheirer.module.decode.p25.message.tdu.TDULinkControlMessage; import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.MotorolaTSBKMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.PatchGroupAdd; -import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.PatchGroupDelete; -import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.PatchGroupVoiceChannelGrant; -import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.PatchGroupVoiceChannelGrantUpdate; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.AcknowledgeResponse; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.AdjacentStatusBroadcast; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.AuthenticationCommand; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.DenyResponse; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.GroupAffiliationResponse; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.IdentifierUpdate; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.LocationRegistrationResponse; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.NetworkStatusBroadcast; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.ProtectionParameterUpdate; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.QueuedResponse; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.RFSSStatusBroadcast; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.RadioUnitMonitorCommand; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.RoamingAddressCommand; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SecondaryControlChannelBroadcast; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.UnitDeregistrationAcknowledge; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.UnitRegistrationResponse; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.data.GroupDataChannelAnnouncement; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.data.GroupDataChannelAnnouncementExplicit; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.data.GroupDataChannelGrant; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.data.IndividualDataChannelGrant; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.data.SNDCPDataChannelAnnouncementExplicit; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.data.SNDCPDataChannelGrant; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.data.SNDCPDataPageRequest; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice.GroupVoiceChannelGrant; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice.GroupVoiceChannelGrantUpdate; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice.GroupVoiceChannelGrantUpdateExplicit; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice.TelephoneInterconnectVoiceChannelGrant; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice.TelephoneInterconnectVoiceChannelGrantUpdate; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice.UnitToUnitVoiceChannelGrant; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice.UnitToUnitVoiceChannelGrantUpdate; -import io.github.dsheirer.module.decode.p25.reference.IPProtocol; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import io.github.dsheirer.module.decode.p25.reference.Response; -import io.github.dsheirer.module.decode.p25.reference.Vendor; +import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp.PatchGroupVoiceChannelGrant; +import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp.PatchGroupVoiceChannelGrantUpdate; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.CancelServiceRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.ExtendedFunctionResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.GroupAffiliationQueryResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.GroupDataServiceRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.GroupVoiceServiceRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.IndividualDataServiceRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.LocationRegistrationRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.MessageUpdateRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.SNDCPDataChannelRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.SNDCPDataPageResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.SNDCPReconnectRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.StatusQueryResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.StatusUpdateRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.TelephoneInterconnectAnswerResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.UnitAcknowledgeResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.UnitRegistrationRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.UnitToUnitVoiceServiceAnswerResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.UnitToUnitVoiceServiceRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.AcknowledgeResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.DenyResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.ExtendedFunctionCommand; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.GroupAffiliationResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.GroupDataChannelGrant; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.GroupVoiceChannelGrant; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.GroupVoiceChannelGrantUpdate; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.GroupVoiceChannelGrantUpdateExplicit; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.LocationRegistrationResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.MessageUpdate; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.QueuedResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.RoamingAddressCommand; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.SNDCPDataChannelGrant; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.StatusUpdate; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.TelephoneInterconnectAnswerRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.TelephoneInterconnectVoiceChannelGrant; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.TelephoneInterconnectVoiceChannelGrantUpdate; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.UnitRegistrationResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.UnitToUnitVoiceChannelGrant; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.UnitToUnitVoiceChannelGrantUpdate; +import io.github.dsheirer.module.decode.p25.network.P25NetworkConfigurationMonitor; +import io.github.dsheirer.module.decode.p25.reference.Encryption; +import io.github.dsheirer.sample.Listener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; - -public class P25DecoderState extends DecoderState +/** + * Decoder state for an APCO25 channel. Maintains the call/data/idle state of the channel and produces events by + * monitoring the decoded message stream. + * + */ +public class P25DecoderState extends DecoderState implements IChannelEventListener { private final static Logger mLog = LoggerFactory.getLogger(P25DecoderState.class); - private static final DecimalFormat mFrequencyFormatter = - new DecimalFormat("0.000000"); - - - private 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 String mLastCommandEventID; - private String mLastPageEventID; - private String mLastQueryEventID; - private String mLastRegistrationEventID; - private String mLastResponseEventID; - - //TODO: create multi-attribute monitor for NAC and System - private String mNAC; - private String mSystem; - private AliasedStringAttributeMonitor mSiteAttributeMonitor; - private AliasedStringAttributeMonitor mFromTalkgroupMonitor; - private AliasedStringAttributeMonitor mToTalkgroupMonitor; - private String mCurrentChannel = "CURRENT"; - private long mCurrentChannelFrequency = 0; private ChannelType mChannelType; private P25Decoder.Modulation mModulation; - private boolean mIgnoreDataCalls; - private boolean mControlChannelShutdownLogged; + private PatchGroupManager mPatchGroupManager = new PatchGroupManager(); + private P25NetworkConfigurationMonitor mNetworkConfigurationMonitor; + private P25TrafficChannelManager mTrafficChannelManager; + private Listener mChannelEventListener; + private DecodeEvent mCurrentCallEvent; - private P25CallEvent mCurrentCallEvent; - private List mCallDetectTalkgroups = new ArrayList<>(); - private Map mChannelCallMap = new HashMap<>(); - private PatchGroupManager mPatchGroupManager; - - public P25DecoderState(AliasList aliasList, - ChannelType channelType, - P25Decoder.Modulation modulation, - boolean ignoreDataCalls) + /** + * Constructs an APCO-25 decoder state with an optional traffic channel manager. + * @param channel with configuration details + * @param trafficChannelManager for handling traffic channel grants. + */ + public P25DecoderState(Channel channel, P25TrafficChannelManager trafficChannelManager) { - super(aliasList); - mChannelType = channelType; - mModulation = modulation; - mIgnoreDataCalls = ignoreDataCalls; + mChannelType = channel.getChannelType(); + mModulation = ((DecodeConfigP25Phase1)channel.getDecodeConfiguration()).getModulation(); + mNetworkConfigurationMonitor = new P25NetworkConfigurationMonitor(mModulation); - mPatchGroupManager = new PatchGroupManager(aliasList, getCallEventBroadcaster()); - mSiteAttributeMonitor = new AliasedStringAttributeMonitor(Attribute.NETWORK_ID_2, - getAttributeChangeRequestListener(), getAliasList(), AliasIDType.SITE); - mFromTalkgroupMonitor = new AliasedStringAttributeMonitor(Attribute.PRIMARY_ADDRESS_FROM, - getAttributeChangeRequestListener(), getAliasList(), AliasIDType.TALKGROUP); - mFromTalkgroupMonitor.addIllegalValue("000000"); - mToTalkgroupMonitor = new AliasedStringAttributeMonitor(Attribute.PRIMARY_ADDRESS_TO, - getAttributeChangeRequestListener(), getAliasList(), AliasIDType.TALKGROUP); - mToTalkgroupMonitor.addIllegalValue("0000"); - mToTalkgroupMonitor.addIllegalValue("000000"); + if(trafficChannelManager != null) + { + mTrafficChannelManager = trafficChannelManager; + mChannelEventListener = trafficChannelManager.getChannelEventListener(); + } + else + { + mChannelEventListener = channelEvent -> { + //do nothing with channel events if we're not configured to process traffic channels + }; + } } + /** + * Constructs an APCO-25 decoder state for a traffic channel. + * @param channel with configuration details + */ + public P25DecoderState(Channel channel) + { + this(channel, null); + } + + /** + * Modulation type for the decoder + */ public P25Decoder.Modulation getModulation() { return mModulation; } + /** + * Identifies the decoder type + */ @Override public DecoderType getDecoderType() { return DecoderType.P25_PHASE1; } + /** + * Implements the IChannelEventListener interface to receive traffic channel teardown notifications so that the + * traffic channel manager can manage traffic channel allocations. + */ + @Override + public Listener getChannelEventListener() + { + return mChannelEventListener; + } + /** * Performs a full reset to prepare this object for reuse on a new channel */ @@ -229,3134 +214,2171 @@ public class P25DecoderState extends DecoderState public void reset() { resetState(); - - mNAC = null; - mSiteAttributeMonitor.reset(); - mSystem = null; } - @Override - public void init() - { - - } - - /** +/** * Resets any temporal state details */ - private void resetState() + protected void resetState() { - mFromTalkgroupMonitor.reset(); - mToTalkgroupMonitor.reset(); - - mCallDetectTalkgroups.clear(); + super.resetState(); if(mCurrentCallEvent != null) { - mCurrentCallEvent.end(); + mCurrentCallEvent.end(System.currentTimeMillis()); broadcast(mCurrentCallEvent); - } - - mCurrentCallEvent = null; - } - - /** - * 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) - { - String channel = event.getChannel(); - - 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); - } + mCurrentCallEvent = null; } } /** * Primary message processing method. */ - public void receive(Message message) + @Override + public void receive(IMessage iMessage) { - if(message instanceof P25Message) + if(iMessage instanceof P25Message) { - updateNAC(((P25Message)message).getNAC()); + P25Message message = (P25Message)iMessage; - /* Voice Vocoder messages */ - if(message instanceof LDUMessage) - { - processLDU((LDUMessage)message); - } + getIdentifierCollection().update(message.getNAC()); - /* Trunking Signalling Messages */ - else if(message instanceof TSBKMessage) + switch(message.getDUID()) { - processTSBK((TSBKMessage)message); - } - - /* Terminator Data Unit with Link Control Message */ - else if(message instanceof TDULinkControlMessage) - { - processTDULC((TDULinkControlMessage)message); - } - - /* Packet Data Unit Messages */ - else if(message instanceof PDUMessage) - { - processPDU((PDUMessage)message); - } - - /* Header Data Unit Message - preceeds voice LDUs */ - 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); + case ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL: + processAMBTC(message); + break; + case HEADER_DATA_UNIT: + processHDU((HDUMessage)message); + break; + case IP_PACKET_DATA: + processPacketData(message); + break; + case LOGICAL_LINK_DATA_UNIT_1: + case LOGICAL_LINK_DATA_UNIT_2: + processLDU(message); + break; + case PACKET_DATA_UNIT: + processPDU(message); + break; + case SUBNETWORK_DEPENDENT_CONVERGENCE_PROTOCOL: + processSNDCP(message); + break; + case TERMINATOR_DATA_UNIT: + processTDU(message); + break; + case TERMINATOR_DATA_UNIT_LINK_CONTROL: + processTDULC(message); + break; + case TRUNKING_SIGNALING_BLOCK_1: + case TRUNKING_SIGNALING_BLOCK_2: + case TRUNKING_SIGNALING_BLOCK_3: + processTSBK(message); + break; + case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL: + processUMBTC(message); + break; + case UNKNOWN: + break; } } } /** - * Terminator Data Unit + * Alternate Multi-Block Trunking Control (AMBTC) + * + * @param message */ - private void processTDU(TDUMessage tdu) + private void processAMBTC(P25Message message) { - if(mChannelType == ChannelType.STANDARD) + if(message instanceof AMBTCMessage && message.isValid()) { - broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE)); - } - } + AMBTCMessage ambtc = (AMBTCMessage)message; - /** - * 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(); - - mToTalkgroupMonitor.process(to); - - if(mCurrentCallEvent == null) + switch(ambtc.getHeader().getOpcode()) { - mCurrentCallEvent = new P25CallEvent.Builder(CallEvent.CallEventType.CALL) - .aliasList(getAliasList()) - .channel(mCurrentChannel) - .details((hdu.isEncryptedAudio() ? "ENCRYPTED " : "")) - .frequency(mCurrentChannelFrequency) - .to(to) - .build(); + case ISP_AUTHENTICATION_RESPONSE: + if(message instanceof AMBTCAuthenticationResponse) + { + AMBTCAuthenticationResponse ar = (AMBTCAuthenticationResponse)ambtc; - broadcast(mCurrentCallEvent); - } + MutableIdentifierCollection icAuthenticationResponse = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icAuthenticationResponse.remove(IdentifierClass.USER); + icAuthenticationResponse.update(ambtc.getIdentifiers()); - } - } + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details("AUTHENTICATION:" + ar.getAuthenticationValue()) + .identifiers(icAuthenticationResponse) + .build()); + } + break; + case ISP_CALL_ALERT_REQUEST: + MutableIdentifierCollection icCallAlertRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icCallAlertRequest.remove(IdentifierClass.USER); + icCallAlertRequest.update(ambtc.getIdentifiers()); - /** - * 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(mCurrentCallEvent != null) - { - mCurrentCallEvent.end(); - broadcast(mCurrentCallEvent); - mCurrentCallEvent = null; - } - - if(tdulc.getOpcode() == LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION) - { - broadcast(new DecoderStateEvent(this, Event.END, State.FADE)); - - } - else - { - broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE)); - } - - 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 CallAlert) - { - CallAlert ca = - (CallAlert)tdulc; - - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.PAGE) - .aliasList(getAliasList()) - .from(ca.getSourceAddress()) - .to(ca.getTargetAddress()) + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) .details("CALL ALERT") + .identifiers(icCallAlertRequest) .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: - /* This message is handled by the P25MessageProcessor and - * inserted into any channels needing frequency band info */ - break; - case CHANNEL_IDENTIFIER_UPDATE_EXPLICIT: - /* This message is handled by the P25MessageProcessor and - * inserted into any channels needing frequency band info */ - break; - case EXTENDED_FUNCTION_COMMAND: - if(tdulc instanceof ExtendedFunctionCommand) - { - ExtendedFunctionCommand efc = (ExtendedFunctionCommand)tdulc; + break; + case ISP_GROUP_AFFILIATION_REQUEST: + MutableIdentifierCollection icGroupAffiliationRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icGroupAffiliationRequest.remove(IdentifierClass.USER); + icGroupAffiliationRequest.update(ambtc.getIdentifiers()); - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.COMMAND) - .aliasList(getAliasList()) - .to(efc.getTargetAddress()) - .details("FUNCTION:" + efc.getExtendedFunction().getLabel() + - " ARG:" + efc.getArgument()) + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("GROUP AFFILIATION") + .identifiers(icGroupAffiliationRequest) .build()); - } - else - { - logAlternateVendorMessage(tdulc); - } - break; - case GROUP_AFFILIATION_QUERY: - if(tdulc instanceof GroupAffiliationQuery) - { - GroupAffiliationQuery gaq = - (GroupAffiliationQuery)tdulc; - - broadcast(new P25CallEvent.Builder(CallEvent.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; - - String groupA = gvcu.getGroupAddressA(); - - if(!mCallDetectTalkgroups.contains(groupA)) + break; + case ISP_INDIVIDUAL_DATA_SERVICE_REQUEST: + if(ambtc instanceof AMBTCIndividualDataServiceRequest) { - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.CALL_DETECT) - .aliasList(getAliasList()) - .channel(gvcu.getChannelA()) - .details((gvcu.isEncrypted() ? "ENCRYPTED" : "")) - .frequency(gvcu.getDownlinkFrequencyA()) - .to(groupA) + AMBTCIndividualDataServiceRequest idsr = (AMBTCIndividualDataServiceRequest)ambtc; + + MutableIdentifierCollection icDataRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icDataRequest.remove(IdentifierClass.USER); + icDataRequest.update(ambtc.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("INDIVIDUAL DATA SERVICE " + idsr.getDataServiceOptions()) + .identifiers(icDataRequest) .build()); - - mCallDetectTalkgroups.add(groupA); } - - String groupB = gvcu.getGroupAddressB(); - - if(!mCallDetectTalkgroups.contains(groupB)) + break; + case ISP_LOCATION_REGISTRATION_REQUEST: + if(ambtc instanceof AMBTCLocationRegistrationRequest) { - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.CALL_DETECT) - .aliasList(getAliasList()) - .channel(gvcu.getChannelB()) - .details((gvcu.isEncrypted() ? "ENCRYPTED" : "")) - .frequency(gvcu.getDownlinkFrequencyB()) - .to(groupB) + AMBTCLocationRegistrationRequest lrr = (AMBTCLocationRegistrationRequest)ambtc; + + MutableIdentifierCollection icLocationRegistrationRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icLocationRegistrationRequest.remove(IdentifierClass.USER); + icLocationRegistrationRequest.update(ambtc.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("LOCATION REGISTRATION - UNIQUE ID:" + lrr.getSourceId()) + .identifiers(icLocationRegistrationRequest) .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)) + break; + case ISP_MESSAGE_UPDATE_REQUEST: + if(ambtc instanceof AMBTCMessageUpdateRequest) { - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.CALL_DETECT) - .aliasList(getAliasList()) - .channel(gvcue.getTransmitChannel()) - .details((gvcue.isEncrypted() ? "ENCRYPTED" : "")) - .frequency(gvcue.getDownlinkFrequency()) - .to(group) + AMBTCMessageUpdateRequest mur = (AMBTCMessageUpdateRequest)ambtc; + + MutableIdentifierCollection icMessageUpdateRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icMessageUpdateRequest.remove(IdentifierClass.USER); + icMessageUpdateRequest.update(ambtc.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.SDM.toString()) + .details("MESSAGE:" + mur.getShortDataMessage()) + .identifiers(icMessageUpdateRequest) .build()); - - mCallDetectTalkgroups.add(group); } - } - break; - case GROUP_VOICE_CHANNEL_USER: - //Used on a traffic channel to reflect that the channel is currently being used by the - //TO talkgroup, but that channel is currently quiet (ie no voice) */ - if(tdulc instanceof GroupVoiceChannelUser) - { - GroupVoiceChannelUser gvcuser = (GroupVoiceChannelUser)tdulc; + break; + case ISP_ROAMING_ADDRESS_REQUEST: + MutableIdentifierCollection icRoamingAddressRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icRoamingAddressRequest.remove(IdentifierClass.USER); + icRoamingAddressRequest.update(ambtc.getIdentifiers()); - broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE)); - - //The from value should always be "000000" indicating that nobody is currently talking - mFromTalkgroupMonitor.reset(); - - String to = gvcuser.getGroupAddress(); - mToTalkgroupMonitor.process(to); - } - break; - case MESSAGE_UPDATE: - if(tdulc instanceof MessageUpdate) - { - MessageUpdate mu = (MessageUpdate)tdulc; - - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.SDM) - .aliasList(getAliasList()) - .from(mu.getSourceAddress()) - .to(mu.getTargetAddress()) - .details("MSG: " + mu.getShortDataMessage()) + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("ROAMING ADDRESS") + .identifiers(icRoamingAddressRequest) .build()); - } - else - { - logAlternateVendorMessage(tdulc); - } - break; - case NETWORK_STATUS_BROADCAST: - if(tdulc instanceof io.github.dsheirer.module.decode.p25.message.tdu.lc.NetworkStatusBroadcast) - { - updateSystem(((io.github.dsheirer.module.decode.p25.message.tdu.lc.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 io.github.dsheirer.module.decode.p25.message.tdu.lc.ProtectionParameterBroadcast) - { - io.github.dsheirer.module.decode.p25.message.tdu.lc.ProtectionParameterBroadcast ppb = - (io.github.dsheirer.module.decode.p25.message.tdu.lc.ProtectionParameterBroadcast)tdulc; + break; + case ISP_STATUS_QUERY_REQUEST: + MutableIdentifierCollection icStatusQuery = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icStatusQuery.remove(IdentifierClass.USER); + icStatusQuery.update(ambtc.getIdentifiers()); - broadcast(new P25CallEvent.Builder(CallEvent.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 io.github.dsheirer.module.decode.p25.message.tdu.lc.RFSSStatusBroadcast) - { - io.github.dsheirer.module.decode.p25.message.tdu.lc.RFSSStatusBroadcast rfsssb = - (io.github.dsheirer.module.decode.p25.message.tdu.lc.RFSSStatusBroadcast)tdulc; - - updateSystem(rfsssb.getSystem()); - - String site = rfsssb.getRFSubsystemID() + "-" + - rfsssb.getSiteID(); - - mSiteAttributeMonitor.process(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 RFSSStatusBroadcastExplicit) - { - RFSSStatusBroadcastExplicit rfsssbe = - (RFSSStatusBroadcastExplicit)tdulc; - - String site = rfsssbe.getRFSubsystemID() + "-" + - rfsssbe.getSiteID(); - - mSiteAttributeMonitor.process(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 io.github.dsheirer.module.decode.p25.message.tdu.lc.SecondaryControlChannelBroadcast) - { - io.github.dsheirer.module.decode.p25.message.tdu.lc.SecondaryControlChannelBroadcast sccb = - (io.github.dsheirer.module.decode.p25.message.tdu.lc.SecondaryControlChannelBroadcast)tdulc; - - String site = sccb.getRFSubsystemID() + "-" + sccb.getSiteID(); - - mSiteAttributeMonitor.process(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(); - - mSiteAttributeMonitor.process(site); - } - else - { - logAlternateVendorMessage(tdulc); - } - break; - case STATUS_QUERY: - if(tdulc instanceof StatusQuery) - { - StatusQuery sq = (StatusQuery)tdulc; - - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.QUERY) - .aliasList(getAliasList()) + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) .details("STATUS QUERY") - .from(sq.getSourceAddress()) - .to(sq.getTargetAddress()) + .identifiers(icStatusQuery) .build()); - } - else - { - logAlternateVendorMessage(tdulc); - } - break; - case STATUS_UPDATE: - if(tdulc instanceof StatusUpdate) - { - StatusUpdate su = (StatusUpdate)tdulc; - - broadcast(new P25CallEvent.Builder(CallEvent.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 TelephoneInterconnectAnswerRequest) - { - TelephoneInterconnectAnswerRequest tiar = (TelephoneInterconnectAnswerRequest)tdulc; - - broadcast(new P25CallEvent.Builder(CallEvent.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 TelephoneInterconnectVoiceChannelUser) - { - TelephoneInterconnectVoiceChannelUser tivcu = (TelephoneInterconnectVoiceChannelUser)tdulc; - - String to = tivcu.getAddress(); - - mToTalkgroupMonitor.process(to); - - if(mCurrentCallEvent == null) + break; + case ISP_STATUS_QUERY_RESPONSE: + if(ambtc instanceof AMBTCStatusQueryResponse) { - mCurrentCallEvent = new P25CallEvent.Builder(CallEvent.CallEventType.TELEPHONE_INTERCONNECT) - .aliasList(getAliasList()) - .channel(mCurrentChannel) - .details((tivcu.isEncrypted() ? "ENCRYPTED" : "") + - (tivcu.isEmergency() ? " EMERGENCY" : "")) - .frequency(mCurrentChannelFrequency) - .to(to) - .build(); + MutableIdentifierCollection icStatusQueryResponse = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icStatusQueryResponse.remove(IdentifierClass.USER); + icStatusQueryResponse.update(ambtc.getIdentifiers()); - broadcast(mCurrentCallEvent); + AMBTCStatusQueryResponse sqr = (AMBTCStatusQueryResponse)ambtc; + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.STATUS.toString()) + .details("UNIT:" + sqr.getUnitStatus() + " USER:" + sqr.getUserStatus()) + .identifiers(icStatusQueryResponse) + .build()); } - } - break; - case UNIT_AUTHENTICATION_COMMAND: - if(tdulc instanceof UnitAuthenticationCommand) - { - UnitAuthenticationCommand uac = (UnitAuthenticationCommand)tdulc; + break; + case ISP_STATUS_UPDATE_REQUEST: + if(ambtc instanceof AMBTCStatusUpdateRequest) + { + AMBTCStatusUpdateRequest sur = (AMBTCStatusUpdateRequest)ambtc; - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.COMMAND) - .aliasList(getAliasList()) - .to(uac.getCompleteTargetAddress()) - .details("AUTHENTICATE") + MutableIdentifierCollection icStatusUpdateRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icStatusUpdateRequest.remove(IdentifierClass.USER); + icStatusUpdateRequest.update(ambtc.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.STATUS.toString()) + .details("UNIT:" + sur.getUnitStatus() + " USER:" + sur.getUserStatus()) + .identifiers(icStatusUpdateRequest) + .build()); + } + break; + case ISP_UNIT_ACKNOWLEDGE_RESPONSE: + if(ambtc instanceof AMBTCUnitAcknowledgeResponse) + { + AMBTCUnitAcknowledgeResponse uar = (AMBTCUnitAcknowledgeResponse)ambtc; + + MutableIdentifierCollection icUnitAcknowledge = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icUnitAcknowledge.remove(IdentifierClass.USER); + icUnitAcknowledge.update(ambtc.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details("ACKNOWLEDGE:" + uar.getAcknowledgedService()) + .identifiers(icUnitAcknowledge) + .build()); + } + break; + case ISP_UNIT_TO_UNIT_VOICE_SERVICE_REQUEST: + if(ambtc instanceof AMBTCUnitToUnitVoiceServiceRequest) + { + AMBTCUnitToUnitVoiceServiceRequest uuvsr = (AMBTCUnitToUnitVoiceServiceRequest)ambtc; + + MutableIdentifierCollection icUnitVoiceService = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icUnitVoiceService.remove(IdentifierClass.USER); + icUnitVoiceService.update(ambtc.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("UNIT-2-UNIT VOICE SERVICE " + uuvsr.getVoiceServiceOptions()) + .identifiers(icUnitVoiceService) + .build()); + } + break; + case ISP_UNIT_TO_UNIT_ANSWER_RESPONSE: + if(ambtc instanceof AMBTCUnitToUnitVoiceServiceAnswerResponse) + { + AMBTCUnitToUnitVoiceServiceAnswerResponse uuvsar = (AMBTCUnitToUnitVoiceServiceAnswerResponse)ambtc; + + MutableIdentifierCollection icUnitAnswerResponse = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icUnitAnswerResponse.remove(IdentifierClass.USER); + icUnitAnswerResponse.update(ambtc.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details(uuvsar.getAnswerResponse() + " UNIT-2-UNIT VOICE SERVICE " + + uuvsar.getVoiceServiceOptions()) + .identifiers(icUnitAnswerResponse) + .build()); + } + break; + + //Network configuration messages + case OSP_ADJACENT_STATUS_BROADCAST: + mNetworkConfigurationMonitor.process(ambtc); + break; + + //Channel grants + case OSP_GROUP_DATA_CHANNEL_GRANT: + if(ambtc instanceof AMBTCGroupDataChannelGrant) + { + AMBTCGroupDataChannelGrant gdcg = (AMBTCGroupDataChannelGrant)ambtc; + + if(mTrafficChannelManager != null) + { + MutableIdentifierCollection identifierCollection = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifierCollection.remove(IdentifierClass.USER); + identifierCollection.update(gdcg.getIdentifiers()); + + mTrafficChannelManager.processChannelGrant(gdcg.getChannel(), gdcg.getDataServiceOptions(), + identifierCollection, ambtc.getHeader().getOpcode(), + ambtc.getTimestamp()); + } + } + break; + case OSP_GROUP_VOICE_CHANNEL_GRANT: + if(ambtc instanceof AMBTCGroupVoiceChannelGrant) + { + AMBTCGroupVoiceChannelGrant gvcg = (AMBTCGroupVoiceChannelGrant)ambtc; + + MutableIdentifierCollection identifierCollection = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifierCollection.remove(IdentifierClass.USER); + identifierCollection.update(gvcg.getIdentifiers()); + + if(mTrafficChannelManager != null) + { + mTrafficChannelManager.processChannelGrant(gvcg.getChannel(), gvcg.getVoiceServiceOptions(), + identifierCollection, ambtc.getHeader().getOpcode(), + ambtc.getTimestamp()); + } + } + break; + case OSP_INDIVIDUAL_DATA_CHANNEL_GRANT: + if(ambtc instanceof AMBTCIndividualDataChannelGrant) + { + AMBTCIndividualDataChannelGrant idcg = (AMBTCIndividualDataChannelGrant)ambtc; + + MutableIdentifierCollection identifierCollection = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifierCollection.remove(IdentifierClass.USER); + identifierCollection.update(idcg.getIdentifiers()); + + if(mTrafficChannelManager != null) + { + mTrafficChannelManager.processChannelGrant(idcg.getChannel(), idcg.getDataServiceOptions(), + identifierCollection, ambtc.getHeader().getOpcode(), + ambtc.getTimestamp()); + } + } + break; + case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: + if(ambtc instanceof AMBTCTelephoneInterconnectChannelGrant) + { + AMBTCTelephoneInterconnectChannelGrant ticg = (AMBTCTelephoneInterconnectChannelGrant)ambtc; + + if(mTrafficChannelManager != null) + { + MutableIdentifierCollection identifierCollection = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifierCollection.remove(IdentifierClass.USER); + identifierCollection.update(ticg.getIdentifiers()); + + mTrafficChannelManager.processChannelGrant(ticg.getChannel(), ticg.getVoiceServiceOptions(), + identifierCollection, ambtc.getHeader().getOpcode(), + ambtc.getTimestamp()); + } + } + break; + case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: + if(ambtc instanceof AMBTCTelephoneInterconnectChannelGrantUpdate) + { + AMBTCTelephoneInterconnectChannelGrantUpdate ticgu = (AMBTCTelephoneInterconnectChannelGrantUpdate)ambtc; + + if(mTrafficChannelManager != null) + { + MutableIdentifierCollection identifierCollection = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifierCollection.remove(IdentifierClass.USER); + identifierCollection.update(ticgu.getIdentifiers()); + + mTrafficChannelManager.processChannelGrant(ticgu.getChannel(), ticgu.getVoiceServiceOptions(), + identifierCollection, ambtc.getHeader().getOpcode(), + ambtc.getTimestamp()); + } + } + break; + case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: + if(ambtc instanceof AMBTCUnitToUnitVoiceServiceChannelGrant) + { + AMBTCUnitToUnitVoiceServiceChannelGrant uuvscg = (AMBTCUnitToUnitVoiceServiceChannelGrant)ambtc; + + if(mTrafficChannelManager != null) + { + MutableIdentifierCollection identifierCollection = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifierCollection.remove(IdentifierClass.USER); + identifierCollection.update(uuvscg.getIdentifiers()); + + mTrafficChannelManager.processChannelGrant(uuvscg.getChannel(), uuvscg.getVoiceServiceOptions(), + identifierCollection, ambtc.getHeader().getOpcode(), + ambtc.getTimestamp()); + } + } + break; + case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: + if(ambtc instanceof AMBTCUnitToUnitVoiceServiceChannelGrantUpdate) + { + AMBTCUnitToUnitVoiceServiceChannelGrantUpdate uuvscgu = (AMBTCUnitToUnitVoiceServiceChannelGrantUpdate)ambtc; + + if(mTrafficChannelManager != null) + { + MutableIdentifierCollection identifierCollection = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifierCollection.remove(IdentifierClass.USER); + identifierCollection.update(uuvscgu.getIdentifiers()); + + mTrafficChannelManager.processChannelGrant(uuvscgu.getChannel(), uuvscgu.getVoiceServiceOptions(), + identifierCollection, ambtc.getHeader().getOpcode(), + ambtc.getTimestamp()); + } + } + break; + case OSP_UNIT_TO_UNIT_ANSWER_REQUEST: + MutableIdentifierCollection icUUAnswerRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icUUAnswerRequest.remove(IdentifierClass.USER); + icUUAnswerRequest.update(ambtc.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.PAGE.toString()) + .details("ANSWER REQUEST") + .identifiers(icUUAnswerRequest) .build()); - } - else - { - logAlternateVendorMessage(tdulc); - } - break; - case UNIT_REGISTRATION_COMMAND: - if(tdulc instanceof UnitRegistrationCommand) - { - UnitRegistrationCommand urc = (UnitRegistrationCommand)tdulc; + case OSP_CALL_ALERT: + MutableIdentifierCollection icCallAlert = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icCallAlert.remove(IdentifierClass.USER); + icCallAlert.update(ambtc.getIdentifiers()); - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.COMMAND) - .aliasList(getAliasList()) - .to(urc.getCompleteTargetAddress()) - .details("REGISTER") + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.PAGE.toString()) + .details("CALL ALERT") + .identifiers(icCallAlert) .build()); - } - else - { - logAlternateVendorMessage(tdulc); - } - break; - case UNIT_TO_UNIT_ANSWER_REQUEST: - if(tdulc instanceof UnitToUnitAnswerRequest) - { - UnitToUnitAnswerRequest uuar = (UnitToUnitAnswerRequest)tdulc; + break; + case OSP_GROUP_AFFILIATION_QUERY: + MutableIdentifierCollection icGroupAffiliationQuery = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icGroupAffiliationQuery.remove(IdentifierClass.USER); + icGroupAffiliationQuery.update(ambtc.getIdentifiers()); - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.PAGE) - .aliasList(getAliasList()) - .from(uuar.getSourceAddress()) - .to(uuar.getTargetAddress()) - .details("UNIT TO UNIT CALL ALERT") + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.QUERY.toString()) + .details("GROUP AFFILIATION") + .identifiers(icGroupAffiliationQuery) .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 UnitToUnitVoiceChannelUser) - { - UnitToUnitVoiceChannelUser uuvcu = (UnitToUnitVoiceChannelUser)tdulc; + break; + case OSP_GROUP_AFFILIATION_RESPONSE: + if(ambtc instanceof AMBTCGroupAffiliationResponse) + { + AMBTCGroupAffiliationResponse gar = (AMBTCGroupAffiliationResponse)ambtc; - mFromTalkgroupMonitor.reset(); + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(ambtc.getIdentifiers()); - String to = uuvcu.getTargetAddress(); - mToTalkgroupMonitor.process(to); - } - break; - default: - break; + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details("AFFILIATION GROUP:" + gar.getGroupId() + + " ANNOUNCEMENT GROUP:" + gar.getAnnouncementGroupId()) + .identifiers(ic) + .build()); + } + break; + case OSP_MESSAGE_UPDATE: + if(ambtc instanceof AMBTCMessageUpdate) + { + AMBTCMessageUpdate mu = (AMBTCMessageUpdate)ambtc; + + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(ambtc.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.SDM.toString()) + .details("MESSAGE:" + mu.getShortDataMessage()) + .identifiers(ic) + .build()); + } + break; + case OSP_PROTECTION_PARAMETER_BROADCAST: + if(ambtc instanceof AMBTCProtectionParameterBroadcast) + { + AMBTCProtectionParameterBroadcast ppb = (AMBTCProtectionParameterBroadcast)ambtc; + + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(ambtc.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details("USE ENCRYPTION " + ppb.getEncryptionKey() + + " OUTBOUND MI:" + ppb.getOutboundMessageIndicator() + + " INBOUND MI:" + ppb.getInboundMessageIndicator()) + .identifiers(ic) + .build()); + } + break; + case OSP_ROAMING_ADDRESS_UPDATE: + MutableIdentifierCollection icRoamingAddressUpdate = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icRoamingAddressUpdate.remove(IdentifierClass.USER); + icRoamingAddressUpdate.update(ambtc.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details("ROAMING ADDRESS UPDATE") + .identifiers(icRoamingAddressUpdate) + .build()); + break; + case OSP_ROAMING_ADDRESS_COMMAND: + MutableIdentifierCollection icRoamingAddressCommand = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icRoamingAddressCommand.remove(IdentifierClass.USER); + icRoamingAddressCommand.update(ambtc.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.COMMAND.toString()) + .details("ROAMING ADDRESS") + .identifiers(icRoamingAddressCommand) + .build()); + break; + case OSP_STATUS_QUERY: + MutableIdentifierCollection icOStatusQuery = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icOStatusQuery.remove(IdentifierClass.USER); + icOStatusQuery.update(ambtc.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.QUERY.toString()) + .details("STATUS") + .identifiers(icOStatusQuery) + .build()); + break; + case OSP_STATUS_UPDATE: + if(ambtc instanceof AMBTCStatusUpdate) + { + AMBTCStatusUpdate su = (AMBTCStatusUpdate)ambtc; + + MutableIdentifierCollection icStatusUpdate = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icStatusUpdate.remove(IdentifierClass.USER); + icStatusUpdate.update(ambtc.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.STATUS.toString()) + .details("UNIT:" + su.getUnitStatus() + " USER:" + su.getUserStatus()) + .identifiers(icStatusUpdate) + .build()); + } + break; + case OSP_UNIT_REGISTRATION_RESPONSE: + if(ambtc instanceof AMBTCUnitRegistrationResponse) + { + AMBTCUnitRegistrationResponse urr = (AMBTCUnitRegistrationResponse)ambtc; + + MutableIdentifierCollection icUnitRegistrationResponse = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icUnitRegistrationResponse.remove(IdentifierClass.USER); + icUnitRegistrationResponse.update(ambtc.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(ambtc.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REGISTER.toString()) + .details(urr.getResponse() + " UNIT REGISTRATION") + .identifiers(icUnitRegistrationResponse) + .build()); + } + break; + default: + mLog.debug("Unrecognized AMBTC Opcode: " + ambtc.getHeader().getOpcode().name()); + break; + } } + + broadcast(new DecoderStateEvent(this, Event.DECODE, State.CONTROL)); } /** - * Log optional vendor format messages that we don't yet support so that we - * can understand those messages and eventually add support. + * Processes a Header Data Unit message and starts a new call event. */ - private void logAlternateVendorMessage(P25Message message) + private void processHDU(HDUMessage message) { -// if( message.isValid() ) -// { -// mLog.info( "PLEASE NOTIFY DEVELOPER - UNRECOGNIZED P25 VENDOR FORMAT -" -// + " DUID:" + message.getDUID() -// + " MSG:" + message.getBinaryMessage() -// + " CLASS:" + message.getClass() ); -// } + if(message.isValid()) + { + HeaderData headerData = message.getHeaderData(); + + if(headerData.isValid()) + { + closeCurrentCallEvent(message.getTimestamp()); + + for(Identifier identifier : headerData.getIdentifiers()) + { + //Add to the identifier collection after filtering through the patch group manager + getIdentifierCollection().update(mPatchGroupManager.update(identifier)); + } + + updateCurrentCall(headerData.isEncryptedAudio() ? DecodeEventType.CALL_ENCRYPTED : + DecodeEventType.CALL, null, message.getTimestamp()); + + return; + } + } + + broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE)); } + /** - * 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. + * Processes an LDU voice message and forwards Link Control and/or Encryption Sync Parameters for + * additional processing. + * + * @param message that is an instance of an LDU1 or LDU2 message */ - private void processLDU(LDUMessage ldu) + private void processLDU(P25Message message) { broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL)); + if(message instanceof LDU1Message) + { + LinkControlWord lcw = ((LDU1Message)message).getLinkControlWord(); + + if(lcw != null && lcw.isValid()) + { + processLinkControl(lcw, message.getTimestamp()); + } + + updateCurrentCall(DecodeEventType.CALL, null, message.getTimestamp()); + } + else if(message instanceof LDU2Message) + { + EncryptionSyncParameters esp = ((LDU2Message)message).getEncryptionSyncParameters(); + + if(esp != null && esp.isValid()) + { + processEncryptionSyncParameters(esp, message.getTimestamp()); + } + + updateCurrentCall(DecodeEventType.CALL, null, message.getTimestamp()); + } + } + + /** + * Processes a Terminator Data Unit with Link Control (TDULC) message and forwards valid + * Link Control Word messages for additional processing. + * + * @param message that is an instance of a TDULC + */ + private void processTDULC(P25Message message) + { + closeCurrentCallEvent(message.getTimestamp()); + broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE)); + + if(message instanceof TDULinkControlMessage) + { + LinkControlWord lcw = ((TDULinkControlMessage)message).getLinkControlWord(); + + if(lcw != null && lcw.isValid()) + { + processLinkControl(lcw, message.getTimestamp()); + } + } + } + + /** + * Updates or creates a current call event. + * + * @param type of call that will be used as an event description + * @param details of the call (optional) + * @param timestamp of the message indicating a call or continuation + */ + private void updateCurrentCall(DecodeEventType type, String details, long timestamp) + { if(mCurrentCallEvent == null) { - mCurrentCallEvent = new P25CallEvent.Builder(CallEvent.CallEventType.CALL) - .aliasList(getAliasList()) - .channel(mCurrentChannel) - .frequency(mCurrentChannelFrequency) + mCurrentCallEvent = P25DecodeEvent.builder(timestamp) + .channel(getCurrentChannel()) + .eventDescription(type.toString()) + .details(details) + .identifiers(getIdentifierCollection().copyOf()) .build(); broadcast(mCurrentCallEvent); - } - - if(ldu instanceof LDU1Message) - { - switch(((LDU1Message)ldu).getOpcode()) - { - case ADJACENT_SITE_STATUS_BROADCAST: - if(ldu instanceof io.github.dsheirer.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 io.github.dsheirer.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 io.github.dsheirer.module.decode.p25.message.ldu.lc.CallAlert) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.CallAlert ca = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.CallAlert)ldu; - broadcast(new P25CallEvent.Builder(CallEvent.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? - - /* This message is handled by the P25MessageProcessor and - * inserted into any channels needing frequency band info */ - break; - case EXTENDED_FUNCTION_COMMAND: - if(ldu instanceof io.github.dsheirer.module.decode.p25.message.ldu.lc.ExtendedFunctionCommand) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.ExtendedFunctionCommand efc = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.ExtendedFunctionCommand)ldu; - - broadcast(new P25CallEvent.Builder(CallEvent.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 io.github.dsheirer.module.decode.p25.message.ldu.lc.GroupAffiliationQuery) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.GroupAffiliationQuery gaq = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.GroupAffiliationQuery)ldu; - - broadcast(new P25CallEvent.Builder(CallEvent.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 io.github.dsheirer.module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate gvcu = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate)ldu; - - String userA = gvcu.getGroupAddressA(); - String userB = gvcu.getGroupAddressB(); - - if(mChannelType == ChannelType.STANDARD) - { - if(!mCallDetectTalkgroups.contains(userA)) - { - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.CALL_DETECT) - .aliasList(getAliasList()) - .channel(gvcu.getChannelA()) - .details((gvcu.isEncryptedLinkControlWord() ? "ENCRYPTED" : "")) - .frequency(gvcu.getDownlinkFrequencyA()) - .to(userA) - .build()); - - mCallDetectTalkgroups.add(userA); - } - - if(userB != null && - !userB.contentEquals("0000") && - !mCallDetectTalkgroups.contains(userB)) - { - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.CALL_DETECT) - .aliasList(getAliasList()) - .channel(gvcu.getChannelB()) - .details((gvcu.isEncryptedLinkControlWord() ? "ENCRYPTED" : "")) - .frequency(gvcu.getDownlinkFrequencyB()) - .to(userB) - .build()); - - mCallDetectTalkgroups.add(userB); - } - } - } - else - { - logAlternateVendorMessage(ldu); - } - break; - case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT: - if(ldu instanceof io.github.dsheirer.module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit gvcue = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit)ldu; - - String group = gvcue.getGroupAddress(); - - if(mChannelType == ChannelType.STANDARD) - { - if(!mCallDetectTalkgroups.contains(group)) - { - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.CALL_DETECT) - .aliasList(getAliasList()) - .channel(gvcue.getTransmitChannel()) - .details((gvcue.isEncryptedLinkControlWord() ? "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 io.github.dsheirer.module.decode.p25.message.ldu.lc.GroupVoiceChannelUser) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.GroupVoiceChannelUser gvcuser = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.GroupVoiceChannelUser)ldu; - - mFromTalkgroupMonitor.process(gvcuser.getSourceAddress()); - mCurrentCallEvent.setFromID(mFromTalkgroupMonitor.getValue()); - - mToTalkgroupMonitor.process(gvcuser.getGroupAddress()); - - String to = mToTalkgroupMonitor.getValue(); - - if(mCurrentCallEvent.getToID() == null || !mCurrentCallEvent.getToID().contentEquals(to)) - { - mCurrentCallEvent.setToID(to); - } - - if(mChannelType == ChannelType.STANDARD) - { - if(mCurrentCallEvent.getCallEventType() != CallEvent.CallEventType.GROUP_CALL) - { - mCurrentCallEvent.setCallEventType(CallEvent.CallEventType.GROUP_CALL); - broadcast(mCurrentCallEvent); - } - - if(mCurrentCallEvent.getDetails() == null) - { - mCurrentCallEvent.setDetails( - (gvcuser.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") + - (gvcuser.isEmergency() ? "EMERGENCY " : "")); - - broadcast(mCurrentCallEvent); - } - } - } - else - { - logAlternateVendorMessage(ldu); - } - break; - case MESSAGE_UPDATE: - if(ldu instanceof io.github.dsheirer.module.decode.p25.message.ldu.lc.MessageUpdate) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.MessageUpdate mu = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.MessageUpdate)ldu; - - broadcast(new P25CallEvent.Builder(CallEvent.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 io.github.dsheirer.module.decode.p25.message.ldu.lc.NetworkStatusBroadcast) - { - updateSystem(((io.github.dsheirer.module.decode.p25.message.ldu.lc.NetworkStatusBroadcast)ldu).getSystem()); - } - else - { - logAlternateVendorMessage(ldu); - } - break; - case NETWORK_STATUS_BROADCAST_EXPLICIT: - if(ldu instanceof io.github.dsheirer.module.decode.p25.message.ldu.lc.NetworkStatusBroadcastExplicit) - { - updateSystem(((io.github.dsheirer.module.decode.p25.message.ldu.lc.NetworkStatusBroadcastExplicit)ldu).getSystem()); - } - else - { - logAlternateVendorMessage(ldu); - } - break; - case PROTECTION_PARAMETER_BROADCAST: - if(ldu instanceof io.github.dsheirer.module.decode.p25.message.ldu.lc.ProtectionParameterBroadcast) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.ProtectionParameterBroadcast ppb = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.ProtectionParameterBroadcast)ldu; - - broadcast(new P25CallEvent.Builder(CallEvent.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 io.github.dsheirer.module.decode.p25.message.ldu.lc.RFSSStatusBroadcast) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.RFSSStatusBroadcast rfsssb = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.RFSSStatusBroadcast)ldu; - - updateSystem(rfsssb.getSystem()); - - String site = rfsssb.getRFSubsystemID() + "-" + - rfsssb.getSiteID(); - - mSiteAttributeMonitor.process(site); - } - else - { - logAlternateVendorMessage(ldu); - } - break; - case RFSS_STATUS_BROADCAST_EXPLICIT: - if(ldu instanceof io.github.dsheirer.module.decode.p25.message.ldu.lc.RFSSStatusBroadcastExplicit) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.RFSSStatusBroadcastExplicit rfsssbe = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.RFSSStatusBroadcastExplicit)ldu; - - String site = rfsssbe.getRFSubsystemID() + "-" + - rfsssbe.getSiteID(); - - mSiteAttributeMonitor.process(site); - } - else - { - logAlternateVendorMessage(ldu); - } - break; - case SECONDARY_CONTROL_CHANNEL_BROADCAST: - if(ldu instanceof io.github.dsheirer.module.decode.p25.message.ldu.lc.SecondaryControlChannelBroadcast) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.SecondaryControlChannelBroadcast sccb = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.SecondaryControlChannelBroadcast)ldu; - - String site = sccb.getRFSubsystemID() + "-" + - sccb.getSiteID(); - - mSiteAttributeMonitor.process(site); - } - else - { - logAlternateVendorMessage(ldu); - } - break; - case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: - if(ldu instanceof io.github.dsheirer.module.decode.p25.message.ldu.lc.SecondaryControlChannelBroadcastExplicit) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.SecondaryControlChannelBroadcastExplicit sccb = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.SecondaryControlChannelBroadcastExplicit)ldu; - - String site = sccb.getRFSubsystemID() + "-" + - sccb.getSiteID(); - - mSiteAttributeMonitor.process(site); - } - else - { - logAlternateVendorMessage(ldu); - } - break; - case STATUS_QUERY: - if(ldu instanceof io.github.dsheirer.module.decode.p25.message.ldu.lc.StatusQuery) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.StatusQuery sq = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.StatusQuery)ldu; - - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.QUERY) - .aliasList(getAliasList()) - .details("STATUS QUERY") - .from(sq.getSourceAddress()) - .to(sq.getTargetAddress()) - .build()); - } - else - { - logAlternateVendorMessage(ldu); - } - break; - case STATUS_UPDATE: - if(ldu instanceof io.github.dsheirer.module.decode.p25.message.ldu.lc.StatusUpdate) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.StatusUpdate su = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.StatusUpdate)ldu; - - broadcast(new P25CallEvent.Builder(CallEvent.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 io.github.dsheirer.module.decode.p25.message.ldu.lc.TelephoneInterconnectAnswerRequest) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.TelephoneInterconnectAnswerRequest tiar = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.TelephoneInterconnectAnswerRequest)ldu; - - broadcast(new P25CallEvent.Builder(CallEvent.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 io.github.dsheirer.module.decode.p25.message.ldu.lc.TelephoneInterconnectVoiceChannelUser) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.TelephoneInterconnectVoiceChannelUser tivcu = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.TelephoneInterconnectVoiceChannelUser)ldu; - - mToTalkgroupMonitor.process(tivcu.getAddress()); - - if(mCurrentCallEvent.getCallEventType() != CallEvent.CallEventType.TELEPHONE_INTERCONNECT) - { - mCurrentCallEvent.setCallEventType(CallEvent.CallEventType.TELEPHONE_INTERCONNECT); - broadcast(mCurrentCallEvent); - } - - if(mCurrentCallEvent.getDetails() == null) - { - mCurrentCallEvent.setDetails( - (tivcu.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") + - (tivcu.isEmergency() ? "EMERGENCY " : "")); - - broadcast(mCurrentCallEvent); - } - } - else - { - logAlternateVendorMessage(ldu); - } - break; - case UNIT_AUTHENTICATION_COMMAND: - if(ldu instanceof io.github.dsheirer.module.decode.p25.message.ldu.lc.UnitAuthenticationCommand) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.UnitAuthenticationCommand uac = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.UnitAuthenticationCommand)ldu; - - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.COMMAND) - .aliasList(getAliasList()) - .to(uac.getCompleteTargetAddress()) - .details("AUTHENTICATE") - .build()); - } - else - { - logAlternateVendorMessage(ldu); - } - break; - case UNIT_REGISTRATION_COMMAND: - if(ldu instanceof io.github.dsheirer.module.decode.p25.message.ldu.lc.UnitRegistrationCommand) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.UnitRegistrationCommand urc = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.UnitRegistrationCommand)ldu; - - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.COMMAND) - .aliasList(getAliasList()) - .to(urc.getCompleteTargetAddress()) - .details("REGISTER") - .build()); - } - else - { - logAlternateVendorMessage(ldu); - } - break; - case UNIT_TO_UNIT_ANSWER_REQUEST: - if(ldu instanceof io.github.dsheirer.module.decode.p25.message.ldu.lc.UnitToUnitAnswerRequest) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.UnitToUnitAnswerRequest uuar = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.UnitToUnitAnswerRequest)ldu; - - broadcast(new P25CallEvent.Builder(CallEvent.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 io.github.dsheirer.module.decode.p25.message.ldu.lc.UnitToUnitVoiceChannelUser) - { - io.github.dsheirer.module.decode.p25.message.ldu.lc.UnitToUnitVoiceChannelUser uuvcu = - (io.github.dsheirer.module.decode.p25.message.ldu.lc.UnitToUnitVoiceChannelUser)ldu; - - mFromTalkgroupMonitor.process(uuvcu.getSourceAddress()); - - mToTalkgroupMonitor.process(uuvcu.getTargetAddress()); - - if(mCurrentCallEvent.getCallEventType() != CallEvent.CallEventType.UNIT_TO_UNIT_CALL) - { - mCurrentCallEvent.setCallEventType(CallEvent.CallEventType.UNIT_TO_UNIT_CALL); - broadcast(mCurrentCallEvent); - } - - if(mCurrentCallEvent.getDetails() == null) - { - mCurrentCallEvent.setDetails( - (uuvcu.isEncryptedLinkControlWord() ? "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)); - - 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: - case IDENTIFIER_UPDATE_TDMA: - 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 = (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: - SecondaryControlChannelBroadcast sccb = - (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); - } - } - - /** - * Process a Packet Data Unit message - */ - private void processPDU(PDUMessage pdu) - { - broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA)); - - if(pdu instanceof PacketData || pdu instanceof PDUTypeUnknown) - { - } - else if(pdu instanceof PDUConfirmedMessage) - { - PDUConfirmedMessage pduc = (PDUConfirmedMessage)pdu; - - switch(pduc.getPDUType()) - { - case SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT: - SNDCPActivateTDSContextAccept satca = - (SNDCPActivateTDSContextAccept)pduc; - - broadcast(new P25CallEvent.Builder(CallEvent.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(CallEvent.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(CallEvent.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(CallEvent.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(CallEvent.CallEventType.QUERY) - .aliasList(getAliasList()) - .channel(mCurrentChannel) - .details("REQUEST DEACTIVATE SNDCP CONTEXT") - .frequency(mCurrentChannelFrequency) - .from(sdtcreq.getLogicalLinkID()) - .build()); - break; - case SNDCP_RF_CONFIRMED_DATA: - if(pduc instanceof SNDCPUserData) - { - 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()); - } - - mFromTalkgroupMonitor.reset(); - mFromTalkgroupMonitor.process(sbFrom.toString()); - mToTalkgroupMonitor.reset(); - mToTalkgroupMonitor.process(pduc.getLogicalLinkID()); - - broadcast(new DecoderStateEvent(this, Event.START, State.DATA)); - - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.DATA_CALL) - .aliasList(getAliasList()) - .channel(mCurrentChannel) - .details("DATA: " + sud.getPayload() + - " RADIO IP:" + sbTo.toString()) - .frequency(mCurrentChannelFrequency) - .from(sbFrom.toString()) - .to(pduc.getLogicalLinkID()) - .build()); - } - else - { - mLog.error("Error - expected SNDCPUserData instance but class was: " + pduc.getClass() + - " and PDU Type:" + pduc.getPDUType().name()); - } - break; - case SNDCP_RF_UNCONFIRMED_DATA: - break; - default: -// mLog.debug( "PDUC - Unrecognized Message: " + pduc.toString() ); - break; - } + broadcast(new DecoderStateEvent(this, Event.START, State.CALL)); } else { - switch(pdu.getOpcode()) + if(type == DecodeEventType.CALL_ENCRYPTED) { - 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) + mCurrentCallEvent.setEventDescription(type.toString()); + mCurrentCallEvent.setDetails(details); + } + mCurrentCallEvent.setIdentifierCollection(getIdentifierCollection().copyOf()); + mCurrentCallEvent.end(timestamp); + broadcast(mCurrentCallEvent); + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL)); + } + } + + /** + * Ends/closes the current call event. + * + * @param timestamp of the message that indicates the event has ended. + */ + private void closeCurrentCallEvent(long timestamp) + { + if(mCurrentCallEvent != null) + { + mCurrentCallEvent.end(timestamp); + broadcast(mCurrentCallEvent); + mCurrentCallEvent = null; + + //Only clear the from identifier at this point ... the channel may still be allocated to the TO talkgroup + getIdentifierCollection().remove(IdentifierClass.USER, Role.FROM); + } + } + + /** + * Terminator Data Unit (TDU). + */ + private void processTDU(P25Message message) + { + closeCurrentCallEvent(message.getTimestamp()); + broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE)); + } + + /** + * Packet Data Unit + * + * @param message + */ + private void processPDU(P25Message message) + { + if(message.isValid() && message instanceof PDUMessage) + { + PDUMessage pdu = (PDUMessage)message; + + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(pdu.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(message.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.DATA_PACKET.toString()) + .details(pdu.toString()) + .identifiers(ic) + .build()); + + } + + broadcast(new DecoderStateEvent(this, Event.DECODE, State.DATA)); + } + + /** + * Unconfirmed Multi-Block Trunking Control (UMBTC) + * + * @param message + */ + private void processUMBTC(P25Message message) + { + if(message.isValid() && message instanceof UMBTCTelephoneInterconnectRequestExplicitDialing) + { + UMBTCTelephoneInterconnectRequestExplicitDialing tired = (UMBTCTelephoneInterconnectRequestExplicitDialing)message; + + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tired.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tired.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("TELEPHONE INTERCONNECT:" + tired.getTelephoneNumber()) + .identifiers(ic) + .build()); + } + + broadcast(new DecoderStateEvent(this, Event.DECODE, State.CONTROL)); + } + + /** + * IP Packet Data + * + * @param message + */ + private void processPacketData(P25Message message) + { + broadcast(new DecoderStateEvent(this, Event.DECODE, State.DATA)); + + if(message instanceof SNDCPPacketMessage) + { + SNDCPPacketMessage sndcp = (SNDCPPacketMessage)message; + getIdentifierCollection().update(sndcp.getIdentifiers()); + } + else if(message instanceof PacketMessage) + { + PacketMessage packetMessage = (PacketMessage)message; + + IPacket packet = packetMessage.getPacket(); + + if(packet instanceof IPV4Packet) + { + IPV4Packet ipv4 = (IPV4Packet)packet; + + IPacket ipPayload = ipv4.getPayload(); + + if(ipPayload instanceof UDPPacket) + { + UDPPacket udpPacket = (UDPPacket)ipPayload; + + IPacket udpPayload = udpPacket.getPayload(); + + Identifier from = packetMessage.getHeader().isOutbound() ? + ipv4.getHeader().getFromAddress() : packetMessage.getHeader().getLLID(); + Identifier to = packetMessage.getHeader().isOutbound() ? + packetMessage.getHeader().getLLID() : ipv4.getHeader().getToAddress(); + + getIdentifierCollection().update(from); + getIdentifierCollection().update(to); + + if(udpPayload instanceof ARSPacket) { - IAdjacentSite ias = (IAdjacentSite)pdu; + ARSPacket arsPacket = (ARSPacket)udpPayload; - mNeighborMap.put(ias.getUniqueID(), ias); + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(message.getIdentifiers()); - updateSystem(ias.getSystemID()); - } - break; - case CALL_ALERT: - if(pdu instanceof CallAlertExtended) - { - CallAlertExtended ca = (CallAlertExtended)pdu; + DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.AUTOMATIC_REGISTRATION_SERVICE.toString()) + .identifiers(ic) + .details(arsPacket.toString() + " " + ipv4.toString()) + .build(); - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.PAGE) - .aliasList(getAliasList()) - .from(ca.getWACN() + "-" + ca.getSystemID() + "-" + - ca.getSourceID()) - .to(ca.getTargetAddress()) - .build()); + broadcast(packetEvent); } else { - logAlternateVendorMessage(pdu); + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(message.getIdentifiers()); + + DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.UDP_PACKET.toString()) + .identifiers(ic) + .details(ipv4.toString()) + .build(); + + broadcast(packetEvent); } + } + } + } + } + + /** + * Sub-Network Dependent Convergence Protocol (SNDCP) + * + * @param message + */ + private void processSNDCP(P25Message message) + { + broadcast(new DecoderStateEvent(this, Event.DECODE, State.DATA)); + + if(message.isValid() && message instanceof SNDCPPacketMessage) + { + SNDCPPacketMessage sndcpPacket = (SNDCPPacketMessage)message; + + SNDCPMessage sndcpMessage = sndcpPacket.getSNDCPMessage(); + + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(sndcpPacket.getIdentifiers()); + + switch(sndcpPacket.getSNDCPPacketHeader().getPDUType()) + { + case OUTBOUND_SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT: + broadcast(P25DecodeEvent.builder(message.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details("SNDCP ACTIVATE TDS CONTEXT ACCEPT") + .identifiers(ic) + .build()); break; - case GROUP_AFFILIATION_QUERY: - if(pdu instanceof GroupAffiliationQueryExtended) - { - GroupAffiliationQueryExtended gaqe = - (GroupAffiliationQueryExtended)pdu; - - if(mLastQueryEventID == null || !gaqe.getTargetAddress() - .contentEquals(mLastQueryEventID)) - { - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.QUERY) - .aliasList(getAliasList()) - .details("GROUP AFFILIATION") - .from(gaqe.getWACN() + "-" + gaqe.getSystemID() + - "-" + gaqe.getSourceID()) - .to(gaqe.getTargetAddress()) - .build()); - - mLastQueryEventID = gaqe.getToID(); - } - } + case OUTBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT: + broadcast(P25DecodeEvent.builder(message.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details("SNDCP DEACTIVATE TDS CONTEXT ACCEPT") + .identifiers(ic) + .build()); break; - case GROUP_AFFILIATION_RESPONSE: - if(pdu instanceof GroupAffiliationResponseExtended) - { - GroupAffiliationResponseExtended gar = - (GroupAffiliationResponseExtended)pdu; - - if(mLastResponseEventID == null || !gar.getTargetAddress() - .contentEquals(mLastResponseEventID)) - { - broadcast(new P25CallEvent.Builder(CallEvent.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); - } + case OUTBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST: + broadcast(P25DecodeEvent.builder(message.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("SNDCP DEACTIVATE TDS CONTEXT") + .identifiers(ic) + .build()); break; - case MESSAGE_UPDATE: - if(pdu instanceof MessageUpdateExtended) - { - MessageUpdateExtended mu = (MessageUpdateExtended)pdu; - - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.SDM) - .aliasList(getAliasList()) - .details("MESSAGE: " + mu.getMessage()) - .from(mu.getSourceWACN() + "-" + mu.getSourceSystemID() + - "-" + mu.getSourceID()) - .to(mu.getTargetAddress()) - .build()); - } - else - { - logAlternateVendorMessage(pdu); - } + case OUTBOUND_SNDCP_ACTIVATE_TDS_CONTEXT_REJECT: + broadcast(P25DecodeEvent.builder(message.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details("SNDCP ACTIVATE TDS CONTEXT REJECT") + .identifiers(ic) + .build()); break; - case NETWORK_STATUS_BROADCAST: - if(pdu instanceof NetworkStatusBroadcastExtended) - { - mNetworkStatusExtended = (NetworkStatusBroadcastExtended)pdu; - } - else - { - logAlternateVendorMessage(pdu); - } + case INBOUND_SNDCP_ACTIVATE_TDS_CONTEXT_REQUEST: + broadcast(P25DecodeEvent.builder(message.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("SNDCP ACTIVATE TDS CONTEXT") + .identifiers(ic) + .build()); break; - case PROTECTION_PARAMETER_BROADCAST: - if(pdu instanceof ProtectionParameterBroadcast) - { - mProtectionParameterBroadcast = - (ProtectionParameterBroadcast)pdu; - } - else - { - logAlternateVendorMessage(pdu); - } + case INBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT: + broadcast(P25DecodeEvent.builder(message.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details("SNDCP DEACTIVATE TDS CONTEXT ACCEPT") + .identifiers(ic) + .build()); break; - case RFSS_STATUS_BROADCAST: - if(pdu instanceof RFSSStatusBroadcastExtended) - { - mRFSSStatusMessageExtended = (RFSSStatusBroadcastExtended)pdu; - - updateNAC(mRFSSStatusMessageExtended.getNAC()); - updateSystem(mRFSSStatusMessageExtended.getSystemID()); - mSiteAttributeMonitor.process(mRFSSStatusMessageExtended.getRFSubsystemID() + - "-" + mRFSSStatusMessageExtended.getSiteID()); - } - else - { - logAlternateVendorMessage(pdu); - } - break; - case ROAMING_ADDRESS_UPDATE: - if(pdu instanceof RoamingAddressUpdateExtended) - { - RoamingAddressUpdateExtended raue = - (RoamingAddressUpdateExtended)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(CallEvent.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(CallEvent.CallEventType.QUERY) - .aliasList(getAliasList()) - .details("STATUS QUERY") - .from(sq.getSourceWACN() + "-" + - sq.getSourceSystemID() + "-" + - sq.getSourceID()) - .to(sq.getTargetAddress()) - .build()); - - mLastQueryEventID = sq.getToID(); - } - } - else - { - logAlternateVendorMessage(pdu); - } - break; - case STATUS_UPDATE: - if(pdu instanceof StatusUpdateExtended) - { - StatusUpdateExtended su = (StatusUpdateExtended)pdu; - - if(mLastResponseEventID == null || !mLastResponseEventID - .contentEquals(su.getTargetAddress())) - { - broadcast(new P25CallEvent.Builder(CallEvent.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; - - if(urr.getResponse() == Response.ACCEPT) - { - mRegistrations.put(urr.getAssignedSourceAddress(), - System.currentTimeMillis()); - } - - if(mLastRegistrationEventID == null || !mLastRegistrationEventID - .contentEquals(urr.getAssignedSourceAddress())) - { - broadcast(new P25CallEvent.Builder(CallEvent.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(CallEvent.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: + case INBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST: + broadcast(P25DecodeEvent.builder(message.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("SNDCP DEACTIVATE TDS CONTEXT") + .identifiers(ic) + .build()); break; } } } - private void processPDUChannelGrant(PDUMessage pdu) + /** + * Trunking Signalling Block (TSBK) + * + * @param message + */ + private void processTSBK(P25Message message) { - String channel = null; - String from = null; - String to = null; + broadcast(new DecoderStateEvent(this, Event.DECODE, State.CONTROL)); - switch(pdu.getOpcode()) + if(message.isValid() && message instanceof TSBKMessage) { - case GROUP_DATA_CHANNEL_GRANT: - if(pdu instanceof GroupDataChannelGrantExtended) - { - GroupDataChannelGrantExtended gdcge = - (GroupDataChannelGrantExtended)pdu; + TSBKMessage tsbk = (TSBKMessage)message; - channel = gdcge.getTransmitChannel(); - from = gdcge.getSourceAddress(); - to = gdcge.getGroupAddress(); - - if(hasCallEvent(channel, from, to)) + switch(tsbk.getOpcode()) + { + //Channel Grant messages + case OSP_GROUP_DATA_CHANNEL_GRANT: + if(tsbk instanceof GroupDataChannelGrant) { - updateCallEvent(channel, from, to); + GroupDataChannelGrant gdcg = (GroupDataChannelGrant)tsbk; + + //Make a copy of current identifiers and remove current user identifiers and replace from message + MutableIdentifierCollection identifiers = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifiers.remove(IdentifierClass.USER); + for(Identifier identifier : gdcg.getIdentifiers()) + { + identifiers.update(mPatchGroupManager.update(identifier)); + } + + if(mTrafficChannelManager != null) + { + mTrafficChannelManager.processChannelGrant(gdcg.getChannel(), gdcg.getDataServiceOptions(), + identifiers, tsbk.getOpcode(), gdcg.getTimestamp()); + } } - else + break; + case OSP_GROUP_VOICE_CHANNEL_GRANT: + if(tsbk instanceof GroupVoiceChannelGrant) { - P25CallEvent callEvent = new P25CallEvent.Builder(CallEvent.CallEventType.DATA_CALL) - .aliasList(getAliasList()) - .channel(channel) - .details((gdcge.isEncrypted() ? "ENCRYPTED" : "") + - (gdcge.isEmergency() ? " EMERGENCY" : "")) - .frequency(gdcge.getDownlinkFrequency()) - .from(from) - .to(to) - .build(); + GroupVoiceChannelGrant gvcg = (GroupVoiceChannelGrant)tsbk; - registerCallEvent(callEvent); - broadcast(callEvent); + //Make a copy of current identifiers and remove current user identifiers and replace from message + MutableIdentifierCollection identifiers = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifiers.remove(IdentifierClass.USER); + for(Identifier identifier : gvcg.getIdentifiers()) + { + identifiers.update(mPatchGroupManager.update(identifier)); + } + + if(mTrafficChannelManager != null) + { + mTrafficChannelManager.processChannelGrant(gvcg.getChannel(), gvcg.getVoiceServiceOptions(), + identifiers, tsbk.getOpcode(), gvcg.getTimestamp()); + } } - } - 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)) + break; + case OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE: + if(tsbk instanceof GroupVoiceChannelGrantUpdate) { - updateCallEvent(channel, from, to); + GroupVoiceChannelGrantUpdate gvcgu = (GroupVoiceChannelGrantUpdate)tsbk; + + //Make a copy of current identifiers and remove current user identifiers and replace from message + MutableIdentifierCollection identifiersA = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifiersA.remove(IdentifierClass.USER); + identifiersA.update(mPatchGroupManager.update(gvcgu.getGroupAddressA())); + + if(mTrafficChannelManager != null) + { + mTrafficChannelManager.processChannelGrant(gvcgu.getChannelA(), null, identifiersA, + tsbk.getOpcode(), gvcgu.getTimestamp()); + } + + if(gvcgu.hasGroupB()) + { + //Make a copy of current identifiers and remove current user identifiers and replace from message + MutableIdentifierCollection identifiersB = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifiersB.remove(IdentifierClass.USER); + identifiersB.update(mPatchGroupManager.update(gvcgu.getGroupAddressB())); + + if(mTrafficChannelManager != null) + { + mTrafficChannelManager.processChannelGrant(gvcgu.getChannelB(), null, identifiersB, + tsbk.getOpcode(), gvcgu.getTimestamp()); + } + } } - else + break; + case OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: + if(tsbk instanceof GroupVoiceChannelGrantUpdateExplicit) { - P25CallEvent callEvent = new P25CallEvent.Builder(CallEvent.CallEventType.GROUP_CALL) - .aliasList(getAliasList()) - .channel(channel) - .details((gvcge.isEncrypted() ? "ENCRYPTED" : "") + - (gvcge.isEmergency() ? " EMERGENCY" : "")) - .frequency(gvcge.getDownlinkFrequency()) - .from(from) - .to(to) - .build(); + GroupVoiceChannelGrantUpdateExplicit gvcgue = (GroupVoiceChannelGrantUpdateExplicit)tsbk; - registerCallEvent(callEvent); - broadcast(callEvent); + //Make a copy of current identifiers and remove current user identifiers and replace from message + MutableIdentifierCollection identifiers = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifiers.remove(IdentifierClass.USER); + identifiers.update(mPatchGroupManager.update(gvcgue.getGroupAddress())); + + if(mTrafficChannelManager != null) + { + mTrafficChannelManager.processChannelGrant(gvcgue.getChannel(), gvcgue.getVoiceServiceOptions(), + identifiers, tsbk.getOpcode(), gvcgue.getTimestamp()); + } } - - 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)) + break; + case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: + if(tsbk instanceof UnitToUnitVoiceChannelGrant) { - updateCallEvent(channel, from, to); + UnitToUnitVoiceChannelGrant uuvcg = (UnitToUnitVoiceChannelGrant)tsbk; + + //Make a copy of current identifiers and remove current user identifiers and replace from message + MutableIdentifierCollection identifiers = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifiers.remove(IdentifierClass.USER); + identifiers.update(uuvcg.getIdentifiers()); + + if(mTrafficChannelManager != null) + { + mTrafficChannelManager.processChannelGrant(uuvcg.getChannel(), null, identifiers, + tsbk.getOpcode(), uuvcg.getTimestamp()); + } } - else + break; + case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: + if(tsbk instanceof UnitToUnitVoiceChannelGrantUpdate) { - P25CallEvent callEvent = new P25CallEvent.Builder(CallEvent.CallEventType.DATA_CALL) - .aliasList(getAliasList()) - .channel(channel) - .details((idcge.isEncrypted() ? "ENCRYPTED" : "") + - (idcge.isEmergency() ? " EMERGENCY" : "")) - .frequency(idcge.getDownlinkFrequency()) - .from(from) - .to(to) - .build(); + UnitToUnitVoiceChannelGrantUpdate uuvcgu = (UnitToUnitVoiceChannelGrantUpdate)tsbk; - registerCallEvent(callEvent); - broadcast(callEvent); + //Make a copy of current identifiers and remove current user identifiers and replace from message + MutableIdentifierCollection identifiers = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifiers.remove(IdentifierClass.USER); + identifiers.update(uuvcgu.getIdentifiers()); + + if(mTrafficChannelManager != null) + { + mTrafficChannelManager.processChannelGrant(uuvcgu.getChannel(), null, identifiers, + tsbk.getOpcode(), uuvcgu.getTimestamp()); + } } - - if(!mIgnoreDataCalls) + break; + case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: + if(tsbk instanceof TelephoneInterconnectVoiceChannelGrant) { - broadcast(new TrafficChannelAllocationEvent(this, - mChannelCallMap.get(channel))); + TelephoneInterconnectVoiceChannelGrant tivcg = (TelephoneInterconnectVoiceChannelGrant)tsbk; + + //Make a copy of current identifiers and remove current user identifiers and replace from message + MutableIdentifierCollection identifiers = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifiers.remove(IdentifierClass.USER); + identifiers.update(tivcg.getIdentifiers()); + + if(mTrafficChannelManager != null) + { + mTrafficChannelManager.processChannelGrant(tivcg.getChannel(), tivcg.getVoiceServiceOptions(), + identifiers, tsbk.getOpcode(), tivcg.getTimestamp()); + } } - } - 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(CallEvent.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)) + break; + case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: + if(tsbk instanceof TelephoneInterconnectVoiceChannelGrantUpdate) { - updateCallEvent(channel, from, to); + TelephoneInterconnectVoiceChannelGrantUpdate tivcgu = (TelephoneInterconnectVoiceChannelGrantUpdate)tsbk; + + //Make a copy of current identifiers and remove current user identifiers and replace from message + MutableIdentifierCollection identifiers = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifiers.remove(IdentifierClass.USER); + identifiers.update(tivcgu.getIdentifiers()); + + if(mTrafficChannelManager != null) + { + mTrafficChannelManager.processChannelGrant(tivcgu.getChannel(), tivcgu.getVoiceServiceOptions(), + identifiers, tsbk.getOpcode(), tivcgu.getTimestamp()); + } } - else + break; + case OSP_SNDCP_DATA_CHANNEL_GRANT: + if(tsbk instanceof SNDCPDataChannelGrant) { - P25CallEvent callEvent = new P25CallEvent.Builder(CallEvent.CallEventType.UNIT_TO_UNIT_CALL) - .aliasList(getAliasList()) - .channel(channel) - .details((uuvcge.isEncrypted() ? "ENCRYPTED" : "") + - (uuvcge.isEmergency() ? " EMERGENCY" : "")) - .frequency(uuvcge.getDownlinkFrequency()) - .from(from) - .to(to) - .build(); + SNDCPDataChannelGrant dcg = (SNDCPDataChannelGrant)tsbk; - registerCallEvent(callEvent); - broadcast(callEvent); + //Make a copy of current identifiers and remove current user identifiers and replace from message + MutableIdentifierCollection identifiers = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifiers.remove(IdentifierClass.USER); + identifiers.update(dcg.getIdentifiers()); + + if(mTrafficChannelManager != null) + { + mTrafficChannelManager.processChannelGrant(dcg.getChannel(), dcg.getServiceOptions(), + identifiers, tsbk.getOpcode(), dcg.getTimestamp()); + } } - - 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)) + break; + case MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT: + if(tsbk instanceof PatchGroupVoiceChannelGrant) { - updateCallEvent(channel, from, to); + PatchGroupVoiceChannelGrant pgvcg = (PatchGroupVoiceChannelGrant)tsbk; + + //Make a copy of current identifiers and remove current user identifiers and replace from message + MutableIdentifierCollection identifiers = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifiers.remove(IdentifierClass.USER); + for(Identifier identifier : pgvcg.getIdentifiers()) + { + identifiers.update(mPatchGroupManager.update(identifier)); + } + + if(mTrafficChannelManager != null) + { + mTrafficChannelManager.processChannelGrant(pgvcg.getChannel(), pgvcg.getVoiceServiceOptions(), + identifiers, tsbk.getOpcode(), pgvcg.getTimestamp()); + } } - else + break; + case MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT_UPDATE: + if(tsbk instanceof PatchGroupVoiceChannelGrantUpdate) { - P25CallEvent callEvent = new P25CallEvent.Builder(CallEvent.CallEventType.UNIT_TO_UNIT_CALL) - .aliasList(getAliasList()) - .channel(channel) - .details((uuvcgue.isEncrypted() ? "ENCRYPTED" : "") + - (uuvcgue.isEmergency() ? " EMERGENCY" : "")) - .frequency(uuvcgue.getDownlinkFrequency()) - .from(from) - .to(to) - .build(); + PatchGroupVoiceChannelGrantUpdate pgvcgu = (PatchGroupVoiceChannelGrantUpdate)tsbk; - registerCallEvent(callEvent); - broadcast(callEvent); + //Make a copy of current identifiers and remove current user identifiers and replace from message + MutableIdentifierCollection identifiersPG1 = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifiersPG1.remove(IdentifierClass.USER); + identifiersPG1.update(mPatchGroupManager.update(pgvcgu.getPatchGroup1())); + + if(mTrafficChannelManager != null) + { + mTrafficChannelManager.processChannelGrant(pgvcgu.getChannel1(), null, identifiersPG1, + tsbk.getOpcode(), pgvcgu.getTimestamp()); + } + + if(pgvcgu.hasPatchGroup2()) + { + //Make a copy of current identifiers and remove current user identifiers and replace from message + MutableIdentifierCollection identifiersPG2 = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + identifiersPG2.remove(IdentifierClass.USER); + identifiersPG2.update(mPatchGroupManager.update(pgvcgu.getPatchGroup2())); + + if(mTrafficChannelManager != null) + { + mTrafficChannelManager.processChannelGrant(pgvcgu.getChannel2(), null, + identifiersPG2, tsbk.getOpcode(), pgvcgu.getTimestamp()); + } + } } + break; - broadcast(new TrafficChannelAllocationEvent(this, - mChannelCallMap.get(channel))); + //Network Configuration Messages + case MOTOROLA_OSP_TRAFFIC_CHANNEL_ID: + case MOTOROLA_OSP_SYSTEM_LOADING: + case MOTOROLA_OSP_CONTROL_CHANNEL_ID: + case MOTOROLA_OSP_CONTROL_CHANNEL_PLANNED_SHUTDOWN: + case OSP_IDENTIFIER_UPDATE_TDMA: + case OSP_IDENTIFIER_UPDATE_VHF_UHF_BANDS: + case OSP_SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: + case OSP_SNDCP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT: + case OSP_TIME_DATE_ANNOUNCEMENT: + case OSP_TDMA_SYNC_BROADCAST: + case OSP_SYSTEM_SERVICE_BROADCAST: + case OSP_SECONDARY_CONTROL_CHANNEL_BROADCAST: + case OSP_RFSS_STATUS_BROADCAST: + case OSP_NETWORK_STATUS_BROADCAST: + case OSP_ADJACENT_STATUS_BROADCAST: + case OSP_IDENTIFIER_UPDATE: + case OSP_PROTECTION_PARAMETER_BROADCAST: + case OSP_PROTECTION_PARAMETER_UPDATE: + mNetworkConfigurationMonitor.process(tsbk); + break; - } - else - { - logAlternateVendorMessage(pdu); - } - break; - default: - break; + case OSP_UNIT_TO_UNIT_ANSWER_REQUEST: + MutableIdentifierCollection icUnitAnswerRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icUnitAnswerRequest.remove(IdentifierClass.USER); + icUnitAnswerRequest.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.PAGE.toString()) + .details("UNIT-TO-UNIT ANSWER REQUEST") + .identifiers(icUnitAnswerRequest) + .build()); + break; + case OSP_TELEPHONE_INTERCONNECT_ANSWER_REQUEST: + if(tsbk instanceof TelephoneInterconnectAnswerRequest) + { + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + TelephoneInterconnectAnswerRequest tiar = (TelephoneInterconnectAnswerRequest)tsbk; + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.PAGE.toString()) + .details("TELEPHONE ANSWER REQUEST: " + tiar.getTelephoneNumber()) + .identifiers(ic) + .build()); + } + break; + case OSP_SNDCP_DATA_PAGE_REQUEST: + MutableIdentifierCollection icDataPage = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icDataPage.remove(IdentifierClass.USER); + icDataPage.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.PAGE.toString()) + .details("SNDCP DATA PAGE REQUEST") + .identifiers(icDataPage) + .build()); + break; + case OSP_STATUS_UPDATE: + if(tsbk instanceof StatusUpdate) + { + StatusUpdate su = (StatusUpdate)tsbk; + + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.STATUS.toString()) + .details("UNIT:" + su.getUnitStatus() + " USER:" + su.getUserStatus()) + .identifiers(ic) + .build()); + } + break; + case OSP_STATUS_QUERY: + MutableIdentifierCollection icStatusQuery = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icStatusQuery.remove(IdentifierClass.USER); + icStatusQuery.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.QUERY.toString()) + .details("STATUS") + .identifiers(icStatusQuery) + .build()); + break; + case OSP_MESSAGE_UPDATE: + if(tsbk instanceof MessageUpdate) + { + MessageUpdate mu = (MessageUpdate)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.SDM.toString()) + .details("MSG:" + mu.getShortDataMessage()) + .identifiers(ic) + .build()); + } + break; + case OSP_RADIO_UNIT_MONITOR_COMMAND: + MutableIdentifierCollection icUnitMonitor = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icUnitMonitor.remove(IdentifierClass.USER); + icUnitMonitor.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.COMMAND.toString()) + .details("RADIO UNIT MONITOR") + .identifiers(icUnitMonitor) + .build()); + break; + case OSP_CALL_ALERT: + MutableIdentifierCollection icCallAlert = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icCallAlert.remove(IdentifierClass.USER); + icCallAlert.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.PAGE.toString()) + .details("CALL ALERT") + .identifiers(icCallAlert) + .build()); + break; + case OSP_ACKNOWLEDGE_RESPONSE: + if(tsbk instanceof AcknowledgeResponse) + { + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + AcknowledgeResponse ar = (AcknowledgeResponse)tsbk; + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details("ACKNOWLEDGE " + ar.getAcknowledgedServiceType().getDescription()) + .identifiers(ic) + .build()); + } + break; + case OSP_QUEUED_RESPONSE: + if(tsbk instanceof QueuedResponse) + { + QueuedResponse qr = (QueuedResponse)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details("QUEUED: " + qr.getQueuedResponseServiceType().getDescription() + + " REASON: " + qr.getQueuedResponseReason() + + " INFO: " + qr.getAdditionalInfo()) + .identifiers(ic) + .build()); + } + break; + case OSP_EXTENDED_FUNCTION_COMMAND: + if(tsbk instanceof ExtendedFunctionCommand) + { + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + ExtendedFunctionCommand efc = (ExtendedFunctionCommand)tsbk; + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.COMMAND.toString()) + .details("EXTENDED FUNCTION: " + efc.getExtendedFunction() + + " ARGUMENTS:" + efc.getArguments()) + .identifiers(ic) + .build()); + } + break; + case OSP_DENY_RESPONSE: + if(tsbk instanceof DenyResponse) + { + DenyResponse dr = (DenyResponse)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details("DENY: " + dr.getDeniedServiceType().getDescription() + + " REASON: " + dr.getDenyReason() + " - INFO: " + dr.getAdditionalInfo()) + .identifiers(ic) + .build()); + } + break; + case OSP_GROUP_AFFILIATION_RESPONSE: + if(tsbk instanceof GroupAffiliationResponse) + { + GroupAffiliationResponse gar = (GroupAffiliationResponse)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details(gar.getAffiliationResponse() + + " AFFILIATION GROUP: " + gar.getGroupAddress() + + (gar.isGlobalAffiliation() ? " (GLOBAL)" : " (LOCAL)") + + " ANNOUNCEMENT GROUP:" + gar.getAnnouncementGroupAddress()) + .identifiers(ic) + .build()); + } + break; + case OSP_GROUP_AFFILIATION_QUERY: + MutableIdentifierCollection icGroupAffiliationQuery = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icGroupAffiliationQuery.remove(IdentifierClass.USER); + icGroupAffiliationQuery.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.QUERY.toString()) + .details("GROUP AFFILIATION") + .identifiers(icGroupAffiliationQuery) + .build()); + break; + case OSP_LOCATION_REGISTRATION_RESPONSE: + if(tsbk instanceof LocationRegistrationResponse) + { + LocationRegistrationResponse lrr = (LocationRegistrationResponse)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REGISTER.toString()) + .details(lrr.getResponse() + " LOCATION REGISTRATION - GROUP:" + lrr.getGroupAddress()) + .identifiers(ic) + .build()); + } + break; + case OSP_UNIT_REGISTRATION_RESPONSE: + if(tsbk instanceof UnitRegistrationResponse) + { + UnitRegistrationResponse urr = (UnitRegistrationResponse)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REGISTER.toString()) + .details(urr.getResponse() + " UNIT REGISTRATION - UNIT ID:" + urr.getTargetUniqueId()) + .identifiers(ic) + .build()); + } + break; + case OSP_UNIT_REGISTRATION_COMMAND: + MutableIdentifierCollection icUnitRegistration = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icUnitRegistration.remove(IdentifierClass.USER); + icUnitRegistration.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.COMMAND.toString()) + .details("UNIT REGISTRATION") + .identifiers(icUnitRegistration) + .build()); + break; + case OSP_AUTHENTICATION_COMMAND: + MutableIdentifierCollection icAuthenticationCommand = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icAuthenticationCommand.remove(IdentifierClass.USER); + icAuthenticationCommand.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.COMMAND.toString()) + .details("AUTHENTICATE") + .identifiers(icAuthenticationCommand) + .build()); + break; + case OSP_UNIT_DEREGISTRATION_ACKNOWLEDGE: + MutableIdentifierCollection icUnitDeregistration = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icUnitDeregistration.remove(IdentifierClass.USER); + icUnitDeregistration.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.DEREGISTER.toString()) + .details("ACKNOWLEDGE UNIT DE-REGISTRATION") + .identifiers(icUnitDeregistration) + .build()); + break; + case OSP_ROAMING_ADDRESS_COMMAND: + if(tsbk instanceof RoamingAddressCommand) + { + RoamingAddressCommand rac = (RoamingAddressCommand)tsbk; + + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.COMMAND.toString()) + .details(rac.getStackOperation() + " ROAMING ADDRESS") + .identifiers(ic) + .build()); + } + break; + + //MOTOROLA PATCH GROUP OPCODES + case MOTOROLA_OSP_PATCH_GROUP_ADD: + mPatchGroupManager.addPatchGroups(tsbk.getIdentifiers()); + break; + case MOTOROLA_OSP_PATCH_GROUP_DELETE: + mPatchGroupManager.removePatchGroups(tsbk.getIdentifiers()); + break; + + //STANDARD - INBOUND OPCODES + case ISP_GROUP_VOICE_SERVICE_REQUEST: + if(tsbk instanceof GroupVoiceServiceRequest) + { + GroupVoiceServiceRequest gvsr = (GroupVoiceServiceRequest)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("GROUP VOICE SERVICE " + gvsr.getVoiceServiceOptions()) + .identifiers(ic) + .build()); + } + break; + case ISP_UNIT_TO_UNIT_VOICE_SERVICE_REQUEST: + if(tsbk instanceof UnitToUnitVoiceServiceRequest) + { + UnitToUnitVoiceServiceRequest uuvsr = (UnitToUnitVoiceServiceRequest)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("UNIT-2-UNIT VOICE SERVICE " + uuvsr.getVoiceServiceOptions()) + .identifiers(ic) + .build()); + } + break; + case ISP_UNIT_TO_UNIT_ANSWER_RESPONSE: + if(tsbk instanceof UnitToUnitVoiceServiceAnswerResponse) + { + UnitToUnitVoiceServiceAnswerResponse uuvsar = (UnitToUnitVoiceServiceAnswerResponse)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details(uuvsar.getAnswerResponse() + " UNIT-2-UNIT VOICE SERVICE " + uuvsar.getVoiceServiceOptions()) + .identifiers(ic) + .build()); + } + break; + case ISP_TELEPHONE_INTERCONNECT_PSTN_REQUEST: + MutableIdentifierCollection icInterconnect = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icInterconnect.remove(IdentifierClass.USER); + icInterconnect.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("TELEPHONE INTERCONNECT") + .identifiers(icInterconnect) + .build()); + break; + case ISP_TELEPHONE_INTERCONNECT_ANSWER_RESPONSE: + if(tsbk instanceof TelephoneInterconnectAnswerResponse) + { + TelephoneInterconnectAnswerResponse tiar = (TelephoneInterconnectAnswerResponse)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details(tiar.getAnswerResponse() + " TELEPHONE INTERCONNECT " + tiar.getVoiceServiceOptions()) + .identifiers(ic) + .build()); + } + break; + case ISP_INDIVIDUAL_DATA_SERVICE_REQUEST: + if(tsbk instanceof IndividualDataServiceRequest) + { + IndividualDataServiceRequest idsr = (IndividualDataServiceRequest)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("INDIVIDUAL DATA SERVICE " + idsr.getVoiceServiceOptions()) + .identifiers(ic) + .build()); + } + break; + case ISP_GROUP_DATA_SERVICE_REQUEST: + if(tsbk instanceof GroupDataServiceRequest) + { + GroupDataServiceRequest gdsr = (GroupDataServiceRequest)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("GROUP DATA SERVICE " + gdsr.getVoiceServiceOptions()) + .identifiers(ic) + .build()); + } + break; + case ISP_SNDCP_DATA_CHANNEL_REQUEST: + if(tsbk instanceof SNDCPDataChannelRequest) + { + SNDCPDataChannelRequest sdcr = (SNDCPDataChannelRequest)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("SNDCP DATA CHANNEL " + sdcr.getDataServiceOptions()) + .identifiers(ic) + .build()); + } + break; + case ISP_SNDCP_DATA_PAGE_RESPONSE: + if(tsbk instanceof SNDCPDataPageResponse) + { + SNDCPDataPageResponse sdpr = (SNDCPDataPageResponse)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details(sdpr.getAnswerResponse() + " SNDCP DATA " + sdpr.getDataServiceOptions()) + .identifiers(ic) + .build()); + } + break; + case ISP_SNDCP_RECONNECT_REQUEST: + if(tsbk instanceof SNDCPReconnectRequest) + { + SNDCPReconnectRequest srr = (SNDCPReconnectRequest)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("SNDCP RECONNECT " + (srr.hasDataToSend() ? "- DATA TO SEND " : "") + + srr.getDataServiceOptions()) + .identifiers(ic) + .build()); + } + break; + case ISP_STATUS_UPDATE_REQUEST: + if(tsbk instanceof StatusUpdateRequest) + { + StatusUpdateRequest sur = (StatusUpdateRequest)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.STATUS.toString()) + .details("UNIT:" + sur.getUnitStatus() + " USER:" + sur.getUserStatus()) + .identifiers(ic) + .build()); + } + break; + case ISP_STATUS_QUERY_RESPONSE: + if(tsbk instanceof StatusQueryResponse) + { + StatusQueryResponse sqr = (StatusQueryResponse)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.STATUS.toString()) + .details("UNIT:" + sqr.getUnitStatus() + " USER:" + sqr.getUserStatus()) + .identifiers(ic) + .build()); + } + break; + case ISP_STATUS_QUERY_REQUEST: + MutableIdentifierCollection icStatusQueryRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icStatusQueryRequest.remove(IdentifierClass.USER); + icStatusQueryRequest.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.QUERY.toString()) + .details("UNIT AND USER STATUS") + .identifiers(icStatusQueryRequest) + .build()); + break; + case ISP_MESSAGE_UPDATE_REQUEST: + if(tsbk instanceof MessageUpdateRequest) + { + MessageUpdateRequest mur = (MessageUpdateRequest)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.SDM.toString()) + .details("MESSAGE:" + mur.getShortDataMessage()) + .identifiers(ic) + .build()); + } + break; + case ISP_RADIO_UNIT_MONITOR_REQUEST: + MutableIdentifierCollection icUnitMonitorRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icUnitMonitorRequest.remove(IdentifierClass.USER); + icUnitMonitorRequest.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("RADIO UNIT MONITOR") + .identifiers(icUnitMonitorRequest) + .build()); + break; + case ISP_CALL_ALERT_REQUEST: + MutableIdentifierCollection icCallAlertRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icCallAlertRequest.remove(IdentifierClass.USER); + icCallAlertRequest.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("CALL ALERT") + .identifiers(icCallAlertRequest) + .build()); + break; + case ISP_UNIT_ACKNOWLEDGE_RESPONSE: + if(tsbk instanceof UnitAcknowledgeResponse) + { + UnitAcknowledgeResponse uar = (UnitAcknowledgeResponse)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details("UNIT ACKNOWLEDGE:" + uar.getAcknowledgedServiceType().getDescription()) + .identifiers(ic) + .build()); + } + break; + case ISP_CANCEL_SERVICE_REQUEST: + if(tsbk instanceof CancelServiceRequest) + { + CancelServiceRequest csr = (CancelServiceRequest)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("CANCEL SERVICE:" + csr.getServiceType() + + " REASON:" + csr.getCancelReason() + (csr.hasAdditionalInformation() ? + " INFO:" + csr.getAdditionalInformation() : "")) + .identifiers(ic) + .build()); + } + break; + case ISP_EXTENDED_FUNCTION_RESPONSE: + if(tsbk instanceof ExtendedFunctionResponse) + { + ExtendedFunctionResponse efr = (ExtendedFunctionResponse)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details("EXTENDED FUNCTION:" + efr.getExtendedFunction() + + " ARGUMENTS:" + efr.getArguments()) + .identifiers(ic) + .build()); + } + break; + case ISP_EMERGENCY_ALARM_REQUEST: + MutableIdentifierCollection icAlarm = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icAlarm.remove(IdentifierClass.USER); + icAlarm.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("EMERGENCY ALARM") + .identifiers(icAlarm) + .build()); + break; + case ISP_GROUP_AFFILIATION_REQUEST: + MutableIdentifierCollection icGroupAffiliationRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icGroupAffiliationRequest.remove(IdentifierClass.USER); + icGroupAffiliationRequest.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("GROUP AFFILIATION") + .identifiers(icGroupAffiliationRequest) + .build()); + break; + case ISP_GROUP_AFFILIATION_QUERY_RESPONSE: + if(tsbk instanceof GroupAffiliationQueryResponse) + { + GroupAffiliationQueryResponse gaqr = (GroupAffiliationQueryResponse)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details("AFFILIATION - GROUP:" + gaqr.getGroupAddress() + + " ANNOUNCEMENT GROUP:" + gaqr.getAnnouncementGroupAddress()) + .identifiers(ic) + .build()); + } + break; + case ISP_UNIT_DE_REGISTRATION_REQUEST: + MutableIdentifierCollection icDeregistrationRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icDeregistrationRequest.remove(IdentifierClass.USER); + icDeregistrationRequest.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.DEREGISTER.toString()) + .details("UNIT DE-REGISTRATION REQUEST") + .identifiers(icDeregistrationRequest) + .build()); + break; + case ISP_UNIT_REGISTRATION_REQUEST: + if(tsbk instanceof UnitRegistrationRequest) + { + UnitRegistrationRequest urr = (UnitRegistrationRequest)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REGISTER.toString()) + .details((urr.isEmergency() ? "EMERGENCY " : "") + + "UNIT REGISTRATION REQUEST - CAPABILITY:" + urr.getCapability()) + .identifiers(ic) + .build()); + } + break; + case ISP_LOCATION_REGISTRATION_REQUEST: + if(tsbk instanceof LocationRegistrationRequest) + { + LocationRegistrationRequest lrr = (LocationRegistrationRequest)tsbk; + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REGISTER.toString()) + .details((lrr.isEmergency() ? "EMERGENCY " : "") + + "LOCATION REGISTRATION REQUEST - CAPABILITY:" + lrr.getCapability()) + .identifiers(ic) + .build()); + } + break; + case ISP_PROTECTION_PARAMETER_REQUEST: + MutableIdentifierCollection icProtection = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icProtection.remove(IdentifierClass.USER); + icProtection.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("ENCRYPTION PARAMETERS") + .identifiers(icProtection) + .build()); + break; + case ISP_IDENTIFIER_UPDATE_REQUEST: + MutableIdentifierCollection icIdentifierUpdate = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icIdentifierUpdate.remove(IdentifierClass.USER); + icIdentifierUpdate.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("FREQUENCY BAND DETAILS") + .identifiers(icIdentifierUpdate) + .build()); + break; + case ISP_ROAMING_ADDRESS_REQUEST: + MutableIdentifierCollection icRoamingRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icRoamingRequest.remove(IdentifierClass.USER); + icRoamingRequest.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.REQUEST.toString()) + .details("ROAMING ADDRESS") + .identifiers(new IdentifierCollection(tsbk.getIdentifiers())) + .build()); + break; + case ISP_ROAMING_ADDRESS_RESPONSE: + MutableIdentifierCollection icRoaming = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icRoaming.remove(IdentifierClass.USER); + icRoaming.update(tsbk.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.RESPONSE.toString()) + .details("ROAMING ADDRESS") + .identifiers(icRoaming) + .build()); + break; + default: + mLog.debug("Unrecognized TSBK Opcode: " + tsbk.getOpcode().name()); + break; + } } } - private void processTSBKCommand(TSBKMessage message) + /** + * Processes encryption sync parameters carried by an LDU2 message + * + * @param esp that is non-null and valid + */ + private void processEncryptionSyncParameters(EncryptionSyncParameters esp, long timestamp) { - switch(message.getOpcode()) + if(esp.isEncryptedAudio()) { - case AUTHENTICATION_COMMAND: - AuthenticationCommand ac = (AuthenticationCommand)message; + for(Identifier identifier : esp.getIdentifiers()) + { + //Add to the identifier collection after filtering through the patch group manager + getIdentifierCollection().update(mPatchGroupManager.update(identifier)); + } + Encryption encryption = Encryption.fromValue(esp.getEncryptionKey().getValue().getAlgorithm()); + updateCurrentCall(DecodeEventType.CALL_ENCRYPTED, "ALGORITHM:" + encryption.toString(), timestamp); + } + else + { + getIdentifierCollection().remove(Form.ENCRYPTION_KEY); + updateCurrentCall(DecodeEventType.CALL, null, timestamp); + } + } - if(mLastCommandEventID == null || !mLastCommandEventID - .contentEquals(ac.getFullTargetID())) + /** + * Processes a Link Control Word (LCW) that is carried by either an LDU1 or a TDULC message. + * + * @param lcw that is non-null and valid + */ + private void processLinkControl(LinkControlWord lcw, long timestamp) + { + switch(lcw.getOpcode()) + { + //Calls in-progress on this channel + case GROUP_VOICE_CHANNEL_USER: + case MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_USER: + case MOTOROLA_TALK_COMPLETE: + case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER: + case UNIT_TO_UNIT_VOICE_CHANNEL_USER: + for(Identifier identifier : lcw.getIdentifiers()) { - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.COMMAND) - .aliasList(getAliasList()) - .details("AUTHENTICATE") - .to(ac.getWACN() + "-" + ac.getSystemID() + "-" + - ac.getTargetID()) - .build()); - - mLastCommandEventID = ac.getFullTargetID(); + //Add to the identifier collection after filtering through the patch group manager + getIdentifierCollection().update(mPatchGroupManager.update(identifier)); } break; + + //Call termination + case CALL_TERMINATION_OR_CANCELLATION: + //Note: we only broadcast an END state if this is a network-commanded channel teardown + if(lcw instanceof LCCallTermination && ((LCCallTermination)lcw).isNetworkCommandedTeardown()) + { + broadcast(new DecoderStateEvent(this, Event.END, State.FADE)); + } + break; + + //Calls in-progress on another channel + case GROUP_VOICE_CHANNEL_UPDATE: + case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT: + break; + + //Network configuration messages + case ADJACENT_SITE_STATUS_BROADCAST: + case ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT: + case NETWORK_STATUS_BROADCAST: + case NETWORK_STATUS_BROADCAST_EXPLICIT: + case PROTECTION_PARAMETER_BROADCAST: + case RFSS_STATUS_BROADCAST: + case RFSS_STATUS_BROADCAST_EXPLICIT: + case SECONDARY_CONTROL_CHANNEL_BROADCAST: + case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: + case SYSTEM_SERVICE_BROADCAST: + mNetworkConfigurationMonitor.process(lcw); + break; + + //Patch Group management + case MOTOROLA_PATCH_GROUP_ADD: + mPatchGroupManager.addPatchGroups(lcw.getIdentifiers()); + break; + case MOTOROLA_PATCH_GROUP_DELETE: + mPatchGroupManager.removePatchGroups(lcw.getIdentifiers()); + break; + case MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_UPDATE: + mPatchGroupManager.addPatchGroups(lcw.getIdentifiers()); + break; + + //Other events + case CALL_ALERT: + MutableIdentifierCollection icCallAlert = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icCallAlert.remove(IdentifierClass.USER); + icCallAlert.update(lcw.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(timestamp) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.PAGE.toString()) + .details("Call Alert") + .identifiers(icCallAlert) + .build()); + break; case EXTENDED_FUNCTION_COMMAND: - io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.ExtendedFunctionCommand efc = (io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.ExtendedFunctionCommand)message; - - if(mLastCommandEventID == null || !mLastCommandEventID - .contentEquals(efc.getTargetAddress())) + if(lcw instanceof LCExtendedFunctionCommand) { - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.FUNCTION) - .aliasList(getAliasList()) - .details("EXTENDED FUNCTION: " + - efc.getExtendedFunction().getLabel()) - .from(efc.getSourceAddress()) - .to(efc.getTargetAddress()) - .build()); + LCExtendedFunctionCommand efc = (LCExtendedFunctionCommand)lcw; - mLastCommandEventID = efc.getTargetAddress(); + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(lcw.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(timestamp) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.COMMAND.toString()) + .details("Extended Function: " + efc.getExtendedFunction() + + " Arguments:" + efc.getExtendedFunctionArguments()) + .identifiers(ic) + .build()); } break; - case RADIO_UNIT_MONITOR_COMMAND: - RadioUnitMonitorCommand rumc = (RadioUnitMonitorCommand)message; - - if(mLastCommandEventID == null || !mLastCommandEventID - .contentEquals(rumc.getTargetAddress())) - { - broadcast(new P25CallEvent.Builder(CallEvent.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(CallEvent.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: - io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.UnitRegistrationCommand urc = (io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.UnitRegistrationCommand)message; - - if(mLastCommandEventID == null || !mLastCommandEventID - .contentEquals(urc.getTargetAddress())) - { - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.COMMAND) - .aliasList(getAliasList()) - .details("REGISTER") - .from(urc.getSourceAddress()) - .to(urc.getTargetAddress()) - .build()); - - mLastCommandEventID = urc.getTargetAddress(); - } - break; - default: - break; - } - } - - private void processTSBKMessage(TSBKMessage message) - { - io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.MessageUpdate mu = (io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.MessageUpdate)message; - - broadcast(new P25CallEvent.Builder(CallEvent.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: - io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.GroupAffiliationQuery gaq = (io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.GroupAffiliationQuery)message; + MutableIdentifierCollection icGroupAffiliation = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icGroupAffiliation.remove(IdentifierClass.USER); + icGroupAffiliation.update(lcw.getIdentifiers()); - if(mLastQueryEventID == null || !mLastQueryEventID - .contentEquals(gaq.getTargetAddress())) + broadcast(P25DecodeEvent.builder(timestamp) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.QUERY.toString()) + .details("Group Affiliation") + .identifiers(icGroupAffiliation) + .build()); + break; + case MESSAGE_UPDATE: + if(lcw instanceof LCMessageUpdate) { - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.QUERY) - .aliasList(getAliasList()) - .details("GROUP AFFILIATION") - .from(gaq.getSourceAddress()) - .to(gaq.getTargetAddress()) + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(lcw.getIdentifiers()); + + LCMessageUpdate mu = (LCMessageUpdate)lcw; + broadcast(P25DecodeEvent.builder(timestamp) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.SDM.toString()) + .details("MSG:" + mu.getShortDataMessage()) + .identifiers(ic) .build()); } break; case STATUS_QUERY: - io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.StatusQuery sq = (io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.StatusQuery)message; + MutableIdentifierCollection icStatusQuery = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icStatusQuery.remove(IdentifierClass.USER); + icStatusQuery.update(lcw.getIdentifiers()); - if(mLastQueryEventID == null || !mLastQueryEventID - .contentEquals(sq.getTargetAddress())) - { - broadcast(new P25CallEvent.Builder(CallEvent.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(CallEvent.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(CallEvent.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(CallEvent.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(CallEvent.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(CallEvent.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(CallEvent.CallEventType.RESPONSE) - .aliasList(getAliasList()) - .details("QUEUED REASON: " + qr.getReason().name() + - " REQUESTED: " + qr.getServiceType().name()) - .from(qr.getSourceAddress()) - .to(qr.getTargetAddress()) - .build()); - - mLastResponseEventID = qr.getTargetAddress(); - } + broadcast(P25DecodeEvent.builder(timestamp) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.QUERY.toString()) + .details("Status") + .identifiers(icStatusQuery) + .build()); break; case STATUS_UPDATE: - io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.StatusUpdate su = - (io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.StatusUpdate)message; - - if(mLastResponseEventID == null || !su.getTargetAddress() - .contentEquals(mLastResponseEventID)) + if(lcw instanceof LCStatusUpdate) { - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.RESPONSE) - .aliasList(getAliasList()) - .details("STATUS USER: " + su.getUserStatus() + - " UNIT: " + su.getUnitStatus()) - .from(su.getSourceAddress()) - .to(su.getTargetAddress()) + LCStatusUpdate su = (LCStatusUpdate)lcw; + + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(lcw.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(timestamp) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.STATUS.toString()) + .details("UNIT:" + su.getUnitStatus() + " USER:" + su.getUserStatus()) + .identifiers(ic) .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(CallEvent.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(CallEvent.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); - } - - mLastRegistrationEventID = udr.getSourceID(); - } - break; - default: - break; - } - } - - private void processTSBKRFSSStatus(RFSSStatusBroadcast message) - { - mRFSSStatusMessage = message; - - updateNAC(message.getNAC()); - - updateSystem(message.getSystemID()); - - mSiteAttributeMonitor.process(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(CallEvent.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(CallEvent.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(CallEvent.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; - - switch(((MotorolaTSBKMessage)tsbk).getMotorolaOpcode()) - { - case PATCH_GROUP_CHANNEL_GRANT: - //Cleanup patch groups - auto-expire any patch groups before we allocate a channel - mPatchGroupManager.cleanupPatchGroups(); - - PatchGroupVoiceChannelGrant pgvcg = (PatchGroupVoiceChannelGrant)tsbk; - - channel = pgvcg.getChannel(); - from = pgvcg.getSourceAddress(); - to = pgvcg.getPatchGroupAddress(); - - if(hasCallEvent(channel, from, to)) - { - updateCallEvent(channel, from, to); - } - else - { - StringBuilder details = new StringBuilder(); - details.append(pgvcg.isTDMAChannel() ? "TDMA " : ""); - details.append(pgvcg.isEmergency() ? "EMERGENCY " : ""); - details.append("PRI:").append(pgvcg.getPriority()); - details.append(pgvcg.isEncryptedChannel() ? " ENCRYPTED" : ""); - details.append(" ").append(pgvcg.getSessionMode().name()); - - P25CallEvent event = new P25CallEvent.Builder(CallEvent.CallEventType.PATCH_GROUP_CALL) - .aliasList(getAliasList()) - .channel(channel) - .details(details.toString()) - .frequency(pgvcg.getDownlinkFrequency()) - .from(from) - .to(to) - .build(); - - registerCallEvent(event); - broadcast(event); - } - - if(!pgvcg.isTDMAChannel()) - { - broadcast(new TrafficChannelAllocationEvent(this, mChannelCallMap.get(channel))); - } - break; - case PATCH_GROUP_CHANNEL_GRANT_UPDATE: - //Cleanup patch groups - auto-expire any patch groups before we allocate a channel - mPatchGroupManager.cleanupPatchGroups(); - - PatchGroupVoiceChannelGrantUpdate gvcgu = (PatchGroupVoiceChannelGrantUpdate)tsbk; - - channel = gvcgu.getChannel1(); - to = gvcgu.getPatchGroupAddress1(); - - if(hasCallEvent(channel, null, to)) - { - updateCallEvent(channel, null, to); - } - else - { - P25CallEvent event = new P25CallEvent.Builder(CallEvent.CallEventType.PATCH_GROUP_CALL) - .aliasList(getAliasList()) - .channel(channel) - .details((gvcgu.isTDMAChannel1() ? "TDMA " : "") + - (gvcgu.isEncrypted() ? "ENCRYPTED " : "")) - .frequency(gvcgu.getDownlinkFrequency1()) - .to(to) - .build(); - - registerCallEvent(event); - broadcast(event); - } - - if(!gvcgu.isTDMAChannel1()) - { - broadcast(new TrafficChannelAllocationEvent(this, mChannelCallMap.get(channel))); - } - - String channel2 = gvcgu.getChannel2(); - String to2 = gvcgu.getPatchGroupAddress2(); - - if(hasCallEvent(channel2, null, to2)) - { - updateCallEvent(channel2, null, to2); - } - else - { - P25CallEvent event = new P25CallEvent.Builder(CallEvent.CallEventType.PATCH_GROUP_CALL) - .aliasList(getAliasList()) - .channel(channel2) - .details((gvcgu.isTDMAChannel2() ? "TDMA " : "") + - (gvcgu.isEncrypted() ? "ENCRYPTED " : "")) - .frequency(gvcgu.getDownlinkFrequency2()) - .to(to2) - .build(); - - registerCallEvent(event); - broadcast(event); - } - - if(!gvcgu.isTDMAChannel2()) - { - broadcast(new TrafficChannelAllocationEvent(this, mChannelCallMap.get(channel2))); - } - - break; - case PATCH_GROUP_ADD: - PatchGroupAdd pga = (PatchGroupAdd)tsbk; - mPatchGroupManager.updatePatchGroup(pga.getPatchGroupAddress(), pga.getPatchedTalkgroups()); - break; - case PATCH_GROUP_DELETE: - PatchGroupDelete pgd = (PatchGroupDelete)tsbk; - mPatchGroupManager.removePatchGroup(pgd.getPatchGroupAddress()); - break; - case CCH_PLANNED_SHUTDOWN: - if(!mControlChannelShutdownLogged) - { - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.NOTIFICATION) - .details("PLANNED CONTROL CHANNEL SHUTDOWN") - .build()); - - mControlChannelShutdownLogged = true; - } - break; - } - } - - /** - * Process a traffic channel allocation message - */ - private void processTSBKChannelGrant(TSBKMessage message) - { - //Cleanup patch groups - auto-expire any patch groups before we allocate a channel - mPatchGroupManager.cleanupPatchGroups(); - - 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 - { - StringBuilder details = new StringBuilder(); - details.append(gdcg.isTDMAChannel() ? "TDMA " : ""); - details.append(gdcg.isEmergency() ? "EMERGENCY " : ""); - details.append("PRI:").append(gdcg.getPriority()).append(" "); - details.append(gdcg.isEncryptedChannel() ? "ENCRYPTED " : ""); - details.append(gdcg.getSessionMode().name()); - - P25CallEvent event = new P25CallEvent.Builder(CallEvent.CallEventType.DATA_CALL) - .aliasList(getAliasList()) - .channel(channel) - .details(details.toString()) - .frequency(gdcg.getDownlinkFrequency()) - .from(from) - .to(to) - .build(); - - registerCallEvent(event); - broadcast(event); - } - - if(!mIgnoreDataCalls && !gdcg.isTDMAChannel()) - { - 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 - { - StringBuilder details = new StringBuilder(); - details.append(gvcg.isTDMAChannel() ? "TDMA " : ""); - details.append(gvcg.isEmergency() ? "EMERGENCY " : ""); - details.append("PRI:").append(gvcg.getPriority()).append(" "); - details.append(gvcg.isEncryptedChannel() ? "ENCRYPTED " : ""); - details.append(gvcg.getSessionMode().name()); - - P25CallEvent event = new P25CallEvent.Builder(CallEvent.CallEventType.GROUP_CALL) - .aliasList(getAliasList()) - .channel(channel) - .details(details.toString()) - .frequency(gvcg.getDownlinkFrequency()) - .from(from) - .to(to) - .build(); - - registerCallEvent(event); - broadcast(event); - } - - if(!gvcg.isTDMAChannel()) - { - 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 - { - StringBuilder details = new StringBuilder(); - details.append(gvcgu.isTDMAChannel() ? "TDMA " : ""); - details.append(gvcgu.isEmergency() ? "EMERGENCY " : ""); - details.append("PRI:").append(gvcgu.getPriority()).append(" "); - details.append(gvcgu.isEncryptedChannel() ? "ENCRYPTED " : ""); - details.append(gvcgu.getSessionMode().name()); - - P25CallEvent event = new P25CallEvent.Builder(CallEvent.CallEventType.GROUP_CALL) - .aliasList(getAliasList()) - .channel(gvcgu.getChannel1()) - .details(details.toString()) - .frequency(gvcgu.getDownlinkFrequency1()) - .from(from) - .to(to) - .build(); - - registerCallEvent(event); - broadcast(event); - } - - if(!gvcgu.isTDMAChannel1()) - { - 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 - { - StringBuilder details = new StringBuilder(); - details.append(gvcgu.isTDMAChannel() ? "TDMA " : ""); - details.append(gvcgu.isEmergency() ? "EMERGENCY " : ""); - details.append("PRI:").append(gvcgu.getPriority()).append(" "); - details.append(gvcgu.isEncryptedChannel() ? "ENCRYPTED " : ""); - details.append(gvcgu.getSessionMode().name()); - - P25CallEvent event2 = new P25CallEvent.Builder(CallEvent.CallEventType.GROUP_CALL) - .aliasList(getAliasList()) - .channel(gvcgu.getChannel2()) - .details(details.toString()) - .frequency(gvcgu.getDownlinkFrequency2()) - .to(gvcgu.getGroupAddress2()) - .build(); - - registerCallEvent(event2); - broadcast(event2); - } - - if(!gvcgu.isTDMAChannel2()) - { - 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 - { - StringBuilder details = new StringBuilder(); - details.append(gvcgue.isTDMAChannel() ? "TDMA " : ""); - details.append(gvcgue.isEmergency() ? "EMERGENCY " : ""); - details.append("PRI:").append(gvcgue.getPriority()).append(" "); - details.append(gvcgue.isEncryptedChannel() ? "ENCRYPTED " : ""); - details.append(gvcgue.getSessionMode().name()); - - P25CallEvent event = new P25CallEvent.Builder(CallEvent.CallEventType.GROUP_CALL) - .aliasList(getAliasList()) - .channel(channel) - .details(details.toString()) - .frequency(gvcgue.getDownlinkFrequency()) - .from(from) - .to(to) - .build(); - - registerCallEvent(event); - broadcast(event); - } - - if(!gvcgue.isTDMAChannel()) - { - 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 - { - StringBuilder details = new StringBuilder(); - details.append(idcg.isTDMAChannel() ? "TDMA " : ""); - details.append(idcg.isEmergency() ? "EMERGENCY " : ""); - details.append("PRI:").append(idcg.getPriority()).append(" "); - details.append(idcg.isEncryptedChannel() ? "ENCRYPTED " : ""); - details.append(idcg.getSessionMode().name()); - - P25CallEvent event = new P25CallEvent.Builder(CallEvent.CallEventType.DATA_CALL) - .aliasList(getAliasList()) - .channel(channel) - .details(details.toString()) - .frequency(idcg.getDownlinkFrequency()) - .from(from) - .to(to) - .build(); - - registerCallEvent(event); - broadcast(event); - } - - if(!mIgnoreDataCalls && !idcg.isTDMAChannel()) - { - 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 - { - StringBuilder details = new StringBuilder(); - details.append(sdcg.isTDMAChannel() ? "TDMA " : ""); - details.append(sdcg.isEmergency() ? "EMERGENCY " : ""); - details.append(sdcg.isEncryptedChannel() ? "ENCRYPTED " : ""); - details.append(sdcg.getSessionMode().name()); - - P25CallEvent event = new P25CallEvent.Builder(CallEvent.CallEventType.DATA_CALL) - .aliasList(getAliasList()) - .channel(channel) - .details(details.toString()) - .frequency(sdcg.getDownlinkFrequency()) - .from(from) - .to(to) - .build(); - - registerCallEvent(event); - broadcast(event); - } - - if(!mIgnoreDataCalls && !sdcg.isTDMAChannel()) - { - 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 - { - StringBuilder details = new StringBuilder(); - details.append(tivcg.isTDMAChannel() ? "TDMA " : ""); - details.append(tivcg.isEmergency() ? "EMERGENCY " : ""); - details.append("PRI:").append(tivcg.getPriority()).append(" "); - details.append(tivcg.isEncryptedChannel() ? "ENCRYPTED " : ""); - details.append(tivcg.getSessionMode().name()).append(" "); - details.append("CALL TIMER:").append(tivcg.getCallTimer()); - - P25CallEvent event = new P25CallEvent.Builder( - CallEvent.CallEventType.TELEPHONE_INTERCONNECT) - .aliasList(getAliasList()) - .channel(channel) - .details(details.toString()) - .frequency(tivcg.getDownlinkFrequency()) - .from(from) - .to(to) - .build(); - - registerCallEvent(event); - broadcast(event); - } - - if(!tivcg.isTDMAChannel()) - { - 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; - - /* 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 - { - StringBuilder details = new StringBuilder(); - details.append(tivcgu.isTDMAChannel() ? "TDMA " : ""); - details.append(tivcgu.isEmergency() ? "EMERGENCY " : ""); - details.append("PRI:").append(tivcgu.getPriority()).append(" "); - details.append(tivcgu.isEncryptedChannel() ? "ENCRYPTED " : ""); - details.append(tivcgu.getSessionMode().name()).append(" "); - details.append("CALL TIMER:").append(tivcgu.getCallTimer()); - - P25CallEvent event = new P25CallEvent.Builder( - CallEvent.CallEventType.TELEPHONE_INTERCONNECT) - .aliasList(getAliasList()) - .channel(channel) - .details(details.toString()) - .frequency(tivcgu.getDownlinkFrequency()) - .from(from) - .to(to) - .build(); - - registerCallEvent(event); - broadcast(event); - } - - if(!tivcgu.isTDMAChannel()) - { - broadcast(new TrafficChannelAllocationEvent(this, mChannelCallMap.get(channel))); - } - break; - case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: - UnitToUnitVoiceChannelGrant uuvcg = (UnitToUnitVoiceChannelGrant)message; - - channel = uuvcg.getChannelIdentifier() + "-" + uuvcg.getChannelNumber(); - from = uuvcg.getSourceAddress(); - to = uuvcg.getTargetAddress(); - - if(hasCallEvent(channel, from, to)) - { - updateCallEvent(channel, from, to); - } - else - { - StringBuilder details = new StringBuilder(); - details.append(uuvcg.isTDMAChannel() ? "TDMA " : ""); - details.append(uuvcg.isEmergency() ? "EMERGENCY " : ""); - details.append("PRI:").append(uuvcg.getPriority()).append(" "); - details.append(uuvcg.isEncryptedChannel() ? " ENCRYPTED " : ""); - details.append(uuvcg.getSessionMode().name()); - - P25CallEvent event = new P25CallEvent.Builder( - CallEvent.CallEventType.UNIT_TO_UNIT_CALL) - .aliasList(getAliasList()) - .channel(channel) - .details(details.toString()) - .frequency(uuvcg.getDownlinkFrequency()) - .from(from) - .to(to) - .build(); - - registerCallEvent(event); - broadcast(event); - } - - if(!uuvcg.isTDMAChannel()) - { - 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 - { - StringBuilder details = new StringBuilder(); - details.append(uuvcgu.isTDMAChannel() ? "TDMA " : ""); - details.append(uuvcgu.isEmergency() ? "EMERGENCY " : ""); - details.append("PRI:").append(uuvcgu.getPriority()).append(" "); - details.append(uuvcgu.isEncryptedChannel() ? "ENCRYPTED " : ""); - details.append(uuvcgu.getSessionMode().name()); - - P25CallEvent event = new P25CallEvent.Builder( - CallEvent.CallEventType.UNIT_TO_UNIT_CALL) - .aliasList(getAliasList()) - .channel(channel) - .details(details.toString()) - .frequency(uuvcgu.getDownlinkFrequency()) - .from(from) - .to(to) - .build(); - - registerCallEvent(event); - broadcast(event); - } - - if(!uuvcgu.isTDMAChannel()) - { - broadcast(new TrafficChannelAllocationEvent(this, mChannelCallMap.get(channel))); - } - break; - default: - break; - } - } - - private void updateSystem(String system) - { - if(mSystem == null || (system != null && !mSystem.contentEquals(system))) - { - mSystem = system; - broadcastSystemAndNACUpdate(); - } - } - - /** - * Broadcasts an update to the NAC - */ - private void updateNAC(String nac) - { - if(mNAC == null || (nac != null && !mNAC.contentEquals(nac))) - { - mNAC = nac; - broadcastSystemAndNACUpdate(); - } - } - - private void broadcastSystemAndNACUpdate() - { - String label = String.format("SYS:%s NAC:%s", mSystem, mNAC); - broadcast(new AttributeChangeRequest(Attribute.NETWORK_ID_1, label)); - } - - /** - * Process a unit paging event message - */ - private void processTSBKPage(TSBKMessage message) - { - switch(message.getOpcode()) - { - case CALL_ALERT: - io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.CallAlert ca = (io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.CallAlert)message; - - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.PAGE) - .aliasList(getAliasList()) - .from(ca.getSourceID()) - .to(ca.getTargetAddress()) - .build()); - break; - case UNIT_TO_UNIT_ANSWER_REQUEST: - io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice.UnitToUnitAnswerRequest utuar = (io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice.UnitToUnitAnswerRequest)message; - - if(mLastPageEventID == null || !mLastPageEventID - .contentEquals(utuar.getTargetAddress())) - { - broadcast(new P25CallEvent.Builder(CallEvent.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(CallEvent.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: - io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice.TelephoneInterconnectAnswerRequest tiar = - (io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice.TelephoneInterconnectAnswerRequest)message; - - if(mLastPageEventID == null || !mLastPageEventID - .contentEquals(tiar.getTargetAddress())) + if(lcw instanceof LCTelephoneInterconnectAnswerRequest) { - broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.PAGE) - .aliasList(getAliasList()) - .details(("TELEPHONE INTERCONNECT")) - .from(tiar.getTelephoneNumber()) - .to(tiar.getTargetAddress()) - .build()); + LCTelephoneInterconnectAnswerRequest tiar = (LCTelephoneInterconnectAnswerRequest)lcw; - mLastPageEventID = tiar.getTargetAddress(); + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(lcw.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(timestamp) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.PAGE.toString()) + .details("Telephone Call:" + tiar.getTelephoneNumber()) + .identifiers(ic) + .build()); } break; + case UNIT_AUTHENTICATION_COMMAND: + MutableIdentifierCollection icAuthenticationCommand = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icAuthenticationCommand.remove(IdentifierClass.USER); + icAuthenticationCommand.update(lcw.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(timestamp) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.COMMAND.toString()) + .details("Authenticate Unit") + .identifiers(icAuthenticationCommand) + .build()); + break; + case UNIT_REGISTRATION_COMMAND: + MutableIdentifierCollection icUnitRegistrationCommand = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icUnitRegistrationCommand.remove(IdentifierClass.USER); + icUnitRegistrationCommand.update(lcw.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(timestamp) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.COMMAND.toString()) + .details("Unit Registration") + .identifiers(icUnitRegistrationCommand) + .build()); + break; + case UNIT_TO_UNIT_ANSWER_REQUEST: + MutableIdentifierCollection icUnitAnswerRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icUnitAnswerRequest.remove(IdentifierClass.USER); + icUnitAnswerRequest.update(lcw.getIdentifiers()); + + broadcast(P25DecodeEvent.builder(timestamp) + .channel(getCurrentChannel()) + .eventDescription(DecodeEventType.PAGE.toString()) + .details("Unit-to-Unit Answer Request") + .identifiers(icUnitAnswerRequest) + .build()); + break; default: + mLog.info("Unrecognized/Unprocessed Link Control Word Opcode: " + lcw.getOpcode().name()); break; } } @@ -3364,187 +2386,7 @@ public class P25DecoderState extends DecoderState @Override public String getActivitySummary() { - StringBuilder sb = new StringBuilder(); - - sb.append("Activity Summary - Decoder:P25 ").append(getModulation().getLabel()).append("\n"); - sb.append(DIVIDER1); - sb.append("SITE "); - - if(mNetworkStatus != null) - { - sb.append("NAC:" + mNetworkStatus.getNAC()); - sb.append("h WACN:" + mNetworkStatus.getWACN()); - sb.append("h SYS:" + mNetworkStatus.getSystemID()); - sb.append("h [").append(mNetworkStatus.getNetworkCallsign()).append("]"); - sb.append(" LRA:" + mNetworkStatus.getLocationRegistrationArea()); - } - else if(mNetworkStatusExtended != null) - { - sb.append("NAC:" + mNetworkStatusExtended.getNAC()); - sb.append("h WACN:" + mNetworkStatusExtended.getWACN()); - sb.append("h SYS:" + mNetworkStatusExtended.getSystemID()); - sb.append("h [").append(mNetworkStatusExtended.getNetworkCallsign()).append("]"); - sb.append(" LRA:" + mNetworkStatusExtended.getLocationRegistrationArea()); - } - - String site = null; - - if(mRFSSStatusMessage != null) - { - site = mRFSSStatusMessage.getRFSubsystemID() + "-" + mRFSSStatusMessage.getSiteID(); - } - else if(mRFSSStatusMessageExtended != null) - { - site = mRFSSStatusMessageExtended.getRFSubsystemID() + "-" + mRFSSStatusMessageExtended.getSiteID(); - } - - sb.append("h RFSS-SITE:").append(site).append("h"); - - if(hasAliasList()) - { - Alias siteAlias = getAliasList().getSiteID(site); - - if(siteAlias != null) - { - sb.append(" " + siteAlias.getName()); - } - } - - sb.append("\n").append(DIVIDER2); - - if(mNetworkStatus != null) - { - sb.append("SERVICES: " + SystemService.toString(mNetworkStatus.getSystemServiceClass())).append("\n"); - sb.append(DIVIDER2); - sb.append("PCCH DOWNLINK ") - .append(mFrequencyFormatter.format((double)mNetworkStatus.getDownlinkFrequency() / 1E6d)) - .append(" [").append(mNetworkStatus.getIdentifier()).append("-").append(mNetworkStatus.getChannel()) - .append("] UPLINK ") - .append(mFrequencyFormatter.format((double)mNetworkStatus.getUplinkFrequency() / 1E6d)) - .append(" [").append(mNetworkStatus.getIdentifier()).append("-").append(mNetworkStatus.getChannel()) - .append("]\n"); - } - else if(mNetworkStatusExtended != null) - { - sb.append("\nSERVICES:").append(SystemService.toString(mNetworkStatusExtended.getSystemServiceClass())) - .append("\n"); - sb.append(DIVIDER2); - sb.append("PCCH DOWNLINK ") - .append(mFrequencyFormatter.format((double)mNetworkStatusExtended.getDownlinkFrequency() / 1E6d)) - .append(" [").append(mNetworkStatusExtended.getTransmitIdentifier()).append("-") - .append(mNetworkStatusExtended.getTransmitChannel()).append("] UPLINK ") - .append(mFrequencyFormatter.format((double)mNetworkStatusExtended.getUplinkFrequency() / 1E6d)) - .append(" [").append(mNetworkStatusExtended.getReceiveIdentifier()).append("-") - .append(mNetworkStatusExtended.getReceiveChannel()).append("]\n"); - } - - if(mSecondaryControlChannels.isEmpty()) - { - sb.append("SCCH: NONE\n"); - } - else - { - for(SecondaryControlChannelBroadcast sec : mSecondaryControlChannels) - { - sb.append("SCCH DOWNLINK ") - .append(mFrequencyFormatter.format((double)sec.getDownlinkFrequency1() / 1E6d)) - .append(" [").append(sec.getIdentifier1()).append("-").append(sec.getChannel1()).append("] UPLINK ") - .append(mFrequencyFormatter.format((double)sec.getUplinkFrequency1() / 1E6d)) - .append(" [").append(sec.getIdentifier1()).append("-").append(sec.getChannel1()).append("]"); - - if(sec.hasChannel2()) - { - sb.append(" SCCH 2 DOWNLINK ") - .append(mFrequencyFormatter.format((double)sec.getDownlinkFrequency2() / 1E6d)) - .append(" [").append(sec.getIdentifier2()).append("-").append(sec.getChannel2()) - .append("] UPLINK ").append(mFrequencyFormatter.format((double)sec.getUplinkFrequency2() / 1E6d)) - .append(" [").append(sec.getIdentifier2()).append("-").append(sec.getChannel2()).append("]"); - } - - sb.append("\n"); - } - } - - - if(mSNDCPDataChannel != null) - { - sb.append("SNDCP DOWNLINK ") - .append(mFrequencyFormatter.format((double)mSNDCPDataChannel.getDownlinkFrequency() / 1E6D)) - .append(" [").append(mSNDCPDataChannel.getTransmitChannel()).append("]").append(" UPLINK ") - .append(mFrequencyFormatter.format((double)mSNDCPDataChannel.getUplinkFrequency() / 1E6D)) - .append(" [").append(mSNDCPDataChannel.getReceiveChannel()).append("]\n"); - } - - if(mProtectionParameterBroadcast != null) - { - sb.append(DIVIDER2); - sb.append("ENCRYPTION TYPE:").append(mProtectionParameterBroadcast.getEncryptionType().name()); - sb.append(" ALGORITHM:").append(mProtectionParameterBroadcast.getAlgorithmID()); - sb.append(" KEY:").append(mProtectionParameterBroadcast.getKeyID()); - sb.append(" INBOUND IV:").append(mProtectionParameterBroadcast.getInboundInitializationVector()); - sb.append(" OUTBOUND IV:").append(mProtectionParameterBroadcast.getOutboundInitializationVector()); - sb.append("\n"); - } - - List identifiers = new ArrayList<>(mBands.keySet()); - - Collections.sort(identifiers); - - sb.append(DIVIDER2).append("FREQUENCY BANDS:\n"); - for(Integer id : identifiers) - { - IBandIdentifier band = mBands.get(id); - sb.append(band.toString()).append("\n"); -// sb.append(" ").append(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"); - } - - sb.append(DIVIDER2).append("NEIGHBOR SITES: "); - - if(mNeighborMap.isEmpty()) - { - sb.append("NONE\n"); - } - else - { - for(IAdjacentSite neighbor : mNeighborMap.values()) - { - sb.append("\n"); - sb.append("NAC:").append(((P25Message)neighbor).getNAC()); - sb.append("h SYSTEM:" + neighbor.getSystemID()); - sb.append("h LRA:" + neighbor.getLRA()); - - String neighborID = neighbor.getRFSS() + "-" + neighbor.getSiteID(); - sb.append("h RFSS-SITE:" + neighborID); - - sb.append("h "); - - if(hasAliasList()) - { - Alias siteAlias = getAliasList().getSiteID(neighborID); - - if(siteAlias != null) - { - sb.append(siteAlias.getName()); - } - } - - sb.append("\n PCCH: DOWNLINK ") - .append(mFrequencyFormatter.format((double)neighbor.getDownlinkFrequency() / 1E6d)) - .append(" [").append(neighbor.getDownlinkChannel()).append("] UPLINK:") - .append(mFrequencyFormatter.format((double)neighbor.getUplinkFrequency() / 1E6d)) - .append(" [").append(neighbor.getDownlinkChannel()).append("] SERVICES: ") - .append(neighbor.getSystemServiceClass()); - } - } - - return sb.toString(); + return mNetworkConfigurationMonitor.getActivitySummary(); } @Override @@ -3554,32 +2396,7 @@ public class P25DecoderState extends DecoderState { 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(new AttributeChangeRequest(Attribute.CHANNEL_FREQUENCY_LABEL, mCurrentChannel)); - - mCurrentChannelFrequency = allocationEvent.getCallEvent().getFrequency(); - broadcast(new AttributeChangeRequest(Attribute.CHANNEL_FREQUENCY, mCurrentChannelFrequency)); - - mFromTalkgroupMonitor.reset(); - mFromTalkgroupMonitor.process(allocationEvent.getCallEvent().getFromID()); - - mToTalkgroupMonitor.reset(); - mToTalkgroupMonitor.process(allocationEvent.getCallEvent().getToID()); - } - } + mNetworkConfigurationMonitor.reset(); break; default: break; @@ -3596,9 +2413,13 @@ public class P25DecoderState extends DecoderState } } + @Override + public void init() + { + } + @Override public void stop() { - } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25Interleave.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25Interleave.java index de6f3231..37e73c01 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25Interleave.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25Interleave.java @@ -1,6 +1,7 @@ package io.github.dsheirer.module.decode.p25; import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,129 +12,152 @@ import java.util.BitSet; */ public class P25Interleave { - private final static Logger mLog = - LoggerFactory.getLogger( P25Interleave.class ); + private final static Logger mLog = + LoggerFactory.getLogger(P25Interleave.class); - private static int[] DATA_INTERLEAVE = new int[] { 0,1,2,3,52,53,54,55,100, - 101,102,103,148,149,150,151,4,5,6,7,56,57,58,59,104,105,106,107,152,153, - 154,155,8,9,10,11,60,61,62,63,108,109,110,111,156,157,158,159,12,13,14, - 15,64,65,66,67,112,113,114,115,160,161,162,163,16,17,18,19,68,69,70,71, - 116,117,118,119,164,165,166,167,20,21,22,23,72,73,74,75,120,121,122,123, - 168,169,170,171,24,25,26,27,76,77,78,79,124,125,126,127,172,173,174,175, - 28,29,30,31,80,81,82,83,128,129,130,131,176,177,178,179,32,33,34,35,84, - 85,86,87,132,133,134,135,180,181,182,183,36,37,38,39,88,89,90,91,136, - 137,138,139,184,185,186,187,40,41,42,43,92,93,94,95,140,141,142,143,188, - 189,190,191,44,45,46,47,96,97,98,99,144,145,146,147,192,193,194,195,48, - 49,50,51 }; + public static int[] DATA_INTERLEAVE = new int[]{0, 1, 2, 3, 52, 53, 54, 55, 100, + 101, 102, 103, 148, 149, 150, 151, 4, 5, 6, 7, 56, 57, 58, 59, 104, 105, 106, 107, 152, 153, + 154, 155, 8, 9, 10, 11, 60, 61, 62, 63, 108, 109, 110, 111, 156, 157, 158, 159, 12, 13, 14, + 15, 64, 65, 66, 67, 112, 113, 114, 115, 160, 161, 162, 163, 16, 17, 18, 19, 68, 69, 70, 71, + 116, 117, 118, 119, 164, 165, 166, 167, 20, 21, 22, 23, 72, 73, 74, 75, 120, 121, 122, 123, + 168, 169, 170, 171, 24, 25, 26, 27, 76, 77, 78, 79, 124, 125, 126, 127, 172, 173, 174, 175, + 28, 29, 30, 31, 80, 81, 82, 83, 128, 129, 130, 131, 176, 177, 178, 179, 32, 33, 34, 35, 84, + 85, 86, 87, 132, 133, 134, 135, 180, 181, 182, 183, 36, 37, 38, 39, 88, 89, 90, 91, 136, + 137, 138, 139, 184, 185, 186, 187, 40, 41, 42, 43, 92, 93, 94, 95, 140, 141, 142, 143, 188, + 189, 190, 191, 44, 45, 46, 47, 96, 97, 98, 99, 144, 145, 146, 147, 192, 193, 194, 195, 48, + 49, 50, 51}; - private static int[] DATA_DEINTERLEAVE = new int[] { 0,1,2,3,16,17,18,19,32, - 33,34,35,48,49,50,51,64,65,66,67,80,81,82,83,96,97,98,99,112,113,114,115, - 128,129,130,131,144,145,146,147,160,161,162,163,176,177,178,179,192,193, - 194,195,4,5,6,7,20,21,22,23,36,37,38,39,52,53,54,55,68,69,70,71,84,85, - 86,87,100,101,102,103,116,117,118,119,132,133,134,135,148,149,150,151, - 164,165,166,167,180,181,182,183,8,9,10,11,24,25,26,27,40,41,42,43,56,57, - 58,59,72,73,74,75,88,89,90,91,104,105,106,107,120,121,122,123,136,137, - 138,139,152,153,154,155,168,169,170,171,184,185,186,187,12,13,14,15,28, - 29,30,31,44,45,46,47,60,61,62,63,76,77,78,79,92,93,94,95,108,109,110, - 111,124,125,126,127,140,141,142,143,156,157,158,159,172,173,174,175,188, - 189,190,191 }; - - private static int[] VOICE_INTERLEAVE = new int[] { 0,24,48,72,96,120,25, - 1,73,49,121,97,2,26,50,74,98,122,27,3,75,51,123,99,4,28,52,76,100,124, - 29,5,77,53,125,101,6,30,54,78,102,126,31,7,79,55,127,103,8,32,56,80,104, - 128,33,9,81,57,129,105,10,34,58,82,106,130,35,11,83,59,131,107,12,36,60, - 84,108,132,37,13,85,61,133,109,14,38,62,86,110,134,39,87,15,63,135,111, - 16,40,64,88,112,136,41,17,89,65,137,113,18,42,66,90,114,138,43,19,91, - 67,139,115,20,44,68,92,116,140,45,21,93,69,141,117,22,46,70,94,119,142, - 47,23,95,71,143,118 }; + public static int[] DATA_DEINTERLEAVE = new int[]{0, 1, 2, 3, 16, 17, 18, 19, 32, + 33, 34, 35, 48, 49, 50, 51, 64, 65, 66, 67, 80, 81, 82, 83, 96, 97, 98, 99, 112, 113, 114, 115, + 128, 129, 130, 131, 144, 145, 146, 147, 160, 161, 162, 163, 176, 177, 178, 179, 192, 193, + 194, 195, 4, 5, 6, 7, 20, 21, 22, 23, 36, 37, 38, 39, 52, 53, 54, 55, 68, 69, 70, 71, 84, 85, + 86, 87, 100, 101, 102, 103, 116, 117, 118, 119, 132, 133, 134, 135, 148, 149, 150, 151, + 164, 165, 166, 167, 180, 181, 182, 183, 8, 9, 10, 11, 24, 25, 26, 27, 40, 41, 42, 43, 56, 57, + 58, 59, 72, 73, 74, 75, 88, 89, 90, 91, 104, 105, 106, 107, 120, 121, 122, 123, 136, 137, + 138, 139, 152, 153, 154, 155, 168, 169, 170, 171, 184, 185, 186, 187, 12, 13, 14, 15, 28, + 29, 30, 31, 44, 45, 46, 47, 60, 61, 62, 63, 76, 77, 78, 79, 92, 93, 94, 95, 108, 109, 110, + 111, 124, 125, 126, 127, 140, 141, 142, 143, 156, 157, 158, 159, 172, 173, 174, 175, 188, + 189, 190, 191}; - private static int[] VOICE_DEINTERLEAVE = new int[] { 0,7,12,19,24,31,36,43, - 48,55,60,67,72,79,84,91,96,103,108,115,120,127,132,139,1,6,13,18,25,30, - 37,42,49,54,61,66,73,78,85,90,97,102,109,114,121,126,133,138,2,9,14,21, - 26,33,38,45,50,57,62,69,74,81,86,93,98,105,110,117,122,129,134,141,3,8, - 15,20,27,32,39,44,51,56,63,68,75,80,87,92,99,104,111,116,123,128,135, - 140,4,11,16,23,28,35,40,47,52,59,64,71,76,83,88,95,100,107,112,119,124, - 131,136,143,5,10,17,22,29,34,41,46,53,58,65,70,77,82,89,94,101,106,113, - 118,125,130,137,142 }; - /** - * Deinterleaves the 196-bit block in message, identified by start and end - * bit positions. Note: end index (exclusive) should be one more than the - * last bit in the block. - * - * @param message - source message to deinterleave - * @param start - starting bit index for the block - * @param end - ending bit index for the block, plus 1 - */ - public static BinaryMessage deinterleaveData( BinaryMessage message, - int start, int end ) - { - return deinterleave( DATA_DEINTERLEAVE, message, start, end ); - } - - public static BinaryMessage deinterleaveVoice( BinaryMessage message, - int start, int end ) - { - return deinterleave( VOICE_DEINTERLEAVE, message, start, end ); - } - - public static BinaryMessage deinterleave( int[] pattern, BinaryMessage message, - int start, int end ) - { - BitSet original = message.get( start, end ); + public static int[] VOICE_INTERLEAVE = new int[]{0, 24, 48, 72, 96, 120, 25, + 1, 73, 49, 121, 97, 2, 26, 50, 74, 98, 122, 27, 3, 75, 51, 123, 99, 4, 28, 52, 76, 100, 124, + 29, 5, 77, 53, 125, 101, 6, 30, 54, 78, 102, 126, 31, 7, 79, 55, 127, 103, 8, 32, 56, 80, 104, + 128, 33, 9, 81, 57, 129, 105, 10, 34, 58, 82, 106, 130, 35, 11, 83, 59, 131, 107, 12, 36, 60, + 84, 108, 132, 37, 13, 85, 61, 133, 109, 14, 38, 62, 86, 110, 134, 39, 87, 15, 63, 135, 111, + 16, 40, 64, 88, 112, 136, 41, 17, 89, 65, 137, 113, 18, 42, 66, 90, 114, 138, 43, 19, 91, + 67, 139, 115, 20, 44, 68, 92, 116, 140, 45, 21, 93, 69, 141, 117, 22, 46, 70, 94, 119, 142, + 47, 23, 95, 71, 143, 118}; - /* Clear block bits in source message */ - message.clear( start, end ); + public static int[] VOICE_DEINTERLEAVE = new int[]{0, 7, 12, 19, 24, 31, 36, 43, + 48, 55, 60, 67, 72, 79, 84, 91, 96, 103, 108, 115, 120, 127, 132, 139, 1, 6, 13, 18, 25, 30, + 37, 42, 49, 54, 61, 66, 73, 78, 85, 90, 97, 102, 109, 114, 121, 126, 133, 138, 2, 9, 14, 21, + 26, 33, 38, 45, 50, 57, 62, 69, 74, 81, 86, 93, 98, 105, 110, 117, 122, 129, 134, 141, 3, 8, + 15, 20, 27, 32, 39, 44, 51, 56, 63, 68, 75, 80, 87, 92, 99, 104, 111, 116, 123, 128, 135, + 140, 4, 11, 16, 23, 28, 35, 40, 47, 52, 59, 64, 71, 76, 83, 88, 95, 100, 107, 112, 119, 124, + 131, 136, 143, 5, 10, 17, 22, 29, 34, 41, 46, 53, 58, 65, 70, 77, 82, 89, 94, 101, 106, 113, + 118, 125, 130, 137, 142}; - /* Iterate only the set bits in the original message and apply - * the deinterleave -- we don't have to evaluate the 0 bits */ - for (int i = original.nextSetBit( 0 ); - i >= 0 && i < pattern.length; - i = original.nextSetBit( i + 1 ) ) - { - message.set( start + pattern[ i ] ); - } - - return message; - } + /** + * Deinterleaves the 196-bit block in message, identified by start and end + * bit positions. Note: end index (exclusive) should be one more than the + * last bit in the block. + * + * @param message - source message to deinterleave + * @param start - starting bit index for the block + * @param end - ending bit index for the block, plus 1 + */ + public static BinaryMessage deinterleaveData(BinaryMessage message, + int start, int end) + { + return deinterleave(DATA_DEINTERLEAVE, message, start, end); + } + + public static BinaryMessage deinterleaveVoice(BinaryMessage message, + int start, int end) + { + return deinterleave(VOICE_DEINTERLEAVE, message, start, end); + } + + public static BinaryMessage deinterleave(int[] pattern, BinaryMessage message, + int start, int end) + { + BitSet original = message.get(start, end); + + /* Clear block bits in source message */ + message.clear(start, end); + + /* Iterate only the set bits in the original message and apply + * the deinterleave -- we don't have to evaluate the 0 bits */ + for(int i = original.nextSetBit(0); + i >= 0 && i < pattern.length; + i = original.nextSetBit(i + 1)) + { + message.set(start + pattern[i]); + } + + return message; + } + + /** + * Deinterleaves the message bits between start and end and returns them in a new message + * @param pattern to use for deinterleaving + * @param interleaved bitset from the original message + * @return binary message with length equal to the pattern size + */ + public static CorrectedBinaryMessage deinterleaveChunk(int[] pattern, BitSet interleaved) + { + CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(pattern.length); + + /* Iterate only the set bits in the original message and apply + * the deinterleave -- we don't have to evaluate the 0 bits */ + for(int i = interleaved.nextSetBit(0); + i >= 0 && i < pattern.length; + i = interleaved.nextSetBit(i + 1)) + { + deinterleaved.set(pattern[i]); + } + + return deinterleaved; + } /** * Interleaves the 196-bit block in message, identified by start and end - * bit positions. Note: end index (exclusive) should be one more than the + * bit positions. Note: end index (exclusive) should be one more than the * last bit in the block. - * + * * @param message - source message to interleave * @param start - starting bit index for the block * @param end - ending bit index for the block, plus 1 */ - public static BinaryMessage interleaveData( BinaryMessage message, - int start, int end ) + public static BinaryMessage interleaveData(BinaryMessage message, + int start, int end) { - return interleave( DATA_INTERLEAVE, message, start, end ); + return interleave(DATA_INTERLEAVE, message, start, end); } - - public static BinaryMessage interleaveVoice( BinaryMessage message, - int start, int end ) + + public static BinaryMessage interleaveVoice(BinaryMessage message, + int start, int end) { - return interleave( VOICE_INTERLEAVE, message, start, end ); + return interleave(VOICE_INTERLEAVE, message, start, end); } - - public static BinaryMessage interleave( int[] pattern, BinaryMessage message, - int start, int end ) + + public static BinaryMessage interleave(int[] pattern, BinaryMessage message, + int start, int end) { - BitSet original = message.get( start, end ); - - /* Clear block bits in source message */ - message.clear( start, end ); + BitSet original = message.get(start, end); + + /* Clear block bits in source message */ + message.clear(start, end); /* Iterate only the set bits in the original message and apply * the deinterleave -- we don't have to evaluate the 0 bits */ - for (int i = original.nextSetBit( 0 ); - i >= 0 && i < pattern.length; - i = original.nextSetBit( i + 1 ) ) + for(int i = original.nextSetBit(0); + i >= 0 && i < pattern.length; + i = original.nextSetBit(i + 1)) { - message.set( start + pattern[ i ] ); + message.set(start + pattern[i]); } - + return message; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageFramer.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageFramer.java deleted file mode 100644 index 54e00ea9..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageFramer.java +++ /dev/null @@ -1,744 +0,0 @@ -package io.github.dsheirer.module.decode.p25; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.bits.BitSetFullException; -import io.github.dsheirer.bits.MultiSyncPatternMatcher; -import io.github.dsheirer.bits.SoftSyncDetector; -import io.github.dsheirer.bits.SyncDetector; -import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop; -import io.github.dsheirer.dsp.symbol.Dibit; -import io.github.dsheirer.dsp.symbol.FrameSync; -import io.github.dsheirer.dsp.symbol.ISyncDetectListener; -import io.github.dsheirer.edac.BCH_63_16_11; -import io.github.dsheirer.edac.CRC; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.message.Message; -import io.github.dsheirer.module.decode.p25.message.P25Message; -import io.github.dsheirer.module.decode.p25.message.hdu.HDUMessage; -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.message.ldu.LDU2Message; -import io.github.dsheirer.module.decode.p25.message.ldu.lc.LDULCMessageFactory; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessageFactory; -import io.github.dsheirer.module.decode.p25.message.pdu.confirmed.PDUConfirmedMessage; -import io.github.dsheirer.module.decode.p25.message.pdu.confirmed.PDUConfirmedMessageFactory; -import io.github.dsheirer.module.decode.p25.message.tdu.TDUMessage; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.TDULCMessageFactory; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.TDULinkControlMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessageFactory; -import io.github.dsheirer.module.decode.p25.message.vselp.VSELP1Message; -import io.github.dsheirer.module.decode.p25.message.vselp.VSELP2Message; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.sample.Listener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class P25MessageFramer implements Listener -{ - private final static Logger mLog = LoggerFactory.getLogger(P25MessageFramer.class); - - /* Determines the threshold for sync pattern soft matching */ - private static final int SYNC_MATCH_THRESHOLD = 2; - private static final int SYNC_IN_CALL_THRESHOLD = 4; - - /* Costas Loop phase lock error correction values. A phase lock error of - * 90 degrees requires a correction of 1/4 of the symbol rate (1200Hz). An - * error of 180 degrees requires a correction of 1/2 of the symbol rate */ - public static final double DEFAULT_SAMPLE_RATE = 25000.0; - public static final double FREQUENCY_PHASE_CORRECTION_90_DEGREES = 1200.0; - public static final double FREQUENCY_PHASE_CORRECTION_180_DEGREES = 2400.0; - - public static final int TSBK_BEGIN = 64; - public static final int TSBK_CRC_START = 144; - public static final int TSBK_END = 260; - public static final int TSBK_DECODED_END = 160; - - public static final int PDU0_BEGIN = 64; - public static final int PDU0_CRC_BEGIN = 144; - public static final int PDU0_END = 260; - public static final int PDU1_BEGIN = 160; - public static final int PDU1_END = 356; - public static final int PDU2_BEGIN = 256; - public static final int PDU2_END = 452; - public static final int PDU3_BEGIN = 352; - public static final int PDU3_END = 548; - public static final int PDU3_DECODED_END = 448; - - private SoftSyncDetector mPrimarySyncDetector = new SoftSyncDetector( - FrameSync.P25_PHASE1_NORMAL.getSync(), SYNC_MATCH_THRESHOLD); - - private PLLPhaseInversionDetector mInversionDetector90CW; - private PLLPhaseInversionDetector mInversionDetector90CCW; - private PLLPhaseInversionDetector mInversionDetector180; - - private MultiSyncPatternMatcher mMatcher; - private P25MessageAssembler mMessageAssembler = new P25MessageAssembler(); - - private Listener mListener; - private AliasList mAliasList; - - private Trellis_1_2_Rate mHalfRate = new Trellis_1_2_Rate(); - private Trellis_3_4_Rate mThreeQuarterRate = new Trellis_3_4_Rate(); - private BCH_63_16_11 mNIDDecoder = new BCH_63_16_11(); - - /** - * Constructs a P25 message framer to receive a stream of symbols and - * detect the sync pattern then capture the following stream of symbols up - * to the message length, and then broadcast that bit buffer to the registered - * listener. - */ - public P25MessageFramer(AliasList aliasList, ISyncDetectListener syncDetectListener) - { - mAliasList = aliasList; - - //Assign the sync detect listener to the matcher with a sync loss threshold equal to the longest message length - mMatcher = new MultiSyncPatternMatcher(syncDetectListener, DataUnitID.LDU1.getMessageLength(), 48); - - mPrimarySyncDetector.setListener(new ISyncDetectListener() - { - @Override - public void syncDetected() - { - if(!mMessageAssembler.isActive()) - { - mMessageAssembler.setActive(true); - } - } - - @Override - public void syncLost() - { - //no-op - } - }); - - mMatcher.add(mPrimarySyncDetector); - } - - public P25MessageFramer(AliasList aliasList, IPhaseLockedLoop phaseLockedLoop, ISyncDetectListener syncDetectListener) - { - this(aliasList, syncDetectListener); - - if(phaseLockedLoop != null) - { - //Add additional sync pattern detectors to detect when we get 90/180 degree out of phase sync pattern - //detections so that we can apply correction to the phase locked loop - mInversionDetector90CW = new PLLPhaseInversionDetector(FrameSync.P25_PHASE1_ERROR_90_CW, - phaseLockedLoop, DEFAULT_SAMPLE_RATE, FREQUENCY_PHASE_CORRECTION_90_DEGREES); - mMatcher.add(mInversionDetector90CW); - - mInversionDetector90CCW = new PLLPhaseInversionDetector(FrameSync.P25_PHASE1_ERROR_90_CCW, - phaseLockedLoop, DEFAULT_SAMPLE_RATE, -FREQUENCY_PHASE_CORRECTION_90_DEGREES); - mMatcher.add(mInversionDetector90CCW); - - mInversionDetector180 = new PLLPhaseInversionDetector(FrameSync.P25_PHASE1_ERROR_180, - phaseLockedLoop, DEFAULT_SAMPLE_RATE, FREQUENCY_PHASE_CORRECTION_180_DEGREES); - mMatcher.add(mInversionDetector180); - } - } - - /** - * Updates the incoming sample stream sample rate to allow the PLL phase inversion detectors to - * recalculate their internal phase correction values. - * - * @param sampleRate of the incoming sample stream - */ - public void setSampleRate(double sampleRate) - { - mInversionDetector180.setSampleRate(sampleRate); - mInversionDetector90CW.setSampleRate(sampleRate); - mInversionDetector90CCW.setSampleRate(sampleRate); - } - - public void dispose() - { - mMessageAssembler.dispose(); - - mListener = null; - mAliasList = null; - - mMatcher.dispose(); - mMatcher = null; - - mPrimarySyncDetector.dispose(); - mPrimarySyncDetector = null; - - } - - private void dispatch(Message message) - { - if(mListener != null) - { - mListener.receive(message); - } - } - - @Override - public void receive(Dibit symbol) - { - if(mMessageAssembler.isActive()) - { - mMessageAssembler.receive(symbol); - - if(mMessageAssembler.complete()) - { - mMessageAssembler.reset(); - } - } - - mMatcher.receive(symbol.getBit1(), symbol.getBit2()); - } - - public void setListener(Listener listener) - { - mListener = listener; - } - - public void removeListener(Listener listener) - { - mListener = null; - } - - private class P25MessageAssembler - { - /* Starting position of the status symbol counter is 24 symbols to - * account for the 48-bit sync pattern which is not included in message */ - private int mStatusSymbolPointer = 24; - private BinaryMessage mMessage; - private int mMessageLength; - private boolean mComplete = false; - private boolean mActive = false; - private DataUnitID mDUID = DataUnitID.NID; - - public P25MessageAssembler() - { - mMessageLength = mDUID.getMessageLength(); - mMessage = new BinaryMessage(mMessageLength); - reset(); - } - - public void receive(Dibit dibit) - { - if(mActive) - { - if(mStatusSymbolPointer == 35) - { - mStatusSymbolPointer = 0; - } - else - { - mStatusSymbolPointer++; - - try - { - mMessage.add(dibit.getBit1()); - mMessage.add(dibit.getBit2()); - } - catch(BitSetFullException e) - { - mComplete = true; - } - - /* Check the message for complete */ - if(mMessage.isFull()) - { - checkComplete(); - } - } - } - } - - public void reset() - { - mDUID = DataUnitID.NID; - mMessage.setSize(mDUID.getMessageLength()); - mMessage.clear(); - mStatusSymbolPointer = 24; - mComplete = false; - mActive = false; - } - - public void setActive(boolean active) - { - mActive = active; - } - - private void setDUID(DataUnitID id) - { - mDUID = id; - mMessageLength = id.getMessageLength(); - mMessage.setSize(mMessageLength); - } - - private void checkComplete() - { - switch(mDUID) - { - case NID: - mMessage = mNIDDecoder.correctNID(mMessage); - - if(mMessage.getCRC() != CRC.FAILED_CRC) - { - int value = mMessage.getInt(P25Message.DUID); - - DataUnitID duid = DataUnitID.fromValue(value); - - if(duid != DataUnitID.UNKN) - { - setDUID(duid); - } - else - { - mComplete = true; - dispatch(new P25Message(mMessage.copy(), mDUID, mAliasList)); - } - } - else - { - mComplete = true; - } - break; - case HDU: - mComplete = true; - dispatch(new HDUMessage(mMessage.copy(), mDUID, mAliasList)); - - /* We're in a call now, lower the sync match threshold */ - mPrimarySyncDetector.setThreshold(SYNC_IN_CALL_THRESHOLD); - break; - case LDU1: - mComplete = true; - - LDU1Message ldu1 = new LDU1Message(mMessage.copy(), - mDUID, mAliasList); - - /* Convert the LDU1 message into a link control LDU1 message */ - dispatch(LDULCMessageFactory.getMessage(ldu1)); - - /* We're in a call now, lower the sync match threshold */ - mPrimarySyncDetector.setThreshold(SYNC_IN_CALL_THRESHOLD); - break; - case LDU2: - mComplete = true; - dispatch(new LDU2Message(mMessage.copy(), mDUID, mAliasList)); - - /* We're in a call now, lower the sync match threshold */ - mPrimarySyncDetector.setThreshold(SYNC_IN_CALL_THRESHOLD); - break; - case PDU0: - - /* Remove interleaving */ - P25Interleave.deinterleaveData(mMessage, PDU0_BEGIN, PDU0_END); - - /* Remove trellis encoding - abort processing if we have an - * unsuccessful decode due to excessive errors */ - if(mHalfRate.decode(mMessage, PDU0_BEGIN, PDU0_END)) - { - mMessage = CRCP25.correctCCITT80(mMessage, - PDU0_BEGIN, PDU0_CRC_BEGIN); - - - if(mMessage.getCRC() != CRC.FAILED_CRC) - { - boolean confirmed = mMessage.get( - PDUMessage.CONFIRMATION_REQUIRED_INDICATOR); - - if(confirmed) - { - setDUID(DataUnitID.PDUC); - } - else - { - setDUID(DataUnitID.PDU1); - } - - mMessage.setPointer(PDU1_BEGIN); - } - else - { - mComplete = true; - } - } - else - { - mComplete = true; - } - - /* Set sync match threshold to normal */ - mPrimarySyncDetector.setThreshold(SYNC_MATCH_THRESHOLD); - - break; - case PDU1: - /* Remove interleaving */ - P25Interleave.deinterleaveData(mMessage, PDU1_BEGIN, PDU1_END); - - /* Remove trellis encoding - abort processing if we have an - * unsuccessful decode due to excessive errors */ - if(mHalfRate.decode(mMessage, PDU1_BEGIN, PDU1_END)) - { - if(mMessage.getInt(PDUMessage.BLOCKS_TO_FOLLOW) == 1) - { - mMessage.setSize(PDU2_BEGIN); - - PDUMessage pduMessage1 = PDUMessageFactory.getMessage( - mMessage.copy(), DataUnitID.PDU1, mAliasList); - - dispatch(pduMessage1); - - mComplete = true; - } - else - { - setDUID(DataUnitID.PDU2); - - mMessage.setPointer(PDU2_BEGIN); - } - } - else - { - mComplete = true; - } - break; - case PDU2: - /* Remove interleaving */ - P25Interleave.deinterleaveData(mMessage, PDU2_BEGIN, PDU2_END); - - /* Remove trellis encoding - abort processing if we have an - * unsuccessful decode due to excessive errors */ - if(mHalfRate.decode(mMessage, PDU2_BEGIN, PDU2_END)) - { - if(mMessage.getInt(PDUMessage.BLOCKS_TO_FOLLOW) == 2) - { - mMessage.setSize(PDU3_BEGIN); - - PDUMessage pduMessage2 = PDUMessageFactory.getMessage( - mMessage.copy(), DataUnitID.PDU2, mAliasList); - - dispatch(pduMessage2); - - mComplete = true; - } - else - { - setDUID(DataUnitID.PDU3); - - mMessage.setPointer(PDU3_BEGIN); - } - } - else - { - mComplete = true; - } - - /* Set sync match threshold to normal */ - mPrimarySyncDetector.setThreshold(SYNC_MATCH_THRESHOLD); - break; - case PDU3: - /* Remove interleaving */ - P25Interleave.deinterleaveData(mMessage, PDU3_BEGIN, PDU3_END); - - /* Remove trellis encoding - abort processing if we have an - * unsuccessful decode due to excessive errors */ - if(mHalfRate.decode(mMessage, PDU3_BEGIN, PDU3_END)) - { - mMessage.setSize(PDU3_DECODED_END); - - PDUMessage pduMessage3 = PDUMessageFactory.getMessage(mMessage.copy(), DataUnitID.PDU3, mAliasList); - - dispatch(pduMessage3); - } - - mComplete = true; - break; - case PDUC: - /* De-interleave the latest block*/ - P25Interleave.deinterleaveData(mMessage, - mMessage.size() - 196, mMessage.size()); - - /* Decode 3/4 rate convolutional encoding from latest block */ - if(mThreeQuarterRate.decode(mMessage, - mMessage.size() - 196, mMessage.size())) - { - /* Resize the message and adjust the message pointer - * to account for removing 48 + 4 parity bits */ - mMessage.setSize(mMessage.size() - 52); - mMessage.adjustPointer(-52); - - int blocks = mMessage.getInt(PDUMessage.BLOCKS_TO_FOLLOW); - - int current = (mMessage.size() - 160) / 144; - - if(current < blocks) - { - mMessage.setSize(mMessage.size() + 196); - mMessageLength = mMessage.size(); - } - else - { - PDUConfirmedMessage pducm = - new PDUConfirmedMessage(mMessage.copy(), - mAliasList); - - /* Translate into correct subclass */ - dispatch(PDUConfirmedMessageFactory.getMessage(pducm)); - - mComplete = true; - } - break; - } - else - { - PDUConfirmedMessage pducm = - new PDUConfirmedMessage(mMessage.copy(), - mAliasList); - - /* Translate into correct subclass */ - dispatch(PDUConfirmedMessageFactory.getMessage(pducm)); - - mComplete = true; - } - - /* Set sync match threshold to normal */ - mPrimarySyncDetector.setThreshold(SYNC_MATCH_THRESHOLD); - break; - case TDU: - dispatch(new TDUMessage(mMessage.copy(), mDUID, mAliasList)); - mComplete = true; - - /* Set sync match threshold to normal */ - mPrimarySyncDetector.setThreshold(SYNC_MATCH_THRESHOLD); - break; - case TDULC: - TDULinkControlMessage tdulc = new TDULinkControlMessage( - mMessage.copy(), mDUID, mAliasList); - - /* Convert to an appropriate link control message */ - tdulc = TDULCMessageFactory.getMessage(tdulc); - - dispatch(tdulc); - mComplete = true; - - /* Set sync match threshold to normal */ - mPrimarySyncDetector.setThreshold(SYNC_MATCH_THRESHOLD); - break; - case TSBK1: - /* Remove interleaving */ - P25Interleave.deinterleaveData(mMessage, TSBK_BEGIN, TSBK_END); - - /* Remove trellis encoding - abort processing if we have an - * unsuccessful decode due to excessive errors */ - - if(mHalfRate.decode(mMessage, TSBK_BEGIN, TSBK_END)) - { - mMessage = CRCP25.correctCCITT80(mMessage, - TSBK_BEGIN, TSBK_CRC_START); - - if(mMessage.getCRC() != CRC.FAILED_CRC) - { - BinaryMessage tsbkBuffer1 = mMessage.copy(); - tsbkBuffer1.setSize(TSBK_DECODED_END); - - TSBKMessage tsbkMessage1 = TSBKMessageFactory.getMessage( - tsbkBuffer1, DataUnitID.TSBK1, mAliasList); - - if(tsbkMessage1.isLastBlock()) - { - mComplete = true; - } - else - { - setDUID(DataUnitID.TSBK2); - mMessage.setPointer(TSBK_BEGIN); - } - - dispatch(tsbkMessage1); - } - else - { - mComplete = true; - } - } - else - { - mComplete = true; - } - - /* Set sync match threshold to normal */ - mPrimarySyncDetector.setThreshold(SYNC_MATCH_THRESHOLD); - break; - case TSBK2: - /* Remove interleaving */ - P25Interleave.deinterleaveData(mMessage, TSBK_BEGIN, TSBK_END); - - /* Remove trellis encoding - abort processing if we have an - * unsuccessful decode due to excessive errors */ - if(mHalfRate.decode(mMessage, TSBK_BEGIN, TSBK_END)) - { - mMessage = CRCP25.correctCCITT80(mMessage, - TSBK_BEGIN, TSBK_CRC_START); - - if(mMessage.getCRC() != CRC.FAILED_CRC) - { - BinaryMessage tsbkBuffer2 = mMessage.copy(); - tsbkBuffer2.setSize(TSBK_DECODED_END); - - TSBKMessage tsbkMessage2 = TSBKMessageFactory.getMessage( - tsbkBuffer2, DataUnitID.TSBK2, mAliasList); - - if(tsbkMessage2.isLastBlock()) - { - mComplete = true; - } - else - { - setDUID(DataUnitID.TSBK3); - mMessage.setPointer(TSBK_BEGIN); - } - - dispatch(tsbkMessage2); - } - else - { - mComplete = true; - } - } - else - { - mComplete = true; - } - break; - case TSBK3: - /* Remove interleaving */ - P25Interleave.deinterleaveData(mMessage, TSBK_BEGIN, TSBK_END); - - /* Remove trellis encoding - abort processing if we have an - * unsuccessful decode due to excessive errors */ - if(mHalfRate.decode(mMessage, TSBK_BEGIN, TSBK_END)) - { - mMessage = CRCP25.correctCCITT80(mMessage, - TSBK_BEGIN, TSBK_CRC_START); - - if(mMessage.getCRC() != CRC.FAILED_CRC) - { - BinaryMessage tsbkBuffer3 = mMessage.copy(); - tsbkBuffer3.setSize(TSBK_DECODED_END); - - TSBKMessage tsbkMessage3 = TSBKMessageFactory.getMessage( - tsbkBuffer3, DataUnitID.TSBK3, mAliasList); - - dispatch(tsbkMessage3); - } - } - - mComplete = true; - break; - case VSELP1: - mComplete = true; - dispatch(new VSELP1Message(mMessage.copy(), mDUID, mAliasList)); - break; - case VSELP2: - mComplete = true; - dispatch(new VSELP2Message(mMessage.copy(), mDUID, mAliasList)); - break; - case UNKN: - mComplete = true; - dispatch(new P25Message(mMessage.copy(), mDUID, mAliasList)); - break; - default: - mComplete = true; - break; - } - } - - public void dispose() - { - mMessage = null; - mHalfRate.dispose(); - } - - /** - * Flag to indicate when this assembler has received all of the bits it - * is looking for (ie message length), and should then be removed from - * receiving any more bits - */ - public boolean complete() - { - return mComplete; - } - - public boolean isActive() - { - return mActive; - } - } - - /** - * Sync pattern detector to listen for costas loop phase lock errors and apply a phase correction to the costas - * loop so that we don't miss any messages. - * - * When the costas loop locks with a +/- 90 degree or 180 degree phase error, the slicer will incorrectly apply - * the symbol pattern rotated left or right by the phase error. However, we can detect these rotated sync patterns - * and apply immediate phase correction so that message processing can continue. - */ - public class PLLPhaseInversionDetector extends SyncDetector - { - private FrameSync mFrameSync; - private IPhaseLockedLoop mPhaseLockedLoop; - private double mSampleRate; - private double mFrequencyCorrection; - private double mPllCorrection; - - /** - * Constructs the PLL phase inversion detector. - * - * @param frameSync pattern to monitor for detecting phase inversion errors - * @param phaseLockedLoop to receive phase correction values - * @param sampleRate of the incoming sample stream - * @param frequencyCorrection to apply to the PLL. Examples: - * QPSK +/-90 degree correction: +/-SYMBOL RATE / 4.0 - * QPSK 180 degree correction: SYMBOL RATE / 2.0 - */ - public PLLPhaseInversionDetector(FrameSync frameSync, IPhaseLockedLoop phaseLockedLoop, double sampleRate, - double frequencyCorrection) - { - super(frameSync.getSync()); - mFrameSync = frameSync; - mPhaseLockedLoop = phaseLockedLoop; - mFrequencyCorrection = frequencyCorrection; - setSampleRate(sampleRate); - - setListener(new ISyncDetectListener() - { - @Override - public void syncDetected() - { - mPhaseLockedLoop.correctInversion(mPllCorrection); - - /* Since we detected a sync pattern, start a message assembler */ - if(!mMessageAssembler.isActive()) - { - mMessageAssembler.setActive(true); - } - } - - @Override - public void syncLost() - { - //no-op - } - }); - - } - - /** - * Sets or adjusts the sample rate so that the phase inversion correction value can be recalculated. - * @param sampleRate - */ - public void setSampleRate(double sampleRate) - { - mSampleRate = sampleRate; - mPllCorrection = 2.0 * Math.PI * mFrequencyCorrection / mSampleRate; - } - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageFramer2.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageFramer2.java new file mode 100644 index 00000000..a830565a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageFramer2.java @@ -0,0 +1,549 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25; + +import io.github.dsheirer.bits.BitSetFullException; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop; +import io.github.dsheirer.dsp.symbol.Dibit; +import io.github.dsheirer.dsp.symbol.ISyncDetectListener; +import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.SyncLossMessage; +import io.github.dsheirer.module.decode.DecoderType; +import io.github.dsheirer.module.decode.p25.message.P25Message; +import io.github.dsheirer.module.decode.p25.message.P25MessageFactory; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessageFactory; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.umbtc.UMBTCMessage; +import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; +import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessageFactory; +import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp.PatchGroupVoiceChannelGrant; +import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp.PatchGroupVoiceChannelGrantUpdate; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.protocol.Protocol; +import io.github.dsheirer.record.binary.BinaryReader; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.sample.buffer.ReusableByteBuffer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * P25 Sync Detector and Message Framer. Includes capability to detect PLL out-of-phase lock errors + * and issue phase corrections. + */ +public class P25MessageFramer2 implements Listener, IDataUnitDetectListener +{ + private final static Logger mLog = LoggerFactory.getLogger(P25MessageFramer2.class); + + private P25DataUnitDetector mDataUnitDetector; + private P25ChannelStatusProcessor mChannelStatusProcessor = new P25ChannelStatusProcessor(); + private Listener mMessageListener; + + private boolean mAssemblingMessage = false; + private CorrectedBinaryMessage mBinaryMessage; + private DataUnitID mDataUnitID; + private PDUSequence mPDUSequence; + private int[] mCorrectedNID; + private int mNAC; + private int mStatusSymbolDibitCounter = 0; + private int mTrailingDibitsToSuppress = 0; + private double mBitRate; + private long mCurrentTime = System.currentTimeMillis(); + private ISyncDetectListener mSyncDetectListener; + + public P25MessageFramer2(IPhaseLockedLoop phaseLockedLoop, int bitRate) + { + mDataUnitDetector = new P25DataUnitDetector(this, phaseLockedLoop); + mBitRate = bitRate; + } + + public P25MessageFramer2(int bitRate) + { + this(null, bitRate); + } + + /** + * Sets the sample rate for the sync detector + */ + public void setSampleRate(double sampleRate) + { + mDataUnitDetector.setSampleRate(sampleRate); + } + + /** + * Registers a sync detect listener to be notified each time a sync pattern and NID are detected. + */ + public void setSyncDetectListener(ISyncDetectListener syncDetectListener) + { + mSyncDetectListener = syncDetectListener; + } + + /** + * Current timestamp or timestamp of incoming message buffers that is continuously updated to as + * close as possible to the bits processed for the expected baud rate. + * + * @return + */ + private long getTimestamp() + { + return mCurrentTime; + } + + /** + * Sets the current time. This should be invoked by an incoming message buffer stream. + * + * @param currentTime + */ + public void setCurrentTime(long currentTime) + { + mCurrentTime = currentTime; + } + + /** + * Updates the current timestamp based on the number of bits processed versus the bit rate per second + * in order to keep an accurate running timestamp to use for timestamped message creation. + * + * @param bitsProcessed thus far + */ + private void updateBitsProcessed(int bitsProcessed) + { + if(bitsProcessed > 0) + { + mCurrentTime += (long)((double)bitsProcessed / mBitRate * 1000.0); + } + } + + /** + * Registers the listener for messages produced by this message framer + * + * @param messageListener to receive framed and decoded messages + */ + public void setListener(Listener messageListener) + { + mMessageListener = messageListener; + } + + public P25DataUnitDetector getDataUnitDetector() + { + return mDataUnitDetector; + } + + /** + * Primary method for streaming decoded symbol dibits for message framing. + * + * @param dibit to process + */ + @Override + public void receive(Dibit dibit) + { + if(mAssemblingMessage) + { + //Strip out the status symbol dibit after every 70 bits or 35 dibits + if(mStatusSymbolDibitCounter == 35) + { + if(mAssemblingMessage) + { + //Send status dibit to channel status processor to identify ISP or OSP channel + mChannelStatusProcessor.receive(dibit); + } + mStatusSymbolDibitCounter = 0; + + return; + } + + mStatusSymbolDibitCounter++; + + try + { + mBinaryMessage.add(dibit.getBit1()); + mBinaryMessage.add(dibit.getBit2()); + } + catch(BitSetFullException bsfe) + { + mLog.debug("Message full exception - unexpected"); + + //Reset so that we can start over again + reset(0); + } + + if(mBinaryMessage.isFull()) + { + //TDU's have a trailing status symbol that has to be removed -- set flag to true to suppress it. + if(mDataUnitID.hasTrailingStatusDibit()) + { + mTrailingDibitsToSuppress = 1; + } + + dispatchMessage(); + } + } + else + { + //Suppress any trailing nulls or status dibits that follow certain DUID sequences + if(mTrailingDibitsToSuppress > 0) + { + mTrailingDibitsToSuppress--; + updateBitsProcessed(2); + return; + } + + mDataUnitDetector.receive(dibit); + } + } + + private void dispatchMessage() + { + if(mMessageListener != null) + { + switch(mDataUnitID) + { + case PACKET_HEADER_DATA_UNIT: + mPDUSequence = PDUMessageFactory.createPacketSequence(mNAC, mCurrentTime, mBinaryMessage); + + if(mPDUSequence != null) + { + if(mPDUSequence.getHeader().isValid() && + mPDUSequence.getHeader().getBlocksToFollowCount() > 0) + { + //Setup to catch the sequence of data blocks that follow the header + mDataUnitID = DataUnitID.PACKET_DATA_UNIT; + mBinaryMessage = new CorrectedBinaryMessage(DataUnitID.PACKET_DATA_UNIT.getMessageLength()); + mAssemblingMessage = true; + } + else + { + //Process 44 bits/22 dibits of trailing nulls + mTrailingDibitsToSuppress = 22; + + mMessageListener.receive(PDUMessageFactory.create(mPDUSequence, mNAC, getTimestamp())); + reset(mPDUSequence.getBitsProcessedCount()); + mPDUSequence = null; + } + } + break; + case PACKET_DATA_UNIT: + if(mPDUSequence != null) + { + if(mPDUSequence.getHeader().isConfirmationRequired()) + { + mPDUSequence.addDataBlock(PDUMessageFactory.createConfirmedDataBlock(mBinaryMessage)); + } + else + { + mPDUSequence.addDataBlock(PDUMessageFactory.createUnconfirmedDataBlock(mBinaryMessage)); + } + + if(mPDUSequence.isComplete()) + { + mMessageListener.receive(PDUMessageFactory.create(mPDUSequence, mNAC, getTimestamp())); + + switch(mPDUSequence.getHeader().getBlocksToFollowCount()) + { + case 1: + mTrailingDibitsToSuppress = 29; + break; + case 2: + //Process 2 bits or 1 dibit of trailing nulls + mTrailingDibitsToSuppress = 1; + break; + case 3: + //Process 16 bits or 8 dibits of trailing nulls + mTrailingDibitsToSuppress = 8; + break; + case 4: + //Process 30 bits or 15 dibits of trailing nulls + mTrailingDibitsToSuppress = 15; + break; + case 5: + //Process 44 bits or 22 dibits of trailing nulls + mTrailingDibitsToSuppress = 22; + break; + default: + mLog.debug("*** MORE THAN 5 PDU BLOCKS DETECTED [" + + mPDUSequence.getHeader().getBlocksToFollowCount() + + "] - DETERMINE TRAILING NULL COUNT TO SUPPRESS AND UPDATE CODE"); + break; + } + + reset(mPDUSequence.getBitsProcessedCount()); + } + else + { + //Setup to catch the next data block + mDataUnitID = DataUnitID.PACKET_DATA_UNIT; + mBinaryMessage = new CorrectedBinaryMessage(DataUnitID.PACKET_DATA_UNIT.getMessageLength()); + mAssemblingMessage = true; + } + } + else + { + mLog.error("Received PDU data block with out a preceeding data header"); + reset(mDataUnitID.getMessageLength()); + } + break; + case TRUNKING_SIGNALING_BLOCK_1: + case TRUNKING_SIGNALING_BLOCK_2: + case TRUNKING_SIGNALING_BLOCK_3: + TSBKMessage tsbkMessage = TSBKMessageFactory.create(mChannelStatusProcessor.getDirection(), + mDataUnitID, mBinaryMessage, mNAC, getTimestamp()); + + mMessageListener.receive(tsbkMessage); + + if(tsbkMessage.isLastBlock()) + { + reset(mDataUnitID.getMessageLength()); + mTrailingDibitsToSuppress = 1; + } + else + { + updateBitsProcessed(mDataUnitID.getMessageLength()); + mBinaryMessage = new CorrectedBinaryMessage(mDataUnitID.getMessageLength()); + if(mDataUnitID == DataUnitID.TRUNKING_SIGNALING_BLOCK_1) + { + mDataUnitID = DataUnitID.TRUNKING_SIGNALING_BLOCK_2; + } + else if(mDataUnitID == DataUnitID.TRUNKING_SIGNALING_BLOCK_2) + { + mDataUnitID = DataUnitID.TRUNKING_SIGNALING_BLOCK_3; + } + } + break; + default: + P25Message message = P25MessageFactory.create(mDataUnitID, mNAC, getTimestamp(), mBinaryMessage); + mMessageListener.receive(message); + reset(mDataUnitID.getMessageLength()); + break; + } + } + else + { + reset(0); + } + } + + private void reset(int bitsProcessed) + { + updateBitsProcessed(bitsProcessed); + mPDUSequence = null; + mBinaryMessage = null; + mAssemblingMessage = false; + mDataUnitID = null; + mNAC = 0; + mDataUnitDetector.reset(); + mStatusSymbolDibitCounter = 0; + } + + /** + * Primary method for streaming decoded symbol byte arrays. + * + * @param buffer to process into a stream of dibits for processing. + */ + public void receive(ReusableByteBuffer buffer) + { + //Updates current timestamp to the timestamp from the incoming buffer + setCurrentTime(buffer.getTimestamp()); + + for(byte value : buffer.getBytes()) + { + for(int x = 0; x <= 3; x++) + { + receive(Dibit.parse(value, x)); + } + } + + buffer.decrementUserCount(); + } + + @Override + public void dataUnitDetected(DataUnitID dataUnitID, int nac, int bitErrors, int discardedDibits, int[] correctedNid) + { + if(discardedDibits > 0) + { + dispatchSyncLoss(discardedDibits * 2); + } + + if(dataUnitID.getMessageLength() < 0) + { + dispatchSyncLoss(112); //Sync (48) and Nid (64) + return; + } + + if(mSyncDetectListener != null) + { + mSyncDetectListener.syncDetected(bitErrors); + } + + mDataUnitID = dataUnitID; + mNAC = nac; + mCorrectedNID = correctedNid; + mBinaryMessage = new CorrectedBinaryMessage(dataUnitID.getMessageLength()); + mBinaryMessage.incrementCorrectedBitCount(bitErrors); + + mAssemblingMessage = true; + mStatusSymbolDibitCounter = 21; + } + + @Override + public void syncLost(int bitsProcessed) + { + dispatchSyncLoss(bitsProcessed); + + if(mSyncDetectListener != null) + { + mSyncDetectListener.syncLost(); + } + } + + private void dispatchSyncLoss(int bitsProcessed) + { + //Updates current timestamp according to the number of bits procesed + updateBitsProcessed(bitsProcessed); + + if(bitsProcessed > 0 && mMessageListener != null) + { + mMessageListener.receive(new SyncLossMessage(getTimestamp(), bitsProcessed, Protocol.APCO25)); + } + } + + public static void main(String[] args) + { + boolean pduOnly = false; + boolean mbtcOnly = false; + boolean sndcpOnly = false; + boolean ippacketOnly = false; + boolean patchOnly = false; + + P25MessageFramer2 messageFramer = new P25MessageFramer2(null, DecoderType.P25_PHASE1.getProtocol().getBitRate()); + messageFramer.setListener(new Listener() + { + @Override + public void receive(Message message) + { + if(mbtcOnly) + { + if(message instanceof AMBTCMessage || message instanceof UMBTCMessage) + { + mLog.debug(message.toString()); + } + } + else if(pduOnly) + { + String s = message.toString(); + + if(s.contains(" PDU ")) + { + mLog.debug(s); + } + } + else if(sndcpOnly) + { + String s = message.toString(); + + if(s.contains("SNDCP")) + { + mLog.debug(s); + } + } + else if(ippacketOnly) + { + String s = message.toString(); + + if(s.contains("IPPKT") || s.contains("SNDCP") || s.contains(" PDU ")) + { + mLog.debug(s); + } + } + else if(patchOnly) + { + if(message instanceof PatchGroupVoiceChannelGrant || message instanceof PatchGroupVoiceChannelGrantUpdate) + { + mLog.debug(message.toString()); + } + } + else + { + String a = message.toString(); + mLog.debug(a); + } + } + }); + +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20180922_040600_9600BPS_CNYICC_Onondaga Simulcast_LCN 09.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20180922_042033_9600BPS_CNYICC_Onondaga Simulcast_LCN 10.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20180923_045614_9600BPS_CNYICC_Onondaga Simulcast_LCN 09.bits"); //Has Moto LC Opcode 01 +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20180923_045647_9600BPS_CNYICC_Onondaga Simulcast_LCN 10.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20180923_050325_9600BPS_CNYICC_Onondaga Simulcast_LCN 09.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20180923_050403_9600BPS_CNYICC_Onondaga Simulcast_LCN 10.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20180923_050721_9600BPS_CNYICC_Onondaga Simulcast_LCN 10.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20180923_051057_9600BPS_CNYICC_Onondaga Simulcast_LCN 09.bits"); //ARS PDUs and SNDCP PDUs +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20180923_051808_9600BPS_CNYICC_Onondaga Simulcast_LCN 09_EMERG_AND_PACKETS.bits"); //ARS and SNCDP PDUs +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20180923_052519_9600BPS_CNYICC_Onondaga Simulcast_LCN 09.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181008_091511_9600BPS_CNYICC_Onondaga Simulcast_LCN 15 Control.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181008_091650_9600BPS_CNYICC_Onondaga Simulcast_LCN 15 Control.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181021_101745_9600BPS_CNYICC_Onondaga Simulcast_LCN 06.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181021_101850_9600BPS_CNYICC_Onondaga Simulcast_LCN 07.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181021_101855_9600BPS_CNYICC_Onondaga Simulcast_LCN 08.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181021_102145_9600BPS_CNYICC_Onondaga Simulcast_LCN 04.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181021_102451_9600BPS_CNYICC_Onondaga Simulcast_LCN 09_lots_of_PACKET_DATA.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181021_102456_9600BPS_CNYICC_Onondaga Simulcast_LCN 10.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181021_102459_9600BPS_CNYICC_Onondaga Simulcast_LCN 11.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181021_102501_9600BPS_CNYICC_Onondaga Simulcast_LCN 12.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181021_103208_9600BPS_CNYICC_Onondaga Simulcast_LCN 09.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181028_140834_9600BPS_CNYICC_Onondaga Simulcast_LCN 06.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181028_140851_9600BPS_CNYICC_Onondaga Simulcast_LCN 07.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181028_141547_9600BPS_CNYICC_Onondaga Simulcast_LCN 06.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181028_141607_9600BPS_CNYICC_Onondaga Simulcast_LCN 07.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181028_142300_9600BPS_CNYICC_Onondaga Simulcast_LCN 06.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181028_142319_9600BPS_CNYICC_Onondaga Simulcast_LCN 07.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181028_143012_9600BPS_CNYICC_Onondaga Simulcast_LCN 06.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181028_143031_9600BPS_CNYICC_Onondaga Simulcast_LCN 07.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181028_143723_9600BPS_CNYICC_Onondaga Simulcast_LCN 06.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181028_143741_9600BPS_CNYICC_Onondaga Simulcast_LCN 07.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181102_100905_9600BPS_CNYICC_Onondaga Simulcast_LCN 08.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181102_101622_9600BPS_CNYICC_Onondaga Simulcast_LCN 08.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181102_102339_9600BPS_CNYICC_Onondaga Simulcast_LCN 08.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181103_134948_9600BPS_CNYICC_Oswego Simulcast_LCN 04.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181103_144312_9600BPS_CNYICC_Oswego Simulcast_LCN 04.bits"); //Interesting UDP port 231 packets (oswego LCN 4) +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181103_144429_9600BPS_CNYICC_Onondaga Simulcast_LCN 09.bits"); +// Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181103_144437_9600BPS_CNYICC_Onondaga Simulcast_LCN 10.bits"); + Path path = Paths.get("/home/denny/SDRTrunk/recordings/20181202_064827_9600BPS_CNYICC_Onondaga Simulcast_LCN 15 Control.bits"); + + + + try(BinaryReader reader = new BinaryReader(path, 200)) + { + while(reader.hasNext()) + { + messageFramer.receive(reader.next()); + } + } + catch(Exception ioe) + { + ioe.printStackTrace(); + } + + + mLog.debug("NIDS Detected: " + messageFramer.getDataUnitDetector().getNIDDetectionCount()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageProcessor.java index c6e5c6be..aefb846b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25MessageProcessor.java @@ -15,95 +15,94 @@ ******************************************************************************/ package io.github.dsheirer.module.decode.p25; -import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.Message; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.ldu.LDUMessage; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBand; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; import io.github.dsheirer.sample.Listener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; +import java.util.List; +import java.util.Map; public class P25MessageProcessor implements Listener { - private final static Logger mLog = LoggerFactory.getLogger( P25MessageProcessor.class ); + private final static Logger mLog = LoggerFactory.getLogger(P25MessageProcessor.class); - private Listener mMessageListener; + private Listener mMessageListener; - /* Map of up to 16 band identifiers per RFSS. These identifier update - * messages are inserted into any message that conveys channel information - * so that the uplink/downlink frequencies can be calculated */ - private HashMap mBandIdentifierMap = - new HashMap(); - - private AliasList mAliasList; - - public P25MessageProcessor( AliasList aliasList ) - { - mAliasList = aliasList; - } - - @Override - public void receive( Message message ) + /* Map of up to 16 band identifiers per RFSS. These identifier update + * messages are inserted into any message that conveys channel information + * so that the uplink/downlink frequencies can be calculated */ + private Map mFrequencyBandMap = new HashMap(); + + public P25MessageProcessor() { - /** - * Capture frequency band identifier messages and inject them into any - * messages that require band information in order to calculate the - * up-link and down-link frequencies for any numeric channel references - * contained within the message. - */ - if( message.isValid() ) - { - /* Insert band identifier update messages into channel-type messages */ - if( message instanceof IdentifierReceiver ) - { - IdentifierReceiver receiver = (IdentifierReceiver)message; - - int[] identifiers = receiver.getIdentifiers(); - - for( int identifier: identifiers ) - { - receiver.setIdentifierMessage( identifier, - mBandIdentifierMap.get( identifier ) ); - } - } - - /* Store band identifiers so that they can be injected into channel - * type messages */ - if( message instanceof IBandIdentifier ) - { - IBandIdentifier bandIdentifier = (IBandIdentifier)message; - - mBandIdentifierMap.put( bandIdentifier.getIdentifier(), - bandIdentifier ); - } - } - - /** - * Broadcast all valid messages and any LDU voice messages regardless if - * they are valid or not, so that we don't miss any voice frames - */ - if( mMessageListener != null && message.isValid() || message instanceof LDUMessage) - { - mMessageListener.receive( message ); - } } - - public void dispose() - { - mBandIdentifierMap.clear(); - mMessageListener = null; - } - - public void setMessageListener( Listener listener ) + + @Override + public void receive(Message message) { - mMessageListener = listener; + /** + * Capture frequency band identifier messages and inject them into any + * messages that require band information in order to calculate the + * up-link and down-link frequencies for any numeric channel references + * contained within the message. + */ + if(message.isValid()) + { + /* Insert frequency band identifier update messages into channel-type messages */ + if(message instanceof IFrequencyBandReceiver) + { + IFrequencyBandReceiver receiver = (IFrequencyBandReceiver)message; + + List channels = receiver.getChannels(); + + for(IChannelDescriptor channel : channels) + { + int[] frequencyBandIdentifiers = channel.getFrequencyBandIdentifiers(); + + for(int id : frequencyBandIdentifiers) + { + if(mFrequencyBandMap.containsKey(id)) + { + channel.setFrequencyBand(mFrequencyBandMap.get(id)); + } + } + } + } + + /* Store band identifiers so that they can be injected into channel + * type messages */ + if(message instanceof IFrequencyBand) + { + IFrequencyBand bandIdentifier = (IFrequencyBand)message; + mFrequencyBandMap.put(bandIdentifier.getIdentifier(), bandIdentifier); + } + } + + if(mMessageListener != null) + { + mMessageListener.receive(message); + } + } + + public void dispose() + { + mFrequencyBandMap.clear(); + mMessageListener = null; + } + + public void setMessageListener(Listener listener) + { + mMessageListener = listener; } public void removeMessageListener() { - mMessageListener = null; + mMessageListener = null; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25Metadata.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25Metadata.java new file mode 100644 index 00000000..09da35e9 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25Metadata.java @@ -0,0 +1,37 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25; + +import io.github.dsheirer.module.decode.p25.message.P25Message; +import io.github.dsheirer.sample.Listener; + +/** + * Decoded metadata for an APCO25 channel + */ +public class P25Metadata implements Listener +{ + + @Override + public void receive(P25Message p25Message) + { + + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25SyncDetector.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25SyncDetector.java new file mode 100644 index 00000000..e4da11e1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25SyncDetector.java @@ -0,0 +1,163 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25; + +import io.github.dsheirer.bits.MultiSyncPatternMatcher; +import io.github.dsheirer.bits.SoftSyncDetector; +import io.github.dsheirer.bits.SyncDetector; +import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop; +import io.github.dsheirer.dsp.symbol.Dibit; +import io.github.dsheirer.dsp.symbol.FrameSync; +import io.github.dsheirer.dsp.symbol.ISyncDetectListener; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.sample.Listener; + +public class P25SyncDetector implements Listener +{ + /* Determines the threshold for sync pattern soft matching */ + private static final int SYNC_MATCH_THRESHOLD = 4; + + /* Costas Loop phase lock error correction values. A phase lock error of + * 90 degrees requires a correction of 1/4 of the symbol rate (1200Hz). An + * error of 180 degrees requires a correction of 1/2 of the symbol rate */ + public static final double DEFAULT_SAMPLE_RATE = 50000.0; + + public static final double FREQUENCY_PHASE_CORRECTION_90_DEGREES = 1200.0; + public static final double FREQUENCY_PHASE_CORRECTION_180_DEGREES = 2400.0; + + private MultiSyncPatternMatcher mMatcher; + private SoftSyncDetector mPrimarySyncDetector; + + private PLLPhaseInversionDetector mInversionDetector90CW; + private PLLPhaseInversionDetector mInversionDetector90CCW; + private PLLPhaseInversionDetector mInversionDetector180; + + public P25SyncDetector(ISyncDetectListener syncDetectListener, IPhaseLockedLoop phaseLockedLoop) + { + //TODO: since we're only going to feed dibits to find next frame, it makes sense to + //TODO: update the sync lost parameter to 48 bits .... + + //TODO: only enable the phase inversion detectors when we're in a sync-lost state + mMatcher = new MultiSyncPatternMatcher(syncDetectListener, DataUnitID.LOGICAL_LINK_DATA_UNIT_1.getMessageLength(), 48); + mPrimarySyncDetector = new SoftSyncDetector(FrameSync.P25_PHASE1_NORMAL.getSync(), SYNC_MATCH_THRESHOLD, syncDetectListener); + mMatcher.add(mPrimarySyncDetector); + + if(phaseLockedLoop != null) + { + //Add additional sync pattern detectors to detect when we get 90/180 degree out of phase sync pattern + //detections so that we can apply correction to the phase locked loop + mInversionDetector90CW = new PLLPhaseInversionDetector(FrameSync.P25_PHASE1_ERROR_90_CW, + phaseLockedLoop, DEFAULT_SAMPLE_RATE, FREQUENCY_PHASE_CORRECTION_90_DEGREES); + mMatcher.add(mInversionDetector90CW); + + mInversionDetector90CCW = new PLLPhaseInversionDetector(FrameSync.P25_PHASE1_ERROR_90_CCW, + phaseLockedLoop, DEFAULT_SAMPLE_RATE, -FREQUENCY_PHASE_CORRECTION_90_DEGREES); + mMatcher.add(mInversionDetector90CCW); + + mInversionDetector180 = new PLLPhaseInversionDetector(FrameSync.P25_PHASE1_ERROR_180, + phaseLockedLoop, DEFAULT_SAMPLE_RATE, FREQUENCY_PHASE_CORRECTION_180_DEGREES); + mMatcher.add(mInversionDetector180); + } + } + + /** + * Calculates the number of bits that match in the current primary detector + * @return + */ + public int getPrimarySyncMatchErrorCount() + { + return Long.bitCount(mMatcher.getCurrentValue() ^ FrameSync.P25_PHASE1_NORMAL.getSync()); + } + + @Override + public void receive(Dibit dibit) + { + mMatcher.receive(dibit.getBit1(), dibit.getBit2()); + } + + /** + * Updates the incoming sample stream sample rate to allow the PLL phase inversion detectors to + * recalculate their internal phase correction values. + * + * @param sampleRate of the incoming sample stream + */ + public void setSampleRate(double sampleRate) + { + mInversionDetector180.setSampleRate(sampleRate); + mInversionDetector90CW.setSampleRate(sampleRate); + mInversionDetector90CCW.setSampleRate(sampleRate); + } + + /** + * Sync pattern detector to listen for costas loop phase lock errors and apply a phase correction to the costas + * loop so that we don't miss any messages. + * + * When the costas loop locks with a +/- 90 degree or 180 degree phase error, the slicer will incorrectly apply + * the symbol pattern rotated left or right by the phase error. However, we can detect these rotated sync patterns + * and apply immediate phase correction so that message processing can continue. + */ + public class PLLPhaseInversionDetector extends SyncDetector + { + private IPhaseLockedLoop mPhaseLockedLoop; + private double mSampleRate; + private double mFrequencyCorrection; + private double mPllCorrection; + + /** + * Constructs the PLL phase inversion detector. + * + * @param frameSync pattern to monitor for detecting phase inversion errors + * @param phaseLockedLoop to receive phase correction values + * @param sampleRate of the incoming sample stream + * @param frequencyCorrection to apply to the PLL. Examples: + * QPSK +/-90 degree correction: +/-SYMBOL RATE / 4.0 + * QPSK 180 degree correction: SYMBOL RATE / 2.0 + */ + public PLLPhaseInversionDetector(FrameSync frameSync, IPhaseLockedLoop phaseLockedLoop, double sampleRate, + double frequencyCorrection) + { + super(frameSync.getSync()); + mPhaseLockedLoop = phaseLockedLoop; + mFrequencyCorrection = frequencyCorrection; + setSampleRate(sampleRate); + + setListener(new ISyncDetectListener() + { + @Override + public void syncDetected(int bitErrors) + { + mPhaseLockedLoop.correctInversion(mPllCorrection); + } + + @Override + public void syncLost() + { + //no-op + } + }); + } + + /** + * Sets or adjusts the sample rate so that the phase inversion correction value can be recalculated. + * @param sampleRate + */ + public void setSampleRate(double sampleRate) + { + mSampleRate = sampleRate; + mPllCorrection = 2.0 * Math.PI * mFrequencyCorrection / mSampleRate; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25TrafficChannelManager.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25TrafficChannelManager.java new file mode 100644 index 00000000..a7bdbb6e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25TrafficChannelManager.java @@ -0,0 +1,514 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25; + +import io.github.dsheirer.controller.channel.Channel; +import io.github.dsheirer.controller.channel.Channel.ChannelType; +import io.github.dsheirer.controller.channel.ChannelEvent; +import io.github.dsheirer.controller.channel.ChannelEvent.Event; +import io.github.dsheirer.controller.channel.ChannelGrantEvent; +import io.github.dsheirer.controller.channel.IChannelEventListener; +import io.github.dsheirer.controller.channel.IChannelEventProvider; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.module.Module; +import io.github.dsheirer.module.decode.config.DecodeConfiguration; +import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; +import io.github.dsheirer.module.decode.event.IDecodeEvent; +import io.github.dsheirer.module.decode.event.IDecodeEventProvider; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.message.tsbk.Opcode; +import io.github.dsheirer.module.decode.p25.reference.ServiceOptions; +import io.github.dsheirer.sample.Listener; +import io.github.dsheirer.source.config.SourceConfigTuner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * Monitors channel grant and channel grant update messages to allocate traffic channels to capture + * traffic channel activity. + * + * Creates and reuses a limited set of Channel instances each with a TRAFFIC channel type. Since each of + * the traffic channels will be using the same decoder type and configuration options, we reuse each of the + * channel instances to allow the ChannelProcessingManager to reuse a cached set of processing chains that + * are created as each channel is activated. + * + * Each traffic channel is activated by sending a ChannelEvent via the channel model. The channel processing + * manager receives the activation request. If successful, a processing chain is activated for the traffic + * channel. Otherwise, a channel event is broadcast indicating that the channel could not be activated. On + * teardown of an activated traffic channel, a channel event is broadcast to indicate the traffic channels + * is no longer active. + * + * This manager monitors channel events watching for events related to managed traffic channels. + */ +public class P25TrafficChannelManager extends Module implements IDecodeEventProvider, IChannelEventListener, + IChannelEventProvider +{ + private final static Logger mLog = LoggerFactory.getLogger(P25TrafficChannelManager.class); + public static final String CHANNEL_START_REJECTED = "CHANNEL START REJECTED"; + public static final String MAX_TRAFFIC_CHANNELS_EXCEEDED = "MAX TRAFFIC CHANNELS EXCEEDED"; + + private Queue mAvailableTrafficChannelQueue = new ConcurrentLinkedQueue<>(); + private List mManagedTrafficChannels; + private Map mAllocatedTrafficChannelMap = new ConcurrentHashMap<>(); + private Map mChannelGrantEventMap = new ConcurrentHashMap<>(); + + private Listener mChannelEventListener; + private Listener mDecodeEventListener; + + private TrafficChannelTeardownMonitor mTrafficChannelTeardownMonitor = new TrafficChannelTeardownMonitor(); + + private boolean mIgnoreDataCalls = false; + + /** + * Monitors call events and allocates traffic decoder channels in response + * to traffic channel allocation call events. Manages a pool of reusable + * traffic channel allocations. + * + * @param parentChannel that owns this traffic channel manager + */ + public P25TrafficChannelManager(Channel parentChannel) + { + createTrafficChannels(parentChannel); + DecodeConfigP25Phase1 p25Config = (DecodeConfigP25Phase1)parentChannel.getDecodeConfiguration(); + mIgnoreDataCalls = p25Config.getIgnoreDataCalls(); + } + + /** + * Creates up to the maximum number of traffic channels for use in allocating traffic channels. + * + * @param parentChannel for inheriting naming, alias list and traffic channel pool size properties + */ + private void createTrafficChannels(Channel parentChannel) + { + DecodeConfiguration decodeConfiguration = parentChannel.getDecodeConfiguration(); + List trafficChannelList = new ArrayList<>(); + + if(decodeConfiguration instanceof DecodeConfigP25Phase1) + { + DecodeConfigP25Phase1 p25DecodeConfig = (DecodeConfigP25Phase1)decodeConfiguration; + + int maxTrafficChannels = p25DecodeConfig.getTrafficChannelPoolSize(); + + for(int x = 0; x < maxTrafficChannels; x++) + { + Channel trafficChannel = new Channel("TRAFFIC", ChannelType.TRAFFIC); + trafficChannel.setAliasListName(parentChannel.getAliasListName()); + trafficChannel.setSystem(parentChannel.getSystem()); + trafficChannel.setSite(parentChannel.getSite()); + trafficChannel.setDecodeConfiguration(p25DecodeConfig); + trafficChannel.setEventLogConfiguration(parentChannel.getEventLogConfiguration()); + trafficChannel.setRecordConfiguration(parentChannel.getRecordConfiguration()); + trafficChannelList.add(trafficChannel); + } + } + + mAvailableTrafficChannelQueue.addAll(trafficChannelList); + mManagedTrafficChannels = Collections.unmodifiableList(trafficChannelList); + } + + /** + * Broadcasts an initial or update decode event to any registered listener. + */ + public void broadcast(DecodeEvent decodeEvent) + { + if(mDecodeEventListener != null) + { + mDecodeEventListener.receive(decodeEvent); + } + } + + /** + * Processes channel grants to allocate traffic channels and track overall channel usage. Generates + * decode events for each new channel that is allocated. + * + * @param apco25Channel for the traffic channel + * @param serviceOptions for the traffic channel - optional can be null + * @param identifierCollection associated with the channel grant + * @param opcode to identify the call type for the event description + */ + public void processChannelGrant(APCO25Channel apco25Channel, ServiceOptions serviceOptions, + IdentifierCollection identifierCollection, Opcode opcode, long timestamp) + { + P25ChannelGrantEvent event = mChannelGrantEventMap.get(apco25Channel); + + if(event != null && isSameCall(identifierCollection, event.getIdentifierCollection())) + { + Identifier from = getIdentifier(identifierCollection, Role.FROM); + + if(from != null) + { + Identifier currentFrom = getIdentifier(event.getIdentifierCollection(), Role.FROM); + if(currentFrom != null && !Objects.equals(from, currentFrom)) + { + event.end(timestamp); + + P25ChannelGrantEvent continuationGrantEvent = P25ChannelGrantEvent.builder(timestamp, serviceOptions) + .channel(apco25Channel) + .eventDescription(getEventType(opcode, serviceOptions).toString() + " - Continue") + .details("CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "UNKNOWN SERVICE OPTIONS")) + .identifiers(identifierCollection) + .build(); + + mChannelGrantEventMap.put(apco25Channel, continuationGrantEvent); + broadcast(continuationGrantEvent); + } + } + + //update the ending timestamp so that the duration value is correctly calculated + event.update(timestamp); + broadcast(event); + + //Even though we have an event, the initial channel grant may have been rejected. Check to see if there + //is a traffic channel allocated. If not, allocate one and update the event description. + if(!mAllocatedTrafficChannelMap.containsKey(apco25Channel) && + !(mIgnoreDataCalls && opcode == Opcode.OSP_SNDCP_DATA_CHANNEL_GRANT)) + { + Channel trafficChannel = mAvailableTrafficChannelQueue.poll(); + + if(trafficChannel != null) + { + event.setEventDescription(getEventType(opcode, serviceOptions).toString()); + event.setDetails("CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "UNKNOWN SERVICE OPTIONS")); + event.setChannelDescriptor(apco25Channel); + broadcast(event); + SourceConfigTuner sourceConfig = new SourceConfigTuner(); + sourceConfig.setFrequency(apco25Channel.getDownlinkFrequency()); + trafficChannel.setSourceConfiguration(sourceConfig); + mAllocatedTrafficChannelMap.put(apco25Channel, trafficChannel); + broadcast(new ChannelGrantEvent(trafficChannel, Event.REQUEST_ENABLE, apco25Channel, identifierCollection)); + } + } + + return; + } + + if(mIgnoreDataCalls && opcode == Opcode.OSP_SNDCP_DATA_CHANNEL_GRANT) + { + P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(timestamp, serviceOptions) + .channel(apco25Channel) + .eventDescription(getEventType(opcode, serviceOptions).toString() + " - Ignored") + .details("DATA CALL IGNORED: " + (serviceOptions != null ? serviceOptions : "UNKNOWN SERVICE OPTIONS")) + .identifiers(identifierCollection) + .build(); + + mChannelGrantEventMap.put(apco25Channel, channelGrantEvent); + + broadcast(channelGrantEvent); + return; + } + + P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(timestamp, serviceOptions) + .channel(apco25Channel) + .eventDescription(getEventType(opcode, serviceOptions).toString()) + .details("CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "UNKNOWN SERVICE OPTIONS")) + .identifiers(identifierCollection) + .build(); + + mChannelGrantEventMap.put(apco25Channel, channelGrantEvent); + + //Allocate a traffic channel for the downlink frequency if one isn't already allocated + //NOTE: we could also allocate a traffic channel for the uplink frequency here, in the future + if(!mAllocatedTrafficChannelMap.containsKey(apco25Channel)) + { + Channel trafficChannel = mAvailableTrafficChannelQueue.poll(); + + if(trafficChannel == null) + { + channelGrantEvent.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED); + channelGrantEvent.setEventDescription(channelGrantEvent.getEventDescription() + " - Ignored"); + return; + } + + SourceConfigTuner sourceConfig = new SourceConfigTuner(); + sourceConfig.setFrequency(apco25Channel.getDownlinkFrequency()); + trafficChannel.setSourceConfiguration(sourceConfig); + mAllocatedTrafficChannelMap.put(apco25Channel, trafficChannel); + broadcast(new ChannelGrantEvent(trafficChannel, Event.REQUEST_ENABLE, apco25Channel, identifierCollection)); + } + + broadcast(channelGrantEvent); + } + + /** + * Creates a call event type description for the specified opcode and service options + */ + private DecodeEventType getEventType(Opcode opcode, ServiceOptions serviceOptions) + { + boolean encrypted = serviceOptions != null ? serviceOptions.isEncrypted() : false; + + DecodeEventType type = null; + + switch(opcode) + { + case OSP_GROUP_VOICE_CHANNEL_GRANT: + case OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE: + case OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: + type = encrypted ? DecodeEventType.CALL_GROUP_ENCRYPTED : DecodeEventType.CALL_GROUP; + break; + + case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: + case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: + type = encrypted ? DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED : DecodeEventType.CALL_UNIT_TO_UNIT; + break; + + case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: + case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: + type = encrypted ? DecodeEventType.CALL_INTERCONNECT_ENCRYPTED : DecodeEventType.CALL_INTERCONNECT; + break; + + case OSP_SNDCP_DATA_CHANNEL_GRANT: + case OSP_INDIVIDUAL_DATA_CHANNEL_GRANT: + type = encrypted ? DecodeEventType.DATA_CALL_ENCRYPTED : DecodeEventType.DATA_CALL; + break; + + case MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT: + case MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT_UPDATE: + type = encrypted ? DecodeEventType.CALL_PATCH_GROUP_ENCRYPTED : DecodeEventType.CALL_PATCH_GROUP; + break; + } + + if(type == null) + { + mLog.debug("Unrecognized opcode for determining decode event type: " + opcode.name()); + type = DecodeEventType.CALL; + } + + return type; + } + + /** + * Channel event listener to receive notifications that a traffic channel has ended processing and we + * can reclaim the traffic channel for reuse. + * + * @return listener for processing channel events. + */ + @Override + public Listener getChannelEventListener() + { + return mTrafficChannelTeardownMonitor; + } + + /** + * Broadcasts a channel event to a registered external listener (for action). + */ + private void broadcast(ChannelEvent channelEvent) + { + if(mChannelEventListener != null) + { + mChannelEventListener.receive(channelEvent); + } + } + + /** + * Sets the external channel event listener to receive channel events from this traffic channel manager + */ + @Override + public void setChannelEventListener(Listener listener) + { + mChannelEventListener = listener; + } + + /** + * Removes the channel event listener + */ + @Override + public void removeChannelEventListener() + { + mChannelEventListener = null; + } + + /** + * Compares the TO role identifier(s) from each collection for equality + * + * @param collection1 containing a TO identifier + * @param collection2 containing a TO identifier + * @return true if both collections contain a TO identifier and the TO identifiers are the same value + */ + private boolean isSameCall(IdentifierCollection collection1, IdentifierCollection collection2) + { + Identifier toIdentifier1 = getIdentifier(collection1, Role.TO); + Identifier toIdentifier2 = getIdentifier(collection2, Role.TO); + return Objects.equals(toIdentifier1, toIdentifier2); + } + + /** + * Retrieves the first identifier with a TO role. + * + * @param collection containing a TO identifier + * @return TO identifier or null + */ + private Identifier getIdentifier(IdentifierCollection collection, Role role) + { + List identifiers = collection.getIdentifiers(role); + + if(identifiers.size() >= 1) + { + return identifiers.get(0); + } + + return null; + } + + @Override + public void dispose() + { + for(Channel trafficChannel : mAvailableTrafficChannelQueue) + { + broadcast(new ChannelEvent(trafficChannel, Event.REQUEST_DISABLE)); + } + + mAvailableTrafficChannelQueue.clear(); + +// mTrafficChannelsInUse.clear(); + } + + /** + * Implements the IDecodeEventProvider interface to provide channel events to an external listener. + */ + @Override + public void addDecodeEventListener(Listener listener) + { + mDecodeEventListener = listener; + } + + /** + * Removes the external decode event listener + */ + @Override + public void removeDecodeEventListener(Listener listener) + { + mDecodeEventListener = null; + } + + @Override + public void reset() + { + } + + @Override + public void start() + { + } + + @Override + public void stop() + { + List channels = new ArrayList<>(mAllocatedTrafficChannelMap.values()); + + //Issue a disable request for each traffic channel + for(Channel channel: channels) + { + mLog.debug("Stopping traffic channel: " + channel); + broadcast(new ChannelEvent(channel, Event.REQUEST_DISABLE)); + } + } + + /** + * Monitors channel teardown events to detect when traffic channel processing has ended. Reclaims the + * channel instance for reuse by future traffic channel grants. + */ + public class TrafficChannelTeardownMonitor implements Listener + { + @Override + public synchronized void receive(ChannelEvent channelEvent) + { + Channel channel = channelEvent.getChannel(); + + if(channel.isTrafficChannel() && mManagedTrafficChannels.contains(channel)) + { + switch(channelEvent.getEvent()) + { + case NOTIFICATION_PROCESSING_STOP: + APCO25Channel toRemove = null; + + for(Map.Entry entry: mAllocatedTrafficChannelMap.entrySet()) + { + if(entry.getValue() == channel) + { + toRemove = entry.getKey(); + continue; + } + } + + if(toRemove != null) + { + mAllocatedTrafficChannelMap.remove(toRemove); + mAvailableTrafficChannelQueue.add(channel); + P25ChannelGrantEvent event = mChannelGrantEventMap.remove(toRemove); + + if(event != null) + { + event.end(System.currentTimeMillis()); + broadcast(event); + } + } + break; + case NOTIFICATION_PROCESSING_START_REJECTED: + APCO25Channel rejected = null; + + for(Map.Entry entry: mAllocatedTrafficChannelMap.entrySet()) + { + if(entry.getValue() == channel) + { + rejected = entry.getKey(); + continue; + } + } + + if(rejected != null) + { + mAllocatedTrafficChannelMap.remove(rejected); + mAvailableTrafficChannelQueue.add(channel); + P25ChannelGrantEvent event = mChannelGrantEventMap.get(rejected); + + if(event != null) + { + event.setEventDescription(event.getEventDescription() + " - Rejected"); + + if(channelEvent.getDescription() != null) + { + event.setDetails(channelEvent.getDescription() + " - " + event.getDetails()); + } + else + { + event.setDetails(CHANNEL_START_REJECTED + " - " + event.getDetails()); + } + + broadcast(event); + } + } + break; + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25Utils.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25Utils.java new file mode 100644 index 00000000..62c0c296 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25Utils.java @@ -0,0 +1,62 @@ +package io.github.dsheirer.module.decode.p25; + +public class P25Utils +{ + /** + * Formats a NAC code to three hexadecimal characters using zeros to prepad the value to three places. + * @param nac to format + * @return formatted nac + */ + public static String formatNAC(int nac) + { + return formatHex(nac, 3); + } + + /** + * Formats the value as hexadecimal with the minimum number of character places prepadding with zeros as needed. + * @param value to format + * @param places minimum for the formatted value + * @return formatted value + */ + public static String formatHex(int value, int places) + { + StringBuilder sb = new StringBuilder(); + String formatted = Integer.toHexString(value).toUpperCase(); + + if(formatted.length() < places) + { + for(int x = 0; x < (places - formatted.length()); x++) + { + sb.append("0"); + } + } + + sb.append(formatted); + + return sb.toString(); + } + + /** + * Adds spaces to the string builder until it is the specified length + * @param sb to pad with spaces + * @param length of the stringbuilder when complete + */ + public static void pad(StringBuilder sb, int length) + { + pad(sb, length, " "); + } + + /** + * Adds pad characters to the string builder until it is the specified length + * @param sb to pad with spaces + * @param length of the stringbuilder when complete + * @param padCharacter to use for padding + */ + public static void pad(StringBuilder sb, int length, String padCharacter) + { + while(sb.length() < length) + { + sb.append(padCharacter); + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/PatchGroup.java b/src/main/java/io/github/dsheirer/module/decode/p25/PatchGroup.java deleted file mode 100644 index 97a1e923..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/PatchGroup.java +++ /dev/null @@ -1,102 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25; - -import java.util.ArrayList; -import java.util.List; - -public class PatchGroup -{ - private long mExpireThreshold = 5000; //5 seconds - private long mLastUpdated; - private String mPatchGroupID; - private List mPatchedGroups = new ArrayList<>(); - - /** - * Constructs a patch group with the specified patch group ID - * @param patchGroupID that identifies this patch group - */ - public PatchGroup(String patchGroupID) - { - mPatchGroupID = patchGroupID; - mLastUpdated = System.currentTimeMillis(); - } - - /** - * Patch group ID for this patch group - */ - public String getPatchGroupID() - { - return mPatchGroupID; - } - - /** - * Adds the patched group to this patch group. Duplicate patched groups are ignored. - * - * Note: the patch group ID will not be added as a patched group, to avoid circular dependencies. - * - * @return true if the patched group was added to this patch group - */ - public boolean addPatchedGroup(String patchedGroup) - { - if(patchedGroup != null && !patchedGroup.equals(mPatchGroupID) && !mPatchedGroups.contains(patchedGroup)) - { - mPatchedGroups.add(patchedGroup); - return true; - } - - return false; - } - - /** - * Patched talkgroups that are part of this patch group. - */ - public List getPatchedGroups() - { - return mPatchedGroups; - } - - /** - * Updates the timestamp for this patch group. - */ - public void updateTimestamp() - { - mLastUpdated = System.currentTimeMillis(); - } - - /** - * Indicates if this patch group is expired, meaning that it hasn't been updated in the past (expire - * threshold) seconds. - */ - public boolean isExpired() - { - return System.currentTimeMillis() - mLastUpdated > mExpireThreshold; - } - - /** - * Sets the patch group expiration threshold (default = 5000 ms). A patch group is considered expired if - * no new updates have been received within the past (expire threshold) milliseconds - * - * @param expireThreshold in milliseconds - */ - public void setExpireThreshold(long expireThreshold) - { - mExpireThreshold = expireThreshold; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/PatchGroupManager.java b/src/main/java/io/github/dsheirer/module/decode/p25/PatchGroupManager.java deleted file mode 100644 index 1208ab22..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/PatchGroupManager.java +++ /dev/null @@ -1,245 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.alias.PatchGroupAlias; -import io.github.dsheirer.alias.id.talkgroup.TalkgroupID; -import io.github.dsheirer.module.decode.event.CallEvent; -import io.github.dsheirer.sample.Broadcaster; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -public class PatchGroupManager -{ - private AliasList mAliasList; - private List mPatchGroups = new ArrayList<>(); - private Broadcaster mCallEventBroadcaster; - - /** - * Manages active P25 patch groups and auto-expires patch groups if not updated every 5 seconds. - * - * @param aliasList to keep updated with patch group changes - * @param broadcaster for rebroadcasting patch group related call events - */ - public PatchGroupManager(AliasList aliasList, Broadcaster broadcaster) - { - mAliasList = aliasList; - mCallEventBroadcaster = broadcaster; - } - - /** - * Removes the patch group. - * - * @param patchGroupID of the patch group to remove - */ - public void removePatchGroup(String patchGroupID) - { - removePatchGroup(patchGroupID, false); - } - - /** - * Removes the patch group. - * - * @param patchGroupID of the patch group to remove - * @param expired true if the patch group is being removed because it expired, false if we receive a - * message commanding commanding a patch group delete. - */ - private void removePatchGroup(String patchGroupID, boolean expired) - { - PatchGroup patchGroup = getPatchGroup(patchGroupID); - - if(patchGroup != null) - { - //Cleanup the alias list - if(mAliasList != null) - { - Alias alias = mAliasList.getTalkgroupAlias(patchGroupID); - - if(alias instanceof PatchGroupAlias) - { - PatchGroupAlias patchGroupAlias = (PatchGroupAlias)alias; - - mAliasList.removeAlias(patchGroupAlias); - - //Replace our temporary patch group alias with the original alias for the patch group ID - if(patchGroupAlias.hasPatchGroupAlias()) - { - mAliasList.addAlias(patchGroupAlias.getPatchGroupAlias()); - } - } - } - - mPatchGroups.remove(patchGroup); - - if(mCallEventBroadcaster != null) - { - StringBuilder sb = new StringBuilder(); - - sb.append((expired ? "EXPIRED " : "DELETED ")); - sb.append("PATCH GROUP:").append(patchGroup.getPatchGroupID()); - sb.append(" ").append(patchGroup.getPatchedGroups()); - - - mCallEventBroadcaster.broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.PATCH_GROUP_DELETE) - .to(patchGroup.getPatchGroupID()) - .details(sb.toString()) - .build()); - } - } - } - - /** - * Adds/updates a patch group alias to the alias list containing aliases for each of the patched talkgroups. If the - * patch group alias already exists, any patched talkgroups will be removed from the existing patch group alias and - * replaced with the aliases corresponding to the patched talkgroup aliases. - * - * @param patchGroupID for the patch group - * @param patchedTalkgroups containing the talkgroup IDs for each of the patched talkgroups - */ - public void updatePatchGroup(String patchGroupID, List patchedTalkgroups) - { - boolean updated = false; - - PatchGroup patchGroup = getPatchGroup(patchGroupID); - - if(patchGroup == null) - { - //Remove any expired patch groups before we add a new one - cleanupPatchGroups(); - patchGroup = new PatchGroup(patchGroupID); - mPatchGroups.add(patchGroup); - updated = true; - } - else - { - patchGroup.updateTimestamp(); - } - - for(String patchedGroup : patchedTalkgroups) - { - if(patchGroup.addPatchedGroup(patchedGroup)) - { - updated = true; - } - } - - //If we make any changes to the patch group, update the patch group alias - if(updated) - { - PatchGroupAlias patchGroupAlias = null; - - //Check for an existing alias for the patch talkgroup - do not include wildcard aliases - if(mAliasList != null) - { - Alias existingAlias = mAliasList.getTalkgroupAlias(patchGroupID, false); - - if(existingAlias instanceof PatchGroupAlias) - { - patchGroupAlias = (PatchGroupAlias)existingAlias; - } - else - { - patchGroupAlias = new PatchGroupAlias(); - - patchGroupAlias.addAliasID(new TalkgroupID(patchGroupID)); - - if(existingAlias != null) - { - mAliasList.removeAlias(existingAlias); - - patchGroupAlias.setPatchGroupAlias(existingAlias); - } - - mAliasList.addAlias(patchGroupAlias); - } - - if(patchGroupAlias != null) - { - patchGroupAlias.setPatchedTalkgroupIDs(patchGroup.getPatchedGroups()); - - patchGroupAlias.clearPatchedAliases(); - - for(String patchedTalkgroup : patchGroup.getPatchedGroups()) - { - Alias patchedAlias = mAliasList.getTalkgroupAlias(patchedTalkgroup); - - if(patchedAlias != null) - { - patchGroupAlias.addPatchedAlias(patchedAlias); - } - } - } - } - - if(mCallEventBroadcaster != null) - { - StringBuilder sb = new StringBuilder(); - - sb.append("PATCH GROUP:").append(patchGroup.getPatchGroupID()); - sb.append(" ").append(patchGroup.getPatchedGroups()); - - mCallEventBroadcaster.broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.PATCH_GROUP_ADD) - .aliasList(mAliasList) - .details(sb.toString()) - .build()); - } - } - } - - /** - * Returns the patch group matching the patch group ID or null - */ - private PatchGroup getPatchGroup(String patchGroupID) - { - for(PatchGroup patchGroup : mPatchGroups) - { - if(patchGroup.getPatchGroupID().equals(patchGroupID)) - { - return patchGroup; - } - } - - return null; - } - - /** - * Checks the last updated timestamp on all patch groups and removes any patch groups that have not been - * updated in the past 5 seconds - */ - public void cleanupPatchGroups() - { - Iterator it = mPatchGroups.iterator(); - - while(it.hasNext()) - { - PatchGroup patchGroup = it.next(); - - if(patchGroup.isExpired()) - { - it.remove(); - - removePatchGroup(patchGroup.getPatchGroupID(), true); - } - } - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/Trellis_1_2_Rate.java b/src/main/java/io/github/dsheirer/module/decode/p25/Trellis_1_2_Rate.java deleted file mode 100644 index 25c8b19a..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/Trellis_1_2_Rate.java +++ /dev/null @@ -1,533 +0,0 @@ -package io.github.dsheirer.module.decode.p25; - -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.dsp.symbol.Dibit; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.Random; - -public class Trellis_1_2_Rate -{ - public final static int MAX_ERROR_THRESHOLD = 7; - - private final static Logger mLog = - LoggerFactory.getLogger( Trellis_1_2_Rate.class ); - - private ArrayList mConstellationNodes = - new ArrayList(); - - private static final int[][] CONSTELLATION_COSTS = - { { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 }, - { 1,0,2,1,2,1,3,2,2,1,3,2,3,2,4,3 }, - { 1,2,0,1,2,3,1,2,2,3,1,2,3,4,2,3 }, - { 2,1,1,0,3,2,2,1,3,2,2,1,4,3,3,2 }, - { 1,2,2,3,0,1,1,2,2,3,3,4,1,2,2,3 }, - { 2,1,3,2,1,0,2,1,3,2,4,3,2,1,3,2 }, - { 2,3,1,2,1,2,0,1,3,4,2,3,2,3,1,2 }, - { 3,2,2,1,2,1,1,0,4,3,3,2,3,2,2,1 }, - { 1,2,2,3,2,3,3,4,0,1,1,2,1,2,2,3 }, - { 2,1,3,2,3,2,4,3,1,0,2,1,2,1,3,2 }, - { 2,3,1,2,3,4,2,3,1,2,0,1,2,3,1,2 }, - { 3,2,2,1,4,3,3,2,2,1,1,0,3,2,2,1 }, - { 2,3,3,4,1,2,2,3,1,2,2,3,0,1,1,2 }, - { 3,2,4,3,2,1,3,2,2,1,3,2,1,0,2,1 }, - { 3,4,2,3,2,3,1,2,2,3,1,2,1,2,0,1 }, - { 4,3,3,2,3,2,2,1,3,2,2,1,2,1,1,0 } }; - - public Trellis_1_2_Rate() - { - /* Create 49 x 4-bit constellation nodes to handle 196-bit blocks */ - ConstellationNode previous = null; - - for( int x = 0; x < 49; x++ ) - { - ConstellationNode node = new ConstellationNode(); - - if( previous != null ) - { - previous.connect( node ); - } - - previous = node; - - mConstellationNodes.add( node ); - } - } - - public void dispose() - { - for( ConstellationNode node: mConstellationNodes ) - { - node.dispose(); - } - - mConstellationNodes.clear(); - } - -// private String constellationsToString( String label ) -// { -// StringBuilder sb = new StringBuilder(); -// -// sb.append( label ); -// -// for( ConstellationNode node: mConstellationNodes ) -// { -// sb.append( " " + node.getConstellation().name() ); -// } -// -// return sb.toString(); -// } -// -// private String inputsToString() -// { -// StringBuilder sb = new StringBuilder(); -// -// sb.append( " INPUTS: " ); -// -// for( ConstellationNode node: mConstellationNodes ) -// { -// sb.append( " " ); -// sb.append( ( node.getConstellation().getInput().getBit1() ? "1" : "0" ) ); -// sb.append( ( node.getConstellation().getInput().getBit2() ? "1" : "0" ) ); -// } -// -// return sb.toString(); -// } - - public boolean decode( BinaryMessage message, int start, int end ) - { - /* load each of the nodes with de-interleaved constellations */ - for( int index = 0; index < 49; index++ ) - { - Constellation c = getConstellation( message, start + index * 4 ); - - mConstellationNodes.get( index ).setConstellation( c ); - } - - /* test to see if constellations are correct - otherwise correct them */ - ConstellationNode firstNode = mConstellationNodes.get( 0 ); - - int errorCount = firstNode.getErrorCount(); - - if( errorCount > 0 ) - { - if( errorCount < MAX_ERROR_THRESHOLD ) - { - /* repair the errors */ - firstNode.correctTo( Dibit.D00_PLUS_1 ); - } - else - { - return false; - } - } - - /* clear constellations from original message */ - message.clear( start, end ); - - /* replace with decoded values from the constellation nodes */ - for( int index = 0; index < 49; index++ ) - { - ConstellationNode node = mConstellationNodes.get( index ); - - Dibit input = node.getInputDibit(); - - if( input.getBit1() ) - { - message.set( start + ( index * 2 ) ); - } - if( input.getBit2() ) - { - message.set( start + ( index * 2 ) + 1 ); - } - } - - return true; - } - - private Constellation getConstellation( BinaryMessage message, int index ) - { - int transmittedValue = 0; - - for( int x = 0; x < 4; x++ ) - { - if( message.get( index + x ) ) - { - transmittedValue += ( 1 << ( 3 - x ) ); - } - } - - return Constellation.fromTransmittedValue( transmittedValue ); - } - - public class ConstellationNode - { - private ConstellationNode mConnectedNode; - private Constellation mConstellation; - private boolean mCorrect; - - public ConstellationNode() - { - } - - public void dispose() - { - mConnectedNode = null; - } - - public Constellation getConstellation() - { - return mConstellation; - } - - public Dibit getInputDibit() - { - return mConstellation.getInput(); - } - - public boolean hasStateDibit( Dibit dibit ) - { - return mConstellation.getState() == dibit; - } - - /** - * Executes a correction down the line of connected nodes. Only nodes - * with the mCorrect flag set to false will be corrected. - * - * Note: Assumes that the starting node's value is 0. Initiate the - * corrective sequence by invoking this method with Dibit.D0 on the - * first node. - * - * @param stateDibit to use for the left side. - */ - public void correctTo( Dibit stateDibit ) - { - if( mCorrect && mConstellation.getState() == stateDibit ) - { - return; - } - - if( isCurrentConnectionCorrect() ) - { - mConstellation = Constellation.fromStateAndInputDibits( stateDibit, - mConstellation.getInput() ); - - mCorrect = true; - - if( mConnectedNode != null ) - { - /* the next node's state was this node's input */ - mConnectedNode.correctTo( mConstellation.getInput() ); - } - } - else - { - Constellation cheapestConstellation = mConstellation; - - int cheapestCost = 100; //arbitrary - - for( Dibit testInput: Dibit.values() ) - { - Constellation testConstellation = Constellation - .fromStateAndInputDibits( stateDibit, testInput ); - - int testCost = mConstellation.costTo( testConstellation ) + - mConnectedNode.costTo( testInput ); - - if( testCost < cheapestCost ) - { - cheapestCost = testCost; - cheapestConstellation = testConstellation; - } - } - - mConstellation = cheapestConstellation; - - mConnectedNode.correctTo( mConstellation.getInput() ); - - mCorrect = true; - } - } - - /** - * Calculates the cost (hamming distance) of using the argument as the - * state dibit for the current node, and recursively finding the - * cheapest corresponding input dibit. - * - * @param stateTest - * @return - */ - public int costTo( Dibit stateTest ) - { - if( isCurrentConnectionCorrect() ) - { - Constellation c = Constellation.fromStateAndInputDibits( - stateTest, mConstellation.getInput() ); - - return mConstellation.costTo( c ); - } - else - { - int cheapestCost = 100; //arbitrary - - for( Dibit inputTest: Dibit.values() ) - { - Constellation constellationTest = - Constellation.fromStateAndInputDibits( stateTest, inputTest ); - - int cost = mConnectedNode.costTo( inputTest ) + - mConstellation.costTo( constellationTest ); - - if( cost < cheapestCost ) - { - cheapestCost = cost; - } - } - - return cheapestCost; - } - } - - /** - * Indicates if the immediate connection to the right is correct - */ - public boolean isCurrentConnectionCorrect() - { - return ( mConnectedNode == null || - mConstellation.getInput() == mConnectedNode.getStateDibit() ); - } - - /** - * Executes a recursive call to all nodes to the right, setting the - * mCorrect flag on all nodes to true, if the node's connection to the - * right node is correct and all nodes to the right are correct. Or, - * false if this node's connection, or any node's connection to the - * right is incorrect. - * - * @return - true - all node connections to the right are correct - * false - one or more nodes to the right are incorrect - */ - public int getErrorCount() - { - if( mConnectedNode == null ) - { - mCorrect = true; - - return 0; - } - - mCorrect = mConstellation.getInput() == mConnectedNode.getStateDibit(); - - return mConnectedNode.getErrorCount() + ( mCorrect ? 0 : 1 ); - } - - public Dibit getStateDibit() - { - return mConstellation.getState(); - } - - public void setConstellation( Constellation constellation ) - { - mConstellation = constellation; - mCorrect = false; - } - - public void connect( ConstellationNode node ) - { - mConnectedNode = node; - } - } - - public enum Constellation - { - /* State (previous), Input (next), Transmit 1, Transmit 2 */ - CB( Dibit.D01_PLUS_3, Dibit.D01_PLUS_3, Dibit.D00_PLUS_1, Dibit.D00_PLUS_1, 0 ), - CC( Dibit.D00_PLUS_1, Dibit.D10_MINUS_1, Dibit.D00_PLUS_1, Dibit.D01_PLUS_3, 1 ), - C0( Dibit.D00_PLUS_1, Dibit.D00_PLUS_1, Dibit.D00_PLUS_1, Dibit.D10_MINUS_1, 2 ), - C7( Dibit.D01_PLUS_3, Dibit.D11_MINUS_3, Dibit.D00_PLUS_1, Dibit.D11_MINUS_3, 3 ), - CE( Dibit.D10_MINUS_1, Dibit.D11_MINUS_3, Dibit.D01_PLUS_3, Dibit.D00_PLUS_1, 4 ), - C9( Dibit.D11_MINUS_3, Dibit.D00_PLUS_1, Dibit.D01_PLUS_3, Dibit.D01_PLUS_3, 5 ), - C5( Dibit.D11_MINUS_3, Dibit.D10_MINUS_1, Dibit.D01_PLUS_3, Dibit.D10_MINUS_1, 6 ), - C2( Dibit.D10_MINUS_1, Dibit.D01_PLUS_3, Dibit.D01_PLUS_3, Dibit.D11_MINUS_3, 7 ), - CA( Dibit.D11_MINUS_3, Dibit.D11_MINUS_3, Dibit.D10_MINUS_1, Dibit.D00_PLUS_1, 8 ), - CD( Dibit.D10_MINUS_1, Dibit.D00_PLUS_1, Dibit.D10_MINUS_1, Dibit.D01_PLUS_3, 9 ), - C1( Dibit.D10_MINUS_1, Dibit.D10_MINUS_1, Dibit.D10_MINUS_1, Dibit.D10_MINUS_1, 10 ), - C6( Dibit.D11_MINUS_3, Dibit.D01_PLUS_3, Dibit.D10_MINUS_1, Dibit.D11_MINUS_3, 11 ), - CF( Dibit.D00_PLUS_1, Dibit.D01_PLUS_3, Dibit.D11_MINUS_3, Dibit.D00_PLUS_1, 12 ), - C8( Dibit.D01_PLUS_3, Dibit.D10_MINUS_1, Dibit.D11_MINUS_3, Dibit.D01_PLUS_3, 13 ), - C4( Dibit.D01_PLUS_3, Dibit.D00_PLUS_1, Dibit.D11_MINUS_3, Dibit.D10_MINUS_1, 14 ), - C3( Dibit.D00_PLUS_1, Dibit.D11_MINUS_3, Dibit.D11_MINUS_3, Dibit.D11_MINUS_3, 15 ); - - private Dibit mStateDibit; - private Dibit mInputDibit; - private Dibit mTransmitDibit1; - private Dibit mTransmitDibit2; - private int mTransmittedValue; - - private Constellation( Dibit stateDibit, - Dibit inputDibit, - Dibit transmitDibit1, - Dibit transmitDibit2, - int transmittedValue ) - { - mStateDibit = stateDibit; - mInputDibit = inputDibit; - mTransmitDibit1 = transmitDibit1; - mTransmitDibit2 = transmitDibit2; - mTransmittedValue = transmittedValue; - } - - public Dibit getState() - { - return mStateDibit; - } - - public Dibit getInput() - { - return mInputDibit; - } - - public Dibit getTransmitDibit1() - { - return mTransmitDibit1; - } - - public Dibit getTransmitDibit2() - { - return mTransmitDibit2; - } - - public int getTransmittedValue() - { - return mTransmittedValue; - } - - public static Constellation fromTransmittedValue( int value ) - { - if( 0 <= value && value <= 15 ) - { - return values()[ value ]; - } - - return null; - } - - public static Constellation fromTransmittedDibits( Dibit left, Dibit right ) - { - return fromTransmittedValue( left.getHighValue() + right.getLowValue() ); - } - - public static Constellation fromStateAndInputDibits( Dibit state, Dibit input ) - { - switch( state ) - { - case D00_PLUS_1: - switch( input ) - { - case D00_PLUS_1: - return C0; - case D01_PLUS_3: - return CF; - case D10_MINUS_1: - return CC; - case D11_MINUS_3: - return C3; - } - break; - case D01_PLUS_3: - switch( input ) - { - case D00_PLUS_1: - return C4; - case D01_PLUS_3: - return CB; - case D10_MINUS_1: - return C8; - case D11_MINUS_3: - return C7; - } - break; - case D10_MINUS_1: - switch( input ) - { - case D00_PLUS_1: - return CD; - case D01_PLUS_3: - return C2; - case D10_MINUS_1: - return C1; - case D11_MINUS_3: - return CE; - } - break; - case D11_MINUS_3: - switch( input ) - { - case D00_PLUS_1: - return C9; - case D01_PLUS_3: - return C6; - case D10_MINUS_1: - return C5; - case D11_MINUS_3: - return CA; - } - break; - } - - /* Should never get to here */ - return C0; - } - - /** - * Returns the cost or hamming distance to the other constellation using - * the values from the constellation costs table - */ - public int costTo( Constellation other ) - { - return CONSTELLATION_COSTS[ getTransmittedValue() ][ other.getTransmittedValue() ]; - } - - } - - /** - * Test harness - */ - public static void main( String[] args ) - { - Random random = new Random(); - - BinaryMessage buffer = new BinaryMessage( 196 ); - - try - { - for( int x = 0; x < 196; x++ ) - { - int number = random.nextInt( 2 ); - - if( number == 1 ) - { - buffer.add( false ); - } - else - { - buffer.add( true ); - } - } - } - catch( Exception e ) - { - e.printStackTrace(); - } - - mLog.debug( " BUFFER: " + buffer.toString() ); - - Trellis_1_2_Rate t = new Trellis_1_2_Rate(); - - t.decode( buffer, 0, 196 ); - mLog.debug( " DECODED: " + buffer.toString() ); - mLog.debug( "Finished!" ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/Trellis_3_4_Rate.java b/src/main/java/io/github/dsheirer/module/decode/p25/Trellis_3_4_Rate.java deleted file mode 100644 index cc38fa45..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/Trellis_3_4_Rate.java +++ /dev/null @@ -1,661 +0,0 @@ -package io.github.dsheirer.module.decode.p25; - -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.dsp.symbol.Dibit; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; - -public class Trellis_3_4_Rate -{ - private final static Logger mLog = - LoggerFactory.getLogger( Trellis_3_4_Rate.class ); - - /* Hamming distance (bit match count) between constellation pairs */ - private static final int[][] CONSTELLATION_METRICS = - { { 4,3,3,2,3,2,2,1,3,2,2,1,2,1,1,0 }, - { 3,4,2,3,2,3,1,2,2,3,1,2,1,2,0,1 }, - { 3,2,4,3,2,1,3,2,2,1,3,2,1,0,2,1 }, - { 2,3,3,4,1,2,2,3,1,2,2,3,0,1,1,2 }, - { 3,2,2,1,4,3,3,2,2,1,1,0,3,2,2,1 }, - { 2,3,1,2,3,4,2,3,1,2,0,1,2,3,1,2 }, - { 2,1,3,2,3,2,4,3,1,0,2,1,2,1,3,2 }, - { 1,2,2,3,2,3,3,4,0,1,1,2,1,2,2,3 }, - { 3,2,2,1,2,1,1,0,4,3,3,2,3,2,2,1 }, - { 2,3,1,2,1,2,0,1,3,4,2,3,2,3,1,2 }, - { 2,1,3,2,1,0,2,1,3,2,4,3,2,1,3,2 }, - { 1,2,2,3,0,1,1,2,2,3,3,4,1,2,2,3 }, - { 2,1,1,0,3,2,2,1,3,2,2,1,4,3,3,2 }, - { 1,2,0,1,2,3,1,2,2,3,1,2,3,4,2,3 }, - { 1,0,2,1,2,1,3,2,2,1,3,2,3,2,4,3 }, - { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 } }; - - /* Even valued state tribits can only produce even valued constellations - * and odd valued state tribits can only produce odd constellations. */ - public static Con[] EVEN_CONSTELLATIONS = new Con[] { Con.C0,Con.C2,Con.C4, - Con.C6,Con.C8,Con.CA,Con.CC,Con.CE }; - public static Con[] ODD_CONSTELLATIONS = new Con[] { Con.C1,Con.C3,Con.C5, - Con.C7,Con.C9,Con.CB,Con.CD,Con.CF }; - - /* Constellation and state tribit lookup map to find the input tribit */ - public static HashMap INPUT_FROM_CONSTELLATION_MAP; - - private ArrayList mTransmittedConstellations = new ArrayList(); - private ArrayList mSurvivorPaths = new ArrayList(); - private ArrayList mNewPaths = new ArrayList(); - private PathMetrics mPathMetrics = new PathMetrics(); - - /** - * Implements the Viterbi algorithm to decode 3/4 rate trellis encoded 196-bit - * packet data messages. - */ - public Trellis_3_4_Rate() - { - createConstellationToTribitMap(); - } - - /** - * Creates a lookup map for state to input tribit values for a given - * constellation. Input tribits are contained in the array using the state - * tribit's value as the lookup index. Null values indicate illegal state - * and input combinations for the specified constellation. - */ - private void createConstellationToTribitMap() - { - INPUT_FROM_CONSTELLATION_MAP = new HashMap(); - - INPUT_FROM_CONSTELLATION_MAP.put( Con.CB, new Tribit[] { null, null, Tribit.T5, Tribit.T3, Tribit.T1, Tribit.T7, null, null } ); - INPUT_FROM_CONSTELLATION_MAP.put( Con.CC, new Tribit[] { Tribit.T3, Tribit.T1, null, null, null, null, Tribit.T7, Tribit.T5 } ); - INPUT_FROM_CONSTELLATION_MAP.put( Con.C0, new Tribit[] { Tribit.T0, Tribit.T6, null, null, null, null, Tribit.T4, Tribit.T2 } ); - INPUT_FROM_CONSTELLATION_MAP.put( Con.C7, new Tribit[] { null, null, Tribit.T6, Tribit.T4, Tribit.T2, Tribit.T0, null, null } ); - INPUT_FROM_CONSTELLATION_MAP.put( Con.CE, new Tribit[] { Tribit.T7, Tribit.T5, null, null, null, null, Tribit.T3, Tribit.T1 } ); - INPUT_FROM_CONSTELLATION_MAP.put( Con.C9, new Tribit[] { null, null, Tribit.T1, Tribit.T7, Tribit.T5, Tribit.T3, null, null } ); - INPUT_FROM_CONSTELLATION_MAP.put( Con.C5, new Tribit[] { null, null, Tribit.T2, Tribit.T0, Tribit.T6, Tribit.T4, null, null } ); - INPUT_FROM_CONSTELLATION_MAP.put( Con.C2, new Tribit[] { Tribit.T4, Tribit.T2, null, null, null, null, Tribit.T0, Tribit.T6 } ); - INPUT_FROM_CONSTELLATION_MAP.put( Con.CA, new Tribit[] { Tribit.T5, Tribit.T3, null, null, null, null, Tribit.T1, Tribit.T7 } ); - INPUT_FROM_CONSTELLATION_MAP.put( Con.CD, new Tribit[] { null, null, Tribit.T3, Tribit.T1, Tribit.T7, Tribit.T5, null, null } ); - INPUT_FROM_CONSTELLATION_MAP.put( Con.C1, new Tribit[] { null, null, Tribit.T0, Tribit.T6, Tribit.T4, Tribit.T2, null, null } ); - INPUT_FROM_CONSTELLATION_MAP.put( Con.C6, new Tribit[] { Tribit.T6, Tribit.T4, null, null, null, null, Tribit.T2, Tribit.T0 } ); - INPUT_FROM_CONSTELLATION_MAP.put( Con.CF, new Tribit[] { null, null, Tribit.T7, Tribit.T5, Tribit.T3, Tribit.T1, null, null } ); - INPUT_FROM_CONSTELLATION_MAP.put( Con.C8, new Tribit[] { Tribit.T1, Tribit.T7, null, null, null, null, Tribit.T5, Tribit.T3 } ); - INPUT_FROM_CONSTELLATION_MAP.put( Con.C4, new Tribit[] { Tribit.T2, Tribit.T0, null, null, null, null, Tribit.T6, Tribit.T4 } ); - INPUT_FROM_CONSTELLATION_MAP.put( Con.C3, new Tribit[] { null, null, Tribit.T4, Tribit.T2, Tribit.T0, Tribit.T6, null, null } );; - } - - /** - * Member object cleanup prior to deleting - */ - public void dispose() - { - reset(); - } - - /** - * Decodes a 196-bit 3/4 rate convolutional encoded message located between - * start and end indexes and returns the decoded 144-bit message overlayed - * upon the original message with the remaining 52 bits cleared to zero. - * - * @return - original message with decoded message bits.. - */ - public boolean decode( BinaryMessage message, int start, int end ) - { - reset(); - - /* Load and decode each of the transmitted constellations */ - for( int index = 0; index < 49; index++ ) - { - Con c = getConstellation( message, start + index * 4 ); - - add( c ); - } - - /* The final decoded survivor path should be at path metrics tribit 0 */ - Path path = mPathMetrics.getPath( Tribit.T0 ); - - /* We should have a path with 50 nodes, counting the starting 000 node - * and the flushing 000 node, otherwise there was an error */ - if( path != null && path.getNodes().size() == 50 ) - { - /* Clear the original message bits */ - message.clear( start, end ); - - List nodes = path.getNodes(); - - /* Load each of the nodes' state tribit values into the original message */ - for( int x = 1; x < 50; x++ ) - { - message.load( start + ( ( x - 1 ) * 3 ), 3, - nodes.get( x ).getState().getValue() ); - } - - return true; - } - - return false; - } - - /** - * Resets the decoder before decoding a new data packet. - */ - private void reset() - { - mTransmittedConstellations.clear(); - mNewPaths.clear(); - - mPathMetrics.reset(); - - mSurvivorPaths.clear(); - - /* Tribit 000 is the only legal start point */ - mSurvivorPaths.add( new Path( new Node( 0, Tribit.T0, Con.C0 ) ) ); - } - - private void add( Con con ) - { - mTransmittedConstellations.add( con ); - - /* Add in any newly created survivor paths */ - mSurvivorPaths.addAll( mNewPaths ); - mNewPaths.clear(); - - /* Set a culling threshold for any path metrics 3 below the best path */ - int survivorThreshold = mPathMetrics.getSurvivorThreshold(); - - /* Reset path metrics */ - mPathMetrics.reset(); - - /* Add constellation to each survivor path. If a survivor path metric - * falls below the culling threshold, remove it */ - Iterator it = mSurvivorPaths.iterator(); - - Path path; - - while( it.hasNext() ) - { - path = it.next(); - - if( path.isDead() || path.getPathMetric() < survivorThreshold ) - { - it.remove(); - } - else - { - path.add( con ); - } - } - } - - private Con getConstellation( BinaryMessage message, int index ) - { - int value = message.getInt( index, index + 3 ); - return Con.fromTransmittedValue( value ); - } - - /** - * Path metrics maintains a map of the current best path metrics that end - * at each of the 8 valid tribit values. Each new path is evaluated against - * the current best path for retention (survivor) or disposal (dead). A - * best metric is maintained to evaluate the Walking Dead for disposal. - * Walking dead is a path lagging the best path metric by more than 3. - */ - public class PathMetrics - { - private HashMap mMetrics = new HashMap(); - - private int mBestMetric = 0; - - public PathMetrics() - { - } - - public int getSurvivorThreshold() - { - return mBestMetric - 3; - } - - /** - * Removes all path metrics - */ - public void reset() - { - mMetrics.clear(); - mBestMetric = 0; - } - - public int getMetric( Tribit tribit ) - { - if( mMetrics.containsKey( tribit ) ) - { - return mMetrics.get( tribit ).getPathMetric(); - } - - return 0; - } - - public Path getPath( Tribit tribit ) - { - return mMetrics.get( tribit ); - } - - /** - * Evaluates the path's metric against the current best path metric for - * the final state tribit of the path. If the path's metric is higher - * than the current best path metric, the current path is marked for - * termination and the new path is promoted to best path. If the new - * path's metric is less than the best path metric, then the new path - * metric is marked for termination. If the metrics are equal, the new - * path is allowed to survive. - * - * @param path/metric to evaluate - */ - public void evalute( Path path ) - { - Node lastNode = path.getLastNode(); - - Path bestPath = mMetrics.get( lastNode.getState() ); - - if( bestPath == null ) - { - mMetrics.put( lastNode.getState(), path ); - } - else - { - if( path.getPathMetric() < bestPath.getPathMetric() ) - { - path.setSurvivor( false ); - } - else if( path.getPathMetric() > bestPath.getPathMetric() ) - { - mMetrics.put( lastNode.getState(), path ); - - bestPath.setSurvivor( false ); - } - - /* Equal metric paths are allowed to survive */ - } - - /* Capture the best path metric */ - if( path.getPathMetric() > mBestMetric ) - { - mBestMetric = path.getPathMetric(); - } - } - } - - /** - * Path is a sequence of nodes where the starting node always contains - * tribit 0 and constellation 0. Path maintains a running path metric - * derived from the sum of each of the node's branch metrics. - */ - public class Path - { - private boolean mSurvivor = true; - private int mPathMetric = 0; - private ArrayList mNodes = new ArrayList(); - - public Path( Node first ) - { - mNodes.add( first ); - - mPathMetric = first.getBranchMetric(); - - mPathMetrics.evalute( this ); - } - - public Path( ArrayList nodes, int metric ) - { - mNodes = nodes; - mPathMetric = metric; - } - - public List getNodes() - { - return mNodes; - } - - public int getBitErrorCount() - { - return ( getLastNode().getIndex() * 4 ) - mPathMetric; - } - - public void setSurvivor( boolean survivor ) - { - mSurvivor = survivor; - } - - public boolean isSurvivor() - { - return mSurvivor; - } - - public boolean isDead() - { - return !mSurvivor; - } - - @SuppressWarnings( "unchecked" ) - public Path copyOf() - { - return new Path( (ArrayList)mNodes.clone(), mPathMetric ); - } - - /** - * Adds the constellation and returns any new paths created as a result - * of the constellation being detected as an errant constellation. - */ - public void add( Con con ) - { - Node current = getLastNode(); - - Tribit input = null; - - if(current != null) - { - input = INPUT_FROM_CONSTELLATION_MAP.get( con )[ current.getState().getValue() ]; - } - - /* A non-null input tribit from the lookup table is valid, otherwise - * create 8 alternate branches from the original path to explore - * where the error occurred. */ - if( input != null ) - { - add( new Node( mNodes.size(), input, con ) ); - } - else - { - /* Create new paths for each of the valid constellations that - * correspond to the parity of the current state tribit */ - Con[] constellations = current.getState().getType() == - Type.EVEN ? EVEN_CONSTELLATIONS : ODD_CONSTELLATIONS; - - for( int x = 0; x < 8; x++ ) - { - /* Get the input tribit corresponding to the state tribit - * and constellation combination */ - Tribit tribit = INPUT_FROM_CONSTELLATION_MAP - .get( constellations[ x ] )[ current.getState().getValue() ]; - - Node candidate = new Node( mNodes.size(), tribit, constellations[ x ] ); - - /* If the current path metric plus the candidate node's - * branch metric is equal or better than the current best - * metric for the given tribit, add it as a potential path, - * otherwise discard it */ - if( this.getPathMetric() + candidate.getBranchMetric() >= - mPathMetrics.getMetric( tribit ) ) - { - /* If this is the final constellation of the set, simply - * add it to the existing path ... */ - if( x == 7 ) - { - add( candidate ); - } - /* ... otherwise create a copy of the current path */ - else - { - Path path = this.copyOf(); - - path.add( candidate ); - - mNewPaths.add( path ); - } - } - } - } - } - - public void add( Node node ) - { - mNodes.add( node ); - - mPathMetric += node.getBranchMetric(); - - mPathMetrics.evalute( this ); - } - - public int getPathMetric() - { - return mPathMetric; - } - - public Node getLastNode() - { - return mNodes.get( mNodes.size() - 1 ); - } - - public String toString() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "PATH [" ); - sb.append( mNodes.size() ); - sb.append( "-" ); - sb.append( mPathMetric ); - sb.append( "] " ); - - for( Node node: mNodes ) - { - sb.append( node.getState().name() ); - sb.append( "/" ); - sb.append( node.getConstellation().name() ); - sb.append( " " ); - } - - sb.append( mSurvivor ? "SURVIVOR" : "DEAD" ); - - return sb.toString(); - } - } - - /** - * Trellis node containing the constellation used to produce the node and - * the input tribit for the constellation. - */ - public class Node - { - private int mIndex; - private int mBranchMetric; - private Tribit mTribit; - private Con mConstellation; - - public Node( int index, Tribit tribit, Con constellation ) - { - mIndex = index; - mTribit = tribit; - mConstellation = constellation; - - if( mIndex > 0 ) - { - Con transmitted = mTransmittedConstellations.get( index - 1 ); - - if( transmitted == null ) - { - throw new IllegalArgumentException( "Cannot calculate " - + "metric - transmitted constellation symbol is null" ); - } - - mBranchMetric = transmitted.getMetricTo( constellation ); - } - } - - public int getIndex() - { - return mIndex; - } - - public Tribit getState() - { - return mTribit; - } - - public Con getConstellation() - { - return mConstellation; - } - - public int getBranchMetric() - { - return mBranchMetric; - } - - public String toString() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "Node " ); - sb.append( mIndex ); - sb.append( " Metric:" ); - sb.append( mBranchMetric ); - sb.append( " Con:" ); - sb.append( mConstellation.name() ); - sb.append( " State:" ); - if( mTribit == null ) - { - sb.append( "null" ); - } - else - { - sb.append( mTribit.name() ); - } - - return sb.toString(); - } - } - - /** - * Constellation Type. Constellations are classified as even or add based - * on the the composition/comparison of the transmitted (representative) - * dibits: - * - * EVEN: - first dibit differs from second dibit in exactly one bit position - * - * ODD: - first dibit differs from second dibit in zero or two bit positions - * following three patterns: - * 1) repeat (10 10), mirror (01 10), or invert(00 11) - */ - public enum Type { EVEN, ODD }; - - /** - * Constellations, ordered by transmitted value. Transmitted value is the - * value of the dibit pair that represents the constellation when transmitted. - */ - public enum Con - { - CB( 0 ), - CC( 1 ), - C0( 2 ), - C7( 3 ), - CE( 4 ), - C9( 5 ), - C5( 6 ), - C2( 7 ), - CA( 8 ), - CD( 9 ), - C1( 10 ), - C6( 11 ), - CF( 12 ), - C8( 13 ), - C4( 14 ), - C3( 15 ); - - private int mTransmittedValue; - - private Con( int transmittedValue ) - { - mTransmittedValue = transmittedValue; - } - - public int getTransmittedValue() - { - return mTransmittedValue; - } - - public static Con fromTransmittedValue( int value ) - { - if( 0 <= value && value <= 15 ) - { - return values()[ value ]; - } - - return null; - } - - public static Con fromTransmittedDibits( Dibit left, Dibit right ) - { - return fromTransmittedValue( left.getHighValue() + right.getLowValue() ); - } - - /** - * Returns the metric or hamming distance to the other constellation - * using the values from the constellation costs table. A perfect match - * has a metric of 4 and a complete miss has a value of 0. - */ - public int getMetricTo( Con other ) - { - return CONSTELLATION_METRICS[ getTransmittedValue() ][ other.getTransmittedValue() ]; - } - - } - - /** - * Tribit. Used to define both the state and the input bit sequences that - * produce the transmitted constellation bit sequences. Each tribit is - * either even or odd valued producing a direct correlation to the types - * of valid constellations containing the tribit as the state tribit. - */ - public enum Tribit - { - T0( 0, Type.EVEN ), - T1( 1, Type.EVEN ), - T2( 2, Type.ODD ), - T3( 3, Type.ODD ), - T4( 4, Type.ODD ), - T5( 5, Type.ODD ), - T6( 6, Type.EVEN ), - T7( 7, Type.EVEN ); - - private int mValue; - private Type mType; - - private Tribit( int value, Type type ) - { - mValue = value; - mType = type; - } - - public Type getType() - { - return mType; - } - - public int getValue() - { - return mValue; - } - } - - /** - * Test harness - */ - public static void main( String[] args ) - { - String raw = "12:58:51.635 DEBUG dsp.fsk.P25MessageFramer - AFTER DEINTERLEAVE: 00100110000011001010010101111101000101010110100111111100101011000111011011000110000000000000000000000111111101011000001000001111000110000000000010101011110110010000000001011011000000010100011110110001000010100100011111000000000100000000000000000000010000000000001100000000000000011010101010101010101010100010001010000110010111111101101000001010000010100000101000001010000010100000101000001010000010100000101000001010000010100000101000001010000010100000010111001001000011100101011010101010101001010011"; - - BinaryMessage message = BinaryMessage.load( raw ); - - mLog.debug( "MSG: " + message.toString() ); - - Trellis_3_4_Rate t = new Trellis_3_4_Rate(); - t.decode( message, 0, 196 ); - - mLog.debug( "DEC: " + message.toString() ); - - mLog.debug( "Finished!" ); - } - - -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25AudioModule.java b/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25AudioModule.java index 8578745c..be9ddf16 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25AudioModule.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25AudioModule.java @@ -15,15 +15,12 @@ ******************************************************************************/ package io.github.dsheirer.module.decode.p25.audio; +import io.github.dsheirer.audio.AbstractAudioModule; import io.github.dsheirer.audio.AudioFormats; -import io.github.dsheirer.audio.IAudioPacketProvider; -import io.github.dsheirer.audio.squelch.ISquelchStateListener; import io.github.dsheirer.audio.squelch.SquelchState; -import io.github.dsheirer.channel.metadata.Metadata; import io.github.dsheirer.dsp.gain.NonClippingGain; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.IMessageListener; -import io.github.dsheirer.message.Message; -import io.github.dsheirer.module.Module; import io.github.dsheirer.module.decode.p25.message.hdu.HDUMessage; import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; import io.github.dsheirer.module.decode.p25.message.ldu.LDU2Message; @@ -36,11 +33,9 @@ import jmbe.iface.AudioConverter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class P25AudioModule extends Module implements Listener, IAudioPacketProvider, IMessageListener, - ISquelchStateListener +public class P25AudioModule extends AbstractAudioModule implements Listener, IMessageListener { - private final static Logger mLog = LoggerFactory.getLogger(P25AudioModule.class); - + private static final Logger mLog = LoggerFactory.getLogger(P25AudioModule.class); private static final String IMBE_CODEC = "IMBE"; private static boolean mLibraryLoadStatusLogged = false; @@ -49,21 +44,18 @@ public class P25AudioModule extends Module implements Listener, IAudioP private boolean mEncryptedCallStateEstablished = false; private AudioConverter mAudioConverter; - private Listener mAudioPacketListener; private SquelchStateListener mSquelchStateListener = new SquelchStateListener(); private NonClippingGain mGain = new NonClippingGain(5.0f, 0.95f); - private Metadata mMetadata; private LDU1Message mCachedLDU1Message = null; private ReusableAudioPacketQueue mAudioPacketQueue = new ReusableAudioPacketQueue("P25AudioModule"); - public P25AudioModule(Metadata metadata) + public P25AudioModule() { - mMetadata = metadata; loadConverter(); } @Override - public Listener getMessageListener() + public Listener getMessageListener() { return this; } @@ -83,6 +75,7 @@ public class P25AudioModule extends Module implements Listener, IAudioP @Override public void reset() { + getIdentifierCollection().clear(); } @Override @@ -94,7 +87,15 @@ public class P25AudioModule extends Module implements Listener, IAudioP @Override public void stop() { - + if(hasAudioPacketListener()) + { + ReusableAudioPacket endAudioPacket = mAudioPacketQueue.getEndAudioBuffer(); + endAudioPacket.resetAttributes(); + endAudioPacket.setAudioChannelId(getAudioChannelId()); + endAudioPacket.setIdentifierCollection(getIdentifierCollection().copyOf()); + endAudioPacket.incrementUserCount(); + getAudioPacketListener().receive(endAudioPacket); + } } /** @@ -104,15 +105,15 @@ public class P25AudioModule extends Module implements Listener, IAudioP * LDU2 message is received and the encryption state can be determined. Both the LDU1 and the LDU2 message are * then processed for audio if the call is unencrypted. */ - public void receive(Message message) + public void receive(IMessage message) { - if(mCanConvertAudio && mAudioPacketListener != null) + if(mCanConvertAudio && hasAudioPacketListener()) { if(mEncryptedCallStateEstablished) { if(message instanceof LDUMessage) { - processAudio((LDUMessage)message); + processAudio((LDUMessage) message); } } else @@ -120,19 +121,19 @@ public class P25AudioModule extends Module implements Listener, IAudioP if(message instanceof HDUMessage) { mEncryptedCallStateEstablished = true; - mEncryptedCall = ((HDUMessage)message).isEncryptedAudio(); + mEncryptedCall = ((HDUMessage) message).getHeaderData().isEncryptedAudio(); } else if(message instanceof LDU1Message) { - //When we receive an LDU1 message with first receiving the HDU message, cache the LDU1 Message + //When we receive an LDU1 message without first receiving the HDU message, cache the LDU1 Message //until we can determine the encrypted call state from the next LDU2 message - mCachedLDU1Message = (LDU1Message)message; + mCachedLDU1Message = (LDU1Message) message; } else if(message instanceof LDU2Message) { mEncryptedCallStateEstablished = true; - LDU2Message ldu2 = (LDU2Message)message; - mEncryptedCall = ldu2.isEncryptedAudio(); + LDU2Message ldu2 = (LDU2Message) message; + mEncryptedCall = ldu2.getEncryptionSyncParameters().isEncryptedAudio(); if(mCachedLDU1Message != null) { @@ -160,10 +161,12 @@ public class P25AudioModule extends Module implements Listener, IAudioP audio = mGain.apply(audio); ReusableAudioPacket audioPacket = mAudioPacketQueue.getBuffer(audio.length); - audioPacket.setMetadata(mMetadata.copyOf()); + audioPacket.resetAttributes(); + audioPacket.setAudioChannelId(getAudioChannelId()); + audioPacket.setIdentifierCollection(getIdentifierCollection().copyOf()); audioPacket.loadAudioFrom(audio); - mAudioPacketListener.receive(audioPacket); + getAudioPacketListener().receive(audioPacket); } } else @@ -186,10 +189,10 @@ public class P25AudioModule extends Module implements Listener, IAudioP @SuppressWarnings("rawtypes") Class temp = Class.forName("jmbe.JMBEAudioLibrary"); - library = (AudioConversionLibrary)temp.newInstance(); + library = (AudioConversionLibrary) temp.newInstance(); if((library.getMajorVersion() == 0 && library.getMinorVersion() >= 3 && - library.getBuildVersion() >= 3) || library.getMajorVersion() >= 1) + library.getBuildVersion() >= 3) || library.getMajorVersion() >= 1) { mAudioConverter = library.getAudioConverter(IMBE_CODEC, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO); @@ -244,24 +247,12 @@ public class P25AudioModule extends Module implements Listener, IAudioP if(!mLibraryLoadStatusLogged) { mLog.error("Couldn't load JMBE audio conversion library due to " - + "security restrictions"); + + "security restrictions"); mLibraryLoadStatusLogged = true; } } } - @Override - public void setAudioPacketListener(Listener listener) - { - mAudioPacketListener = listener; - } - - @Override - public void removeAudioPacketListener() - { - mAudioPacketListener = null; - } - /** * Wrapper for squelch state to process end of call actions. At call end the encrypted call state established * flag is reset so that the encrypted audio state for the next call can be properly detected and we send an @@ -274,12 +265,14 @@ public class P25AudioModule extends Module implements Listener, IAudioP { if(state == SquelchState.SQUELCH) { - if(mAudioPacketListener != null) + if(hasAudioPacketListener()) { ReusableAudioPacket endAudioPacket = mAudioPacketQueue.getEndAudioBuffer(); - endAudioPacket.setMetadata(mMetadata.copyOf()); + endAudioPacket.resetAttributes(); + endAudioPacket.setAudioChannelId(getAudioChannelId()); + endAudioPacket.setIdentifierCollection(getIdentifierCollection().copyOf()); endAudioPacket.incrementUserCount(); - mAudioPacketListener.receive(endAudioPacket); + getAudioPacketListener().receive(endAudioPacket); } mEncryptedCallStateEstablished = false; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Lra.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Lra.java new file mode 100644 index 00000000..4d42499a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Lra.java @@ -0,0 +1,49 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.integer.IntegerIdentifier; +import io.github.dsheirer.protocol.Protocol; + +public class APCO25Lra extends IntegerIdentifier +{ + public APCO25Lra(int value) + { + super(value, IdentifierClass.NETWORK, Form.LOCATION_REGISTRATION_AREA, Role.BROADCAST); + } + + @Override + public Protocol getProtocol() + { + return Protocol.APCO25; + } + + /** + * Creates a new APCO-25 identifier + */ + public static Identifier create(int lra) + { + return new APCO25Lra(lra); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Nac.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Nac.java new file mode 100644 index 00000000..e5227844 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Nac.java @@ -0,0 +1,55 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.integer.IntegerIdentifier; +import io.github.dsheirer.protocol.Protocol; + +public class APCO25Nac extends IntegerIdentifier +{ + public APCO25Nac(int value) + { + super(value, IdentifierClass.NETWORK, Form.NETWORK_ACCESS_CODE, Role.BROADCAST); + } + + @Override + public Protocol getProtocol() + { + return Protocol.APCO25; + } + + @Override + public boolean isValid() + { + return getValue() > 0; + } + + /** + * Creates a new APCO-25 identifier + */ + public static Identifier create(int nac) + { + return new APCO25Nac(nac); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Rfss.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Rfss.java new file mode 100644 index 00000000..10d8f9a1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Rfss.java @@ -0,0 +1,49 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.integer.IntegerIdentifier; +import io.github.dsheirer.protocol.Protocol; + +public class APCO25Rfss extends IntegerIdentifier +{ + public APCO25Rfss(int value) + { + super(value, IdentifierClass.NETWORK, Form.RF_SUBSYSTEM, Role.BROADCAST); + } + + @Override + public Protocol getProtocol() + { + return Protocol.APCO25; + } + + /** + * Creates a new APCO-25 identifier + */ + public static Identifier create(int rfss) + { + return new APCO25Rfss(rfss); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Site.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Site.java new file mode 100644 index 00000000..72cd8268 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Site.java @@ -0,0 +1,49 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.integer.IntegerIdentifier; +import io.github.dsheirer.protocol.Protocol; + +public class APCO25Site extends IntegerIdentifier +{ + public APCO25Site(int value) + { + super(value, IdentifierClass.NETWORK, Form.SITE, Role.BROADCAST); + } + + @Override + public Protocol getProtocol() + { + return Protocol.APCO25; + } + + /** + * Creates a new APCO-25 site identifier + */ + public static Identifier create(int site) + { + return new APCO25Site(site); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25System.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25System.java new file mode 100644 index 00000000..6eb0d6d3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25System.java @@ -0,0 +1,48 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.integer.IntegerIdentifier; +import io.github.dsheirer.protocol.Protocol; + +public class APCO25System extends IntegerIdentifier +{ + public APCO25System(int value) + { + super(value, IdentifierClass.NETWORK, Form.SYSTEM, Role.BROADCAST); + } + + public Protocol getProtocol() + { + return Protocol.APCO25; + } + + /** + * Creates a new APCO-25 identifier + */ + public static Identifier create(int system) + { + return new APCO25System(system); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Wacn.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Wacn.java new file mode 100644 index 00000000..b65330f3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Wacn.java @@ -0,0 +1,49 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.integer.IntegerIdentifier; +import io.github.dsheirer.protocol.Protocol; + +public class APCO25Wacn extends IntegerIdentifier +{ + public APCO25Wacn(int value) + { + super(value, IdentifierClass.NETWORK, Form.WACN, Role.BROADCAST); + } + + @Override + public Protocol getProtocol() + { + return Protocol.APCO25; + } + + /** + * Creates a new APCO-25 identifier + */ + public static Identifier create(int wacn) + { + return new APCO25Wacn(wacn); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/APCO25Channel.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/APCO25Channel.java new file mode 100644 index 00000000..e87dffe8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/APCO25Channel.java @@ -0,0 +1,135 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier.channel; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBand; +import io.github.dsheirer.protocol.Protocol; + +/** + * APCO-25 Channel composed of a frequency band and a channel number within the frequency band. + * + * This class supports inserting a band identifier message that allows the uplink and downlink freuqencies + * for the channel to be calculated. + */ +public class APCO25Channel extends Identifier implements IChannelDescriptor, Comparable +{ + /** + * Creates an APCO-25 channel identifier + */ + public APCO25Channel(P25Channel p25Channel) + { + super(p25Channel, IdentifierClass.NETWORK, Form.CHANNEL, Role.BROADCAST); + } + + @Override + public Protocol getProtocol() + { + return Protocol.APCO25; + } + + @Override + public int[] getFrequencyBandIdentifiers() + { + return getValue().getFrequencyBandIdentifiers(); + } + + @Override + public int getTimeslotCount() + { + return getValue().getTimeslotCount(); + } + + @Override + public boolean isTDMAChannel() + { + return getValue().isTDMAChannel(); + } + + public void setFrequencyBand(IFrequencyBand bandIdentifier) + { + getValue().setFrequencyBand(bandIdentifier); + } + + /** + * Downlink frequency for this channel. + * + * @return frequency in hertz + */ + public long getDownlinkFrequency() + { + return getValue().getDownlinkFrequency(); + } + + /** + * Uplink frequency for this channel. + * + * @return frequency in hertz + */ + public long getUplinkFrequency() + { + return getValue().getUplinkFrequency(); + } + + + /** + * Creates a new APCO-25 identifier + */ + public static APCO25Channel create(int frequencyBand, int channelNumber) + { + return new APCO25Channel(new P25Channel(frequencyBand, channelNumber)); + } + + @Override + public boolean equals(Object o) + { + if(this == o) + { + return true; + } + if(o == null || !(o instanceof APCO25Channel) || ((APCO25Channel)o).getValue() == null) + { + return false; + } + + return getValue().equals(((APCO25Channel)o).getValue()); + } + + @Override + public int hashCode() + { + return getValue().hashCode(); + } + + @Override + public int compareTo(IChannelDescriptor o) + { + if(o == null) + { + return -1; + } + + return Long.compare(getDownlinkFrequency(), o.getDownlinkFrequency()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/APCO25ExplicitChannel.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/APCO25ExplicitChannel.java new file mode 100644 index 00000000..a43e0d8f --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/APCO25ExplicitChannel.java @@ -0,0 +1,44 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier.channel; + +/** + * APCO-25 Channel composed of separate uplink and downlink channel numbers and frequency bands. + * + * This class supports inserting band identifier message(s) that allows the uplink and downlink freuqencies + * for the channel to be calculated. + */ +public class APCO25ExplicitChannel extends APCO25Channel +{ + public APCO25ExplicitChannel(P25ExplicitChannel p25ExplicitChannel) + { + super(p25ExplicitChannel); + } + + /** + * Creates a new APCO-25 explicit channel + */ + public static APCO25ExplicitChannel create(int downlinkFrequencyBand, int downlinkChannelNumber, + int uplinkFrequencyBand, int uplinkChannelNumber) + { + return new APCO25ExplicitChannel(new P25ExplicitChannel(downlinkFrequencyBand, downlinkChannelNumber, + uplinkFrequencyBand, uplinkChannelNumber)); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/P25Channel.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/P25Channel.java new file mode 100644 index 00000000..954d5213 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/P25Channel.java @@ -0,0 +1,189 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier.channel; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBand; +import io.github.dsheirer.protocol.Protocol; + +import java.util.Objects; + +public class P25Channel implements IChannelDescriptor +{ + private IFrequencyBand mFrequencyBand; + private int mBandIdentifier; + private int mChannelNumber; + + public P25Channel(int bandIdentifier, int channelNumber) + { + mBandIdentifier = bandIdentifier; + mChannelNumber = channelNumber; + } + + @Override + public Protocol getProtocol() + { + return Protocol.APCO25; + } + + /** + * Frequency band information that backs this channel + */ + public IFrequencyBand getFrequencyBand() + { + return mFrequencyBand; + } + + public int getDownlinkBandIdentifier() + { + return mBandIdentifier; + } + + public int getDownlinkChannelNumber() + { + return mChannelNumber; + } + + public int getUplinkBandIdentifier() + { + return mBandIdentifier; + } + + public int getUplinkChannelNumber() + { + return mChannelNumber; + } + + @Override + public long getDownlinkFrequency() + { + if(mFrequencyBand != null) + { + return mFrequencyBand.getDownlinkFrequency(getDownlinkChannelNumber()); + } + + return 0; + } + + @Override + public long getUplinkFrequency() + { + if(mFrequencyBand != null) + { + return mFrequencyBand.getUplinkFrequency(getUplinkChannelNumber()); + } + + return 0; + } + + public boolean hasUplinkChannel() + { + return getUplinkChannelNumber() != 4095; + } + + @Override + public int[] getFrequencyBandIdentifiers() + { + if(getDownlinkBandIdentifier() != getUplinkBandIdentifier()) + { + int[] identifiers = new int[2]; + identifiers[0] = getDownlinkBandIdentifier(); + identifiers[1] = getUplinkBandIdentifier(); + return identifiers; + } + else + { + int[] identifiers = new int[1]; + identifiers[0] = getDownlinkBandIdentifier(); + return identifiers; + } + } + + @Override + public void setFrequencyBand(IFrequencyBand frequencyBand) + { + mFrequencyBand = frequencyBand; + } + + @Override + public int getTimeslotCount() + { + if(mFrequencyBand != null) + { + return mFrequencyBand.getTimeslotCount(); + } + + return 0; + } + + @Override + public boolean isTDMAChannel() + { + if(mFrequencyBand != null) + { + return mFrequencyBand.isTDMA(); + } + + return false; + } + + /** + * Formatted channel number + */ + public String toString() + { + if(getDownlinkBandIdentifier() == getUplinkBandIdentifier() && getDownlinkChannelNumber() == getUplinkChannelNumber()) + { + return getDownlinkBandIdentifier() + "-" + getDownlinkChannelNumber(); + } + else if(hasUplinkChannel()) + { + return getDownlinkBandIdentifier() + "-" + getDownlinkChannelNumber() + "/" + + getUplinkBandIdentifier() + "-" + getUplinkChannelNumber(); + } + else + { + return getDownlinkBandIdentifier() + "-" + getDownlinkChannelNumber() + "/-----"; + } + } + + @Override + public boolean equals(Object o) + { + if(this == o) + { + return true; + } + if(o == null || getClass() != o.getClass()) + { + return false; + } + P25Channel that = (P25Channel)o; + return mBandIdentifier == that.mBandIdentifier && + mChannelNumber == that.mChannelNumber; + } + + @Override + public int hashCode() + { + return Objects.hash(mBandIdentifier, mChannelNumber); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/P25ExplicitChannel.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/P25ExplicitChannel.java new file mode 100644 index 00000000..0b696e2f --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/P25ExplicitChannel.java @@ -0,0 +1,84 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier.channel; + +import io.github.dsheirer.module.decode.p25.message.IFrequencyBand; + +public class P25ExplicitChannel extends P25Channel implements Comparable +{ + private IFrequencyBand mUplinkFrequencyBand; + private int mUplinkBandIdentifier; + private int mUplinkChannelNumber; + + public P25ExplicitChannel(int downlinkBandIdentifier, int downlinkChannelNumber, + int uplinkBandIdentifier, int uplinkChannelNumber) + { + super(downlinkBandIdentifier, downlinkChannelNumber); + mUplinkBandIdentifier = uplinkBandIdentifier; + mUplinkChannelNumber = uplinkChannelNumber; + } + + public int getUplinkBandIdentifier() + { + return mUplinkBandIdentifier; + } + + public int getUplinkChannelNumber() + { + return mUplinkChannelNumber; + } + + @Override + public long getUplinkFrequency() + { + if(mUplinkFrequencyBand != null) + { + return mUplinkFrequencyBand.getUplinkFrequency(getUplinkChannelNumber()); + } + + return 0; + } + + @Override + public void setFrequencyBand(IFrequencyBand frequencyBand) + { + if(frequencyBand.getIdentifier() == getUplinkBandIdentifier()) + { + mUplinkFrequencyBand = frequencyBand; + } + + super.setFrequencyBand(frequencyBand); + } + + @Override + public int compareTo(P25Channel other) + { + //Ignores the uplink channel ... for ordering we'll simply order by the downlink channel + if(getDownlinkBandIdentifier() == other.getDownlinkBandIdentifier()) + { + return Integer.compare(getDownlinkChannelNumber(), other.getDownlinkChannelNumber()); + } + else + { + return Integer.compare(getDownlinkBandIdentifier(), other.getDownlinkBandIdentifier()); + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/encryption/APCO25EncryptionKey.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/encryption/APCO25EncryptionKey.java new file mode 100644 index 00000000..9c933a75 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/encryption/APCO25EncryptionKey.java @@ -0,0 +1,49 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier.encryption; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.encryption.EncryptionKey; +import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier; +import io.github.dsheirer.protocol.Protocol; + +public class APCO25EncryptionKey extends EncryptionKeyIdentifier +{ + public APCO25EncryptionKey(EncryptionKey encryptionKey) + { + super(encryptionKey, IdentifierClass.NETWORK, Form.ENCRYPTION_KEY, Role.BROADCAST); + } + + @Override + public Protocol getProtocol() + { + return Protocol.APCO25; + } + + /** + * Creates a new APCO-25 encryption algorithm + */ + public static APCO25EncryptionKey create(int alogorithm, int keyId) + { + return new APCO25EncryptionKey(new EncryptionKey(alogorithm, keyId)); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/ipv4/APCO25IpAddress.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/ipv4/APCO25IpAddress.java new file mode 100644 index 00000000..89d6195a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/ipv4/APCO25IpAddress.java @@ -0,0 +1,52 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier.ipv4; + +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.ipv4.IPV4Address; +import io.github.dsheirer.identifier.ipv4.IPV4Identifier; +import io.github.dsheirer.protocol.Protocol; + +/** + * APCO25 IPV4 Address + */ +public class APCO25IpAddress extends IPV4Identifier +{ + public APCO25IpAddress(IPV4Address ipAddress, Role role) + { + super(ipAddress, role); + } + + public Protocol getProtocol() + { + return Protocol.APCO25; + } + + public static APCO25IpAddress createTo(int value) + { + return new APCO25IpAddress(new IPV4Address(value), Role.TO); + } + + public static APCO25IpAddress createFrom(int value) + { + return new APCO25IpAddress(new IPV4Address(value), Role.FROM); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/message/APCO25ShortDataMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/message/APCO25ShortDataMessage.java new file mode 100644 index 00000000..24dd1428 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/message/APCO25ShortDataMessage.java @@ -0,0 +1,58 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier.message; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.integer.IntegerIdentifier; +import io.github.dsheirer.protocol.Protocol; + +/** + * APCO-25 short data message + */ +public class APCO25ShortDataMessage extends IntegerIdentifier +{ + /** + * Constructs an APCO-25 status + * + * @param shortDataMessage value + */ + public APCO25ShortDataMessage(int shortDataMessage) + { + super(shortDataMessage, IdentifierClass.USER, Form.SHORT_DATA_MESSAGE, Role.ANY); + } + + @Override + public Protocol getProtocol() + { + return Protocol.APCO25; + } + + /** + * Creates a short data message + * + * @param messageId + */ + public static APCO25ShortDataMessage create(int messageId) + { + return new APCO25ShortDataMessage(messageId); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/patch/APCO25PatchGroup.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/patch/APCO25PatchGroup.java new file mode 100644 index 00000000..95a1bdfe --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/patch/APCO25PatchGroup.java @@ -0,0 +1,46 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier.patch; + +import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.protocol.Protocol; + +/** + * APCO25 Patch Group + */ +public class APCO25PatchGroup extends PatchGroupIdentifier +{ + public APCO25PatchGroup(PatchGroup value) + { + super(value); + } + + @Override + public Protocol getProtocol() + { + return Protocol.APCO25; + } + + public static APCO25PatchGroup create(PatchGroup patchGroup) + { + return new APCO25PatchGroup(patchGroup); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/status/APCO25Status.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/status/APCO25Status.java new file mode 100644 index 00000000..22b5b4d2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/status/APCO25Status.java @@ -0,0 +1,69 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier.status; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.integer.IntegerIdentifier; +import io.github.dsheirer.protocol.Protocol; + +/** + * APCO-25 Unit or User status + */ +public class APCO25Status extends IntegerIdentifier +{ + private Role mRole; + + /** + * Constructs an APCO-25 status + * @param status value + * @param role of the user/unit that the status applies to + */ + public APCO25Status(int status, IdentifierClass identifierClass, Form form, Role role) + { + super(status, identifierClass, form, role); + mRole = role; + } + + @Override + public Protocol getProtocol() + { + return Protocol.APCO25; + } + + /** + * Creates a unit status + * @param status + * @return + */ + public static APCO25Status createUnitStatus(int status) + { + return new APCO25Status(status, IdentifierClass.USER, Form.UNIT_STATUS, Role.STATUS); + } + + /** + * Creates a User status + */ + public static APCO25Status createUserStatus(int status) + { + return new APCO25Status(status, IdentifierClass.USER, Form.USER_STATUS, Role.STATUS); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25AnnouncementTalkgroup.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25AnnouncementTalkgroup.java new file mode 100644 index 00000000..9dad5d83 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25AnnouncementTalkgroup.java @@ -0,0 +1,47 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier.talkgroup; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.Role; + +/** + * APCO25 Announcement Talkgroup - grouping of talkgroups within a system. + */ +public class APCO25AnnouncementTalkgroup extends APCO25Talkgroup +{ + /** + * Constructs an APCO25 Talkgroup Identifier with a FROM role. + * + * @param value of the talkgroup + */ + public APCO25AnnouncementTalkgroup(Integer value) + { + super(value, Role.BROADCAST, true); + } + + /** + * Creates an APCO-25 announcement group talkgroup identifier + */ + public static Identifier create(int talkgroup) + { + return new APCO25AnnouncementTalkgroup(talkgroup); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25AnyTalkgroup.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25AnyTalkgroup.java new file mode 100644 index 00000000..60768302 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25AnyTalkgroup.java @@ -0,0 +1,47 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier.talkgroup; + +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; + +/** + * APCO25 Talkgroup Identifier with an ANY role. + */ +public class APCO25AnyTalkgroup extends APCO25Talkgroup +{ + /** + * Constructs an APCO25 Talkgroup Identifier with an ANY role. + * + * @param value of the talkgroup + */ + public APCO25AnyTalkgroup(Integer value) + { + super(value, Role.ANY, true); + } + + /** + * Creates an ANY APCO-25 talkgroup identifier + */ + public static TalkgroupIdentifier create(int talkgroup) + { + return new APCO25AnyTalkgroup(talkgroup); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25FromTalkgroup.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25FromTalkgroup.java new file mode 100644 index 00000000..7de37e91 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25FromTalkgroup.java @@ -0,0 +1,50 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier.talkgroup; + +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; + +/** + * APCO25 Talkgroup Identifier with a FROM role. + */ +public class APCO25FromTalkgroup extends APCO25Talkgroup +{ + public APCO25FromTalkgroup(Integer value, boolean isGroup) + { + super(value, Role.FROM, isGroup); + } + + /** + * Creates an individual FROM APCO-25 talkgroup identifier + */ + public static TalkgroupIdentifier createIndividual(int talkgroup) + { + return new APCO25FromTalkgroup(talkgroup, false); + } + + /** + * Creates a FROM APCO-25 talkgroup identifier + */ + public static TalkgroupIdentifier createGroup(int talkgroup) + { + return new APCO25FromTalkgroup(talkgroup, true); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25IdentifierTalkgroup.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25IdentifierTalkgroup.java new file mode 100644 index 00000000..4b867db3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25IdentifierTalkgroup.java @@ -0,0 +1,50 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier.talkgroup; + +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; + +/** + * APCO25 Talkgroup Identifier with a FROM role. + */ +public class APCO25IdentifierTalkgroup extends APCO25Talkgroup +{ + public APCO25IdentifierTalkgroup(Integer value, boolean isGroup) + { + super(value, Role.IDENTIFIER, isGroup); + } + + /** + * Creates an individual FROM APCO-25 talkgroup identifier + */ + public static TalkgroupIdentifier createIndividual(int talkgroup) + { + return new APCO25IdentifierTalkgroup(talkgroup, false); + } + + /** + * Creates a FROM APCO-25 talkgroup identifier + */ + public static TalkgroupIdentifier createGroup(int talkgroup) + { + return new APCO25IdentifierTalkgroup(talkgroup, true); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25Talkgroup.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25Talkgroup.java new file mode 100644 index 00000000..4972d59c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25Talkgroup.java @@ -0,0 +1,39 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier.talkgroup; + +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.protocol.Protocol; + +public class APCO25Talkgroup extends TalkgroupIdentifier +{ + public APCO25Talkgroup(Integer value, Role role, boolean isGroup) + { + super(value, role, isGroup); + } + + @Override + public Protocol getProtocol() + { + return Protocol.APCO25; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25ToTalkgroup.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25ToTalkgroup.java new file mode 100644 index 00000000..93065781 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25ToTalkgroup.java @@ -0,0 +1,50 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier.talkgroup; + +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; + +/** + * APCO25 Talkgroup Identifier with a FROM role. + */ +public class APCO25ToTalkgroup extends APCO25Talkgroup +{ + public APCO25ToTalkgroup(Integer value, boolean isGroup) + { + super(value, Role.TO, isGroup); + } + + /** + * Creates a TO APCO-25 group talkgroup identifier + */ + public static TalkgroupIdentifier createGroup(int talkgroup) + { + return new APCO25ToTalkgroup(talkgroup, true); + } + + /** + * Creates a TO APCO-25 individual talkgroup identifier + */ + public static TalkgroupIdentifier createIndividual(int talkgroup) + { + return new APCO25ToTalkgroup(talkgroup, false); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/telephone/APCO25TelephoneNumber.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/telephone/APCO25TelephoneNumber.java new file mode 100644 index 00000000..1350da92 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/telephone/APCO25TelephoneNumber.java @@ -0,0 +1,65 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.identifier.telephone; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.string.StringIdentifier; +import io.github.dsheirer.protocol.Protocol; + +public class APCO25TelephoneNumber extends StringIdentifier +{ + public APCO25TelephoneNumber(String telephoneNumber, Role role) + { + super(telephoneNumber, IdentifierClass.USER, Form.TELEPHONE_NUMBER, role); + } + + @Override + public Protocol getProtocol() + { + return Protocol.APCO25; + } + + /** + * Creates a new APCO-25 telephone number + */ + public static Identifier createFrom(String telephoneNumber) + { + return new APCO25TelephoneNumber(telephoneNumber, Role.FROM); + } + + /** + * Creates a new APCO-25 telephone number + */ + public static Identifier createTo(String telephoneNumber) + { + return new APCO25TelephoneNumber(telephoneNumber, Role.TO); + } + + /** + * Creates a new APCO-25 telephone number + */ + public static Identifier createAny(String telephoneNumber) + { + return new APCO25TelephoneNumber(telephoneNumber, Role.ANY); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/IAdjacentSite.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/IAdjacentSite.java index afd06a5e..7d068e85 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/IAdjacentSite.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/IAdjacentSite.java @@ -1,20 +1,24 @@ package io.github.dsheirer.module.decode.p25.message; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; + /** * Interface for adjacent site (ie neighbor) messages */ public interface IAdjacentSite { - public String getUniqueID(); - public String getRFSS(); - public String getSystemID(); - public String getSiteID(); - public String getLRA(); - public String getSystemServiceClass(); - - public String getDownlinkChannel(); - public long getDownlinkFrequency(); - - public String getUplinkChannel(); - public long getUplinkFrequency(); + String getUniqueID(); + + Identifier getRFSSId(); + + Identifier getSystemID(); + + Identifier getSiteID(); + + Identifier getLRAId(); + + String getSystemServiceClass(); + + IChannelDescriptor getChannel(); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/IBandIdentifier.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/IBandIdentifier.java deleted file mode 100644 index 3471efba..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/IBandIdentifier.java +++ /dev/null @@ -1,64 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message; - -public interface IBandIdentifier -{ - - /** - * (Band) Identifier - */ - public abstract int getIdentifier(); - - /** - * Channel spacing in hertz - */ - public abstract long getChannelSpacing(); - - /** - * Base frequency in hertz - */ - public abstract long getBaseFrequency(); - - - /** - * Channel bandwidth in hertz - */ - public abstract int getBandwidth(); - - /** - * Transmit offset in hertz - */ - public abstract long getTransmitOffset(); - - /** - * Downlink (tower to mobile) Frequency for the specified channel - */ - public abstract long getDownlinkFrequency(int channelNumber); - - /** - * Uplink (mobile to tower) Frequency for the specified channel - */ - public abstract long getUplinkFrequency(int channelNumber); - - /** - * Indicates if this band is an FDMA (false) or TDMA (true) band - */ - public boolean isTDMA(); -} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/IFrequencyBand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/IFrequencyBand.java new file mode 100644 index 00000000..6ebd3fbe --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/IFrequencyBand.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message; + +public interface IFrequencyBand +{ + /** + * (Band) Identifier + */ + public abstract int getIdentifier(); + + /** + * Channel spacing in hertz + */ + public abstract long getChannelSpacing(); + + /** + * Base frequency in hertz + */ + public abstract long getBaseFrequency(); + + + /** + * Channel bandwidth in hertz + */ + public abstract int getBandwidth(); + + /** + * Transmit offset in hertz + */ + public abstract long getTransmitOffset(); + + /** + * Downlink (tower to mobile) Frequency for the specified channel + */ + public abstract long getDownlinkFrequency(int channelNumber); + + /** + * Uplink (mobile to tower) Frequency for the specified channel + */ + public abstract long getUplinkFrequency(int channelNumber); + + /** + * Indicates if this band is an FDMA (false) or TDMA (true) band + */ + public boolean isTDMA(); + + /** + * Number of timeslots available on this channel + */ + public int getTimeslotCount(); +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/IFrequencyBandReceiver.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/IFrequencyBandReceiver.java new file mode 100644 index 00000000..1c5c2441 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/IFrequencyBandReceiver.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message; + + +import io.github.dsheirer.channel.IChannelDescriptor; + +import java.util.List; + +/** + * Interface to allow messages to be augmented with IdentiferUpdateXXX type + * messages that provide the channel information necessary to calculate the + * uplink and downlink frequency for the channel. + */ +public interface IFrequencyBandReceiver +{ + /** + * List of APCO-25 channels provided by the message. Channels may be enriched with frequency band + * details by an external provider. + * + * @return list of channels + */ + List getChannels(); +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/IdentifierReceiver.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/IdentifierReceiver.java deleted file mode 100644 index af1c6313..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/IdentifierReceiver.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message; - - -/** - * Interface to allow messages to be augmented with IdentiferUpdateXXX type - * messages that provide the channel information necessary to calculate the - * uplink and downlink frequency for the channel. - */ -public interface IdentifierReceiver -{ - public void setIdentifierMessage( int identifier, IBandIdentifier message ); - - public int[] getIdentifiers(); -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/P25Message.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/P25Message.java index 594c510b..b00bbf5d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/P25Message.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/P25Message.java @@ -18,81 +18,97 @@ ******************************************************************************/ package io.github.dsheirer.module.decode.p25.message; -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRC; -import io.github.dsheirer.map.Plottable; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.message.Message; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Nac; import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.protocol.Protocol; -import java.text.SimpleDateFormat; - -public class P25Message extends Message +public abstract class P25Message extends Message { - public enum DuplexMode {HALF, FULL}; - public enum SessionMode {PACKET, CIRCUIT}; - - public static final int[] NAC = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; - public static final int[] DUID = {12, 13, 14, 15}; - public static final int[] BCH = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, - 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - - protected SimpleDateFormat mTimeDurationFormat = new SimpleDateFormat("HH:mm:ss.SSS"); - - protected BinaryMessage mMessage; - protected DataUnitID mDUID; - protected AliasList mAliasList; - - protected CRC[] mCRC; - - public P25Message(BinaryMessage message, DataUnitID duid, AliasList aliasList) + public enum DuplexMode { - super(); + HALF, FULL + } + public enum SessionMode + { + PACKET, CIRCUIT + } + + private CorrectedBinaryMessage mMessage; + private boolean mValid = true; + private Identifier mNAC; + + /** + * Constructs a P25 message. + * + * @param message containing the binary message and optional corrected bit count + * @param nac Network Access Code (NAC) for the message + * @param timestamp when the message was transmitted + */ + public P25Message(CorrectedBinaryMessage message, int nac, long timestamp) + { + super(timestamp); mMessage = message; - mDUID = duid; - mAliasList = aliasList; + mNAC = APCO25Nac.create(nac); } - public CRC[] getCRCResults() + /** + * Constructs a P25 message using current system time for when the message was transmitted. + * + * @param message containing the binary message and optional corrected bit count + * @param nac Network Access Code (NAC) for the message + */ + public P25Message(CorrectedBinaryMessage message, int nac) { - return mCRC; + this(message, nac, System.currentTimeMillis()); } - public BinaryMessage getSourceMessage() + /** + * Constructs a P25 message where the binary message will be created/updated after construction. + * @param nac Network Access Code (NAC) for the message + * @param timestamp when the message was transmitted + */ + protected P25Message(int nac, long timestamp) + { + super(timestamp); + mNAC = APCO25Nac.create(nac); + } + + /** + * Establishes the message binary sequence. + */ + protected void setMessage(CorrectedBinaryMessage message) + { + mMessage = message; + } + + /** + * The transmitted binary message + */ + protected CorrectedBinaryMessage getMessage() { return mMessage; } - public AliasList getAliasList() + public Identifier getNAC() { - return mAliasList; + return mNAC; } - public String getNAC() + /** + * Data Unit ID indicates the type of P25 message + */ + public abstract DataUnitID getDUID(); + + public String toString() { - return mMessage.getHex(NAC, 3); + return getMessageStub(); } - public DataUnitID getDUID() - { - return mDUID; - } - - @Override - public String getErrorStatus() - { - if(mCRC == null) - { - return "UNKNOWN"; - } - - return CRC.format(mCRC); - } - - @Override - public String getMessage() + protected String getMessageStub() { StringBuilder sb = new StringBuilder(); @@ -100,89 +116,30 @@ public class P25Message extends Message sb.append(getNAC()); sb.append(" "); sb.append(getDUID().getLabel()); - sb.append(" DATA UNIT ID:" + mMessage.getInt(DUID)); return sb.toString(); } @Override - public String getBinaryMessage() + public Protocol getProtocol() { - return mMessage.toString(); - } - - @Override - public String getProtocol() - { - return "P25 Phase 1"; - } - - @Override - public String getEventType() - { - return mDUID.name(); - } - - @Override - public String getFromID() - { - return null; - } - - @Override - public Alias getFromIDAlias() - { - if(mAliasList != null) - { - return mAliasList.getTalkgroupAlias(getFromID()); - } - - return null; - } - - @Override - public String getToID() - { - return null; - } - - @Override - public Alias getToIDAlias() - { - if(mAliasList != null) - { - return mAliasList.getTalkgroupAlias(getToID()); - } - - return null; - } - - @Override - public Plottable getPlottable() - { - return null; - } - - @Override - public String toString() - { - return getMessage(); + return Protocol.APCO25; } + /** + * Indicates if this message is valid and has passed all error detection and correction routines. + */ @Override public boolean isValid() { - if(mCRC != null) - { - for(CRC crc : mCRC) - { - if(crc == CRC.FAILED_CRC) - { - return false; - } - } - } + return mValid; + } - return true; + /** + * Sets this message to the valid/invalid state. The default state is valid (true). + */ + protected void setValid(boolean valid) + { + mValid = valid; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/P25MessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/P25MessageFactory.java new file mode 100644 index 00000000..cb213945 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/P25MessageFactory.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.module.decode.p25.message.hdu.HDUMessage; +import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; +import io.github.dsheirer.module.decode.p25.message.ldu.LDU2Message; +import io.github.dsheirer.module.decode.p25.message.tdu.TDULinkControlMessage; +import io.github.dsheirer.module.decode.p25.message.tdu.TDUMessage; +import io.github.dsheirer.module.decode.p25.message.vselp.VSELP1Message; +import io.github.dsheirer.module.decode.p25.message.vselp.VSELP2Message; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Factory for creating P25 message parser instances. + */ +public class P25MessageFactory +{ + private final static Logger mLog = LoggerFactory.getLogger(P25MessageFactory.class); + + /** + * Creates a P25 message for known message types. + * + * NOTE: TSBK and PDU messages are not processed by this factory class. + * + * @param dataUnitID that identifies the message type + * @param nac decoded from the network identifier (NID) + * @param timestamp of the message + * @param message containing message bits and bit error count results from nid error detection and + * correction + * @return constructed message parser + */ + public static P25Message create(DataUnitID dataUnitID, int nac, long timestamp, CorrectedBinaryMessage message) + { + switch(dataUnitID) + { + case HEADER_DATA_UNIT: + return new HDUMessage(message, nac, timestamp); + case LOGICAL_LINK_DATA_UNIT_1: + return new LDU1Message(message, nac, timestamp); + case LOGICAL_LINK_DATA_UNIT_2: + return new LDU2Message(message, nac, timestamp); + case PACKET_HEADER_DATA_UNIT: + mLog.warn("WARNING: PDU messages must be created by the PDUMessageFactory"); + return null; + case TERMINATOR_DATA_UNIT: + return new TDUMessage(message, nac, timestamp); + case TERMINATOR_DATA_UNIT_LINK_CONTROL: + return new TDULinkControlMessage(message, nac, timestamp); + case TRUNKING_SIGNALING_BLOCK_1: + case TRUNKING_SIGNALING_BLOCK_2: + case TRUNKING_SIGNALING_BLOCK_3: + mLog.warn("WARNING: TSBK messages must be created by the TSBKMessageFactory"); + return null; + case VSELP1: + return new VSELP1Message(message, nac, timestamp); + case VSELP2: + return new VSELP2Message(message, nac, timestamp); + default: + return new UnknownP25Message(message, nac, timestamp, dataUnitID); + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/UnknownP25Message.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/UnknownP25Message.java new file mode 100644 index 00000000..3b6ac01d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/UnknownP25Message.java @@ -0,0 +1,63 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.Collections; +import java.util.List; + +/** + * P25 Message with unknown or unrecognized data unit id + */ +public class UnknownP25Message extends P25Message +{ + private DataUnitID mDataUnitID; + + public UnknownP25Message(CorrectedBinaryMessage message, int nac, long timestamp, DataUnitID dataUnitID) + { + super(message, nac); + mDataUnitID = dataUnitID; + } + + @Override + public DataUnitID getDUID() + { + return mDataUnitID; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/AMBTCMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/AMBTCMessageFilter.java new file mode 100644 index 00000000..233f34e5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/AMBTCMessageFilter.java @@ -0,0 +1,55 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.filter; + +import io.github.dsheirer.filter.Filter; +import io.github.dsheirer.filter.FilterElement; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.Collections; +import java.util.List; + +public class AMBTCMessageFilter extends Filter +{ + public AMBTCMessageFilter() + { + super("Alternate Multi-Block Trunking Control"); + } + + @Override + public boolean passes(IMessage message) + { + return mEnabled && canProcess(message); + } + + @Override + public boolean canProcess(IMessage message) + { + return message instanceof AMBTCMessage; + } + + @Override + public List> getFilterElements() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/HDUMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/HDUMessageFilter.java index 0ea7fd80..5e7d88d3 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/HDUMessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/HDUMessageFilter.java @@ -2,34 +2,34 @@ package io.github.dsheirer.module.decode.p25.message.filter; import io.github.dsheirer.filter.Filter; import io.github.dsheirer.filter.FilterElement; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.p25.message.hdu.HDUMessage; import java.util.Collections; import java.util.List; -public class HDUMessageFilter extends Filter +public class HDUMessageFilter extends Filter { - public HDUMessageFilter() - { - super( "Header Data Unit" ); - } - - @Override - public boolean passes( Message message ) + public HDUMessageFilter() { - return mEnabled && canProcess( message ); + super("Header Data Unit"); } - @Override - public boolean canProcess( Message message ) + @Override + public boolean passes(IMessage message) { - return message instanceof HDUMessage; + return mEnabled && canProcess(message); } - @Override + @Override + public boolean canProcess(IMessage message) + { + return message instanceof HDUMessage; + } + + @Override public List> getFilterElements() { - return Collections.EMPTY_LIST; + return Collections.EMPTY_LIST; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/IPPacketMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/IPPacketMessageFilter.java new file mode 100644 index 00000000..17bf3ef1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/IPPacketMessageFilter.java @@ -0,0 +1,55 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.filter; + +import io.github.dsheirer.filter.Filter; +import io.github.dsheirer.filter.FilterElement; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.packet.PacketMessage; + +import java.util.Collections; +import java.util.List; + +public class IPPacketMessageFilter extends Filter +{ + public IPPacketMessageFilter() + { + super("Packet Message"); + } + + @Override + public boolean passes(IMessage message) + { + return mEnabled && canProcess(message); + } + + @Override + public boolean canProcess(IMessage message) + { + return message instanceof PacketMessage; + } + + @Override + public List> getFilterElements() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/LDUMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/LDUMessageFilter.java index b94a1232..febf1a91 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/LDUMessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/LDUMessageFilter.java @@ -2,34 +2,34 @@ package io.github.dsheirer.module.decode.p25.message.filter; import io.github.dsheirer.filter.Filter; import io.github.dsheirer.filter.FilterElement; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.p25.message.ldu.LDUMessage; import java.util.Collections; import java.util.List; -public class LDUMessageFilter extends Filter +public class LDUMessageFilter extends Filter { - public LDUMessageFilter() - { - super( "Link Data Unit" ); - } - - @Override - public boolean passes( Message message ) + public LDUMessageFilter() { - return mEnabled && canProcess( message ); + super("Link Data Unit"); } - @Override - public boolean canProcess( Message message ) + @Override + public boolean passes(IMessage message) { - return message instanceof LDUMessage; + return mEnabled && canProcess(message); } - @Override + @Override + public boolean canProcess(IMessage message) + { + return message instanceof LDUMessage; + } + + @Override public List> getFilterElements() { - return Collections.EMPTY_LIST; + return Collections.EMPTY_LIST; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/P25MessageFilterSet.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/P25MessageFilterSet.java index 83c50038..8f6993bb 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/P25MessageFilterSet.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/P25MessageFilterSet.java @@ -1,26 +1,32 @@ package io.github.dsheirer.module.decode.p25.message.filter; import io.github.dsheirer.filter.FilterSet; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.message.SyncLossMessage; import io.github.dsheirer.module.decode.p25.message.P25Message; -public class P25MessageFilterSet extends FilterSet +public class P25MessageFilterSet extends FilterSet { - public P25MessageFilterSet() - { - super( "P25 Message Filter" ); - - addFilter( new HDUMessageFilter() ); - addFilter( new LDUMessageFilter() ); - addFilter( new PDUMessageFilter() ); - addFilter( new TDUMessageFilter() ); - addFilter( new TDULCMessageFilter() ); - addFilter( new TSBKMessageFilterSet() ); - } - - @Override - public boolean canProcess( Message message ) + public P25MessageFilterSet() { - return message instanceof P25Message; + super("P25 Message Filter"); + + addFilter(new AMBTCMessageFilter()); + addFilter(new HDUMessageFilter()); + addFilter(new IPPacketMessageFilter()); + addFilter(new LDUMessageFilter()); + addFilter(new PDUMessageFilter()); + addFilter(new SNDCPMessageFilter()); + addFilter(new SyncLossMessageFilter()); + addFilter(new TDUMessageFilter()); + addFilter(new TDULCMessageFilter()); + addFilter(new TSBKMessageFilterSet()); + addFilter(new UMBTCMessageFilter()); + } + + @Override + public boolean canProcess(IMessage message) + { + return message instanceof P25Message || message instanceof SyncLossMessage; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/PDUMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/PDUMessageFilter.java index e5a6ad76..7ea842c0 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/PDUMessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/PDUMessageFilter.java @@ -2,53 +2,53 @@ package io.github.dsheirer.module.decode.p25.message.filter; import io.github.dsheirer.filter.Filter; import io.github.dsheirer.filter.FilterElement; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.reference.Opcode; +import io.github.dsheirer.module.decode.p25.message.tsbk.Opcode; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; -public class PDUMessageFilter extends Filter +public class PDUMessageFilter extends Filter { - private HashMap> mOpcodeFilterElements = - new HashMap>(); - - public PDUMessageFilter() - { - super( "PDU - Packet Data Unit" ); - - for( Opcode opcode: Opcode.values() ) - { - mOpcodeFilterElements.put( opcode, new FilterElement( opcode ) ); - } - } - - @Override - public boolean passes( Message message ) - { - if( mEnabled && message instanceof PDUMessage) - { - PDUMessage pdu = (PDUMessage)message; - - Opcode opcode = pdu.getOpcode(); - - return mOpcodeFilterElements.get( opcode ).isEnabled(); - } + private Map> mOpcodeFilterElements = new HashMap>(); - return false; + public PDUMessageFilter() + { + super("PDU - Packet Data Unit"); + + for(Opcode opcode : Opcode.values()) + { + mOpcodeFilterElements.put(opcode, new FilterElement(opcode)); + } } - @Override - public boolean canProcess( Message message ) + @Override + public boolean passes(IMessage message) { - return message instanceof PDUMessage; + if(mEnabled && message instanceof PDUMessage) + { + PDUMessage pdu = (PDUMessage) message; + + Opcode opcode = pdu.getOpcode(); + + return mOpcodeFilterElements.get(opcode).isEnabled(); + } + + return false; } - @Override + @Override + public boolean canProcess(IMessage message) + { + return message instanceof PDUMessage; + } + + @Override public List> getFilterElements() { - return new ArrayList>( mOpcodeFilterElements.values() ); + return new ArrayList>(mOpcodeFilterElements.values()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/SNDCPMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/SNDCPMessageFilter.java new file mode 100644 index 00000000..54e7e621 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/SNDCPMessageFilter.java @@ -0,0 +1,55 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.filter; + +import io.github.dsheirer.filter.Filter; +import io.github.dsheirer.filter.FilterElement; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.packet.sndcp.SNDCPMessage; + +import java.util.Collections; +import java.util.List; + +public class SNDCPMessageFilter extends Filter +{ + public SNDCPMessageFilter() + { + super("Sub-Network Dependent Convergence Protocol"); + } + + @Override + public boolean passes(IMessage message) + { + return mEnabled && canProcess(message); + } + + @Override + public boolean canProcess(IMessage message) + { + return message instanceof SNDCPMessage; + } + + @Override + public List> getFilterElements() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/SyncLossMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/SyncLossMessageFilter.java new file mode 100644 index 00000000..d32c0aac --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/SyncLossMessageFilter.java @@ -0,0 +1,55 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.filter; + +import io.github.dsheirer.filter.Filter; +import io.github.dsheirer.filter.FilterElement; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.message.SyncLossMessage; + +import java.util.Collections; +import java.util.List; + +public class SyncLossMessageFilter extends Filter +{ + public SyncLossMessageFilter() + { + super("Sync Loss"); + } + + @Override + public boolean passes(IMessage message) + { + return mEnabled && canProcess(message); + } + + @Override + public boolean canProcess(IMessage message) + { + return message instanceof SyncLossMessage; + } + + @Override + public List> getFilterElements() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/TDULCMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/TDULCMessageFilter.java index b123fabf..7266535c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/TDULCMessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/TDULCMessageFilter.java @@ -2,34 +2,34 @@ package io.github.dsheirer.module.decode.p25.message.filter; import io.github.dsheirer.filter.Filter; import io.github.dsheirer.filter.FilterElement; -import io.github.dsheirer.message.Message; -import io.github.dsheirer.module.decode.p25.message.tdu.lc.TDULinkControlMessage; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.module.decode.p25.message.tdu.TDULinkControlMessage; import java.util.Collections; import java.util.List; -public class TDULCMessageFilter extends Filter +public class TDULCMessageFilter extends Filter { - public TDULCMessageFilter() - { - super( "TDU Terminator Data Unit with Link Control" ); - } - - @Override - public boolean passes( Message message ) + public TDULCMessageFilter() { - return mEnabled && canProcess( message ); + super("TDU Terminator Data Unit with Link Control"); } - @Override - public boolean canProcess( Message message ) + @Override + public boolean passes(IMessage message) { - return message instanceof TDULinkControlMessage; + return mEnabled && canProcess(message); } - @Override + @Override + public boolean canProcess(IMessage message) + { + return message instanceof TDULinkControlMessage; + } + + @Override public List> getFilterElements() { - return Collections.EMPTY_LIST; + return Collections.EMPTY_LIST; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/TDUMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/TDUMessageFilter.java index ef50e572..97dfa4c0 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/TDUMessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/TDUMessageFilter.java @@ -2,34 +2,34 @@ package io.github.dsheirer.module.decode.p25.message.filter; import io.github.dsheirer.filter.Filter; import io.github.dsheirer.filter.FilterElement; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.p25.message.tdu.TDUMessage; import java.util.Collections; import java.util.List; -public class TDUMessageFilter extends Filter +public class TDUMessageFilter extends Filter { - public TDUMessageFilter() - { - super( "TDU Terminator Data Unit" ); - } - - @Override - public boolean passes( Message message ) + public TDUMessageFilter() { - return mEnabled && canProcess( message ); + super("TDU Terminator Data Unit"); } - @Override - public boolean canProcess( Message message ) + @Override + public boolean passes(IMessage message) { - return message instanceof TDUMessage; + return mEnabled && canProcess(message); } - @Override + @Override + public boolean canProcess(IMessage message) + { + return message instanceof TDUMessage; + } + + @Override public List> getFilterElements() { - return Collections.EMPTY_LIST; + return Collections.EMPTY_LIST; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/TSBKMessageFilterSet.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/TSBKMessageFilterSet.java index 34e3f0e4..e96abe9e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/TSBKMessageFilterSet.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/TSBKMessageFilterSet.java @@ -3,28 +3,24 @@ package io.github.dsheirer.module.decode.p25.message.filter; import io.github.dsheirer.filter.Filter; import io.github.dsheirer.filter.FilterElement; import io.github.dsheirer.filter.FilterSet; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.module.decode.p25.message.tsbk.Opcode; import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.MotorolaOpcode; -import io.github.dsheirer.module.decode.p25.message.tsbk.vendor.VendorOpcode; -import io.github.dsheirer.module.decode.p25.reference.Opcode; import io.github.dsheirer.module.decode.p25.reference.Vendor; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -public class TSBKMessageFilterSet extends FilterSet +public class TSBKMessageFilterSet extends FilterSet { - public TSBKMessageFilterSet() - { - super( "TSBK Trunking Signalling Block" ); - - addFilter( new StandardOpcodeFilter() ); - addFilter( new MotorolaOpcodeFilter() ); - addFilter( new VendorOpcodeFilter() ); - } - + public TSBKMessageFilterSet() + { + super("TSBK Trunking Signalling Block"); + + addFilter(new StandardOpcodeFilter()); + } + // @Override // public boolean passes( Message message ) // { @@ -45,156 +41,56 @@ public class TSBKMessageFilterSet extends FilterSet // return false; // } - @Override - public boolean canProcess( Message message ) + @Override + public boolean canProcess(IMessage message) { - return message instanceof TSBKMessage; + return message instanceof TSBKMessage; } - public class StandardOpcodeFilter extends Filter - { - private HashMap> mStandardElements = - new HashMap>(); - - public StandardOpcodeFilter() - { - super( "Standard Opcode Filter" ); - - for( Opcode opcode: Opcode.values() ) - { - if( opcode != Opcode.UNKNOWN ) - { - mStandardElements.put( opcode, - new FilterElement( opcode ) ); - } - } - } - - @Override - public boolean passes( Message message ) - { - if( mEnabled && canProcess( message ) ) - { - Opcode opcode = ((TSBKMessage)message).getOpcode(); + public class StandardOpcodeFilter extends Filter + { + private HashMap> mStandardElements = + new HashMap>(); - if( mStandardElements.containsKey( opcode ) ) - { - return mStandardElements.get( opcode ).isEnabled(); - } - } - - return false; + public StandardOpcodeFilter() + { + super("Standard Opcode Filter"); + + for(Opcode opcode : Opcode.values()) + { + if(opcode != Opcode.OSP_UNKNOWN) + { + mStandardElements.put(opcode, new FilterElement(opcode)); + } + } } - @Override - public boolean canProcess( Message message ) + @Override + public boolean passes(IMessage message) { - - return ((TSBKMessage)message).getVendor() == Vendor.STANDARD; + if(mEnabled && canProcess(message)) + { + Opcode opcode = ((TSBKMessage) message).getOpcode(); + + if(mStandardElements.containsKey(opcode)) + { + return mStandardElements.get(opcode).isEnabled(); + } + } + + return false; } - @Override + @Override + public boolean canProcess(IMessage message) + { + return ((TSBKMessage) message).getVendor() == Vendor.STANDARD; + } + + @Override public List> getFilterElements() { - return new ArrayList>( mStandardElements.values() ); + return new ArrayList>(mStandardElements.values()); } - } - - public class MotorolaOpcodeFilter extends Filter - { - private HashMap> mMotorolaElements = - new HashMap>(); - - public MotorolaOpcodeFilter() - { - super( "Motorola Opcode Filter" ); - - for( MotorolaOpcode opcode: MotorolaOpcode.values() ) - { - if( opcode != MotorolaOpcode.UNKNOWN ) - { - mMotorolaElements.put( opcode, - new FilterElement( opcode ) ); - } - } - } - - @Override - public boolean passes( Message message ) - { - if( mEnabled && canProcess( message ) ) - { - MotorolaOpcode opcode = ((TSBKMessage)message).getMotorolaOpcode(); - - if( mMotorolaElements.containsKey( opcode ) ) - { - return mMotorolaElements.get( opcode ).isEnabled(); - } - } - - return false; - } - - @Override - public boolean canProcess( Message message ) - { - return ((TSBKMessage)message).getVendor() == Vendor.MOTOROLA; - } - - @Override - public List> getFilterElements() - { - return new ArrayList>( mMotorolaElements.values() ); - } - } - - public class VendorOpcodeFilter extends Filter - { - private HashMap> mVendorElements = - new HashMap>(); - - public VendorOpcodeFilter() - { - super( "Vendor Opcode Filter" ); - - for( VendorOpcode opcode: VendorOpcode.values() ) - { - if( opcode != VendorOpcode.UNKNOWN ) - { - mVendorElements.put( opcode, - new FilterElement( opcode ) ); - } - } - } - - @Override - public boolean passes( Message message ) - { - if( mEnabled && canProcess( message ) ) - { - MotorolaOpcode opcode = ((TSBKMessage)message).getMotorolaOpcode(); - - if( mVendorElements.containsKey( opcode ) ) - { - return mVendorElements.get( opcode ).isEnabled(); - } - } - - return false; - } - - @Override - public boolean canProcess( Message message ) - { - Vendor vendor = ((TSBKMessage)message).getVendor(); - - return vendor != Vendor.STANDARD && vendor != Vendor.MOTOROLA; - } - - @Override - public List> getFilterElements() - { - return new ArrayList>( mVendorElements.values() ); - } - } + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/UMBTCMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/UMBTCMessageFilter.java new file mode 100644 index 00000000..a8c923ea --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/filter/UMBTCMessageFilter.java @@ -0,0 +1,55 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.filter; + +import io.github.dsheirer.filter.Filter; +import io.github.dsheirer.filter.FilterElement; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.umbtc.UMBTCMessage; + +import java.util.Collections; +import java.util.List; + +public class UMBTCMessageFilter extends Filter +{ + public UMBTCMessageFilter() + { + super("Unconfirmed Multi-Block Trunking Control"); + } + + @Override + public boolean passes(IMessage message) + { + return mEnabled && canProcess(message); + } + + @Override + public boolean canProcess(IMessage message) + { + return message instanceof UMBTCMessage; + } + + @Override + public List> getFilterElements() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/hdu/HDUMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/hdu/HDUMessage.java index a72894f9..016fa344 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/hdu/HDUMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/hdu/HDUMessage.java @@ -1,271 +1,214 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.hdu; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRC; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.edac.Golay18; import io.github.dsheirer.edac.ReedSolomon_63_47_17; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.message.P25Message; import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Encryption; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; + public class HDUMessage extends P25Message { - private final static Logger mLog = LoggerFactory.getLogger( HDUMessage.class ); + private final static Logger mLog = LoggerFactory.getLogger(HDUMessage.class); - public static final int[] MESSAGE_INDICATOR_A = { 64,65,66,67,68,69,82,83, - 84,85,86,87,100,101,102,103,104,105,118,119,120,121,122,123,136,137,138, - 139,140,141,154,155,156,157,158,159 }; - public static final int[] MESSAGE_INDICATOR_B = { 172,173,174,175,176,177, - 190,191,192,193,194,195,208,209,210,211,212,213,226,227,228,229,230,231, - 244,245,246,247,248,249,262,263,264,265,266,267 }; - public static final int[] VENDOR_ID = { 280,281,282,283,284,285,298,299 }; - public static final int[] ALGORITHM_ID = { 300,301,302,303,316,317,318,319 }; - public static final int[] KEY_ID = { 320,321,334,335,336,337,338,339,352, - 353,354,355,356,357,370,371 }; - public static final int[] TALKGROUP_ID = { 372,373,374,375,388,389,390,391, - 392,393,406,407,408,409,410,411 }; + private static final int[] GOLAY_WORD_STARTS = {0, 18, 36, 54, 72, 90, 108, 126, 144, 162, 180, 198, 216, 234, 252, + 270, 288, 306, 324, 342, 360, 278, 396, 414, 432, 450, 468, 486, 504, 522, 540, 558, 576, 594, 612, 630}; - public static final int[] GOLAY_WORD_STARTS = { 64,82,100,118,136,154,172, - 190,208,226,244,262,280,298,316,334,352,370,388,406,424,442,460,478, - 496,514,532,550,568,586,604,622,640,658,676,694 }; + private static final int[] CW_HEX_0 = {0, 1, 2, 3, 4, 5, 6}; + private static final int[] CW_HEX_1 = {18, 19, 20, 21, 22, 23}; + private static final int[] CW_HEX_2 = {36, 37, 38, 39, 40, 41}; + private static final int[] CW_HEX_3 = {54, 55, 56, 57, 58, 59}; + private static final int[] CW_HEX_4 = {72, 73, 74, 75, 76, 77}; + private static final int[] CW_HEX_5 = {90, 91, 92, 93, 94, 95}; + private static final int[] CW_HEX_6 = {108, 109, 110, 111, 112, 113}; + private static final int[] CW_HEX_7 = {126, 127, 128, 129, 130, 131}; + private static final int[] CW_HEX_8 = {144, 145, 146, 147, 148, 149}; + private static final int[] CW_HEX_9 = {162, 163, 164, 165, 166, 167}; + private static final int[] CW_HEX_10 = {180, 181, 182, 183, 184, 185}; + private static final int[] CW_HEX_11 = {198, 199, 200, 201, 202, 203}; + private static final int[] CW_HEX_12 = {216, 217, 218, 219, 220, 221}; + private static final int[] CW_HEX_13 = {234, 235, 236, 237, 238, 239}; + private static final int[] CW_HEX_14 = {252, 253, 254, 255, 256, 257}; + private static final int[] CW_HEX_15 = {270, 271, 272, 273, 274, 275}; + private static final int[] CW_HEX_16 = {288, 289, 290, 291, 292, 293}; + private static final int[] CW_HEX_17 = {306, 307, 308, 309, 310, 311}; + private static final int[] CW_HEX_18 = {324, 325, 326, 327, 328, 329}; + private static final int[] CW_HEX_19 = {342, 343, 344, 345, 346, 347}; + private static final int[] RS_HEX_0 = {360, 361, 362, 363, 364, 365}; + private static final int[] RS_HEX_1 = {378, 379, 380, 381, 382, 383}; + private static final int[] RS_HEX_2 = {396, 397, 398, 399, 400, 401}; + private static final int[] RS_HEX_3 = {414, 415, 416, 417, 418, 419}; + private static final int[] RS_HEX_4 = {432, 433, 434, 435, 436, 437}; + private static final int[] RS_HEX_5 = {450, 451, 452, 453, 454, 455}; + private static final int[] RS_HEX_6 = {468, 469, 470, 471, 472, 473}; + private static final int[] RS_HEX_7 = {486, 487, 488, 489, 490, 491}; + private static final int[] RS_HEX_8 = {504, 505, 506, 507, 508, 509}; + private static final int[] RS_HEX_9 = {522, 523, 524, 525, 526, 527}; + private static final int[] RS_HEX_10 = {540, 541, 542, 543, 544, 545}; + private static final int[] RS_HEX_11 = {558, 559, 560, 561, 562, 563}; + private static final int[] RS_HEX_12 = {576, 577, 578, 579, 580, 581}; + private static final int[] RS_HEX_13 = {594, 595, 596, 597, 598, 599}; + private static final int[] RS_HEX_14 = {612, 613, 614, 615, 616, 617}; + private static final int[] RS_HEX_15 = {630, 631, 632, 633, 634, 635}; - public static final int[] CW_HEX_0 = { 64,65,66,67,68,69 }; - public static final int[] CW_HEX_1 = { 82,83,84,85,86,87 }; - public static final int[] CW_HEX_2 = { 100,101,102,103,104,105 }; - public static final int[] CW_HEX_3 = { 118,119,120,121,122,123 }; - public static final int[] CW_HEX_4 = { 136,137,138,139,140,141 }; - public static final int[] CW_HEX_5 = { 154,155,156,157,158,159 }; - public static final int[] CW_HEX_6 = { 172,173,174,175,176,177 }; - public static final int[] CW_HEX_7 = { 190,191,192,193,194,195 }; - public static final int[] CW_HEX_8 = { 208,209,210,211,212,213 }; - public static final int[] CW_HEX_9 = { 226,227,228,229,230,231 }; - public static final int[] CW_HEX_10 = { 244,245,246,247,248,249 }; - public static final int[] CW_HEX_11 = { 262,263,264,265,266,267 }; - public static final int[] CW_HEX_12 = { 280,281,282,283,284,285 }; - public static final int[] CW_HEX_13 = { 298,299,300,301,302,303 }; - public static final int[] CW_HEX_14 = { 316,317,318,319,320,321 }; - public static final int[] CW_HEX_15 = { 334,335,336,337,338,339 }; - public static final int[] CW_HEX_16 = { 352,353,354,355,356,357 }; - public static final int[] CW_HEX_17 = { 370,371,372,373,374,375 }; - public static final int[] CW_HEX_18 = { 388,389,390,391,392,393 }; - public static final int[] CW_HEX_19 = { 406,407,408,409,410,411 }; - public static final int[] RS_HEX_0 = { 424,425,426,427,428,429 }; - public static final int[] RS_HEX_1 = { 442,443,444,445,446,447 }; - public static final int[] RS_HEX_2 = { 460,461,462,463,464,465 }; - public static final int[] RS_HEX_3 = { 478,479,480,481,482,483 }; - public static final int[] RS_HEX_4 = { 496,497,498,499,500,501 }; - public static final int[] RS_HEX_5 = { 514,515,516,517,518,519 }; - public static final int[] RS_HEX_6 = { 532,533,534,535,536,537 }; - public static final int[] RS_HEX_7 = { 550,551,552,553,554,555 }; - public static final int[] RS_HEX_8 = { 568,569,570,571,572,573 }; - public static final int[] RS_HEX_9 = { 586,587,588,589,590,591 }; - public static final int[] RS_HEX_10 = { 604,605,606,607,608,609 }; - public static final int[] RS_HEX_11 = { 622,623,624,625,626,627 }; - public static final int[] RS_HEX_12 = { 640,641,642,643,644,645 }; - public static final int[] RS_HEX_13 = { 658,659,660,661,662,663 }; - public static final int[] RS_HEX_14 = { 676,677,678,679,680,681 }; - public static final int[] RS_HEX_15 = { 694,695,696,697,698,699 }; - - /* Reed-Solomon(36,20,17) code protects the header word. Maximum - * correctable errors are: Hamming Distance(17) / 2 = 8 */ - public static final ReedSolomon_63_47_17 mReedSolomonDecoder = - new ReedSolomon_63_47_17( 8 ); - - public HDUMessage( BinaryMessage message, DataUnitID duid, - AliasList aliasList ) + private HeaderData mHeaderData; + + public HDUMessage(CorrectedBinaryMessage message, int nac, long timestamp) { - super( message, duid, aliasList ); - - /* NID CRC is checked in the message framer, thus a constructed message - * means it passed the CRC */ - mCRC = new CRC[ 3 ]; - mCRC[ 0 ] = CRC.PASSED; - - checkCRC(); + super(message, nac, timestamp); } - - private void checkCRC() - { - /* Golay( 18,6,18 ) error detection and correction */ - for( int index: GOLAY_WORD_STARTS ) - { - Golay18.checkAndCorrect( mMessage, index ); - } - - mCRC[ 1 ] = CRC.PASSED; - /* Reed-Solomon( 36,20,17 ) error detection and correction - * Check the Reed-Solomon parity bits. The RS decoder expects the link - * control data and reed solomon parity hex codewords in reverse order. - * The RS(24,12,13) code used by P25 removes the left-hand 47 data hex - * words, so we replace them with zeros. */ - int[] input = new int [63]; - int[] output = new int [63]; - - input[ 0 ] = mMessage.getInt( RS_HEX_15 ); - input[ 1 ] = mMessage.getInt( RS_HEX_14 ); - input[ 2 ] = mMessage.getInt( RS_HEX_13 ); - input[ 3 ] = mMessage.getInt( RS_HEX_12 ); - input[ 4 ] = mMessage.getInt( RS_HEX_11 ); - input[ 5 ] = mMessage.getInt( RS_HEX_10 ); - input[ 6 ] = mMessage.getInt( RS_HEX_9 ); - input[ 7 ] = mMessage.getInt( RS_HEX_8 ); - input[ 8 ] = mMessage.getInt( RS_HEX_7 ); - input[ 9 ] = mMessage.getInt( RS_HEX_6 ); - input[ 10 ] = mMessage.getInt( RS_HEX_5 ); - input[ 11 ] = mMessage.getInt( RS_HEX_4 ); - input[ 12 ] = mMessage.getInt( RS_HEX_3 ); - input[ 13 ] = mMessage.getInt( RS_HEX_2 ); - input[ 14 ] = mMessage.getInt( RS_HEX_1 ); - input[ 15 ] = mMessage.getInt( RS_HEX_0 ); - - input[ 16 ] = mMessage.getInt( CW_HEX_19 ); - input[ 17 ] = mMessage.getInt( CW_HEX_18 ); - input[ 18 ] = mMessage.getInt( CW_HEX_17 ); - input[ 19 ] = mMessage.getInt( CW_HEX_16 ); - input[ 20 ] = mMessage.getInt( CW_HEX_15 ); - input[ 21 ] = mMessage.getInt( CW_HEX_14 ); - input[ 22 ] = mMessage.getInt( CW_HEX_13 ); - input[ 23 ] = mMessage.getInt( CW_HEX_12 ); - input[ 24 ] = mMessage.getInt( CW_HEX_11 ); - input[ 25 ] = mMessage.getInt( CW_HEX_10 ); - input[ 26 ] = mMessage.getInt( CW_HEX_9 ); - input[ 27 ] = mMessage.getInt( CW_HEX_8 ); - input[ 28 ] = mMessage.getInt( CW_HEX_7 ); - input[ 29 ] = mMessage.getInt( CW_HEX_6 ); - input[ 30 ] = mMessage.getInt( CW_HEX_5 ); - input[ 31 ] = mMessage.getInt( CW_HEX_4 ); - input[ 32 ] = mMessage.getInt( CW_HEX_3 ); - input[ 33 ] = mMessage.getInt( CW_HEX_2 ); - input[ 34 ] = mMessage.getInt( CW_HEX_1 ); - input[ 35 ] = mMessage.getInt( CW_HEX_0 ); + @Override + public DataUnitID getDUID() + { + return DataUnitID.HEADER_DATA_UNIT; + } + + public HeaderData getHeaderData() + { + if(mHeaderData == null) + { + extractHeaderData(); + } + + return mHeaderData; + } + + /** + * Performs error detection and correction and extracts the header data from the HDU message + */ + private void extractHeaderData() + { + for(int index : GOLAY_WORD_STARTS) + { + Golay18.checkAndCorrect(getMessage(), index); + } + + /* Reed-Solomon( 36,20,17 ) error detection and correction + * Check the Reed-Solomon parity bits. The RS decoder expects the link + * control data and reed solomon parity hex codewords in reverse order. + * The RS(24,12,13) code used by P25 removes the left-hand 47 data hex + * words, so we replace them with zeros. */ + int[] input = new int[63]; + int[] output = new int[63]; + + input[0] = getMessage().getInt(RS_HEX_15); + input[1] = getMessage().getInt(RS_HEX_14); + input[2] = getMessage().getInt(RS_HEX_13); + input[3] = getMessage().getInt(RS_HEX_12); + input[4] = getMessage().getInt(RS_HEX_11); + input[5] = getMessage().getInt(RS_HEX_10); + input[6] = getMessage().getInt(RS_HEX_9); + input[7] = getMessage().getInt(RS_HEX_8); + input[8] = getMessage().getInt(RS_HEX_7); + input[9] = getMessage().getInt(RS_HEX_6); + input[10] = getMessage().getInt(RS_HEX_5); + input[11] = getMessage().getInt(RS_HEX_4); + input[12] = getMessage().getInt(RS_HEX_3); + input[13] = getMessage().getInt(RS_HEX_2); + input[14] = getMessage().getInt(RS_HEX_1); + input[15] = getMessage().getInt(RS_HEX_0); + + input[16] = getMessage().getInt(CW_HEX_19); + input[17] = getMessage().getInt(CW_HEX_18); + input[18] = getMessage().getInt(CW_HEX_17); + input[19] = getMessage().getInt(CW_HEX_16); + input[20] = getMessage().getInt(CW_HEX_15); + input[21] = getMessage().getInt(CW_HEX_14); + input[22] = getMessage().getInt(CW_HEX_13); + input[23] = getMessage().getInt(CW_HEX_12); + input[24] = getMessage().getInt(CW_HEX_11); + input[25] = getMessage().getInt(CW_HEX_10); + input[26] = getMessage().getInt(CW_HEX_9); + input[27] = getMessage().getInt(CW_HEX_8); + input[28] = getMessage().getInt(CW_HEX_7); + input[29] = getMessage().getInt(CW_HEX_6); + input[30] = getMessage().getInt(CW_HEX_5); + input[31] = getMessage().getInt(CW_HEX_4); + input[32] = getMessage().getInt(CW_HEX_3); + input[33] = getMessage().getInt(CW_HEX_2); + input[34] = getMessage().getInt(CW_HEX_1); + input[35] = getMessage().getInt(CW_HEX_0); /* indexes 36 - 62 are defaulted to zero */ - boolean irrecoverableErrors = mReedSolomonDecoder.decode( input, output ); + //Reed-Solomon(36,20,17) code protects the header word. Maximum correctable errors are: 8 + ReedSolomon_63_47_17 reedSolomon_63_47_17 = new ReedSolomon_63_47_17(8); - if( irrecoverableErrors ) + boolean irrecoverableErrors; + + try { - mCRC[ 2 ] = CRC.FAILED_CRC; + irrecoverableErrors = reedSolomon_63_47_17.decode(input, output); + } + catch(Exception e) + { + irrecoverableErrors = true; + } + + BinaryMessage binaryMessage = new BinaryMessage(120); + + int pointer = 0; + + for(int x = 35; x >= 16; x--) + { + if(output[x] != -1) + { + binaryMessage.load(pointer, 6, output[x]); + } + + pointer += 6; + } + + mHeaderData = new HeaderData(binaryMessage); + + if(irrecoverableErrors) + { + mHeaderData.setValid(false); } else { - mCRC[ 2 ] = CRC.PASSED; - - /* Only fix the codeword data hex codewords */ - repairHexCodeword( input, output, 16, CW_HEX_19 ); - repairHexCodeword( input, output, 17, CW_HEX_18 ); - repairHexCodeword( input, output, 18, CW_HEX_17 ); - repairHexCodeword( input, output, 19, CW_HEX_16 ); - repairHexCodeword( input, output, 20, CW_HEX_15 ); - repairHexCodeword( input, output, 21, CW_HEX_14 ); - repairHexCodeword( input, output, 22, CW_HEX_13 ); - repairHexCodeword( input, output, 23, CW_HEX_12 ); - repairHexCodeword( input, output, 24, CW_HEX_11 ); - repairHexCodeword( input, output, 25, CW_HEX_10 ); - repairHexCodeword( input, output, 26, CW_HEX_9 ); - repairHexCodeword( input, output, 27, CW_HEX_8 ); - repairHexCodeword( input, output, 28, CW_HEX_7 ); - repairHexCodeword( input, output, 29, CW_HEX_6 ); - repairHexCodeword( input, output, 30, CW_HEX_5 ); - repairHexCodeword( input, output, 31, CW_HEX_4 ); - repairHexCodeword( input, output, 32, CW_HEX_3 ); - repairHexCodeword( input, output, 33, CW_HEX_2 ); - repairHexCodeword( input, output, 34, CW_HEX_1 ); - repairHexCodeword( input, output, 35, CW_HEX_0 ); + //If we corrected any bit errors, update the original message with the bit error count + for(int x = 0; x < 23; x++) + { + if(output[x] != input[x]) + { + getMessage().incrementCorrectedBitCount(Integer.bitCount((output[x] ^ input[x]))); + } + } } - } - - private void repairHexCodeword( int[] input, int[] output, int index, int[] indexSet ) - { - if( input[ index ] != output[ index ] ) - { - mMessage.load( indexSet[ 0 ], 6, output[ index ] ); - mCRC[ 2 ] = CRC.CORRECTED; - } - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "NAC:" ); - sb.append( getNAC() ); - sb.append( " " ); - sb.append( getDUID().getLabel() ); - - sb.append( " TALKGROUP:" + getTalkgroupID() ); - - Encryption encryption = getEncryption(); - - if( encryption != Encryption.UNENCRYPTED ) - { - sb.append( " ENCRYPTION:" ); - - if( encryption == Encryption.UNKNOWN ) - { - sb.append( "UNK ALGO ID:" ); - sb.append( mMessage.getInt( ALGORITHM_ID ) ); - } - else - { - sb.append( encryption.name() ); - } - sb.append( " KEY ID:" + getKeyID() ); - sb.append( " MSG INDICATOR:" + getMessageIndicator() ); - } - else - { - sb.append( " UNENCRYPTED" ); - } - - sb.append( " " ); - sb.append( mMessage.toString() ); - - return sb.toString(); } - - public String getMessageIndicator() - { - StringBuilder sb = new StringBuilder(); - - sb.append( mMessage.getHex( MESSAGE_INDICATOR_A, 9 ) ); - sb.append( mMessage.getHex( MESSAGE_INDICATOR_B, 9 ) ); - return sb.toString(); - } - - public Encryption getEncryption() - { - return Encryption.fromValue( mMessage.getInt( ALGORITHM_ID ) ); - } - - public boolean isEncryptedAudio() - { - return getEncryption() != Encryption.UNENCRYPTED; - } - - public int getKeyID() - { - return mMessage.getInt( KEY_ID ); - } - - public String getTalkgroupID() - { - return mMessage.getHex( TALKGROUP_ID, 4 ); - } - - @Override - public String getToID() - { - return getTalkgroupID(); - } - - public boolean isValid() - { - return mCRC[ 2 ] != null && mCRC[ 2 ] != CRC.FAILED_CRC; - } + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("NAC:").append(getNAC()); + sb.append(" HDU ").append(getHeaderData()); + return sb.toString(); + } + + public List getIdentifiers() + { + return getHeaderData().getIdentifiers(); + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/hdu/HeaderData.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/hdu/HeaderData.java new file mode 100644 index 00000000..a3e7c399 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/hdu/HeaderData.java @@ -0,0 +1,175 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.hdu; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.encryption.APCO25EncryptionKey; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.reference.Encryption; +import io.github.dsheirer.module.decode.p25.reference.Vendor; + +import java.util.ArrayList; +import java.util.List; + +/** + * Header Data Unit (HDU) Information + */ +public class HeaderData +{ + private static final int[] MESSAGE_INDICATOR_A = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35}; + private static final int[] MESSAGE_INDICATOR_B = {36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] VENDOR_ID = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] ALGORITHM_ID = {80, 81, 82, 83, 84, 85, 86, 87}; + private static final int[] KEY_ID = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103}; + private static final int[] TALKGROUP_ID = {104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119}; + + private boolean mValid = true; + private BinaryMessage mMessage; + private Identifier mEncryptionKey; + private Identifier mTalkgroup; + private List mIdentifiers; + + public HeaderData(BinaryMessage message) + { + mMessage = message; + } + + public BinaryMessage getMessage() + { + return mMessage; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(isValid()) + { + sb.append("TALKGROUP:" + getTalkgroup()); + + Vendor vendor = getVendor(); + + if(vendor != Vendor.STANDARD) + { + sb.append(" VENDOR:").append(vendor.getLabel()); + } + + if(isEncryptedAudio()) + { + sb.append(" ENCRYPTION:").append(getEncryptionKey()); + sb.append(" MI:").append(getMessageIndicator()); + } + else + { + sb.append(" UNENCRYPTED"); + } + } + else + { + sb.append(" **CRC FAILED**"); + } + + return sb.toString(); + } + + /** + * Indicates if this message is valid, meaning that it has passed the CRC error detection and + * correction check. + */ + public boolean isValid() + { + return mValid; + } + + /** + * Sets the valid flag for this message set + */ + public void setValid(boolean valid) + { + mValid = valid; + } + + public String getMessageIndicator() + { + StringBuilder sb = new StringBuilder(); + + sb.append(getMessage().getHex(MESSAGE_INDICATOR_A, 9)); + sb.append(getMessage().getHex(MESSAGE_INDICATOR_B, 9)); + + return sb.toString(); + } + + public Vendor getVendor() + { + return Vendor.fromValue(getMessage().getInt(VENDOR_ID)); + } + + public Identifier getEncryptionKey() + { + if(mEncryptionKey == null && isEncryptedAudio()) + { + mEncryptionKey = APCO25EncryptionKey.create(getMessage().getInt(ALGORITHM_ID), + getMessage().getInt(KEY_ID)); + } + + return mEncryptionKey; + } + + public Encryption getEncryption() + { + return Encryption.fromValue(getMessage().getInt(ALGORITHM_ID)); + } + + public boolean isEncryptedAudio() + { + return getEncryption() != Encryption.UNENCRYPTED; + } + + public Identifier getTalkgroup() + { + if(mTalkgroup == null) + { + mTalkgroup = APCO25ToTalkgroup.createGroup(getMessage().getInt(TALKGROUP_ID)); + } + + return mTalkgroup; + } + + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + + if(isValid()) + { + mIdentifiers.add(getTalkgroup()); + mIdentifiers.add(getEncryptionKey()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/LinkControlOpcode.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/LinkControlOpcode.java new file mode 100644 index 00000000..a7c6e1a8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/LinkControlOpcode.java @@ -0,0 +1,155 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc; + +import io.github.dsheirer.module.decode.p25.reference.Vendor; + +public enum LinkControlOpcode +{ + GROUP_VOICE_CHANNEL_USER("GROUP VOICE CHANNEL USER", 0), + RESERVED_01("RESERVED-01", 1), + GROUP_VOICE_CHANNEL_UPDATE("GROUP VOICE CHANNEL UPDATE", 2), + UNIT_TO_UNIT_VOICE_CHANNEL_USER("UNIT-TO-UNIT VOICE CHANNEL USER", 3), + GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT("GROUP VOICE CHANNEL UPDATE EXPLICIT", 4), + UNIT_TO_UNIT_ANSWER_REQUEST("UNIT-TO-UNIT ANSWER REQUEST", 5), + TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER("TELEPHONE INTERCONNECT VOICE CHANNEL USER", 6), + TELEPHONE_INTERCONNECT_ANSWER_REQUEST("TELEPHONE INTERCONNECT ANSWER REQUEST", 7), + RESERVED_08("RESERVED-08", 8), + RESERVED_09("RESERVED-09", 9), + RESERVED_0A("RESERVED-0A", 10), + RESERVED_0B("RESERVED-0B", 11), + RESERVED_0C("RESERVED-0C", 12), + RESERVED_0D("RESERVED-0D", 13), + RESERVED_0E("RESERVED-0E", 14), + CALL_TERMINATION_OR_CANCELLATION("CALL TERMINATION", 15), + GROUP_AFFILIATION_QUERY("GROUP AFFILIATION QUERY", 16), + UNIT_REGISTRATION_COMMAND("UNIT REGISTRATION COMMAND", 17), + UNIT_AUTHENTICATION_COMMAND("UNIT AUTHENTICATION COMMAND", 18), + STATUS_QUERY("STATUS QUERY", 19), + STATUS_UPDATE("STATUS_UPDATE", 20), + MESSAGE_UPDATE("MESSAGE UPDATE", 21), + CALL_ALERT("CALL ALERT", 22), + EXTENDED_FUNCTION_COMMAND("EXTENDED FUNCTION COMMAND", 23), + CHANNEL_IDENTIFIER_UPDATE("CHANNEL IDENTIFIER UPDATE", 24), + CHANNEL_IDENTIFIER_UPDATE_EXPLICIT("CHANNEL IDENTIFIER UPDATE EXPLICIT", 25), + RESERVED_1A("RESERVED-1A", 26), + RESERVED_1B("RESERVED-1B", 27), + RESERVED_1C("RESERVED-1C", 28), + RESERVED_1D("RESERVED-1D", 29), + RESERVED_1E("RESERVED-1E", 30), + RESERVED_1F("RESERVED-1F", 31), + SYSTEM_SERVICE_BROADCAST("SYSTEM SERVICE BROADCAST", 32), + SECONDARY_CONTROL_CHANNEL_BROADCAST("SECONDARY CONTROL CHANNEL BROADCAST", 33), + ADJACENT_SITE_STATUS_BROADCAST("ADJACENT SITE STATUS", 34), + RFSS_STATUS_BROADCAST("RFSS STATUS BROADCAST", 35), + NETWORK_STATUS_BROADCAST("NET STATUS BROADCAST", 36), + PROTECTION_PARAMETER_BROADCAST("ENCRYPTION PARAMETERS BROADCAST", 37), + SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT("SECONDARY CONTROL CHANNEL BROADCAST EXPLICIT", 38), + ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT("ADJACENT SITE STATUS EXPLICIT", 39), + RFSS_STATUS_BROADCAST_EXPLICIT("RFSS STATUS BROADCAST EXPLICIT", 40), + NETWORK_STATUS_BROADCAST_EXPLICIT("NETWORK STATUS BROADCAST EXPLICIT", 41), + RESERVED_2A("RESERVED-2A", 42), + RESERVED_2B("RESERVED-2B", 43), + RESERVED_2C("RESERVED-2C", 44), + RESERVED_2D("RESERVED-2D", 45), + RESERVED_2E("RESERVED-2E", 46), + RESERVED_2F("RESERVED-2F", 47), + RESERVED_30("RESERVED-30", 48), + RESERVED_31("RESERVED-31", 49), + RESERVED_32("RESERVED-32", 50), + RESERVED_33("RESERVED-33", 51), + RESERVED_34("RESERVED-34", 52), + RESERVED_35("RESERVED-35", 53), + RESERVED_36("RESERVED-36", 54), + RESERVED_37("RESERVED-37", 55), + RESERVED_38("RESERVED-38", 56), + RESERVED_39("RESERVED-39", 57), + RESERVED_3A("RESERVED-3A", 58), + RESERVED_3B("RESERVED-3B", 59), + RESERVED_3C("RESERVED-3C", 60), + RESERVED_3D("RESERVED-3D", 61), + RESERVED_3E("RESERVED-3E", 62), + RESERVED_3F("RESERVED-3F", 63), + + MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_USER("PATCH GROUP VOICE CHANNEL USER", 0), + MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_UPDATE("PATCH GROUP VOICE CHANNEL UPDATE", 1), + MOTOROLA_PATCH_GROUP_ADD("PATCH GROUP ADD", 3), + MOTOROLA_PATCH_GROUP_DELETE("PATCH GROUP DELETE", 4), + MOTOROLA_TALK_COMPLETE("TALK_COMPLETE", 15), + MOTOROLA_UNKNOWN("UNKNOWN", -1), + + UNKNOWN("UNKNOWN", -1); + + private String mLabel; + private int mCode; + + LinkControlOpcode(String label, int code) + { + mLabel = label; + mCode = code; + } + + public String getLabel() + { + return mLabel; + } + + public int getCode() + { + return mCode; + } + + public static LinkControlOpcode fromValue(int value, Vendor vendor) + { + switch(vendor) + { + case STANDARD: + if(0 <= value && value <= 63) + { + return values()[value]; + } + break; + case MOTOROLA: + switch(value) + { + case 0: + return MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_USER; + case 1: + return MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_UPDATE; + case 3: + return MOTOROLA_PATCH_GROUP_ADD; + case 4: + return MOTOROLA_PATCH_GROUP_DELETE; + case 15: + return MOTOROLA_TALK_COMPLETE; + default: + return MOTOROLA_UNKNOWN; + } + default: + if(0 <= value && value <= 63) + { + return values()[value]; + } + } + + return UNKNOWN; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/LinkControlWord.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/LinkControlWord.java new file mode 100644 index 00000000..b1b75e34 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/LinkControlWord.java @@ -0,0 +1,164 @@ +package io.github.dsheirer.module.decode.p25.message.lc; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.reference.Vendor; + +import java.util.List; + +/** + * APCO 25 Link Control Word. This message word is contained in Logical Link Data Unit 1 and Terminator with + * Link Control messages. + */ +public abstract class LinkControlWord +{ + private static final int ENCRYPTION_FLAG = 0; + private static final int STANDARD_VENDOR_ID_FLAG = 1; + private static final int[] OPCODE = {2, 3, 4, 5, 6, 7}; + private static final int[] VENDOR = {8, 9, 10, 11, 12, 13, 14, 15}; + + private BinaryMessage mMessage; + private LinkControlOpcode mLinkControlOpcode; + private boolean mValid = true; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LinkControlWord(BinaryMessage message) + { + mMessage = message; + } + + /** + * Binary message sequence for this LCW + */ + protected BinaryMessage getMessage() + { + return mMessage; + } + + /** + * Sets the valid flag for this message to mark a flag as invalid (false) or the default value is true. + */ + public void setValid(boolean valid) + { + mValid = valid; + } + + /** + * Indicates if this link control word is valid and has passed all error detection and correction routines. + */ + public boolean isValid() + { + return mValid; + } + + /** + * Indicates if this is an encrypted LCW + */ + public boolean isEncrypted() + { + return getMessage().get(ENCRYPTION_FLAG); + } + + /** + * Indicates if this is a standard vendor format LCW + */ + public boolean isStandardVendorFormat() + { + return isStandardVendorFormat(getMessage()); + } + + /** + * Indicates if the link control word message has standard vendor format. + */ + public static boolean isStandardVendorFormat(BinaryMessage binaryMessage) + { + return binaryMessage.get(STANDARD_VENDOR_ID_FLAG); + } + + /** + * Vendor format for this link control word. + */ + public Vendor getVendor() + { + return getVendor(getMessage()); + } + + /** + * Lookup the Vendor format for the specified LCW + */ + public static Vendor getVendor(BinaryMessage binaryMessage) + { + if(isStandardVendorFormat(binaryMessage)) + { + return Vendor.STANDARD; + } + else + { + return Vendor.fromValue(binaryMessage.getInt(VENDOR)); + } + } + + /** + * Opcode for this LCW + */ + public LinkControlOpcode getOpcode() + { + if(mLinkControlOpcode == null) + { + mLinkControlOpcode = getOpcode(getMessage()); + } + + return mLinkControlOpcode; + } + + /** + * Identifies the link control word opcode from the binary message. + */ + public static LinkControlOpcode getOpcode(BinaryMessage binaryMessage) + { + return LinkControlOpcode.fromValue(binaryMessage.getInt(OPCODE), getVendor(binaryMessage)); + } + + /** + * List of identifiers provided by the message + */ + public abstract List getIdentifiers(); + + /** + * Creates a string with the basic Link Control Word information + */ + public String getMessageStub() + { + StringBuilder sb = new StringBuilder(); + + if(!isValid()) + { + sb.append("***LINK CONTROL CRC FAIL*** "); + } + + sb.append(getOpcode().getLabel()); + + if(isEncrypted()) + { + sb.append(" ENCRYPTED"); + } + else + { + if(!isStandardVendorFormat()) + { + Vendor vendor = getVendor(); + + if(vendor != Vendor.STANDARD) + { + sb.append(" ").append(vendor.getLabel()); + } + } + } + + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/LinkControlWordFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/LinkControlWordFactory.java new file mode 100644 index 00000000..0773ee1d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/LinkControlWordFactory.java @@ -0,0 +1,148 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.module.decode.p25.message.lc.motorola.LCMotorolaPatchGroupAdd; +import io.github.dsheirer.module.decode.p25.message.lc.motorola.LCMotorolaPatchGroupDelete; +import io.github.dsheirer.module.decode.p25.message.lc.motorola.LCMotorolaPatchGroupVoiceChannelUpdate; +import io.github.dsheirer.module.decode.p25.message.lc.motorola.LCMotorolaPatchGroupVoiceChannelUser; +import io.github.dsheirer.module.decode.p25.message.lc.motorola.LCMotorolaTalkComplete; +import io.github.dsheirer.module.decode.p25.message.lc.motorola.LCMotorolaUnknownOpcode; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCAdjacentSiteStatusBroadcast; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCAdjacentSiteStatusBroadcastExplicit; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCCallAlert; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCCallTermination; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCExtendedFunctionCommand; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCFrequencyBandUpdate; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCFrequencyBandUpdateExplicit; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCGroupAffiliationQuery; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCGroupVoiceChannelUpdate; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCGroupVoiceChannelUpdateExplicit; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCGroupVoiceChannelUser; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCMessageUpdate; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCNetworkStatusBroadcast; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCNetworkStatusBroadcastExplicit; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCProtectionParameterBroadcast; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCRFSSStatusBroadcast; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCRFSSStatusBroadcastExplicit; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCSecondaryControlChannelBroadcast; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCSecondaryControlChannelBroadcastExplicit; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCStatusQuery; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCStatusUpdate; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCSystemServiceBroadcast; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCTelephoneInterconnectAnswerRequest; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCTelephoneInterconnectVoiceChannelUser; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCUnitAuthenticationCommand; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCUnitRegistrationCommand; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCUnitToUnitAnswerRequest; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCUnitToUnitVoiceChannelUser; + +/** + * Factory class for creating link control word (LCW) message parsers. + */ +public class LinkControlWordFactory +{ + /** + * Creates a link control word from the binary message sequence. + * + * @param binaryMessage containing the LCW binary message sequence. + */ + public static LinkControlWord create(BinaryMessage binaryMessage) + { + LinkControlOpcode opcode = LinkControlWord.getOpcode(binaryMessage); + switch(opcode) + { + case ADJACENT_SITE_STATUS_BROADCAST: + return new LCAdjacentSiteStatusBroadcast(binaryMessage); + case ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT: + return new LCAdjacentSiteStatusBroadcastExplicit(binaryMessage); + case CALL_ALERT: + return new LCCallAlert(binaryMessage); + case CALL_TERMINATION_OR_CANCELLATION: + return new LCCallTermination(binaryMessage); + case CHANNEL_IDENTIFIER_UPDATE: + return new LCFrequencyBandUpdate(binaryMessage); + case CHANNEL_IDENTIFIER_UPDATE_EXPLICIT: + return new LCFrequencyBandUpdateExplicit(binaryMessage); + case EXTENDED_FUNCTION_COMMAND: + return new LCExtendedFunctionCommand(binaryMessage); + case GROUP_AFFILIATION_QUERY: + return new LCGroupAffiliationQuery(binaryMessage); + case GROUP_VOICE_CHANNEL_USER: + return new LCGroupVoiceChannelUser(binaryMessage); + case GROUP_VOICE_CHANNEL_UPDATE: + return new LCGroupVoiceChannelUpdate(binaryMessage); + case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT: + return new LCGroupVoiceChannelUpdateExplicit(binaryMessage); + case MESSAGE_UPDATE: + return new LCMessageUpdate(binaryMessage); + case NETWORK_STATUS_BROADCAST: + return new LCNetworkStatusBroadcast(binaryMessage); + case NETWORK_STATUS_BROADCAST_EXPLICIT: + return new LCNetworkStatusBroadcastExplicit(binaryMessage); + case PROTECTION_PARAMETER_BROADCAST: + return new LCProtectionParameterBroadcast(binaryMessage); + case RFSS_STATUS_BROADCAST: + return new LCRFSSStatusBroadcast(binaryMessage); + case RFSS_STATUS_BROADCAST_EXPLICIT: + return new LCRFSSStatusBroadcastExplicit(binaryMessage); + case SECONDARY_CONTROL_CHANNEL_BROADCAST: + return new LCSecondaryControlChannelBroadcast(binaryMessage); + case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: + return new LCSecondaryControlChannelBroadcastExplicit(binaryMessage); + case STATUS_QUERY: + return new LCStatusQuery(binaryMessage); + case STATUS_UPDATE: + return new LCStatusUpdate(binaryMessage); + case SYSTEM_SERVICE_BROADCAST: + return new LCSystemServiceBroadcast(binaryMessage); + case TELEPHONE_INTERCONNECT_ANSWER_REQUEST: + return new LCTelephoneInterconnectAnswerRequest(binaryMessage); + case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER: + return new LCTelephoneInterconnectVoiceChannelUser(binaryMessage); + case UNIT_AUTHENTICATION_COMMAND: + return new LCUnitAuthenticationCommand(binaryMessage); + case UNIT_REGISTRATION_COMMAND: + return new LCUnitRegistrationCommand(binaryMessage); + case UNIT_TO_UNIT_ANSWER_REQUEST: + return new LCUnitToUnitAnswerRequest(binaryMessage); + case UNIT_TO_UNIT_VOICE_CHANNEL_USER: + return new LCUnitToUnitVoiceChannelUser(binaryMessage); + + case MOTOROLA_PATCH_GROUP_ADD: + return new LCMotorolaPatchGroupAdd(binaryMessage); + case MOTOROLA_PATCH_GROUP_DELETE: + return new LCMotorolaPatchGroupDelete(binaryMessage); + case MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_USER: + return new LCMotorolaPatchGroupVoiceChannelUser(binaryMessage); + case MOTOROLA_TALK_COMPLETE: + return new LCMotorolaTalkComplete(binaryMessage); + case MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_UPDATE: + return new LCMotorolaPatchGroupVoiceChannelUpdate(binaryMessage); + case MOTOROLA_UNKNOWN: + return new LCMotorolaUnknownOpcode(binaryMessage); + + default: + return new UnknownLinkControlWord(binaryMessage); + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/UnknownLinkControlWord.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/UnknownLinkControlWord.java new file mode 100644 index 00000000..27445e6d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/UnknownLinkControlWord.java @@ -0,0 +1,39 @@ +package io.github.dsheirer.module.decode.p25.message.lc; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; + +import java.util.Collections; +import java.util.List; + +/** + * Unknown link control word. + */ +public class UnknownLinkControlWord extends LinkControlWord +{ + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public UnknownLinkControlWord(BinaryMessage message) + { + super(message); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" UNKNOWN/UNRECOGNIZED OPCODE"); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/LCMotorolaPatchGroupAdd.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/LCMotorolaPatchGroupAdd.java new file mode 100644 index 00000000..13c36c12 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/LCMotorolaPatchGroupAdd.java @@ -0,0 +1,144 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.motorola; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25AnyTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; + +import java.util.ArrayList; +import java.util.List; + +public class LCMotorolaPatchGroupAdd extends MotorolaLinkControlWord +{ + private static final int[] PATCH_GROUP = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] PATCHED_GROUP_1 = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] PATCHED_GROUP_2 = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + + private APCO25PatchGroup mPatchGroup; + private TalkgroupIdentifier mPatchedGroup1; + private TalkgroupIdentifier mPatchedGroup2; + private List mIdentifiers; + + public LCMotorolaPatchGroupAdd(BinaryMessage message) + { + super(message); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("MOTOROLA ADD PATCH GROUP:").append(getPatchGroup()); + sb.append(" MSG:").append(getMessage().toHexString()); + + return sb.toString(); + } + + /** + * Patch Group + */ + public Identifier getPatchGroup() + { + if(mPatchGroup == null) + { + PatchGroup patchGroup = new PatchGroup(APCO25AnyTalkgroup.create(getMessage().getInt(PATCH_GROUP))); + patchGroup.addPatchedGroups(getPatchedGroups()); + mPatchGroup = APCO25PatchGroup.create(patchGroup); + } + + return mPatchGroup; + } + + public List getPatchedGroups() + { + List patchedGroups = new ArrayList<>(); + + if(hasPatchedGroup1()) + { + patchedGroups.add(getPatchedGroup1()); + } + + if(hasPatchedGroup2()) + { + patchedGroups.add(getPatchedGroup2()); + } + + return patchedGroups; + } + + /** + * Patched Group 1 + */ + public TalkgroupIdentifier getPatchedGroup1() + { + if(mPatchedGroup1 == null) + { + mPatchedGroup1 = APCO25ToTalkgroup.createGroup(getMessage().getInt(PATCHED_GROUP_1)); + } + + return mPatchedGroup1; + } + + public boolean hasPatchedGroup1() + { + return getMessage().getInt(PATCHED_GROUP_1) != 0 && + (getMessage().getInt(PATCH_GROUP) != getMessage().getInt(PATCHED_GROUP_1)); + } + + /** + * Patched Group 2 + */ + public TalkgroupIdentifier getPatchedGroup2() + { + if(mPatchedGroup2 == null) + { + mPatchedGroup2 = APCO25ToTalkgroup.createGroup(getMessage().getInt(PATCHED_GROUP_2)); + } + + return mPatchedGroup2; + } + + public boolean hasPatchedGroup2() + { + return getMessage().getInt(PATCHED_GROUP_2) != 0 && + (getMessage().getInt(PATCH_GROUP) != getMessage().getInt(PATCHED_GROUP_2)); + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getPatchGroup()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/LCMotorolaPatchGroupDelete.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/LCMotorolaPatchGroupDelete.java new file mode 100644 index 00000000..c4d1ca49 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/LCMotorolaPatchGroupDelete.java @@ -0,0 +1,143 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.motorola; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; + +import java.util.ArrayList; +import java.util.List; + +public class LCMotorolaPatchGroupDelete extends MotorolaLinkControlWord +{ + private static final int[] PATCH_GROUP = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] PATCHED_GROUP_1 = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] PATCHED_GROUP_2 = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + + private APCO25PatchGroup mPatchGroup; + private TalkgroupIdentifier mPatchedGroup1; + private TalkgroupIdentifier mPatchedGroup2; + private List mIdentifiers; + + public LCMotorolaPatchGroupDelete(BinaryMessage message) + { + super(message); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("MOTOROLA DELETE PATCH GROUP:").append(getPatchGroup()); + sb.append(" MSG:").append(getMessage().toHexString()); + + return sb.toString(); + } + + /** + * Patch Group + */ + public Identifier getPatchGroup() + { + if(mPatchGroup == null) + { + PatchGroup patchGroup = new PatchGroup(APCO25ToTalkgroup.createGroup(getMessage().getInt(PATCH_GROUP))); + patchGroup.addPatchedGroups(getPatchedGroups()); + mPatchGroup = APCO25PatchGroup.create(patchGroup); + } + + return mPatchGroup; + } + + public List getPatchedGroups() + { + List patchedGroups = new ArrayList<>(); + + if(hasPatchedGroup1()) + { + patchedGroups.add(getPatchedGroup1()); + } + + if(hasPatchedGroup2()) + { + patchedGroups.add(getPatchedGroup2()); + } + + return patchedGroups; + } + + /** + * Patched Group 1 + */ + public TalkgroupIdentifier getPatchedGroup1() + { + if(mPatchedGroup1 == null) + { + mPatchedGroup1 = APCO25ToTalkgroup.createGroup(getMessage().getInt(PATCHED_GROUP_1)); + } + + return mPatchedGroup1; + } + + public boolean hasPatchedGroup1() + { + return getMessage().getInt(PATCHED_GROUP_1) != 0 && + (getMessage().getInt(PATCH_GROUP) != getMessage().getInt(PATCHED_GROUP_1)); + } + + /** + * Patched Group 2 + */ + public TalkgroupIdentifier getPatchedGroup2() + { + if(mPatchedGroup2 == null) + { + mPatchedGroup2 = APCO25ToTalkgroup.createGroup(getMessage().getInt(PATCHED_GROUP_2)); + } + + return mPatchedGroup2; + } + + public boolean hasPatchedGroup2() + { + return getMessage().getInt(PATCHED_GROUP_2) != 0 && + (getMessage().getInt(PATCH_GROUP) != getMessage().getInt(PATCHED_GROUP_2)); + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getPatchGroup()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/LCMotorolaPatchGroupVoiceChannelUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/LCMotorolaPatchGroupVoiceChannelUpdate.java new file mode 100644 index 00000000..4ca11908 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/LCMotorolaPatchGroupVoiceChannelUpdate.java @@ -0,0 +1,132 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.motorola; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; + +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola Patch Group Voice Channel Update + */ +public class LCMotorolaPatchGroupVoiceChannelUpdate extends MotorolaLinkControlWord implements IFrequencyBandReceiver +{ + private static final int[] UNKNOWN_1 = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] PATCH_GROUP = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] UNKNOWN_2 = {40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] UNKNOWN_3 = {48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] FREQUENCY_BAND = {56, 57, 58, 59}; + private static final int[] CHANNEL_NUMBER = {60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; + + private APCO25PatchGroup mPatchGroup; + private IChannelDescriptor mChannel; + private List mIdentifiers; + private List mChannels; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCMotorolaPatchGroupVoiceChannelUpdate(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("MOTOROLA PATCH VOICE CHANNEL UPDATE"); + sb.append(" PATCH GROUP:").append(getPatchGroup()); + sb.append(" CHANNEL:").append(getChannel()); + sb.append(" UNK1:").append(getUnknownField1()); + sb.append(" UNK2:").append(getUnknownField2()); + sb.append(" UNK3:").append(getUnknownField3()); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } + + public APCO25PatchGroup getPatchGroup() + { + if(mPatchGroup == null) + { + PatchGroup patchGroup = new PatchGroup(APCO25ToTalkgroup.createGroup(getMessage().getInt(PATCH_GROUP))); + mPatchGroup = APCO25PatchGroup.create(patchGroup); + } + + return mPatchGroup; + } + + public IChannelDescriptor getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), getMessage().getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + public String getUnknownField1() + { + return getMessage().getHex(UNKNOWN_1, 2); + } + public String getUnknownField2() + { + return getMessage().getHex(UNKNOWN_2, 2); + } + public String getUnknownField3() + { + return getMessage().getHex(UNKNOWN_3, 2); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getPatchGroup()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + if(mChannels == null) + { + mChannels = new ArrayList<>(); + mChannels.add(getChannel()); + } + + return mChannels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/LCMotorolaPatchGroupVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/LCMotorolaPatchGroupVoiceChannelUser.java new file mode 100644 index 00000000..6773aa27 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/LCMotorolaPatchGroupVoiceChannelUser.java @@ -0,0 +1,118 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.motorola; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +public class LCMotorolaPatchGroupVoiceChannelUser extends MotorolaLinkControlWord +{ + private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] PATCH_GROUP_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71}; + + private VoiceServiceOptions mVoiceServiceOptions; + private APCO25PatchGroup mGroupAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + public LCMotorolaPatchGroupVoiceChannelUser(BinaryMessage message) + { + super(message); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("MOTOROLA PATCH GROUP VOICE CHANNEL USER FM:").append(getSourceAddress()); + sb.append(" TO:").append(getGroupAddress()); + sb.append(" ").append(getVoiceServiceOptions()); + sb.append(" MSG:").append(getMessage().toHexString()); + + return sb.toString(); + } + + /** + * Service Options for this channel + */ + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + /** + * Talkgroup address + */ + public APCO25PatchGroup getGroupAddress() + { + if(mGroupAddress == null) + { + PatchGroup patchGroup = new PatchGroup(APCO25ToTalkgroup.createGroup(getMessage().getInt(PATCH_GROUP_ADDRESS))); + mGroupAddress = APCO25PatchGroup.create(patchGroup); + } + + return mGroupAddress; + } + + /** + * Source address + */ + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getGroupAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/LCMotorolaTalkComplete.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/LCMotorolaTalkComplete.java new file mode 100644 index 00000000..d67f9fc9 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/LCMotorolaTalkComplete.java @@ -0,0 +1,115 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.motorola; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25AnyTalkgroup; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; + +import java.util.ArrayList; +import java.util.List; + +/** + * Talk complete (but channel will continue to transmit) + */ +public class LCMotorolaTalkComplete extends LinkControlWord +{ + private static final int[] UNKNOWN_FIELD_1 = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] UNKNOWN_FIELD_2 = {40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 69, 70, 71}; + + private Identifier mAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCMotorolaTalkComplete(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(!isValid()) + { + sb.append("**CRC-FAILED** "); + } + + if(isEncrypted()) + { + sb.append(" ENCRYPTED"); + } + else + { + sb.append("MOTOROLA TALK COMPLETE"); + sb.append(" BY:").append(getAddress()); + sb.append(" UNK1:").append(getUnknownField1()); + sb.append(" UNK2:").append(getUnknownField2()); + sb.append(" MSG:").append(getMessage().toHexString()); + } + return sb.toString(); + } + + public String getUnknownField1() + { + return getMessage().getHex(UNKNOWN_FIELD_1, 2); + } + + public String getUnknownField2() + { + return getMessage().getHex(UNKNOWN_FIELD_2, 2); + } + + /** + * To/From radio identifier communicating with a landline + */ + public Identifier getAddress() + { + if(mAddress == null) + { + mAddress = APCO25AnyTalkgroup.create(getMessage().getInt(ADDRESS)); + } + + return mAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/LCMotorolaUnknownOpcode.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/LCMotorolaUnknownOpcode.java new file mode 100644 index 00000000..8e4e6f50 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/LCMotorolaUnknownOpcode.java @@ -0,0 +1,58 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.motorola; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; + +import java.util.Collections; +import java.util.List; + +/** + * Unknown link control word. + */ +public class LCMotorolaUnknownOpcode extends MotorolaLinkControlWord +{ + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCMotorolaUnknownOpcode(BinaryMessage message) + { + super(message); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(" MOTOROLA UNKNOWN/UNRECOGNIZED OPCODE:").append(getOpcode()); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/MotorolaLinkControlWord.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/MotorolaLinkControlWord.java new file mode 100644 index 00000000..0ad4ffaa --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/motorola/MotorolaLinkControlWord.java @@ -0,0 +1,37 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.motorola; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; + +public abstract class MotorolaLinkControlWord extends LinkControlWord +{ + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public MotorolaLinkControlWord(BinaryMessage message) + { + super(message); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCAdjacentSiteStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCAdjacentSiteStatusBroadcast.java new file mode 100644 index 00000000..efdd966f --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCAdjacentSiteStatusBroadcast.java @@ -0,0 +1,168 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Secondary control channel broadcast information. + */ +public class LCAdjacentSiteStatusBroadcast extends LinkControlWord implements IFrequencyBandReceiver +{ + private static final int[] LRA = {8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] SYSTEM = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] RFSS = {32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] SITE = {40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] FREQUENCY_BAND = {48, 49, 50, 51}; + private static final int[] CHANNEL_NUMBER = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] SERVICE_CLASS = {64, 65, 66, 67, 68, 69, 70, 71}; + + private List mIdentifiers; + private Identifier mLRA; + private Identifier mSystem; + private Identifier mRFSS; + private Identifier mSite; + private IChannelDescriptor mChannel; + private VoiceServiceOptions mVoiceServiceOptions; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCAdjacentSiteStatusBroadcast(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" RFSS:" + getRfss()); + sb.append(" SITE:" + getSite()); + sb.append(" SYSTEM:").append(getSystem()); + sb.append(" LRA:").append(getLocationRegistrationArea()); + sb.append(" CHAN:" + getChannel()); + sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + return sb.toString(); + } + + public Identifier getLocationRegistrationArea() + { + if(mLRA == null) + { + mLRA = APCO25Lra.create(getMessage().getInt(LRA)); + } + + return mLRA; + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getMessage().getInt(SYSTEM)); + } + + return mSystem; + } + + public Identifier getRfss() + { + if(mRFSS == null) + { + mRFSS = APCO25Rfss.create(getMessage().getInt(RFSS)); + } + + return mRFSS; + } + + public Identifier getSite() + { + if(mSite == null) + { + mSite = APCO25Site.create(getMessage().getInt(SITE)); + } + + return mSite; + } + + public IChannelDescriptor getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), + getMessage().getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_CLASS)); + } + + return mVoiceServiceOptions; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getLocationRegistrationArea()); + mIdentifiers.add(getSystem()); + mIdentifiers.add(getRfss()); + mIdentifiers.add(getSite()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCAdjacentSiteStatusBroadcastExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCAdjacentSiteStatusBroadcastExplicit.java new file mode 100644 index 00000000..093414f4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCAdjacentSiteStatusBroadcastExplicit.java @@ -0,0 +1,157 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Secondary control channel broadcast explicit information for V/UHF channels + */ +public class LCAdjacentSiteStatusBroadcastExplicit extends LinkControlWord implements IFrequencyBandReceiver +{ + private static final int[] LRA = {8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] DOWNLINK_FREQUENCY_BAND = {16, 17, 18, 19}; + private static final int[] DOWNLINK_CHANNEL_NUMBER = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] RFSS = {32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] SITE = {40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] UPLINK_FREQUENCY_BAND = {48, 49, 50, 51}; + private static final int[] UPLINK_CHANNEL_NUMBER = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] SERVICE_CLASS = {64, 65, 66, 67, 68, 69, 70, 71}; + + private List mIdentifiers; + private Identifier mLRA; + private Identifier mRFSS; + private Identifier mSite; + private IChannelDescriptor mChannel; + private VoiceServiceOptions mVoiceServiceOptions; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCAdjacentSiteStatusBroadcastExplicit(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" RFSS:" + getRfss()); + sb.append(" SITE:" + getSite()); + sb.append(" LRA:").append(getLocationRegistrationArea()); + sb.append(" CHAN:" + getChannel()); + sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + return sb.toString(); + } + + public Identifier getLocationRegistrationArea() + { + if(mLRA == null) + { + mLRA = APCO25Lra.create(getMessage().getInt(LRA)); + } + + return mLRA; + } + + public Identifier getRfss() + { + if(mRFSS == null) + { + mRFSS = APCO25Rfss.create(getMessage().getInt(RFSS)); + } + + return mRFSS; + } + + public Identifier getSite() + { + if(mSite == null) + { + mSite = APCO25Site.create(getMessage().getInt(SITE)); + } + + return mSite; + } + + public IChannelDescriptor getChannel() + { + if(mChannel == null) + { + mChannel = APCO25ExplicitChannel.create(getMessage().getInt(DOWNLINK_FREQUENCY_BAND), + getMessage().getInt(DOWNLINK_CHANNEL_NUMBER), getMessage().getInt(UPLINK_FREQUENCY_BAND), + getMessage().getInt(UPLINK_CHANNEL_NUMBER)); + } + + return mChannel; + } + + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_CLASS)); + } + + return mVoiceServiceOptions; + } + + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getLocationRegistrationArea()); + mIdentifiers.add(getRfss()); + mIdentifiers.add(getSite()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCCallAlert.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCCallAlert.java new file mode 100644 index 00000000..180ca27e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCCallAlert.java @@ -0,0 +1,107 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; + +import java.util.ArrayList; +import java.util.List; + +/** + * Echo of the status update from a subscriber when the destination of the update is another subscriber unit. + */ +public class LCCallAlert extends LinkControlWord +{ + private static final int[] RESERVED = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] TARGET_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47}; + private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71}; + + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCCallAlert(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" FM:").append(getSourceAddress()); + return sb.toString(); + } + + /** + * Talkgroup address + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * Source address + */ + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCCallTermination.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCCallTermination.java new file mode 100644 index 00000000..8e59840e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCCallTermination.java @@ -0,0 +1,96 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25AnyTalkgroup; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; + +import java.util.ArrayList; +import java.util.List; + +/** + * Termination or cancellation of a call and release of the channel. + */ +public class LCCallTermination extends LinkControlWord +{ + private static final int SYSTEM_CONTROLLER = 0xFFFFFD; + private static final int[] ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 69, 70, 71}; + + private Identifier mAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCCallTermination(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" BY:").append(getAddress()); + return sb.toString(); + } + + /** + * To/From radio identifier communicating with a landline + */ + public Identifier getAddress() + { + if(mAddress == null) + { + mAddress = APCO25AnyTalkgroup.create(getMessage().getInt(ADDRESS)); + } + + return mAddress; + } + + /** + * Indicates if the call termination was ordered by the system controller + */ + public boolean isNetworkCommandedTeardown() + { + return getMessage().getInt(ADDRESS) == SYSTEM_CONTROLLER; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCExtendedFunctionCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCExtendedFunctionCommand.java new file mode 100644 index 00000000..223e0e84 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCExtendedFunctionCommand.java @@ -0,0 +1,109 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.reference.ExtendedFunction; + +import java.util.ArrayList; +import java.util.List; + +/** + * Extended function command to a target address + */ +public class LCExtendedFunctionCommand extends LinkControlWord +{ + private static final int[] EXTENDED_FUNCTION = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] EXTENDED_FUNCTION_ARGUMENTS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] TARGET_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71}; + + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCExtendedFunctionCommand(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" ").append(getExtendedFunction()); + sb.append(" ARGUMENTS:").append(getExtendedFunctionArguments()); + return sb.toString(); + } + + /** + * Indicates the type of extended function + */ + public ExtendedFunction getExtendedFunction() + { + return ExtendedFunction.fromValue(getMessage().getInt(EXTENDED_FUNCTION)); + } + + /** + * Argument(s). May not be required for the class/function and will be set to null (0) if not required. + */ + public String getExtendedFunctionArguments() + { + return getMessage().getHex(EXTENDED_FUNCTION_ARGUMENTS, 6); + } + + /** + * Talkgroup address + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCFrequencyBandUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCFrequencyBandUpdate.java new file mode 100644 index 00000000..16bd6433 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCFrequencyBandUpdate.java @@ -0,0 +1,148 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBand; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; + +import java.util.Collections; +import java.util.List; + +/** + * Echo of the status update from a subscriber when the destination of the update is another subscriber unit. + */ +public class LCFrequencyBandUpdate extends LinkControlWord implements IFrequencyBand +{ + private static final int[] FREQUENCY_BAND_IDENTIFIER = {8, 9, 10, 11}; + private static final int[] BANDWIDTH = {12, 13, 14, 15, 16, 17, 18, 19, 20}; + private static final int TRANSMIT_OFFSET_SIGN = 21; + private static final int[] TRANSMIT_OFFSET = {22, 23, 24, 25, 26, 27, 28, 29}; + private static final int[] CHANNEL_SPACING = {30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] BASE_FREQUENCY = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71}; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCFrequencyBandUpdate(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" ID:").append(getIdentifier()); + sb.append(" BW:").append(getBandwidth()); + sb.append(" OFFSET:").append(getTransmitOffset()); + sb.append(" SPACING:").append(getChannelSpacing()); + sb.append(" BASE:").append(getBaseFrequency()); + return sb.toString(); + } + + @Override + public int getIdentifier() + { + return getMessage().getInt(FREQUENCY_BAND_IDENTIFIER); + } + + @Override + public long getChannelSpacing() + { + return getMessage().getInt(CHANNEL_SPACING) * 125l; + } + + @Override + public long getBaseFrequency() + { + return getMessage().getLong(BASE_FREQUENCY) * 5l; + } + + @Override + public int getBandwidth() + { + return getMessage().getInt(BANDWIDTH) * 125; + } + + @Override + public long getTransmitOffset() + { + long offset = getMessage().getLong(TRANSMIT_OFFSET) * 250000l; + + if(!getMessage().get(TRANSMIT_OFFSET_SIGN)) + { + offset *= -1; + } + + return offset; + } + + /** + * Indicates if the frequency band has a transmit option for the subscriber unit. + */ + public boolean hasTransmitOffset() + { + return getMessage().getInt(TRANSMIT_OFFSET) != 0x80; + } + + @Override + public long getDownlinkFrequency(int channelNumber) + { + return getBaseFrequency() + (channelNumber * getChannelSpacing()); + } + + @Override + public long getUplinkFrequency(int channelNumber) + { + if(hasTransmitOffset()) + { + return getDownlinkFrequency(channelNumber) + getTransmitOffset(); + } + + return 0; + } + + @Override + public boolean isTDMA() + { + return false; + } + + @Override + public int getTimeslotCount() + { + return 1; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCFrequencyBandUpdateExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCFrequencyBandUpdateExplicit.java new file mode 100644 index 00000000..7bb8a994 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCFrequencyBandUpdateExplicit.java @@ -0,0 +1,160 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBand; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; + +import java.util.Collections; +import java.util.List; + +/** + * Identifier update explicit + */ +public class LCFrequencyBandUpdateExplicit extends LinkControlWord implements IFrequencyBand +{ + private static final int[] FREQUENCY_BAND_IDENTIFIER = {8, 9, 10, 11}; + private static final int[] BANDWIDTH = {12, 13, 14, 15}; + private static final int TRANSMIT_OFFSET_SIGN = 16; + private static final int[] TRANSMIT_OFFSET = {17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}; + private static final int[] CHANNEL_SPACING = {30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] BASE_FREQUENCY = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCFrequencyBandUpdateExplicit(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" ID:").append(getIdentifier()); + sb.append(" BW:").append(getBandwidth()); + sb.append(" OFFSET:").append(getTransmitOffset()); + sb.append(" SPACING:").append(getChannelSpacing()); + sb.append(" BASE:").append(getBaseFrequency()); + return sb.toString(); + } + + @Override + public int getIdentifier() + { + return getMessage().getInt(FREQUENCY_BAND_IDENTIFIER); + } + + @Override + public long getChannelSpacing() + { + return getMessage().getInt(CHANNEL_SPACING) * 125l; + } + + @Override + public long getBaseFrequency() + { + return getMessage().getLong(BASE_FREQUENCY) * 5l; + } + + @Override + public int getBandwidth() + { + int bandwidth = getMessage().getInt(BANDWIDTH); + + if(bandwidth == 0x4) + { + return 6250; + } + else if(bandwidth == 0x5) + { + return 12500; + } + + return 0; + } + + @Override + public long getTransmitOffset() + { + long offset = getMessage().getLong(TRANSMIT_OFFSET) * 250000l; + + if(!getMessage().get(TRANSMIT_OFFSET_SIGN)) + { + offset *= -1; + } + + return offset; + } + + /** + * Indicates if the frequency band has a transmit option for the subscriber unit. + */ + public boolean hasTransmitOffset() + { + return getMessage().getInt(TRANSMIT_OFFSET) != 0x80; + } + + + @Override + public long getDownlinkFrequency(int channelNumber) + { + return getBaseFrequency() + (channelNumber * getChannelSpacing()); + } + + @Override + public long getUplinkFrequency(int channelNumber) + { + if(hasTransmitOffset()) + { + return getDownlinkFrequency(channelNumber) + getTransmitOffset(); + } + + return 0; + } + + @Override + public boolean isTDMA() + { + return false; + } + + @Override + public int getTimeslotCount() + { + return 1; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCGroupAffiliationQuery.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCGroupAffiliationQuery.java new file mode 100644 index 00000000..458020fc --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCGroupAffiliationQuery.java @@ -0,0 +1,106 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; + +import java.util.ArrayList; +import java.util.List; + +/** + * Current user of an APCO25 channel on both inbound and outbound channels + */ +public class LCGroupAffiliationQuery extends LinkControlWord +{ + private static final int[] TARGET_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47}; + private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71}; + + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCGroupAffiliationQuery(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" FM:").append(getSourceAddress()); + return sb.toString(); + } + + /** + * Talkgroup address + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * Source address + */ + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCGroupVoiceChannelUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCGroupVoiceChannelUpdate.java new file mode 100644 index 00000000..bae78da1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCGroupVoiceChannelUpdate.java @@ -0,0 +1,146 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; + +import java.util.ArrayList; +import java.util.List; + +/** + * Update detailing other users/channels that are active on the network. + */ +public class LCGroupVoiceChannelUpdate extends LinkControlWord implements IFrequencyBandReceiver +{ + public static final int[] FREQUENCY_BAND_A = {8, 9, 10, 11}; + public static final int[] CHANNEL_A = {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; + public static final int[] GROUP_ADDRESS_A = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + public static final int[] FREQUENCY_BAND_B = {40, 41, 42, 43}; + public static final int[] CHANNEL_B = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + public static final int[] GROUP_ADDRESS_B = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; + + private IChannelDescriptor mChannelA; + private IChannelDescriptor mChannelB; + private Identifier mTalkgroupA; + private Identifier mTalkgroupB; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + */ + public LCGroupVoiceChannelUpdate(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TALKGROUP A:").append(getGroupAddressA()); + sb.append(" CHAN A:").append(getChannelA()); + + if(hasChannelB()) + { + sb.append(" TALKGROUP B:").append(getGroupAddressB()); + sb.append(" CHAN B:").append(getChannelB()); + } + + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getGroupAddressA()); + + if(hasChannelB()) + { + mIdentifiers.add(getGroupAddressB()); + } + } + + return mIdentifiers; + } + + public IChannelDescriptor getChannelA() + { + if(mChannelA == null) + { + mChannelA = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND_A), getMessage().getInt(CHANNEL_A)); + } + + return mChannelA; + } + + public IChannelDescriptor getChannelB() + { + if(mChannelB == null) + { + mChannelB = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND_B), getMessage().getInt(CHANNEL_B)); + } + + return mChannelB; + } + + public boolean hasChannelB() + { + return getMessage().getInt(CHANNEL_B) != 0 && getMessage().getInt(GROUP_ADDRESS_A) != getMessage().getInt(GROUP_ADDRESS_B); + } + + public Identifier getGroupAddressA() + { + if(mTalkgroupA == null) + { + mTalkgroupA = APCO25ToTalkgroup.createGroup(getMessage().getInt(GROUP_ADDRESS_A)); + } + + return mTalkgroupA; + } + + public Identifier getGroupAddressB() + { + if(mTalkgroupB == null) + { + mTalkgroupB = APCO25ToTalkgroup.createGroup(getMessage().getInt(GROUP_ADDRESS_B)); + } + + return mTalkgroupB; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannelA()); + channels.add(getChannelB()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCGroupVoiceChannelUpdateExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCGroupVoiceChannelUpdateExplicit.java new file mode 100644 index 00000000..e3d39419 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCGroupVoiceChannelUpdateExplicit.java @@ -0,0 +1,126 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Update detailing other users/channels that are active on the network. + */ +public class LCGroupVoiceChannelUpdateExplicit extends LinkControlWord implements IFrequencyBandReceiver +{ + public static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + public static final int[] GROUP_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + public static final int[] DOWNLINK_FREQUENCY_BAND = {40, 41, 42, 43}; + public static final int[] DOWNLINK_CHANNEL = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + public static final int[] UPLINK_FREQUENCY_BAND = {56, 57, 58, 59}; + public static final int[] UPLINK_CHANNEL = {60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; + + private VoiceServiceOptions mVoiceServiceOptions; + private IChannelDescriptor mChannel; + private List mChannels; + private Identifier mTalkgroup; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + */ + public LCGroupVoiceChannelUpdateExplicit(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TALKGROUP:").append(getGroupAddress()); + sb.append(" ").append(getChannel()); + sb.append(" ").append(getVoiceServiceOptions()); + + return sb.toString(); + } + + /** + * Service options for this channel + */ + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getGroupAddress()); + } + + return mIdentifiers; + } + + public IChannelDescriptor getChannel() + { + if(mChannel == null) + { + mChannel = APCO25ExplicitChannel.create(getMessage().getInt(DOWNLINK_FREQUENCY_BAND), + getMessage().getInt(DOWNLINK_CHANNEL), getMessage().getInt(UPLINK_FREQUENCY_BAND), + getMessage().getInt(UPLINK_CHANNEL)); + } + + return mChannel; + } + + public Identifier getGroupAddress() + { + if(mTalkgroup == null) + { + mTalkgroup = APCO25ToTalkgroup.createGroup(getMessage().getInt(GROUP_ADDRESS)); + } + + return mTalkgroup; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCGroupVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCGroupVoiceChannelUser.java new file mode 100644 index 00000000..f9dc42dc --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCGroupVoiceChannelUser.java @@ -0,0 +1,122 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Current user of an APCO25 channel on both inbound and outbound channels + */ +public class LCGroupVoiceChannelUser extends LinkControlWord +{ + private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] GROUP_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71}; + + private VoiceServiceOptions mVoiceServiceOptions; + private Identifier mGroupAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCGroupVoiceChannelUser(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getGroupAddress()); + sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + return sb.toString(); + } + + /** + * Service Options for this channel + */ + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + /** + * Talkgroup address + */ + public Identifier getGroupAddress() + { + if(mGroupAddress == null) + { + mGroupAddress = APCO25ToTalkgroup.createGroup(getMessage().getInt(GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + /** + * Source address + */ + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getGroupAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCMessageUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCMessageUpdate.java new file mode 100644 index 00000000..158ef5ed --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCMessageUpdate.java @@ -0,0 +1,121 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.message.APCO25ShortDataMessage; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; + +import java.util.ArrayList; +import java.util.List; + +/** + * Echo of the status update from a subscriber when the destination of the update is another subscriber unit. + */ +public class LCMessageUpdate extends LinkControlWord +{ + private static final int[] MESSAGE = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] TARGET_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47}; + private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71}; + + private Identifier mShortDataMessage; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCMessageUpdate(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" SDM:").append(getShortDataMessage()); + return sb.toString(); + } + + public Identifier getShortDataMessage() + { + if(mShortDataMessage == null) + { + mShortDataMessage = APCO25ShortDataMessage.create(getMessage().getInt(MESSAGE)); + } + + return mShortDataMessage; + } + + /** + * Talkgroup address + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * Source address + */ + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + mIdentifiers.add(getShortDataMessage()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCNetworkStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCNetworkStatusBroadcast.java new file mode 100644 index 00000000..f17caeb6 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCNetworkStatusBroadcast.java @@ -0,0 +1,140 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Network status broadcast. + */ +public class LCNetworkStatusBroadcast extends LinkControlWord implements IFrequencyBandReceiver +{ + private static final int[] RESERVED = {8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] WACN = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35}; + private static final int[] SYSTEM = {36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] FREQUENCY_BAND = {48, 49, 50, 51}; + private static final int[] CHANNEL_NUMBER = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] SERVICE_CLASS = {64, 65, 66, 67, 68, 69, 70, 71}; + + private List mIdentifiers; + private Identifier mWACN; + private Identifier mSystem; + private IChannelDescriptor mChannel; + private VoiceServiceOptions mVoiceServiceOptions; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCNetworkStatusBroadcast(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" WACN:").append(getWACN()); + sb.append(" SYSTEM:").append(getSystem()); + sb.append(" CHAN:" + getChannel()); + sb.append(" SERVICE OPTIONS:" + getVoiceServiceOptions()); + return sb.toString(); + } + + public Identifier getWACN() + { + if(mWACN == null) + { + mWACN = APCO25Wacn.create(getMessage().getInt(WACN)); + } + + return mWACN; + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getMessage().getInt(SYSTEM)); + } + + return mSystem; + } + + public IChannelDescriptor getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), + getMessage().getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_CLASS)); + } + + return mVoiceServiceOptions; + } + + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getWACN()); + mIdentifiers.add(getSystem()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCNetworkStatusBroadcastExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCNetworkStatusBroadcastExplicit.java new file mode 100644 index 00000000..39d70e4b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCNetworkStatusBroadcastExplicit.java @@ -0,0 +1,127 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; + +import java.util.ArrayList; +import java.util.List; + +/** + * Secondary control channel broadcast information. + */ +public class LCNetworkStatusBroadcastExplicit extends LinkControlWord implements IFrequencyBandReceiver +{ + private static final int[] WACN = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}; + private static final int[] SYSTEM = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] DOWNLINK_FREQUENCY_BAND = {40, 41, 42, 43}; + private static final int[] DOWNLINK_CHANNEL_NUMBER = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] UPLINK_FREQUENCY_BAND = {56, 57, 58, 59}; + private static final int[] UPLINK_CHANNEL_NUMBER = {60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; + + private List mIdentifiers; + private Identifier mWACN; + private Identifier mSystem; + private IChannelDescriptor mChannel; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCNetworkStatusBroadcastExplicit(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" WACN:").append(getWACN()); + sb.append(" SYSTEM:").append(getSystem()); + sb.append(" CHAN:" + getChannel()); + return sb.toString(); + } + + public Identifier getWACN() + { + if(mWACN == null) + { + mWACN = APCO25Wacn.create(getMessage().getInt(WACN)); + } + + return mWACN; + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getMessage().getInt(SYSTEM)); + } + + return mSystem; + } + + public IChannelDescriptor getChannel() + { + if(mChannel == null) + { + mChannel = APCO25ExplicitChannel.create(getMessage().getInt(DOWNLINK_FREQUENCY_BAND), + getMessage().getInt(DOWNLINK_CHANNEL_NUMBER), getMessage().getInt(UPLINK_FREQUENCY_BAND), + getMessage().getInt(UPLINK_CHANNEL_NUMBER)); + } + + return mChannel; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getWACN()); + mIdentifiers.add(getSystem()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCProtectionParameterBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCProtectionParameterBroadcast.java new file mode 100644 index 00000000..a2c2bf2e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCProtectionParameterBroadcast.java @@ -0,0 +1,104 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.encryption.APCO25EncryptionKey; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; + +import java.util.ArrayList; +import java.util.List; + +/** + * Echo of the status update from a subscriber when the destination of the update is another subscriber unit. + */ +public class LCProtectionParameterBroadcast extends LinkControlWord +{ + private static final int[] RESERVED = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] ALGORITHM_ID = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] KEY_ID = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] TARGET_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71}; + + private Identifier mEncryptionKey; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCProtectionParameterBroadcast(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" USE:").append(getEncryptionKey()); + return sb.toString(); + } + + public Identifier getEncryptionKey() + { + if(mEncryptionKey == null) + { + mEncryptionKey = APCO25EncryptionKey.create(getMessage().getInt(ALGORITHM_ID), getMessage().getInt(KEY_ID)); + } + + return mEncryptionKey; + } + + /** + * Talkgroup address + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getEncryptionKey()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCRFSSStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCRFSSStatusBroadcast.java new file mode 100644 index 00000000..0277aa8d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCRFSSStatusBroadcast.java @@ -0,0 +1,168 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * RF SubSystem broadcast information. + */ +public class LCRFSSStatusBroadcast extends LinkControlWord implements IFrequencyBandReceiver +{ + private static final int[] LRA = {8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] SYSTEM = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] RFSS = {32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] SITE = {40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] FREQUENCY_BAND = {48, 49, 50, 51}; + private static final int[] CHANNEL_NUMBER = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] SERVICE_CLASS = {64, 65, 66, 67, 68, 69, 70, 71}; + + private List mIdentifiers; + private Identifier mLRA; + private Identifier mSystem; + private Identifier mRFSS; + private Identifier mSite; + private IChannelDescriptor mChannel; + private VoiceServiceOptions mVoiceServiceOptions; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCRFSSStatusBroadcast(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" LRA:").append(getLocationRegistrationArea()); + sb.append(" SYSTEM:").append(getSystem()); + sb.append(" SITE:" + getRfss() + "-" + getSite()); + sb.append(" CHAN:" + getChannel()); + sb.append(" SERVICE OPTIONS:" + getVoiceServiceOptions()); + return sb.toString(); + } + + public Identifier getLocationRegistrationArea() + { + if(mLRA == null) + { + mLRA = APCO25Lra.create(getMessage().getInt(LRA)); + } + + return mLRA; + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getMessage().getInt(SYSTEM)); + } + + return mSystem; + } + + public Identifier getRfss() + { + if(mRFSS == null) + { + mRFSS = APCO25Rfss.create(getMessage().getInt(RFSS)); + } + + return mRFSS; + } + + public Identifier getSite() + { + if(mSite == null) + { + mSite = APCO25Site.create(getMessage().getInt(SITE)); + } + + return mSite; + } + + public IChannelDescriptor getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), + getMessage().getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_CLASS)); + } + + return mVoiceServiceOptions; + } + + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getLocationRegistrationArea()); + mIdentifiers.add(getSystem()); + mIdentifiers.add(getRfss()); + mIdentifiers.add(getSite()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCRFSSStatusBroadcastExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCRFSSStatusBroadcastExplicit.java new file mode 100644 index 00000000..c113ff1b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCRFSSStatusBroadcastExplicit.java @@ -0,0 +1,155 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Secondary control channel broadcast information. + */ +public class LCRFSSStatusBroadcastExplicit extends LinkControlWord implements IFrequencyBandReceiver +{ + private static final int[] LRA = {8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] DOWNLINK_FREQUENCY_BAND = {16, 17, 18, 19}; + private static final int[] DOWNLINK_CHANNEL_NUMBER = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] RFSS = {32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] SITE = {40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] UPLINK_FREQUENCY_BAND = {48, 49, 50, 51}; + private static final int[] UPLINK_CHANNEL_NUMBER = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] SERVICE_CLASS = {64, 65, 66, 67, 68, 69, 70, 71}; + + private List mIdentifiers; + private Identifier mLRA; + private Identifier mRFSS; + private Identifier mSite; + private IChannelDescriptor mChannel; + private VoiceServiceOptions mVoiceServiceOptions; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCRFSSStatusBroadcastExplicit(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" LRA:").append(getLocationRegistrationArea()); + sb.append(" SITE:" + getRfss() + "-" + getSite()); + sb.append(" CHAN:" + getChannel()); + sb.append(" SERVICE OPTIONS:" + getVoiceServiceOptions()); + return sb.toString(); + } + + public Identifier getLocationRegistrationArea() + { + if(mLRA == null) + { + mLRA = APCO25Lra.create(getMessage().getInt(LRA)); + } + + return mLRA; + } + + public Identifier getRfss() + { + if(mRFSS == null) + { + mRFSS = APCO25Rfss.create(getMessage().getInt(RFSS)); + } + + return mRFSS; + } + + public Identifier getSite() + { + if(mSite == null) + { + mSite = APCO25Site.create(getMessage().getInt(SITE)); + } + + return mSite; + } + + public IChannelDescriptor getChannel() + { + if(mChannel == null) + { + mChannel = APCO25ExplicitChannel.create(getMessage().getInt(DOWNLINK_FREQUENCY_BAND), + getMessage().getInt(DOWNLINK_CHANNEL_NUMBER), getMessage().getInt(UPLINK_FREQUENCY_BAND), + getMessage().getInt(UPLINK_CHANNEL_NUMBER)); + } + + return mChannel; + } + + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_CLASS)); + } + + return mVoiceServiceOptions; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getLocationRegistrationArea()); + mIdentifiers.add(getRfss()); + mIdentifiers.add(getSite()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCSecondaryControlChannelBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCSecondaryControlChannelBroadcast.java new file mode 100644 index 00000000..41a76c73 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCSecondaryControlChannelBroadcast.java @@ -0,0 +1,181 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Secondary control channel broadcast information. + */ +public class LCSecondaryControlChannelBroadcast extends LinkControlWord implements IFrequencyBandReceiver +{ + private static final int[] RFSS = {8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] SITE = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] FREQUENCY_BAND_A = {24, 25, 26, 27}; + private static final int[] CHANNEL_NUMBER_A = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] SERVICE_CLASS_A = {40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] FREQUENCY_BAND_B = {48, 49, 50, 51}; + private static final int[] CHANNEL_NUMBER_B = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] SERVICE_CLASS_B = {64, 65, 66, 67, 68, 69, 70, 71}; + + private List mIdentifiers; + private Identifier mRFSS; + private Identifier mSite; + private IChannelDescriptor mChannelA; + private IChannelDescriptor mChannelB; + private VoiceServiceOptions mVoiceServiceOptionsA; + private VoiceServiceOptions mVoiceServiceOptionsB; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCSecondaryControlChannelBroadcast(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" SITE:" + getRFSS() + "-" + getSite()); + sb.append(" CHAN A:" + getChannelA()); + sb.append(" SERVICE OPTIONS:" + getVoiceServiceOptionsA()); + + if(hasChannelB()) + { + sb.append(" CHAN B:" + getChannelB()); + sb.append(" SERVICE OPTIONS:" + getVoiceServiceOptionsB()); + } + return sb.toString(); + } + + public Identifier getRFSS() + { + if(mRFSS == null) + { + mRFSS = APCO25Rfss.create(getMessage().getInt(RFSS)); + } + + return mRFSS; + } + + public Identifier getSite() + { + if(mSite == null) + { + mSite = APCO25Site.create(getMessage().getInt(SITE)); + } + + return mSite; + } + + public IChannelDescriptor getChannelA() + { + if(mChannelA == null) + { + mChannelA = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND_A), + getMessage().getInt(CHANNEL_NUMBER_A)); + } + + return mChannelA; + } + + private boolean hasChannelB() + { + return getMessage().getInt(CHANNEL_NUMBER_A) != getMessage().getInt(CHANNEL_NUMBER_B) && + getMessage().getInt(SERVICE_CLASS_B) != 0; + } + + public IChannelDescriptor getChannelB() + { + if(mChannelB == null) + { + mChannelB = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND_B), + getMessage().getInt(CHANNEL_NUMBER_B)); + } + + return mChannelB; + } + + public VoiceServiceOptions getVoiceServiceOptionsA() + { + if(mVoiceServiceOptionsA == null) + { + mVoiceServiceOptionsA = new VoiceServiceOptions(getMessage().getInt(SERVICE_CLASS_A)); + } + + return mVoiceServiceOptionsA; + } + + + public VoiceServiceOptions getVoiceServiceOptionsB() + { + if(mVoiceServiceOptionsB == null) + { + mVoiceServiceOptionsB = new VoiceServiceOptions(getMessage().getInt(SERVICE_CLASS_B)); + } + + return mVoiceServiceOptionsB; + } + + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getRFSS()); + mIdentifiers.add(getSite()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannelA()); + if(hasChannelB()) + { + channels.add(getChannelB()); + } + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCSecondaryControlChannelBroadcastExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCSecondaryControlChannelBroadcastExplicit.java new file mode 100644 index 00000000..08dae298 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCSecondaryControlChannelBroadcastExplicit.java @@ -0,0 +1,141 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Secondary control channel broadcast explicit information for V/UHF channels + */ +public class LCSecondaryControlChannelBroadcastExplicit extends LinkControlWord implements IFrequencyBandReceiver +{ + private static final int[] RFSS = {8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] SITE = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] DOWNLINK_FREQUENCY_BAND = {24, 25, 26, 27}; + private static final int[] DOWNLINK_CHANNEL_NUMBER = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] UPLINK_FREQUENCY_BAND = {40, 41, 42, 43}; + private static final int[] UPNLINK_CHANNEL_NUMBER = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SERVICE_CLASS = {56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] RESERVED = {64, 65, 66, 67, 68, 69, 70, 71}; + + private List mIdentifiers; + private Identifier mRFSS; + private Identifier mSite; + private IChannelDescriptor mChannel; + private VoiceServiceOptions mVoiceServiceOptions; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCSecondaryControlChannelBroadcastExplicit(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" SITE:" + getRFSS() + "-" + getSite()); + sb.append(" CHAN:" + getChannel()); + sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + return sb.toString(); + } + + public Identifier getRFSS() + { + if(mRFSS == null) + { + mRFSS = APCO25Rfss.create(getMessage().getInt(RFSS)); + } + + return mRFSS; + } + + public Identifier getSite() + { + if(mSite == null) + { + mSite = APCO25Site.create(getMessage().getInt(SITE)); + } + + return mSite; + } + + public IChannelDescriptor getChannel() + { + if(mChannel == null) + { + mChannel = APCO25ExplicitChannel.create(getMessage().getInt(DOWNLINK_FREQUENCY_BAND), + getMessage().getInt(DOWNLINK_CHANNEL_NUMBER), getMessage().getInt(UPLINK_FREQUENCY_BAND), + getMessage().getInt(UPNLINK_CHANNEL_NUMBER)); + } + + return mChannel; + } + + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_CLASS)); + } + + return mVoiceServiceOptions; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getRFSS()); + mIdentifiers.add(getSite()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCStatusQuery.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCStatusQuery.java new file mode 100644 index 00000000..32042879 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCStatusQuery.java @@ -0,0 +1,106 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; + +import java.util.ArrayList; +import java.util.List; + +/** + * Current user of an APCO25 channel on both inbound and outbound channels + */ +public class LCStatusQuery extends LinkControlWord +{ + private static final int[] TARGET_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47}; + private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71}; + + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCStatusQuery(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" FM:").append(getSourceAddress()); + return sb.toString(); + } + + /** + * Talkgroup address + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * Source address + */ + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCStatusUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCStatusUpdate.java new file mode 100644 index 00000000..810ecb89 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCStatusUpdate.java @@ -0,0 +1,135 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.status.APCO25Status; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; + +import java.util.ArrayList; +import java.util.List; + +/** + * Echo of the status update from a subscriber when the destination of the update is another subscriber unit. + */ +public class LCStatusUpdate extends LinkControlWord +{ + private static final int[] UNIT_STATUS = {8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] USER_STATUS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] TARGET_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47}; + private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71}; + + private Identifier mUnitStatus; + private Identifier mUserStatus; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCStatusUpdate(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" UNIT:").append(getUnitStatus()); + sb.append(" USER:").append(getUserStatus()); + return sb.toString(); + } + + public Identifier getUnitStatus() + { + if(mUnitStatus == null) + { + mUnitStatus = APCO25Status.createUnitStatus(getMessage().getInt(UNIT_STATUS)); + } + + return mUnitStatus; + } + + public Identifier getUserStatus() + { + if(mUserStatus == null) + { + mUserStatus = APCO25Status.createUserStatus(getMessage().getInt(USER_STATUS)); + } + + return mUserStatus; + } + + /** + * Talkgroup address + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * Source address + */ + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + mIdentifiers.add(getUnitStatus()); + mIdentifiers.add(getUserStatus()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCSystemServiceBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCSystemServiceBroadcast.java new file mode 100644 index 00000000..0db52d79 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCSystemServiceBroadcast.java @@ -0,0 +1,91 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.reference.Service; + +import java.util.Collections; +import java.util.List; + +/** + * Termination or cancellation of a call and release of the channel. + */ +public class LCSystemServiceBroadcast extends LinkControlWord +{ + private static final int[] REQUEST_PRIORITY_LEVEL = {20, 21, 22, 23}; + private static final int[] AVAILABLE_SERVICES = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47}; + private static final int[] SUPPORTED_SERVICES = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCSystemServiceBroadcast(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" REQUEST PRIORITY LEVEL:").append(getRequestPriorityLevel()); + sb.append(" AVAILABLE SERVICES ").append(getAvailableServices()); + sb.append(" SUPPORTED SERVICES ").append(getSupportedServices()); + return sb.toString(); + } + + /** + * Threshold priority for service requests for this site + */ + public int getRequestPriorityLevel() + { + return getMessage().getInt(REQUEST_PRIORITY_LEVEL); + } + + + public List getAvailableServices() + { + long bitmap = getMessage().getLong(AVAILABLE_SERVICES); + return Service.getServices(bitmap); + } + + public List getSupportedServices() + { + long bitmap = getMessage().getLong(SUPPORTED_SERVICES); + return Service.getServices(bitmap); + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCTelephoneInterconnectAnswerRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCTelephoneInterconnectAnswerRequest.java new file mode 100644 index 00000000..48dd63da --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCTelephoneInterconnectAnswerRequest.java @@ -0,0 +1,125 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.telephone.APCO25TelephoneNumber; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.reference.Digit; + +import java.util.ArrayList; +import java.util.List; + +/** + * Request to a radio user to answer a telephone call + */ +public class LCTelephoneInterconnectAnswerRequest extends LinkControlWord +{ + private static final int[] DIGIT_1 = {8, 9, 10, 11}; + private static final int[] DIGIT_2 = {12, 13, 14, 15}; + private static final int[] DIGIT_3 = {16, 17, 18, 19}; + private static final int[] DIGIT_4 = {20, 21, 22, 23}; + private static final int[] DIGIT_5 = {24, 25, 26, 27}; + private static final int[] DIGIT_6 = {28, 29, 30, 31}; + private static final int[] DIGIT_7 = {32, 33, 34, 35}; + private static final int[] DIGIT_8 = {36, 37, 38, 39}; + private static final int[] DIGIT_9 = {40, 41, 42, 43}; + private static final int[] DIGIT_10 = {44, 45, 46, 47}; + private static final int[] TARGET_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71}; + + private Identifier mTargetAddress; + private Identifier mTelephoneNumber; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCTelephoneInterconnectAnswerRequest(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" TELEPHONE:").append(getTelephoneNumber()); + + return sb.toString(); + } + + public Identifier getTelephoneNumber() + { + if(mTelephoneNumber == null) + { + List digits = new ArrayList<>(); + digits.add(getMessage().getInt(DIGIT_1)); + digits.add(getMessage().getInt(DIGIT_2)); + digits.add(getMessage().getInt(DIGIT_3)); + digits.add(getMessage().getInt(DIGIT_4)); + digits.add(getMessage().getInt(DIGIT_5)); + digits.add(getMessage().getInt(DIGIT_6)); + digits.add(getMessage().getInt(DIGIT_7)); + digits.add(getMessage().getInt(DIGIT_8)); + digits.add(getMessage().getInt(DIGIT_9)); + digits.add(getMessage().getInt(DIGIT_10)); + + mTelephoneNumber = APCO25TelephoneNumber.createFrom(Digit.decode(digits)); + } + + return mTelephoneNumber; + } + + /** + * To/From radio identifier communicating with a landline + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTelephoneNumber()); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCTelephoneInterconnectVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCTelephoneInterconnectVoiceChannelUser.java new file mode 100644 index 00000000..ccda878a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCTelephoneInterconnectVoiceChannelUser.java @@ -0,0 +1,118 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25AnyTalkgroup; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Current user of an APCO25 channel on both inbound and outbound channels + */ +public class LCTelephoneInterconnectVoiceChannelUser extends LinkControlWord +{ + private static final int[] RESERVED_1 = {8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] RESERVED_2 = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] CALL_TIMER = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71}; + + private VoiceServiceOptions mVoiceServiceOptions; + private Identifier mAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCTelephoneInterconnectVoiceChannelUser(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" ID:").append(getAddress()); + sb.append(" CALL TIMER:").append(getCallTimerDuration()).append("MS"); + sb.append(" ").append(getVoiceServiceOptions()); + + return sb.toString(); + } + + /** + * Service Options for this channel + */ + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + /** + * Call timer duration in milliseconds. + */ + public long getCallTimerDuration() + { + //Convert from 100 millisecond intervals to milliseconds. + return getMessage().getInt(CALL_TIMER) * 100; + } + + /** + * To/From radio identifier communicating with a landline + */ + public Identifier getAddress() + { + if(mAddress == null) + { + mAddress = APCO25AnyTalkgroup.create(getMessage().getInt(ADDRESS)); + } + + return mAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCUnitAuthenticationCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCUnitAuthenticationCommand.java new file mode 100644 index 00000000..51bc2790 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCUnitAuthenticationCommand.java @@ -0,0 +1,124 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit authentication command + */ +public class LCUnitAuthenticationCommand extends LinkControlWord +{ + private static final int[] WACN = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}; + private static final int[] SYSTEM_ID = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] TARGET_ADDRESS = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63}; + private static final int[] RESERVED = {64, 65, 66, 67, 68, 69, 70, 71}; + + private Identifier mWACN; + private Identifier mSystem; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCUnitAuthenticationCommand(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" WACN:").append(getWACN()); + sb.append(" SYSTEM:").append(getSystem()); + sb.append(" RADIO:").append(getTargetAddress()); + return sb.toString(); + } + + /** + * WACN + */ + public Identifier getWACN() + { + if(mWACN == null) + { + mWACN = APCO25Wacn.create(getMessage().getInt(WACN)); + } + + return mWACN; + } + + /** + * System + */ + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getMessage().getInt(SYSTEM_ID)); + } + + return mSystem; + } + + /** + * Source address + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getWACN()); + mIdentifiers.add(getSystem()); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCUnitRegistrationCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCUnitRegistrationCommand.java new file mode 100644 index 00000000..0575e46d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCUnitRegistrationCommand.java @@ -0,0 +1,124 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit registration command + */ +public class LCUnitRegistrationCommand extends LinkControlWord +{ + private static final int[] WACN = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}; + private static final int[] SYSTEM_ID = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] TARGET_ADDRESS = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63}; + private static final int[] RESERVED = {64, 65, 66, 67, 68, 69, 70, 71}; + + private Identifier mWACN; + private Identifier mSystem; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCUnitRegistrationCommand(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" WACN:").append(getWACN()); + sb.append(" SYSTEM:").append(getSystem()); + sb.append(" RADIO:").append(getTargetAddress()); + return sb.toString(); + } + + /** + * WACN + */ + public Identifier getWACN() + { + if(mWACN == null) + { + mWACN = APCO25Wacn.create(getMessage().getInt(WACN)); + } + + return mWACN; + } + + /** + * System + */ + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getMessage().getInt(SYSTEM_ID)); + } + + return mSystem; + } + + /** + * Source address + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getWACN()); + mIdentifiers.add(getSystem()); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCUnitToUnitAnswerRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCUnitToUnitAnswerRequest.java new file mode 100644 index 00000000..1a61ddce --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCUnitToUnitAnswerRequest.java @@ -0,0 +1,124 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Current user of an APCO25 channel on both inbound and outbound channels + */ +public class LCUnitToUnitAnswerRequest extends LinkControlWord +{ + private static final int[] SERVICE_OPTIONS = {8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] TARGET_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47}; + private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71}; + + private VoiceServiceOptions mVoiceServiceOptions; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCUnitToUnitAnswerRequest(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" ").append(getVoiceServiceOptions()); + return sb.toString(); + } + + /** + * Service Options for this channel + */ + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + /** + * Talkgroup address + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * Source address + */ + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCUnitToUnitVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCUnitToUnitVoiceChannelUser.java new file mode 100644 index 00000000..205db5ca --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/lc/standard/LCUnitToUnitVoiceChannelUser.java @@ -0,0 +1,123 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.lc.standard; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Current user of an APCO25 channel on both inbound and outbound channels + */ +public class LCUnitToUnitVoiceChannelUser extends LinkControlWord +{ + private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] TARGET_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47}; + private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71}; + + private VoiceServiceOptions mVoiceServiceOptions; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCUnitToUnitVoiceChannelUser(BinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" ").append(getVoiceServiceOptions()); + return sb.toString(); + } + + /** + * Service Options for this channel + */ + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + /** + * Talkgroup address + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * Source address + */ + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/EncryptionSyncParameters.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/EncryptionSyncParameters.java new file mode 100644 index 00000000..847c601a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/EncryptionSyncParameters.java @@ -0,0 +1,141 @@ +package io.github.dsheirer.module.decode.p25.message.ldu; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.encryption.APCO25EncryptionKey; + +import java.util.ArrayList; +import java.util.List; + +/** + * Encryption Sync Parameters from Logical Link Data Unit 2 voice frame. + */ +public class EncryptionSyncParameters +{ + private static final int[] MESSAGE_INDICATOR_1 = {0, 1, 2, 3, 4, 5, 6, 7}; + private static final int[] MESSAGE_INDICATOR_2 = {8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] MESSAGE_INDICATOR_3 = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] MESSAGE_INDICATOR_4 = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] MESSAGE_INDICATOR_5 = {32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] MESSAGE_INDICATOR_6 = {40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] MESSAGE_INDICATOR_7 = {48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] MESSAGE_INDICATOR_8 = {56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] MESSAGE_INDICATOR_9 = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] ALGORITHM_ID = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] KEY_ID = {80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95}; + + private BinaryMessage mMessage; + private boolean mValid = true; + private String mMessageIndicator; + private APCO25EncryptionKey mEncryptionKey; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public EncryptionSyncParameters(BinaryMessage message) + { + mMessage = message; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(isValid()) + { + if(isEncryptedAudio()) + { + sb.append(getEncryptionKey()); + sb.append(" MSG INDICATOR:").append(getMessageIndicator()); + } + else + { + sb.append("UNENCRYPTED "); + } + } + else + { + sb.append("***CRC-FAIL***"); + } + + return sb.toString(); + } + + private BinaryMessage getMessage() + { + return mMessage; + } + + /** + * Indicates if this message is valid or not. + */ + public boolean isValid() + { + return mValid; + } + + /** + * Flags this message as valid or invalid + */ + public void setValid(boolean valid) + { + mValid = valid; + } + + public String getMessageIndicator() + { + if(mMessageIndicator == null) + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessage().getHex(MESSAGE_INDICATOR_1, 2)); + sb.append(getMessage().getHex(MESSAGE_INDICATOR_2, 2)); + sb.append(getMessage().getHex(MESSAGE_INDICATOR_3, 2)); + sb.append(getMessage().getHex(MESSAGE_INDICATOR_4, 2)); + sb.append(getMessage().getHex(MESSAGE_INDICATOR_5, 2)); + sb.append(getMessage().getHex(MESSAGE_INDICATOR_6, 2)); + sb.append(getMessage().getHex(MESSAGE_INDICATOR_7, 2)); + sb.append(getMessage().getHex(MESSAGE_INDICATOR_8, 2)); + sb.append(getMessage().getHex(MESSAGE_INDICATOR_9, 2)); + mMessageIndicator = sb.toString(); + } + + return mMessageIndicator; + } + + public APCO25EncryptionKey getEncryptionKey() + { + if(mEncryptionKey == null) + { + mEncryptionKey = APCO25EncryptionKey.create(getMessage().getInt(ALGORITHM_ID), getMessage().getInt(KEY_ID)); + } + + return mEncryptionKey; + } + + /** + * Indicates if the audio stream is encrypted + */ + public boolean isEncryptedAudio() + { + return getEncryptionKey().getValue().isEncrypted(); + } + + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + + if(isEncryptedAudio()) + { + mIdentifiers.add(getEncryptionKey()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/LDU1Message.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/LDU1Message.java index a39f2706..1d18872c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/LDU1Message.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/LDU1Message.java @@ -1,246 +1,218 @@ package io.github.dsheirer.module.decode.p25.message.ldu; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRC; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.edac.Hamming10; import io.github.dsheirer.edac.ReedSolomon_63_47_17; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWordFactory; import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import io.github.dsheirer.module.decode.p25.reference.Vendor; -import io.github.dsheirer.module.decode.p25.reference.VendorLinkControlOpcode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class LDU1Message extends LDUMessage +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * LDU Message is formatted as: + * + * IMBE 1: 0000-0143 + * IMBE 2: 0144-0287 + * LC: 0288-0327 + * IMBE 3: 0328-0471 + * LC: 0472-0511 + * IMBE 4: 0512-0655 + * LC: 0656-0695 + * IMBE 5: 0696-0839 + * LC: 0840-0879 + * IMBE 6: 0880-1023 + * LC: 1024-1063 + * IMBE 7: 1064-1207 + * LC: 1208-1247 + * IMBE 8: 1248-1391 + * LSD: 1392-1423 + * IMBE 9: 1424-1567 + */ + +public class LDU1Message extends LDUMessage implements IFrequencyBandReceiver { private final static Logger mLog = LoggerFactory.getLogger(LDU1Message.class); - public static final int ENCRYPTED_LINK_CONTROL_WORD_FLAG = 352; - public static final int IMPLICIT_VENDOR_FLAG = 353; - public static final int[] OPCODE = {354, 355, 356, 357, 362, 363}; - public static final int[] VENDOR = {364, 365, 366, 367, 372, 373, 374, 375}; + private static final int[] GOLAY_WORD_STARTS = {288, 298, 308, 318, 472, 482, 492, 502, 656, 666, 676, 686, + 840, 850, 860, 870, 1024, 1034, 1044, 1054, 1208, 1218, 1228, 1238}; + private static final int[] CW_HEX_0 = {288, 289, 290, 291, 292, 293}; + private static final int[] CW_HEX_1 = {298, 299, 300, 301, 302, 303}; + private static final int[] CW_HEX_2 = {308, 309, 310, 311, 312, 313}; + private static final int[] CW_HEX_3 = {318, 319, 320, 321, 322, 323}; + private static final int[] CW_HEX_4 = {472, 473, 474, 475, 475, 477}; + private static final int[] CW_HEX_5 = {482, 483, 484, 485, 486, 487}; + private static final int[] CW_HEX_6 = {492, 493, 494, 495, 496, 497}; + private static final int[] CW_HEX_7 = {502, 503, 504, 505, 506, 507}; + private static final int[] CW_HEX_8 = {656, 657, 658, 659, 660, 661}; + private static final int[] CW_HEX_9 = {666, 667, 668, 669, 670, 671}; + private static final int[] CW_HEX_10 = {676, 677, 678, 679, 680, 681}; + private static final int[] CW_HEX_11 = {686, 686, 688, 689, 690, 691}; + private static final int[] RS_HEX_0 = {840, 841, 842, 843, 844, 845}; + private static final int[] RS_HEX_1 = {850, 851, 852, 853, 854, 855}; + private static final int[] RS_HEX_2 = {860, 861, 862, 863, 864, 865}; + private static final int[] RS_HEX_3 = {870, 871, 872, 873, 874, 875}; + private static final int[] RS_HEX_4 = {1024, 1025, 1026, 1027, 1028, 1029}; + private static final int[] RS_HEX_5 = {1034, 1035, 1036, 1037, 1038, 1039}; + private static final int[] RS_HEX_6 = {1044, 1045, 1046, 1047, 1048, 1049}; + private static final int[] RS_HEX_7 = {1054, 1055, 1056, 1057, 1058, 1059}; + private static final int[] RS_HEX_8 = {1208, 1209, 1210, 1211, 1212, 1213}; + private static final int[] RS_HEX_9 = {1218, 1219, 1220, 1221, 1222, 1223}; + private static final int[] RS_HEX_10 = {1228, 1229, 1230, 1231, 1232, 1233}; + private static final int[] RS_HEX_11 = {1238, 1239, 1240, 1241, 1242, 1243}; - public static final int[] GOLAY_WORD_STARTS = { - 352, 362, 372, 382, 536, 546, 556, 566, 720, 730, 740, 750, 904, 914, 924, 934, 1088, - 1098, 1108, 1118, 1272, 1282, 1292, 1302}; + private LinkControlWord mLinkControlWord; + private List mIdentifiers; - public static final int[] CW_HEX_0 = {352, 353, 354, 355, 356, 357}; - public static final int[] CW_HEX_1 = {362, 363, 364, 365, 366, 367}; - public static final int[] CW_HEX_2 = {372, 373, 374, 375, 376, 377}; - public static final int[] CW_HEX_3 = {382, 383, 384, 385, 386, 387}; - public static final int[] CW_HEX_4 = {536, 537, 538, 539, 540, 541}; - public static final int[] CW_HEX_5 = {546, 547, 548, 549, 550, 551}; - public static final int[] CW_HEX_6 = {556, 557, 558, 559, 560, 561}; - public static final int[] CW_HEX_7 = {566, 567, 568, 569, 570, 571}; - public static final int[] CW_HEX_8 = {720, 721, 722, 723, 724, 725}; - public static final int[] CW_HEX_9 = {730, 731, 732, 733, 734, 735}; - public static final int[] CW_HEX_10 = {740, 741, 742, 743, 744, 745}; - public static final int[] CW_HEX_11 = {750, 751, 752, 753, 754, 755}; - public static final int[] RS_HEX_0 = {904, 905, 906, 907, 908, 909}; - public static final int[] RS_HEX_1 = {914, 915, 916, 917, 918, 919}; - public static final int[] RS_HEX_2 = {924, 925, 926, 927, 928, 929}; - public static final int[] RS_HEX_3 = {934, 935, 936, 937, 938, 939}; - public static final int[] RS_HEX_4 = {1088, 1089, 1090, 1091, 1092, 1093}; - public static final int[] RS_HEX_5 = {1098, 1099, 1100, 1101, 1102, 1103}; - public static final int[] RS_HEX_6 = {1108, 1109, 1110, 1111, 1112, 1113}; - public static final int[] RS_HEX_7 = {1118, 1119, 1120, 1121, 1122, 1123}; - public static final int[] RS_HEX_8 = {1272, 1273, 1274, 1275, 1276, 1277}; - public static final int[] RS_HEX_9 = {1282, 1283, 1284, 1285, 1286, 1287}; - public static final int[] RS_HEX_10 = {1292, 1293, 1294, 1295, 1296, 1297}; - public static final int[] RS_HEX_11 = {1302, 1303, 1304, 1305, 1306, 1307}; - - /* Reed-Solomon(24,12,13) code protects the link control word. Maximum - * correctable errors are: Hamming Distance(13) / 2 = 6 */ - public static final ReedSolomon_63_47_17 mReedSolomonDecoder = - new ReedSolomon_63_47_17(6); - - public LDU1Message(BinaryMessage message, DataUnitID duid, - AliasList aliasList) + public LDU1Message(CorrectedBinaryMessage message, int nac, long timestamp) { - super(message, duid, aliasList); - - - /* NID CRC is checked in the message framer, thus a constructed message - * means it passed the CRC */ - mCRC = new CRC[3]; - mCRC[0] = CRC.PASSED; - - checkCRC(); + super(message, nac, timestamp); } - /** - * Subclass constructor from an existing LDU1Message - */ - protected LDU1Message(LDU1Message message) + @Override + public DataUnitID getDUID() { - super(message.getSourceMessage(), DataUnitID.LDU1, message.getAliasList()); - - mCRC = message.mCRC; + return DataUnitID.LOGICAL_LINK_DATA_UNIT_1; } - /** - * Indicates if the contents of the Link Control Word are encrypted. - * - * @return true if the contents of the LCW are encrypted. - */ - public boolean isEncryptedLinkControlWord() + public LinkControlWord getLinkControlWord() { - return mMessage.get(ENCRYPTED_LINK_CONTROL_WORD_FLAG); - } - - private void checkCRC() - { - mCRC[1] = CRC.PASSED; - - /* Hamming( 10,6,3 ) error detection and correction */ - for(int index : GOLAY_WORD_STARTS) + if(mLinkControlWord == null) { - int errors = Hamming10.checkAndCorrect(mMessage, index); + createLinkControlWord(); + } - if(errors > 1) + return mLinkControlWord; + } + + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getNAC()); + + if(getLinkControlWord().isValid()) { - mCRC[1] = CRC.FAILED_CRC; + mIdentifiers.addAll(getLinkControlWord().getIdentifiers()); } } - /* Reed-Solomon( 24,16,9 ) error detection and correction - * Check the Reed-Solomon parity bits. The RS decoder expects the code - * words and reed solomon parity hex codewords in reverse order. - * - * Since this is a truncated RS(63) codes, we pad the code with zeros */ + return mIdentifiers; + } + + /** + * Performs error detection and correction on Link Control Word bits and extracts the 72-bit word + */ + private void createLinkControlWord() + { + //Perform error detection and a maximum of one bit error correction using Hamming( 10,6,3 ) + for(int index : GOLAY_WORD_STARTS) + { + //Attempt to fix any single-bit errors + Hamming10.checkAndCorrect(getMessage(), index); + } + + //Perform Reed-Solomon( 24,16,9 ) error detection and correction. Check the Reed-Solomon parity bits. The RS + // decoder expects the code words and reed solomon parity hex codewords in reverse order. Since this is a + // truncated RS(63) codes, we pad the code with zeros int[] input = new int[63]; int[] output = new int[63]; - input[0] = mMessage.getInt(RS_HEX_11); - input[1] = mMessage.getInt(RS_HEX_10); - input[2] = mMessage.getInt(RS_HEX_9); - input[3] = mMessage.getInt(RS_HEX_8); - input[4] = mMessage.getInt(RS_HEX_7); - input[5] = mMessage.getInt(RS_HEX_6); - input[6] = mMessage.getInt(RS_HEX_5); - input[7] = mMessage.getInt(RS_HEX_4); - input[8] = mMessage.getInt(RS_HEX_3); - input[9] = mMessage.getInt(RS_HEX_2); - input[10] = mMessage.getInt(RS_HEX_1); - input[11] = mMessage.getInt(RS_HEX_0); + input[0] = getMessage().getInt(RS_HEX_11); + input[1] = getMessage().getInt(RS_HEX_10); + input[2] = getMessage().getInt(RS_HEX_9); + input[3] = getMessage().getInt(RS_HEX_8); + input[4] = getMessage().getInt(RS_HEX_7); + input[5] = getMessage().getInt(RS_HEX_6); + input[6] = getMessage().getInt(RS_HEX_5); + input[7] = getMessage().getInt(RS_HEX_4); + input[8] = getMessage().getInt(RS_HEX_3); + input[9] = getMessage().getInt(RS_HEX_2); + input[10] = getMessage().getInt(RS_HEX_1); + input[11] = getMessage().getInt(RS_HEX_0); - input[12] = mMessage.getInt(CW_HEX_11); - input[13] = mMessage.getInt(CW_HEX_10); - input[14] = mMessage.getInt(CW_HEX_9); - input[15] = mMessage.getInt(CW_HEX_8); - input[16] = mMessage.getInt(CW_HEX_7); - input[17] = mMessage.getInt(CW_HEX_6); - input[18] = mMessage.getInt(CW_HEX_5); - input[19] = mMessage.getInt(CW_HEX_4); - input[20] = mMessage.getInt(CW_HEX_3); - input[21] = mMessage.getInt(CW_HEX_2); - input[22] = mMessage.getInt(CW_HEX_1); - input[23] = mMessage.getInt(CW_HEX_0); + input[12] = getMessage().getInt(CW_HEX_11); + input[13] = getMessage().getInt(CW_HEX_10); + input[14] = getMessage().getInt(CW_HEX_9); + input[15] = getMessage().getInt(CW_HEX_8); + input[16] = getMessage().getInt(CW_HEX_7); + input[17] = getMessage().getInt(CW_HEX_6); + input[18] = getMessage().getInt(CW_HEX_5); + input[19] = getMessage().getInt(CW_HEX_4); + input[20] = getMessage().getInt(CW_HEX_3); + input[21] = getMessage().getInt(CW_HEX_2); + input[22] = getMessage().getInt(CW_HEX_1); + input[23] = getMessage().getInt(CW_HEX_0); /* indexes 24 - 62 are defaulted to zero */ - boolean irrecoverableErrors = mReedSolomonDecoder.decode(input, output); + //Reed-Solomon(24,12,13) code protects the link control word. Maximum correctable errors are: 6 + ReedSolomon_63_47_17 reedSolomon_63_47_17 = new ReedSolomon_63_47_17(6); + boolean irrecoverableErrors = reedSolomon_63_47_17.decode(input, output); + + //Transfer error corrected output to a new binary message + BinaryMessage binaryMessage = new BinaryMessage(72); + + int pointer = 0; + + for(int x = 23; x >= 12; x--) + { + if(output[x] != -1) + { + binaryMessage.load(pointer, 6, output[x]); + } + else + { + mLog.error("-1 output"); + } + + pointer += 6; + } + + mLinkControlWord = LinkControlWordFactory.create(binaryMessage); if(irrecoverableErrors) { - mCRC[2] = CRC.FAILED_CRC; + mLinkControlWord.setValid(false); } - else + + //If we corrected any bit errors, update the original message with the bit error count + for(int x = 0; x < 23; x++) { - /* Since we've detected that we can correct the RS words, mark the - * hamming crc as corrected as well, if it is currently failed */ - if(mCRC[1] == CRC.FAILED_CRC) + if(output[x] != input[x]) { - mCRC[1] = CRC.CORRECTED; + getMessage().incrementCorrectedBitCount(Integer.bitCount((output[x] ^ input[x]))); } - - mCRC[2] = CRC.PASSED; - - /* Only fix the codeword data hex codewords */ - repairHexCodeword(input, output, 12, CW_HEX_11); - repairHexCodeword(input, output, 13, CW_HEX_10); - repairHexCodeword(input, output, 14, CW_HEX_9); - repairHexCodeword(input, output, 15, CW_HEX_8); - repairHexCodeword(input, output, 16, CW_HEX_7); - repairHexCodeword(input, output, 17, CW_HEX_6); - repairHexCodeword(input, output, 18, CW_HEX_5); - repairHexCodeword(input, output, 19, CW_HEX_4); - repairHexCodeword(input, output, 20, CW_HEX_3); - repairHexCodeword(input, output, 21, CW_HEX_2); - repairHexCodeword(input, output, 22, CW_HEX_1); - repairHexCodeword(input, output, 23, CW_HEX_0); - } - } - - private void repairHexCodeword(int[] input, int[] output, int index, int[] indexSet) - { - if(input[index] != output[index]) - { - mMessage.load(indexSet[0], 6, output[index]); - mCRC[2] = CRC.CORRECTED; } } @Override - public String getMessage() + public List getChannels() + { + if(getLinkControlWord().isValid() && getLinkControlWord() instanceof IFrequencyBandReceiver) + { + return ((IFrequencyBandReceiver) getLinkControlWord()).getChannels(); + } + + return Collections.EMPTY_LIST; + } + + @Override + public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getMessageStub()); - sb.append(" "); - sb.append(mMessage.toString()); + sb.append(" ").append(getLinkControlWord()); return sb.toString(); } - - public String getMessageStub() - { - StringBuilder sb = new StringBuilder(); - - sb.append(super.getMessageStub()); - - if(isEncryptedLinkControlWord()) - { - sb.append("ENCRYPTED"); - } - else - { - if(isImplicitFormat() || getVendor() == Vendor.STANDARD) - { - sb.append(getOpcode().getLabel()); - } - else - { - sb.append("VENDOR:" + getVendor().getLabel() + " "); - sb.append(getVendorOpcode().getLabel()); - } - } - - return sb.toString(); - } - - public boolean isImplicitFormat() - { - return mMessage.get(IMPLICIT_VENDOR_FLAG); - } - - public LinkControlOpcode getOpcode() - { - return LinkControlOpcode.fromValue(mMessage.getInt(OPCODE)); - } - - public VendorLinkControlOpcode getVendorOpcode() - { - return VendorLinkControlOpcode.fromValue(mMessage.getInt(OPCODE)); - } - - public Vendor getVendor() - { - if(isImplicitFormat()) - { - return Vendor.STANDARD; - } - - return Vendor.fromValue(mMessage.getInt(VENDOR)); - } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/LDU2Message.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/LDU2Message.java index ec72fe01..45b29866 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/LDU2Message.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/LDU2Message.java @@ -1,231 +1,182 @@ package io.github.dsheirer.module.decode.p25.message.ldu; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRC; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.edac.Hamming10; import io.github.dsheirer.edac.ReedSolomon_63_47_17; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Encryption; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; + public class LDU2Message extends LDUMessage { - private final static Logger mLog = LoggerFactory.getLogger( LDU2Message.class ); + private final static Logger mLog = LoggerFactory.getLogger(LDU2Message.class); - public static final int[] MESSAGE_INDICATOR_A = { 352,353,354,355,356,357, - 362,363,364,365,366,367,372,373,374,375,376,377,382,383,384,385,386,387, - 536,537,538,539,540,541,546,547,548,549,550,551 }; - public static final int[] MESSAGE_INDICATOR_B = { 556,557,558,559,560,561, - 566,567,568,569,570,571,720,721,722,723,724,725,730,731,732,733,734,735, - 740,741,742,743,744,745,750,751,752,753,754,755 }; - public static final int[] ALGORITHM_ID = { 904,905,906,907,908,909,914,915 }; - public static final int[] KEY_ID = { 916,917,918,919,924,925,926,927,928, - 929,934,935,936,937,938,939 }; + private static final int[] GOLAY_WORD_STARTS = {288, 298, 308, 318, 472, 482, 492, 502, 656, 666, 676, 686, + 840, 850, 860, 870, 1024, 1034, 1044, 1054, 1208, 1218, 1228, 1238}; + private static final int[] CW_HEX_0 = {288, 289, 290, 291, 292, 293}; + private static final int[] CW_HEX_1 = {298, 299, 300, 301, 302, 303}; + private static final int[] CW_HEX_2 = {308, 309, 310, 311, 312, 313}; + private static final int[] CW_HEX_3 = {318, 319, 320, 321, 322, 323}; + private static final int[] CW_HEX_4 = {472, 473, 474, 475, 475, 477}; + private static final int[] CW_HEX_5 = {482, 483, 484, 485, 486, 487}; + private static final int[] CW_HEX_6 = {492, 493, 494, 495, 496, 497}; + private static final int[] CW_HEX_7 = {502, 503, 504, 505, 506, 507}; + private static final int[] CW_HEX_8 = {656, 657, 658, 659, 660, 661}; + private static final int[] CW_HEX_9 = {666, 667, 668, 669, 670, 671}; + private static final int[] CW_HEX_10 = {676, 677, 678, 679, 680, 681}; + private static final int[] CW_HEX_11 = {686, 686, 688, 689, 690, 691}; + private static final int[] CW_HEX_12 = {840, 841, 842, 843, 844, 845}; + private static final int[] CW_HEX_13 = {850, 851, 852, 853, 854, 855}; + private static final int[] CW_HEX_14 = {860, 861, 862, 863, 864, 865}; + private static final int[] CW_HEX_15 = {870, 871, 872, 873, 874, 875}; + private static final int[] RS_HEX_0 = {1024, 1025, 1026, 1027, 1028, 1029}; + private static final int[] RS_HEX_1 = {1034, 1035, 1036, 1037, 1038, 1039}; + private static final int[] RS_HEX_2 = {1044, 1045, 1046, 1047, 1048, 1049}; + private static final int[] RS_HEX_3 = {1054, 1055, 1056, 1057, 1058, 1059}; + private static final int[] RS_HEX_4 = {1208, 1209, 1210, 1211, 1212, 1213}; + private static final int[] RS_HEX_5 = {1218, 1219, 1220, 1221, 1222, 1223}; + private static final int[] RS_HEX_6 = {1228, 1229, 1230, 1231, 1232, 1233}; + private static final int[] RS_HEX_7 = {1238, 1239, 1240, 1241, 1242, 1243}; - public static final int[] GOLAY_WORD_STARTS = { - 352,362,372,382,536,546,556,566,720,730,740,750,904,914,924,934,1088, - 1098,1108,1118,1272,1282,1292,1302 }; + private EncryptionSyncParameters mEncryptionSyncParameters; + private List mIdentifiers; - public static final int[] CW_HEX_0 = { 352,353,354,355,356,357 }; - public static final int[] CW_HEX_1 = { 362,363,364,365,366,367 }; - public static final int[] CW_HEX_2 = { 372,373,374,375,376,377 }; - public static final int[] CW_HEX_3 = { 382,383,384,385,386,387 }; - public static final int[] CW_HEX_4 = { 536,537,538,539,540,541 }; - public static final int[] CW_HEX_5 = { 546,547,548,549,550,551 }; - public static final int[] CW_HEX_6 = { 556,557,558,559,560,561 }; - public static final int[] CW_HEX_7 = { 566,567,568,569,570,571 }; - public static final int[] CW_HEX_8 = { 720,721,722,723,724,725 }; - public static final int[] CW_HEX_9 = { 730,731,732,733,734,735 }; - public static final int[] CW_HEX_10 = { 740,741,742,743,744,745 }; - public static final int[] CW_HEX_11 = { 750,751,752,753,754,755 }; - public static final int[] CW_HEX_12 = { 904,905,906,907,908,909 }; - public static final int[] CW_HEX_13 = { 914,915,916,917,918,919 }; - public static final int[] CW_HEX_14 = { 924,925,926,927,928,929 }; - public static final int[] CW_HEX_15 = { 934,935,936,937,938,939 }; - public static final int[] RS_HEX_0 = { 1088,1089,1090,1091,1092,1093 }; - public static final int[] RS_HEX_1 = { 1098,1099,1100,1101,1102,1103 }; - public static final int[] RS_HEX_2 = { 1108,1109,1110,1111,1112,1113 }; - public static final int[] RS_HEX_3 = { 1118,1119,1120,1121,1122,1123 }; - public static final int[] RS_HEX_4 = { 1272,1273,1274,1275,1276,1277 }; - public static final int[] RS_HEX_5 = { 1282,1283,1284,1285,1286,1287 }; - public static final int[] RS_HEX_6 = { 1292,1293,1294,1295,1296,1297 }; - public static final int[] RS_HEX_7 = { 1302,1303,1304,1305,1306,1307 }; - - /* Reed-Solomon(24,16,9) code protects the encryption sync word. Maximum - * correctable errors are: Hamming Distance(9) / 2 = 4 */ - public static final ReedSolomon_63_47_17 mReedSolomonDecoder = - new ReedSolomon_63_47_17( 4 ); - - public LDU2Message( BinaryMessage message, DataUnitID duid, AliasList aliasList ) + public LDU2Message(CorrectedBinaryMessage message, int nac, long timestamp) { - super( message, duid, aliasList ); - - /* NID CRC is checked in the message framer, thus a constructed message - * means it passed the CRC */ - mCRC = new CRC[ 3 ]; - mCRC[ 0 ] = CRC.PASSED; - - checkCRC(); + super(message, nac, timestamp); } - /** - * Indicates if the audio stream is encrypted - */ - public boolean isEncryptedAudio() - { - Encryption encryption = getEncryption(); - - return encryption != Encryption.UNENCRYPTED && - encryption != Encryption.UNKNOWN; - } - - private void checkCRC() - { - mCRC[ 1 ] = CRC.PASSED; + @Override + public DataUnitID getDUID() + { + return DataUnitID.LOGICAL_LINK_DATA_UNIT_2; + } - /* Hamming( 10,6,3 ) error detection and correction */ - for( int index: GOLAY_WORD_STARTS ) - { - int errors = Hamming10.checkAndCorrect( mMessage, index ); - - if( errors > 1 ) - { - mCRC[ 1 ] = CRC.FAILED_CRC; - } - } + /** + * Encryption sync parameters allow a user to join an encrypted audio call. + */ + public EncryptionSyncParameters getEncryptionSyncParameters() + { + if(mEncryptionSyncParameters == null) + { + createEncryptionSyncParameters(); + } - /* Reed-Solomon( 24,16,9 ) error detection and correction - * Check the Reed-Solomon parity bits. The RS decoder expects the code - * words and reed solomon parity hex codewords in reverse order. - * - * Since this is a truncated RS(63) codes, we pad the code with zeros */ - int[] input = new int [63]; - int[] output = new int [63]; - - input[ 0 ] = mMessage.getInt( RS_HEX_7 ); - input[ 1 ] = mMessage.getInt( RS_HEX_6 ); - input[ 2 ] = mMessage.getInt( RS_HEX_5 ); - input[ 3 ] = mMessage.getInt( RS_HEX_4 ); - input[ 4 ] = mMessage.getInt( RS_HEX_3 ); - input[ 5 ] = mMessage.getInt( RS_HEX_2 ); - input[ 6 ] = mMessage.getInt( RS_HEX_1 ); - input[ 7 ] = mMessage.getInt( RS_HEX_0 ); - - input[ 8 ] = mMessage.getInt( CW_HEX_15 ); - input[ 9 ] = mMessage.getInt( CW_HEX_14 ); - input[ 10 ] = mMessage.getInt( CW_HEX_13 ); - input[ 11 ] = mMessage.getInt( CW_HEX_12 ); - input[ 12 ] = mMessage.getInt( CW_HEX_11 ); - input[ 13 ] = mMessage.getInt( CW_HEX_10 ); - input[ 14 ] = mMessage.getInt( CW_HEX_9 ); - input[ 15 ] = mMessage.getInt( CW_HEX_8 ); - input[ 16 ] = mMessage.getInt( CW_HEX_7 ); - input[ 17 ] = mMessage.getInt( CW_HEX_6 ); - input[ 18 ] = mMessage.getInt( CW_HEX_5 ); - input[ 19 ] = mMessage.getInt( CW_HEX_4 ); - input[ 20 ] = mMessage.getInt( CW_HEX_3 ); - input[ 21 ] = mMessage.getInt( CW_HEX_2 ); - input[ 22 ] = mMessage.getInt( CW_HEX_1 ); - input[ 23 ] = mMessage.getInt( CW_HEX_0 ); + return mEncryptionSyncParameters; + } + + /** + * Performs error detection and correction against the encryption sync parameters portion of the message and + * extracts that message into an encryption sync parameters instance. + */ + private void createEncryptionSyncParameters() + { + /* Hamming( 10,6,3 ) error detection and correction */ + for(int index : GOLAY_WORD_STARTS) + { + int errors = Hamming10.checkAndCorrect(getMessage(), index); + } + + /* Reed-Solomon( 24,16,9 ) error detection and correction + * Check the Reed-Solomon parity bits. The RS decoder expects the code + * words and reed solomon parity hex codewords in reverse order. + * + * Since this is a truncated RS(63) codes, we pad the code with zeros */ + int[] input = new int[63]; + int[] output = new int[63]; + + input[0] = getMessage().getInt(RS_HEX_7); + input[1] = getMessage().getInt(RS_HEX_6); + input[2] = getMessage().getInt(RS_HEX_5); + input[3] = getMessage().getInt(RS_HEX_4); + input[4] = getMessage().getInt(RS_HEX_3); + input[5] = getMessage().getInt(RS_HEX_2); + input[6] = getMessage().getInt(RS_HEX_1); + input[7] = getMessage().getInt(RS_HEX_0); + + input[8] = getMessage().getInt(CW_HEX_15); + input[9] = getMessage().getInt(CW_HEX_14); + input[10] = getMessage().getInt(CW_HEX_13); + input[11] = getMessage().getInt(CW_HEX_12); + input[12] = getMessage().getInt(CW_HEX_11); + input[13] = getMessage().getInt(CW_HEX_10); + input[14] = getMessage().getInt(CW_HEX_9); + input[15] = getMessage().getInt(CW_HEX_8); + input[16] = getMessage().getInt(CW_HEX_7); + input[17] = getMessage().getInt(CW_HEX_6); + input[18] = getMessage().getInt(CW_HEX_5); + input[19] = getMessage().getInt(CW_HEX_4); + input[20] = getMessage().getInt(CW_HEX_3); + input[21] = getMessage().getInt(CW_HEX_2); + input[22] = getMessage().getInt(CW_HEX_1); + input[23] = getMessage().getInt(CW_HEX_0); /* indexes 24 - 62 are defaulted to zero */ - boolean irrecoverableErrors = mReedSolomonDecoder.decode( input, output ); + //Reed-Solomon(24,16,9) code protects the encryption sync word. Maximum correctable errors are: 4 + ReedSolomon_63_47_17 reedSolomon_63_47_17 = new ReedSolomon_63_47_17(4); + boolean irrecoverableErrors = reedSolomon_63_47_17.decode(input, output); - if( irrecoverableErrors ) + BinaryMessage binaryMessage = new BinaryMessage(96); + + int pointer = 0; + + for(int x = 23; x >= 8; x--) { - mCRC[ 2 ] = CRC.FAILED_CRC; + if(output[x] != -1) + { + binaryMessage.load(pointer, 6, output[x]); + } + + pointer += 6; + } + + mEncryptionSyncParameters = new EncryptionSyncParameters(binaryMessage); + + if(irrecoverableErrors) + { + mEncryptionSyncParameters.setValid(false); } else { - /* Since we've detected that we can correct the RS words, mark the - * hamming crc as corrected as well, if it is currently failed */ - if( mCRC[ 1 ] == CRC.FAILED_CRC ) - { - mCRC[ 1 ] = CRC.CORRECTED; - } - - mCRC[ 2 ] = CRC.PASSED; - - /* Only fix the codeword data hex codewords */ - repairHexCodeword( input, output, 8, CW_HEX_15 ); - repairHexCodeword( input, output, 9, CW_HEX_14 ); - repairHexCodeword( input, output, 10, CW_HEX_13 ); - repairHexCodeword( input, output, 11, CW_HEX_12 ); - repairHexCodeword( input, output, 12, CW_HEX_11 ); - repairHexCodeword( input, output, 13, CW_HEX_10 ); - repairHexCodeword( input, output, 14, CW_HEX_9 ); - repairHexCodeword( input, output, 15, CW_HEX_8 ); - repairHexCodeword( input, output, 16, CW_HEX_7 ); - repairHexCodeword( input, output, 17, CW_HEX_6 ); - repairHexCodeword( input, output, 18, CW_HEX_5 ); - repairHexCodeword( input, output, 19, CW_HEX_4 ); - repairHexCodeword( input, output, 20, CW_HEX_3 ); - repairHexCodeword( input, output, 21, CW_HEX_2 ); - repairHexCodeword( input, output, 22, CW_HEX_1 ); - repairHexCodeword( input, output, 23, CW_HEX_0 ); + //If we corrected any bit errors, update the original message with the bit error count + for(int x = 0; x < 23; x++) + { + if(output[x] != input[x]) + { + getMessage().incrementCorrectedBitCount(Integer.bitCount((output[x] ^ input[x]))); + } + } } - } - - private void repairHexCodeword( int[] input, int[] output, int index, int[] indexSet ) - { - if( input[ index ] != output[ index ] ) - { - mMessage.load( indexSet[ 0 ], 6, output[ index ] ); - mCRC[ 2 ] = CRC.CORRECTED; - } - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - - Encryption encryption = getEncryption(); - - if( encryption != Encryption.UNENCRYPTED ) - { - sb.append( "NAC:" ); - sb.append( getNAC() ); - sb.append( " " ); - sb.append( getDUID().getLabel() ); - - sb.append( " ENCRYPTED VOICE:" ); - sb.append( encryption.name() ); - sb.append( " KEY ID:" + getKeyID() ); - sb.append( " MSG INDICATOR:" + getMessageIndicator() ); - } - else - { - sb.append( super.getMessageStub() ); - sb.append( "UNENCRYPTED" ); - } - - sb.append( " " ); - sb.append( mMessage.toString() ); - - return sb.toString(); } - - public String getMessageIndicator() - { - StringBuilder sb = new StringBuilder(); - - sb.append( mMessage.getHex( MESSAGE_INDICATOR_A, 9 ) ); - sb.append( mMessage.getHex( MESSAGE_INDICATOR_B, 9 ) ); - return sb.toString(); - } - - public Encryption getEncryption() - { - return Encryption.fromValue( mMessage.getInt( ALGORITHM_ID ) ); - } - - public int getKeyID() - { - return mMessage.getInt( KEY_ID ); - } + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getNAC()); + + if(getEncryptionSyncParameters().isValid()) + { + mIdentifiers.addAll(getEncryptionSyncParameters().getIdentifiers()); + } + } + + return mIdentifiers; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" ").append(getEncryptionSyncParameters()); + + return sb.toString(); + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/LDUMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/LDUMessage.java index 149f7dc2..bbb17d36 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/LDUMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/LDUMessage.java @@ -1,9 +1,7 @@ package io.github.dsheirer.module.decode.p25.message.ldu; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.module.decode.p25.message.P25Message; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,78 +10,64 @@ import java.util.List; public abstract class LDUMessage extends P25Message { - private final static Logger mLog = LoggerFactory.getLogger( LDUMessage.class ); + private final static Logger mLog = LoggerFactory.getLogger(LDUMessage.class); - public static final int IMBE_FRAME_1 = 64; - public static final int IMBE_FRAME_2 = 208; - public static final int IMBE_FRAME_3 = 392; - public static final int IMBE_FRAME_4 = 576; - public static final int IMBE_FRAME_5 = 760; - public static final int IMBE_FRAME_6 = 944; - public static final int IMBE_FRAME_7 = 1128; - public static final int IMBE_FRAME_8 = 1312; - public static final int IMBE_FRAME_9 = 1488; + public static final int IMBE_FRAME_1 = 0; + public static final int IMBE_FRAME_2 = 144; + public static final int IMBE_FRAME_3 = 328; + public static final int IMBE_FRAME_4 = 512; + public static final int IMBE_FRAME_5 = 696; + public static final int IMBE_FRAME_6 = 880; + public static final int IMBE_FRAME_7 = 1064; + public static final int IMBE_FRAME_8 = 1248; + public static final int IMBE_FRAME_9 = 1424; - public static final int[] LOW_SPEED_DATA = { 1456,1457,1458,1459,1460,1461, - 1462,1463,1472,1473,1474,1475,1476,1477,1478,1479 }; + public static final int[] LOW_SPEED_DATA = {1392, 1393, 1394, 1395, 1396, 1397, + 1398, 1399, 1408, 1409, 1410, 1411, 1412, 1413, 1414, 1415}; - public LDUMessage( BinaryMessage message, DataUnitID duid, - AliasList aliasList ) + public LDUMessage(CorrectedBinaryMessage message, int nac, long timestamp) { - super( message, duid, aliasList ); + super(message, nac, timestamp); } - - @Override - public String getMessage() + + /** + * Low speed data field contents. + */ + public String getLowSpeedData() { - return getMessageStub(); + return getMessage().getHex(LOW_SPEED_DATA, 4); } - + public String getMessageStub() { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); - sb.append( "NAC:" ); - sb.append( getNAC() ); - sb.append( " " ); - sb.append( getDUID().getLabel() ); - sb.append( " VOICE LSD:" ); - sb.append( getLowSpeedData() ); - sb.append( " " ); - - return sb.toString(); + sb.append(super.getMessageStub()); + sb.append(" VOICE LSD:"); + sb.append(getLowSpeedData()); + + return sb.toString(); } - - public String getLowSpeedData() - { - return mMessage.getHex( LOW_SPEED_DATA, 4 ); - } - public boolean isValid() - { -// return mCRC[ 2 ] != null && mCRC[ 2 ] != CRC.FAILED_CRC; - return true; - } + /** + * Returns a 162 byte array containing 9 IMBE voice frames of 18-bytes + * (144-bits) each. Each frame is intact as transmitted and requires + * deinterleaving, error correction, derandomizing, etc. + */ + public List getIMBEFrames() + { + List frames = new ArrayList(); - /** - * Returns a 162 byte array containing 9 IMBE voice frames of 18-bytes - * (144-bits) each. Each frame is intact as transmitted and requires - * deinterleaving, error correction, derandomizing, etc. - */ - public List getIMBEFrames() - { - List frames = new ArrayList(); - - frames.add( mMessage.get( IMBE_FRAME_1, IMBE_FRAME_1 + 144 ).toByteArray() ); - frames.add( mMessage.get( IMBE_FRAME_2, IMBE_FRAME_2 + 144 ).toByteArray() ); - frames.add( mMessage.get( IMBE_FRAME_3, IMBE_FRAME_3 + 144 ).toByteArray() ); - frames.add( mMessage.get( IMBE_FRAME_4, IMBE_FRAME_4 + 144 ).toByteArray() ); - frames.add( mMessage.get( IMBE_FRAME_5, IMBE_FRAME_5 + 144 ).toByteArray() ); - frames.add( mMessage.get( IMBE_FRAME_6, IMBE_FRAME_6 + 144 ).toByteArray() ); - frames.add( mMessage.get( IMBE_FRAME_7, IMBE_FRAME_7 + 144 ).toByteArray() ); - frames.add( mMessage.get( IMBE_FRAME_8, IMBE_FRAME_8 + 144 ).toByteArray() ); - frames.add( mMessage.get( IMBE_FRAME_9, IMBE_FRAME_9 + 144 ).toByteArray() ); + frames.add(getMessage().get(IMBE_FRAME_1, IMBE_FRAME_1 + 144).toByteArray()); + frames.add(getMessage().get(IMBE_FRAME_2, IMBE_FRAME_2 + 144).toByteArray()); + frames.add(getMessage().get(IMBE_FRAME_3, IMBE_FRAME_3 + 144).toByteArray()); + frames.add(getMessage().get(IMBE_FRAME_4, IMBE_FRAME_4 + 144).toByteArray()); + frames.add(getMessage().get(IMBE_FRAME_5, IMBE_FRAME_5 + 144).toByteArray()); + frames.add(getMessage().get(IMBE_FRAME_6, IMBE_FRAME_6 + 144).toByteArray()); + frames.add(getMessage().get(IMBE_FRAME_7, IMBE_FRAME_7 + 144).toByteArray()); + frames.add(getMessage().get(IMBE_FRAME_8, IMBE_FRAME_8 + 144).toByteArray()); + frames.add(getMessage().get(IMBE_FRAME_9, IMBE_FRAME_9 + 144).toByteArray()); - return frames; - } + return frames; + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/AdjacentSiteStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/AdjacentSiteStatusBroadcast.java deleted file mode 100644 index c1684e42..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/AdjacentSiteStatusBroadcast.java +++ /dev/null @@ -1,172 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.IAdjacentSite; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class AdjacentSiteStatusBroadcast extends LDU1Message implements IdentifierReceiver, IAdjacentSite -{ - public static final int[] LRA = {364, 365, 366, 367, 372, 373, 374, 375}; - public static final int[] SYSTEM_ID = {384, 385, 386, 387, 536, 537, 538, 539, 540, 541, 546, 547}; - public static final int[] RFSS_ID = {548, 549, 550, 551, 556, 557, 558, 559}; - public static final int[] SITE_ID = {560, 561, 566, 567, 568, 569, 570, 571}; - public static final int[] IDENTIFIER = {720, 721, 722, 723}; - public static final int[] CHANNEL = {724, 725, 730, 731, 732, 733, 734, 735, 740, 741, 742, 743}; - public static final int[] SYSTEM_SERVICE_CLASS = {744, 745, 750, 751, 752, 753, 754, 755}; - - private IBandIdentifier mIdentifierUpdate; - - public AdjacentSiteStatusBroadcast(LDU1Message message) - { - super(message); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.ADJACENT_SITE_STATUS_BROADCAST.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" LRA:" + getLRA()); - - sb.append(" SYS:" + getSystemID()); - - sb.append(" SITE:" + getRFSS() + "-" + getSiteID()); - - sb.append(" CHAN:" + getChannel()); - - sb.append(" " + getSystemServiceClass()); - - return sb.toString(); - } - - public String getUniqueID() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getSystemID()); - sb.append(":"); - sb.append(getRFSS()); - sb.append(":"); - sb.append(getSiteID()); - - return sb.toString(); - } - - public String getLRA() - { - return mMessage.getHex(LRA, 2); - } - - public String getSystemID() - { - return mMessage.getHex(SYSTEM_ID, 3); - } - - public String getRFSS() - { - return mMessage.getHex(RFSS_ID, 2); - } - - public String getSiteID() - { - return mMessage.getHex(SITE_ID, 2); - } - - public int getIdentifier() - { - return mMessage.getInt(IDENTIFIER); - } - - public String getChannel() - { - return getIdentifier() + "-" + getChannelNumber(); - } - - public int getChannelNumber() - { - return mMessage.getInt(CHANNEL); - } - - public String getSystemServiceClass() - { - return SystemService.toString(mMessage.getInt(SYSTEM_SERVICE_CLASS)); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - mIdentifierUpdate = message; - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[1]; - - identifiers[0] = getIdentifier(); - - return identifiers; - } - - - @Override - public String getDownlinkChannel() - { - return getChannel(); - } - - @Override - public String getUplinkChannel() - { - return getChannel(); - } - - public long getDownlinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getDownlinkFrequency(getChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getUplinkFrequency(getChannelNumber()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/AdjacentSiteStatusBroadcastExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/AdjacentSiteStatusBroadcastExplicit.java deleted file mode 100644 index 7543f785..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/AdjacentSiteStatusBroadcastExplicit.java +++ /dev/null @@ -1,183 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.IAdjacentSite; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class AdjacentSiteStatusBroadcastExplicit extends LDU1Message implements IdentifierReceiver, IAdjacentSite -{ - public static final int[] LRA = {364, 365, 366, 367, 372, 373, 374, 375}; - public static final int[] TRANSMIT_IDENTIFIER = {376, 377, 382, 383}; - public static final int[] TRANSMIT_CHANNEL = {384, 385, 386, 387, 536, 537, 538, 539, 540, 541, 546, 547}; - public static final int[] RFSS_ID = {548, 549, 550, 551, 556, 557, 558, 559}; - public static final int[] SITE_ID = {560, 561, 566, 567, 568, 569, 570, 571}; - public static final int[] RECEIVE_IDENTIFIER = {720, 721, 722, 723}; - public static final int[] RECEIVE_CHANNEL = {724, 725, 730, 731, 732, 733, 734, 735, 740, 741, 742, 743}; - - private IBandIdentifier mTransmitBand; - private IBandIdentifier mReceiveBand; - - public AdjacentSiteStatusBroadcastExplicit(LDU1Message message) - { - super(message); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" LRA:" + getLRA()); - - sb.append(" SITE:" + getRFSS() + "-" + getSiteID()); - - sb.append(" DNLINK:" + getDownlinkChannel()); - - sb.append(" UPLINK:" + getUplinkChannel()); - - return sb.toString(); - } - - public String getUniqueID() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getSystemID()); - sb.append(":"); - sb.append(getRFSS()); - sb.append(":"); - sb.append(getSiteID()); - - return sb.toString(); - } - - public String getLRA() - { - return mMessage.getHex(LRA, 2); - } - - public String getRFSS() - { - return mMessage.getHex(RFSS_ID, 2); - } - - public String getSystemID() - { - return "***"; - } - - public String getSiteID() - { - return mMessage.getHex(SITE_ID, 2); - } - - public int getTransmitIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public int getTransmitChannel() - { - return mMessage.getInt(TRANSMIT_CHANNEL); - } - - public String getDownlinkChannel() - { - return getTransmitIdentifier() + "-" + getTransmitChannel(); - } - - public int getReceiveIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public int getReceiveChannel() - { - return mMessage.getInt(RECEIVE_CHANNEL); - } - - public String getUplinkChannel() - { - return getReceiveIdentifier() + "-" + getReceiveChannel(); - } - - @Override - public String getSystemServiceClass() - { - return "[]"; - } - - public long getDownlinkFrequency() - { - if(mTransmitBand != null) - { - return mTransmitBand.getDownlinkFrequency(getTransmitChannel()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mReceiveBand != null) - { - return mReceiveBand.getUplinkFrequency(getReceiveChannel()); - } - - return 0; - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitIdentifier()) - { - mTransmitBand = message; - } - - if(identifier == getReceiveIdentifier()) - { - mReceiveBand = message; - } - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[2]; - - identifiers[0] = getTransmitIdentifier(); - identifiers[1] = getReceiveIdentifier(); - - return identifiers; - } - -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/CallAlert.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/CallAlert.java deleted file mode 100644 index 034b1dcd..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/CallAlert.java +++ /dev/null @@ -1,45 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class CallAlert extends LDU1Message -{ - public static final int[] TARGET_ADDRESS = { 536,537,538,539,540,541,546, - 547,548,549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 }; - public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730, - 731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 }; - - public CallAlert( LDU1Message message ) - { - super( message ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.CALL_ALERT.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " SRC ADDR: " + getSourceAddress() ); - sb.append( " TGT ADDR: " + getTargetAddress() ); - - return sb.toString(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/CallTermination.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/CallTermination.java deleted file mode 100644 index 7bc4e929..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/CallTermination.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class CallTermination extends LDU1Message -{ - public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730, - 731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 }; - - public CallTermination( LDU1Message message ) - { - super( message ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " FROM:" + getSourceAddress() ); - - return sb.toString(); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/ChannelIdentifierUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/ChannelIdentifierUpdate.java deleted file mode 100644 index 8654d9e5..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/ChannelIdentifierUpdate.java +++ /dev/null @@ -1,118 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class ChannelIdentifierUpdate extends LDU1Message implements IBandIdentifier -{ - public static final int[] IDENTIFIER = {364, 365, 366, 367}; - public static final int[] BANDWIDTH = {372, 373, 374, 375, 376, 377, 382, 383, 384}; - public static final int[] TRANSMIT_OFFSET = {385, 386, 387, 536, 537, 538, 539, - 540, 541}; - public static final int[] CHANNEL_SPACING = {546, 547, 548, 549, 550, 551, 556, - 557, 558, 559}; - public static final int[] BASE_FREQUENCY = {560, 561, 566, 567, 568, 569, 570, - 571, 720, 721, 722, 723, 724, 725, 730, 731, 732, 733, 734, 735, 740, 741, 742, 743, 744, - 745, 750, 751, 752, 753, 754, 755}; - - public ChannelIdentifierUpdate(LDU1Message message) - { - super(message); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.CHANNEL_IDENTIFIER_UPDATE.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" IDEN:" + getIdentifier()); - - sb.append(" BASE:" + getBaseFrequency()); - - sb.append(" BW:" + getBandwidth()); - - sb.append(" SPACING:" + getChannelSpacing()); - - sb.append(" OFFSET:" + getTransmitOffset()); - - return sb.toString(); - } - - @Override - public int getIdentifier() - { - return mMessage.getInt(IDENTIFIER); - } - - /** - * Channel bandwidth in hertz - */ - @Override - public int getBandwidth() - { - return mMessage.getInt(BANDWIDTH) * 125; - } - - @Override - public long getChannelSpacing() - { - return mMessage.getLong(CHANNEL_SPACING) * 125l; - } - - @Override - public long getBaseFrequency() - { - return mMessage.getLong(BASE_FREQUENCY) * 5l; - } - - @Override - public long getTransmitOffset() - { - return -1 * mMessage.getLong(TRANSMIT_OFFSET) * 250000l; - } - - @Override - public long getDownlinkFrequency(int channelNumber) - { - return getBaseFrequency() + (channelNumber * getChannelSpacing()); - } - - @Override - public long getUplinkFrequency(int channelNumber) - { - return getDownlinkFrequency(channelNumber) + getTransmitOffset(); - } - - @Override - public boolean isTDMA() - { - return false; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/ChannelIdentifierUpdateExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/ChannelIdentifierUpdateExplicit.java deleted file mode 100644 index 7945971b..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/ChannelIdentifierUpdateExplicit.java +++ /dev/null @@ -1,100 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class ChannelIdentifierUpdateExplicit extends LDU1Message implements IBandIdentifier -{ - public static final int[] IDENTIFIER = { 364,365,366,367 }; - public static final int[] BANDWIDTH = { 372,373,374,375 }; - public static final int[] TRANSMIT_OFFSET = { 376,377,382,383,384,385,386, - 387,536,537,538,539,540,541 }; - public static final int[] CHANNEL_SPACING = { 546,547,548,549,550,551,556, - 557,558,559 }; - public static final int[] BASE_FREQUENCY = { 560,561,566,567,568,569,570, - 571,720,721,722,723,724,725,730,731,732,733,734,735,740,741,742,743,744, - 745,750,751,752,753,754,755 }; - - public ChannelIdentifierUpdateExplicit( LDU1Message source ) - { - super( source ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.CHANNEL_IDENTIFIER_UPDATE_EXPLICIT.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " IDEN:" + getIdentifier() ); - - sb.append( " BASE:" + getBaseFrequency() ); - - sb.append( " BW:" + getBandwidth() ); - - sb.append( " SPACING:" + getChannelSpacing() ); - - sb.append( " OFFSET:" + getTransmitOffset() ); - - return sb.toString(); - } - - @Override - public int getIdentifier() - { - return mMessage.getInt( IDENTIFIER ); - } - - /** - * Channel bandwidth in hertz - */ - @Override - public int getBandwidth() - { - return mMessage.getInt( BANDWIDTH ) * 125; - } - - @Override - public long getChannelSpacing() - { - return mMessage.getLong( CHANNEL_SPACING ) * 125l; - } - - @Override - public long getBaseFrequency() - { - return mMessage.getLong( BASE_FREQUENCY ) * 5l; - } - - @Override - public long getTransmitOffset() - { - return -1 * mMessage.getLong( TRANSMIT_OFFSET ) * 250000l; - } - - @Override - public long getDownlinkFrequency(int channelNumber) - { - return getBaseFrequency() + ( channelNumber * getChannelSpacing() ); - } - - @Override - public long getUplinkFrequency(int channelNumber) - { - return getDownlinkFrequency(channelNumber) + getTransmitOffset(); - } - - @Override - public boolean isTDMA() - { - return false; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/ExtendedFunctionCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/ExtendedFunctionCommand.java deleted file mode 100644 index 77fa4256..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/ExtendedFunctionCommand.java +++ /dev/null @@ -1,86 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.ExtendedFunction; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ExtendedFunctionCommand extends LDU1Message -{ - private final static Logger mLog = - LoggerFactory.getLogger( ExtendedFunctionCommand.class ); - public static final int[] EXTENDED_FUNCTION = { 364,365,366,367,372,373,374, - 375,376,377,382,383,384,385,386,387 }; - public static final int[] ARGUMENT = { 536,537,538,539,540,541,546,547,548, - 549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 }; - public static final int[] TARGET_ADDRESS = { 720,721,722,723,724,725,730, - 731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 }; - - public ExtendedFunctionCommand( LDU1Message source ) - { - super( source ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.EXTENDED_FUNCTION_COMMAND.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " EXTENDED FUNCTION:" + getExtendedFunction().getLabel() ); - - sb.append( " ARGUMENT: " + getTargetAddress() ); - - sb.append( " TGT ADDR: " + getTargetAddress() ); - - return sb.toString(); - } - - public ExtendedFunction getExtendedFunction() - { - return ExtendedFunction.fromValue( mMessage.getInt( EXTENDED_FUNCTION ) ); - } - - public String getArgument() - { - return mMessage.getHex( ARGUMENT, 6 ); - } - - public String getSourceAddress() - { - switch( getExtendedFunction() ) - { - case RADIO_CHECK: - case RADIO_CHECK_ACK: - case RADIO_DETACH: - case RADIO_DETACH_ACK: - case RADIO_INHIBIT: - case RADIO_INHIBIT_ACK: - case RADIO_UNINHIBIT: - case RADIO_UNINHIBIT_ACK: - return getArgument(); - default: - break; - } - - return null; - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - @Override - public String getToID() - { - return getTargetAddress(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/GroupAffiliationQuery.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/GroupAffiliationQuery.java deleted file mode 100644 index 71257357..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/GroupAffiliationQuery.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class GroupAffiliationQuery extends LDU1Message -{ - private final static Logger mLog = - LoggerFactory.getLogger( GroupAffiliationQuery.class ); - public static final int[] TARGET_ADDRESS = { 536,537,538,539,540,541,546, - 547,548,549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 }; - public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730, - 731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 }; - - public GroupAffiliationQuery( LDU1Message message ) - { - super( message ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.GROUP_AFFILIATION_QUERY.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " FROM:" + getSourceAddress() ); - - sb.append( " TO:" + getTargetAddress() ); - - return sb.toString(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/GroupVoiceChannelUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/GroupVoiceChannelUpdate.java deleted file mode 100644 index 0f2580b6..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/GroupVoiceChannelUpdate.java +++ /dev/null @@ -1,189 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class GroupVoiceChannelUpdate extends LDU1Message implements IdentifierReceiver -{ - public static final int[] IDENTIFIER_A = {364, 365, 366, 367}; - public static final int[] CHANNEL_A = {372, 373, 374, 375, 376, 377, 382, 383, 384, 385, 386, 387}; - public static final int[] GROUP_ADDRESS_A = {536, 537, 538, 539, 540, 541, 546, 547, 548, 549, 550, 551, 556, 557, - 558, 559}; - public static final int[] IDENTIFIER_B = {560, 561, 566, 567}; - public static final int[] CHANNEL_B = {568, 569, 570, 571, 720, 721, 722, 723, 724, 725, 730, 731}; - public static final int[] GROUP_ADDRESS_B = {732, 733, 734, 735, 740, 741, 742, 743, 744, 745, 750, 751, 752, 753, - 754, 755}; - - private IBandIdentifier mIdentifierUpdateA; - private IBandIdentifier mIdentifierUpdateB; - - public GroupVoiceChannelUpdate(LDU1Message message) - { - super(message); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.GROUP_VOICE_CHANNEL_UPDATE.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" CHAN A:" + getChannelA()); - - sb.append(" GRP A:" + getGroupAddressA()); - - if(hasChannelB()) - { - sb.append(" CHAN B:" + getChannelA()); - - sb.append(" GRP B:" + getGroupAddressA()); - } - - return sb.toString(); - } - - public int getChannelIdentifierA() - { - return mMessage.getInt(IDENTIFIER_A); - } - - public int getChannelNumberA() - { - return mMessage.getInt(CHANNEL_A); - } - - public String getChannelA() - { - return getChannelIdentifierA() + "-" + getChannelNumberA(); - } - - public int getChannelIdentifierB() - { - return mMessage.getInt(IDENTIFIER_B); - } - - public int getChannelNumberB() - { - return mMessage.getInt(CHANNEL_B); - } - - public String getChannelB() - { - return getChannelIdentifierB() + "-" + getChannelNumberB(); - } - - public boolean hasChannelB() - { - return getChannelNumberB() != 0; - } - - public String getGroupAddressA() - { - return mMessage.getHex(GROUP_ADDRESS_A, 4); - } - - public String getGroupAddressB() - { - return mMessage.getHex(GROUP_ADDRESS_B, 4); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getChannelIdentifierA()) - { - mIdentifierUpdateA = message; - } - if(identifier == getChannelIdentifierB()) - { - mIdentifierUpdateB = message; - } - } - - @Override - public int[] getIdentifiers() - { - if(hasChannelB()) - { - int[] identifiers = new int[2]; - identifiers[0] = getChannelIdentifierA(); - identifiers[1] = getChannelIdentifierB(); - - return identifiers; - } - else - { - int[] identifiers = new int[1]; - identifiers[0] = getChannelIdentifierA(); - - return identifiers; - } - } - - public long getDownlinkFrequencyA() - { - if(mIdentifierUpdateA != null) - { - return mIdentifierUpdateA.getDownlinkFrequency(getChannelNumberA()); - } - - return 0; - } - - public long getUplinkFrequencyA() - { - if(mIdentifierUpdateA != null) - { - return mIdentifierUpdateA.getUplinkFrequency(getChannelNumberA()); - } - - return 0; - } - - public long getDownlinkFrequencyB() - { - if(mIdentifierUpdateB != null) - { - return mIdentifierUpdateB.getDownlinkFrequency(getChannelNumberB()); - } - - return 0; - } - - public long getUplinkFrequencyB() - { - if(mIdentifierUpdateB != null) - { - return mIdentifierUpdateB.getUplinkFrequency(getChannelNumberB()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/GroupVoiceChannelUpdateExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/GroupVoiceChannelUpdateExplicit.java deleted file mode 100644 index 91cc6eaa..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/GroupVoiceChannelUpdateExplicit.java +++ /dev/null @@ -1,171 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class GroupVoiceChannelUpdateExplicit extends LDU1Message implements IdentifierReceiver -{ - public static final int EMERGENCY_FLAG = 376; - public static final int ENCRYPTED_CHANNEL_FLAG = 377; - public static final int DUPLEX_MODE = 382; - public static final int SESSION_MODE = 383; - public static final int[] GROUP_ADDRESS = {536, 537, 538, 539, 540, 541, 546, 547, 548, 549, 550, 551, 556, 557, 558, 559}; - public static final int[] TRANSMIT_IDENTIFIER = {560, 561, 566, 567}; - public static final int[] TRANSMIT_CHANNEL = {568, 569, 570, 571, 720, 721, 722, 723, 724, 725, 730, 731}; - public static final int[] RECEIVE_IDENTIFIER = {732, 733, 734, 735}; - public static final int[] RECEIVE_CHANNEL = {740, 741, 742, 743, 744, 745, 750, 751, 752, 753, 754, 755}; - - private IBandIdentifier mTransmitIdentifierUpdate; - private IBandIdentifier mReceiveIdentifierUpdate; - - public GroupVoiceChannelUpdateExplicit(LDU1Message message) - { - super(message); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - if(isEmergency()) - { - sb.append(" EMERGENCY"); - } - if(isEncryptedChannel()) - { - sb.append(" ENCRYPTED CHANNEL"); - } - - - return sb.toString(); - } - - public boolean isEmergency() - { - return mMessage.get(EMERGENCY_FLAG); - } - - public boolean isEncryptedChannel() - { - return mMessage.get(ENCRYPTED_CHANNEL_FLAG); - } - - public DuplexMode getDuplexMode() - { - return mMessage.get(DUPLEX_MODE) ? DuplexMode.FULL : DuplexMode.HALF; - } - - public SessionMode getSessionMode() - { - return mMessage.get(SESSION_MODE) ? - SessionMode.CIRCUIT : SessionMode.PACKET; - } - - public int getTransmitChannelIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public int getTransmitChannelNumber() - { - return mMessage.getInt(TRANSMIT_CHANNEL); - } - - public String getTransmitChannel() - { - return getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber(); - } - - public int getReceiveChannelIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public int getReceiveChannelNumber() - { - return mMessage.getInt(RECEIVE_CHANNEL); - } - - public String getReceiveChannel() - { - return getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber(); - } - - public String getGroupAddress() - { - return mMessage.getHex(GROUP_ADDRESS, 4); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitChannelIdentifier()) - { - mTransmitIdentifierUpdate = message; - } - if(identifier == getReceiveChannelIdentifier()) - { - mReceiveIdentifierUpdate = message; - } - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[2]; - - identifiers[0] = getTransmitChannelIdentifier(); - identifiers[1] = getReceiveChannelIdentifier(); - - return identifiers; - } - - public long getDownlinkFrequency() - { - if(mTransmitIdentifierUpdate != null) - { - return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mReceiveIdentifierUpdate != null) - { - return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/GroupVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/GroupVoiceChannelUser.java deleted file mode 100644 index 91a3d306..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/GroupVoiceChannelUser.java +++ /dev/null @@ -1,86 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class GroupVoiceChannelUser extends LDU1Message -{ - private final static Logger mLog = - LoggerFactory.getLogger( GroupVoiceChannelUser.class ); - /* Service Options */ - public static final int EMERGENCY_FLAG = 376; - public static final int ENCRYPTED_CHANNEL_FLAG = 377; - public static final int DUPLEX_MODE = 378; - public static final int SESSION_MODE = 379; - public static final int[] GROUP_ADDRESS = { 548,549,550,551,556,557,558,559, - 560,561,566,567,568,569,570,571 }; - public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730, - 731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 }; - - public GroupVoiceChannelUser( LDU1Message message ) - { - super( message ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.GROUP_VOICE_CHANNEL_USER.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - if( isEmergency() ) - { - sb.append( " EMERGENCY" ); - } - if( isEncryptedChannel() ) - { - sb.append( " ENCRYPTED CHANNEL" ); - } - - sb.append( " FROM:" + getSourceAddress() ); - - sb.append( " TO:" + getGroupAddress() ); - - return sb.toString(); - } - - public boolean isEmergency() - { - return mMessage.get( EMERGENCY_FLAG ); - } - - public boolean isEncryptedChannel() - { - return mMessage.get( ENCRYPTED_CHANNEL_FLAG ); - } - - public DuplexMode getDuplexMode() - { - return mMessage.get( DUPLEX_MODE ) ? DuplexMode.FULL : DuplexMode.HALF; - } - - public SessionMode getSessionMode() - { - return mMessage.get( SESSION_MODE ) ? - SessionMode.CIRCUIT : SessionMode.PACKET; - } - - public String getGroupAddress() - { - return mMessage.getHex( GROUP_ADDRESS, 4 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/LDULCMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/LDULCMessageFactory.java deleted file mode 100644 index de4ef637..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/LDULCMessageFactory.java +++ /dev/null @@ -1,95 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.Vendor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - -public class LDULCMessageFactory -{ - private final static Logger mLog = - LoggerFactory.getLogger( LDULCMessageFactory.class ); - - /** - * Constructs a new sub-class of the TDULC message if the opcode is recognized - * or returns the original message. - */ - public static LDU1Message getMessage(LDU1Message message ) - { - if( message.isImplicitFormat() || message.getVendor() == Vendor.STANDARD ) - { - switch( message.getOpcode() ) - { - case ADJACENT_SITE_STATUS_BROADCAST: - return new AdjacentSiteStatusBroadcast( message ); - case ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT: - return new AdjacentSiteStatusBroadcastExplicit( message ); - case CALL_ALERT: - return new CallAlert( message ); - case CALL_TERMINATION_OR_CANCELLATION: - return new CallTermination( message ); - case CHANNEL_IDENTIFIER_UPDATE: - return new ChannelIdentifierUpdate( message ); - case CHANNEL_IDENTIFIER_UPDATE_EXPLICIT: - return new ChannelIdentifierUpdateExplicit( message ); - case EXTENDED_FUNCTION_COMMAND: - return new ExtendedFunctionCommand( message ); - case GROUP_AFFILIATION_QUERY: - return new GroupAffiliationQuery( message ); - case GROUP_VOICE_CHANNEL_UPDATE: - return new GroupVoiceChannelUpdate( message ); - case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT: - return new GroupVoiceChannelUpdateExplicit( message ); - case GROUP_VOICE_CHANNEL_USER: - return new GroupVoiceChannelUser( message ); - case MESSAGE_UPDATE: - return new MessageUpdate( message ); - case NETWORK_STATUS_BROADCAST: - return new NetworkStatusBroadcast( message ); - case NETWORK_STATUS_BROADCAST_EXPLICIT: - return new NetworkStatusBroadcastExplicit( message ); - case PROTECTION_PARAMETER_BROADCAST: - return new ProtectionParameterBroadcast( message ); - case RFSS_STATUS_BROADCAST: - return new RFSSStatusBroadcast( message ); - case RFSS_STATUS_BROADCAST_EXPLICIT: - return new RFSSStatusBroadcastExplicit( message ); - case SECONDARY_CONTROL_CHANNEL_BROADCAST: - return new SecondaryControlChannelBroadcast( message ); - case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: - return new SecondaryControlChannelBroadcastExplicit( message ); - case STATUS_QUERY: - return new StatusQuery( message ); - case STATUS_UPDATE: - return new StatusUpdate( message ); - case SYSTEM_SERVICE_BROADCAST: - return new SystemServiceBroadcast( message ); - case TELEPHONE_INTERCONNECT_ANSWER_REQUEST: - return new TelephoneInterconnectAnswerRequest( message ); - case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER: - return new TelephoneInterconnectVoiceChannelUser( message ); - case UNIT_AUTHENTICATION_COMMAND: - return new UnitAuthenticationCommand( message ); - case UNIT_REGISTRATION_COMMAND: - return new UnitRegistrationCommand( message ); - case UNIT_TO_UNIT_ANSWER_REQUEST: - return new UnitToUnitAnswerRequest( message ); - case UNIT_TO_UNIT_VOICE_CHANNEL_USER: - return new UnitToUnitVoiceChannelUser( message ); - default: - break; - } - } - else - { - switch( message.getVendor() ) - { - default: - break; - } - } - - return message; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/MessageUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/MessageUpdate.java deleted file mode 100644 index f4668f99..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/MessageUpdate.java +++ /dev/null @@ -1,57 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MessageUpdate extends LDU1Message -{ - private final static Logger mLog = - LoggerFactory.getLogger( MessageUpdate.class ); - public static final int[] SHORT_DATA_MESSAGE = { 364,365,366,367,372,373, - 374,375,376,377,382,383,384,385,386,387 }; - public static final int[] TARGET_ADDRESS = { 536,537,538,539,540,541,546, - 547,548,549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 }; - public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730, - 731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 }; - - public MessageUpdate( LDU1Message message ) - { - super( message ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.MESSAGE_UPDATE.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " MSG:" + getShortDataMessage() ); - sb.append( " SRC ADDR: " + getSourceAddress() ); - sb.append( " TGT ADDR: " + getTargetAddress() ); - - return sb.toString(); - } - - public String getShortDataMessage() - { - return mMessage.getHex( SHORT_DATA_MESSAGE, 4 ); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/NetworkStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/NetworkStatusBroadcast.java deleted file mode 100644 index 528439e5..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/NetworkStatusBroadcast.java +++ /dev/null @@ -1,129 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import io.github.dsheirer.module.decode.p25.reference.Service; - -public class NetworkStatusBroadcast extends LDU1Message implements IdentifierReceiver -{ - public static final int[] NETWORK_ID = {376, 377, 382, 383, 384, 385, 386, 387, 536, 537, 538, 539, 540, 541, 546, - 547, 548, 549, 550, 551}; - public static final int[] SYSTEM_ID = {556, 557, 558, 559, 560, 561, 566, 567, 568, 569, 570, 571}; - public static final int[] IDENTIFIER = {720, 721, 722, 723}; - public static final int[] CHANNEL = {724, 725, 730, 731, 732, 733, 734, 735, 740, 741, 742, 743}; - public static final int[] SYSTEM_SERVICE_CLASS = {744, 745, 750, 751, 752, 753, 754, 755}; - - private IBandIdentifier mIdentifierUpdate; - - public NetworkStatusBroadcast(LDU1Message source) - { - super(source); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.NETWORK_STATUS_BROADCAST.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" NETWORK:" + getNetworkID()); - sb.append(" SYS:" + getSystem()); - sb.append(" CHAN:" + getChannel()); - sb.append(" " + Service.getServices(getSystemServiceClass()).toString()); - - return sb.toString(); - } - - public String getNetworkID() - { - return mMessage.getHex(NETWORK_ID, 5); - } - - public String getSystem() - { - return mMessage.getHex(SYSTEM_ID, 3); - } - - public int getIdentifier() - { - return mMessage.getInt(IDENTIFIER); - } - - public int getChannelNumber() - { - return mMessage.getInt(CHANNEL); - } - - public String getChannel() - { - return getIdentifier() + "-" + getChannelNumber(); - } - - public int getSystemServiceClass() - { - return mMessage.getInt(SYSTEM_SERVICE_CLASS); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - mIdentifierUpdate = message; - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[1]; - - identifiers[0] = getIdentifier(); - - return identifiers; - } - - public long getDownlinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getDownlinkFrequency(getChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getUplinkFrequency(getChannelNumber()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/NetworkStatusBroadcastExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/NetworkStatusBroadcastExplicit.java deleted file mode 100644 index b51bf9d0..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/NetworkStatusBroadcastExplicit.java +++ /dev/null @@ -1,148 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class NetworkStatusBroadcastExplicit extends LDU1Message implements IdentifierReceiver -{ - public static final int[] NETWORK_ID = {364, 365, 366, 367, 372, 373, 374, 375, 376, 377, 382, 383, 384, 385, 386, - 387, 536, 537, 538, 539}; - public static final int[] SYSTEM_ID = {540, 541, 546, 547, 548, 549, 550, 551, 556, 557, 558, 559}; - public static final int[] TRANSMIT_IDENTIFIER = {560, 561, 566, 567}; - public static final int[] TRANSMIT_CHANNEL = {568, 569, 570, 571, 720, 721, 722, 723, 724, 725, 730, 731}; - public static final int[] RECEIVE_IDENTIFIER = {732, 733, 734, 735}; - public static final int[] RECEIVE_CHANNEL = {740, 741, 742, 743, 744, 745, 750, 751, 752, 753, 754, 755}; - - private IBandIdentifier mTransmitIdentifierUpdate; - private IBandIdentifier mReceiveIdentifierUpdate; - - public NetworkStatusBroadcastExplicit(LDU1Message message) - { - super(message); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.NETWORK_STATUS_BROADCAST_EXPLICIT.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" NETWORK:" + getNetworkID()); - sb.append(" SYS:" + getSystem()); - sb.append(" TRANSMIT:" + getTransmitChannel()); - sb.append(" RECEIVE:" + getReceiveChannel()); - - return sb.toString(); - } - - public String getNetworkID() - { - return mMessage.getHex(NETWORK_ID, 5); - } - - public String getSystem() - { - return mMessage.getHex(SYSTEM_ID, 3); - } - - public int getTransmitIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public int getTransmitChannelNumber() - { - return mMessage.getInt(TRANSMIT_CHANNEL); - } - - public String getTransmitChannel() - { - return getTransmitIdentifier() + "-" + getTransmitChannelNumber(); - } - - public int getReceiveIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public int getReceiveChannelNumber() - { - return mMessage.getInt(RECEIVE_CHANNEL); - } - - public String getReceiveChannel() - { - return getReceiveIdentifier() + "-" + getReceiveChannelNumber(); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitIdentifier()) - { - mTransmitIdentifierUpdate = message; - } - if(identifier == getReceiveIdentifier()) - { - mReceiveIdentifierUpdate = message; - } - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[2]; - - identifiers[0] = getTransmitIdentifier(); - identifiers[1] = getReceiveIdentifier(); - - return identifiers; - } - - public long getDownlinkFrequency() - { - if(mTransmitIdentifierUpdate != null) - { - return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mReceiveIdentifierUpdate != null) - { - return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/ProtectionParameterBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/ProtectionParameterBroadcast.java deleted file mode 100644 index 3bb551d0..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/ProtectionParameterBroadcast.java +++ /dev/null @@ -1,58 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.Encryption; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ProtectionParameterBroadcast extends LDU1Message -{ - private final static Logger mLog = - LoggerFactory.getLogger( ProtectionParameterBroadcast.class ); - - public static final int[] ALGORITHM_ID = { 536,537,538,539,540,541,546,547 }; - public static final int[] KEY_ID = { 548,549,550,551,556,557,558,559,560, - 561,566,567,568,569,570,571 }; - public static final int[] TARGET_ADDRESS = { 720,721,722,723,724,725,730, - 731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 }; - - public ProtectionParameterBroadcast( LDU1Message message ) - { - super( message ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.PROTECTION_PARAMETER_BROADCAST.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " ENCRYPTION:" + getEncryption().name() ); - sb.append( " KEY:" + getEncryptionKey() ); - sb.append( " ADDRESSS:" + getTargetAddress() ); - - return sb.toString(); - } - - public Encryption getEncryption() - { - return Encryption.fromValue( mMessage.getInt( ALGORITHM_ID ) ); - } - - public int getEncryptionKey() - { - return mMessage.getInt( KEY_ID ); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/RFSSStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/RFSSStatusBroadcast.java deleted file mode 100644 index bae98a54..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/RFSSStatusBroadcast.java +++ /dev/null @@ -1,147 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class RFSSStatusBroadcast extends LDU1Message implements IdentifierReceiver -{ - public static final int[] LRA = {364, 365, 366, 367, 372, 373, 374, 375}; - public static final int[] SYSTEM_ID = {384, 385, 386, 387, 536, 537, 538, 539, 540, 541, 546, 547}; - public static final int[] RFSS_ID = {548, 549, 550, 551, 556, 557, 558, 559}; - public static final int[] SITE_ID = {560, 561, 566, 567, 568, 569, 570, 571}; - public static final int[] IDENTIFIER = {720, 721, 722, 723}; - public static final int[] CHANNEL = {724, 725, 730, 731, 732, 733, 734, 735, 740, 741, 742, 743}; - public static final int[] SYSTEM_SERVICE_CLASS = {744, 745, 750, 751, 752, 753, 754, 755}; - - private IBandIdentifier mIdentifierUpdate; - - public RFSSStatusBroadcast(LDU1Message message) - { - super(message); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.RFSS_STATUS_BROADCAST.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" LRA:" + getLocationRegistrationArea()); - - sb.append(" SYS:" + getSystem()); - - sb.append(" SITE:" + getRFSubsystemID() + "-" + getSiteID()); - - sb.append(" CHAN:" + getChannelNumber()); - - sb.append(" " + SystemService.toString(getSystemServiceClass())); - - sb.append(" " + mMessage.toString()); - - return sb.toString(); - } - - public String getLocationRegistrationArea() - { - return mMessage.getHex(LRA, 2); - } - - public String getSystem() - { - return mMessage.getHex(SYSTEM_ID, 3); - } - - public String getRFSubsystemID() - { - return mMessage.getHex(RFSS_ID, 2); - } - - public String getSiteID() - { - return mMessage.getHex(SITE_ID, 2); - } - - public int getIdentifier() - { - return mMessage.getInt(IDENTIFIER); - } - - public String getChannel() - { - return getIdentifier() + "-" + getChannelNumber(); - } - - public int getChannelNumber() - { - return mMessage.getInt(CHANNEL); - } - - public int getSystemServiceClass() - { - return mMessage.getInt(SYSTEM_SERVICE_CLASS); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - mIdentifierUpdate = message; - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[1]; - - identifiers[0] = getIdentifier(); - - return identifiers; - } - - public long getDownlinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getDownlinkFrequency(getChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getUplinkFrequency(getChannelNumber()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/RFSSStatusBroadcastExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/RFSSStatusBroadcastExplicit.java deleted file mode 100644 index 17c5a9d6..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/RFSSStatusBroadcastExplicit.java +++ /dev/null @@ -1,168 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class RFSSStatusBroadcastExplicit extends LDU1Message implements IdentifierReceiver -{ - public static final int[] LRA = {364, 365, 366, 367, 372, 373, 374, 375}; - public static final int[] RECEIVE_IDENTIFIER = {376, 377, 382, 383}; - public static final int[] RECEIVE_CHANNEL = {384, 385, 386, 387, 536, 537, 538, 539, 540, 541, 546, 547}; - public static final int[] RFSS_ID = {548, 549, 550, 551, 556, 557, 558, 559}; - public static final int[] SITE_ID = {560, 561, 566, 567, 568, 569, 570, 571}; - public static final int[] TRANSMIT_IDENTIFIER = {720, 721, 722, 723}; - public static final int[] TRANSMIT_CHANNEL = {724, 725, 730, 731, 732, 733, 734, 735, 740, 741, 742, 743}; - public static final int[] SYSTEM_SERVICE_CLASS = {744, 745, 750, 751, 752, 753, 754, 755}; - - private IBandIdentifier mTransmitIdentifierUpdate; - private IBandIdentifier mReceiveIdentifierUpdate; - - public RFSSStatusBroadcastExplicit(LDU1Message source) - { - super(source); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.RFSS_STATUS_BROADCAST_EXPLICIT.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" LRA:" + getLocationRegistrationArea()); - - sb.append(" SITE:" + getRFSubsystemID() + "-" + getSiteID()); - - sb.append(" TRANSMIT:" + getTransmitChannelNumber()); - - sb.append(" RECEIVE:" + getReceiveChannelNumber()); - - sb.append(" " + SystemService.toString(getSystemServiceClass())); - - sb.append(" " + mMessage.toString()); - - return sb.toString(); - } - - public String getLocationRegistrationArea() - { - return mMessage.getHex(LRA, 2); - } - - public String getRFSubsystemID() - { - return mMessage.getHex(RFSS_ID, 2); - } - - public String getSiteID() - { - return mMessage.getHex(SITE_ID, 2); - } - - public int getTransmitIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public String getTransmitChannel() - { - return getTransmitIdentifier() + "-" + getTransmitChannelNumber(); - } - - public int getTransmitChannelNumber() - { - return mMessage.getInt(TRANSMIT_CHANNEL); - } - - public int getReceiveIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public String getReceiveChannel() - { - return getReceiveIdentifier() + "-" + getReceiveChannelNumber(); - } - - public int getReceiveChannelNumber() - { - return mMessage.getInt(RECEIVE_CHANNEL); - } - - public int getSystemServiceClass() - { - return mMessage.getInt(SYSTEM_SERVICE_CLASS); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitIdentifier()) - { - mTransmitIdentifierUpdate = message; - } - - if(identifier == getReceiveIdentifier()) - { - mReceiveIdentifierUpdate = message; - } - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[2]; - - identifiers[0] = getTransmitIdentifier(); - identifiers[1] = getReceiveIdentifier(); - - return identifiers; - } - - public long getDownlinkFrequency() - { - if(mTransmitIdentifierUpdate != null) - { - return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mReceiveIdentifierUpdate != null) - { - return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/SecondaryControlChannelBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/SecondaryControlChannelBroadcast.java deleted file mode 100644 index 6beae5d3..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/SecondaryControlChannelBroadcast.java +++ /dev/null @@ -1,185 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class SecondaryControlChannelBroadcast extends LDU1Message implements IdentifierReceiver -{ - public static final int[] RFSS_ID = {364, 365, 366, 367, 372, 373, 374, 375}; - public static final int[] SITE_ID = {376, 377, 382, 383, 384, 385, 386, 387}; - public static final int[] IDENTIFIER_A = {536, 537, 538, 539}; - public static final int[] CHANNEL_A = {540, 541, 546, 547, 548, 549, 550, 551, 556, 557, 558, 559}; - public static final int[] SYSTEM_SERVICE_CLASS_A = {560, 561, 566, 567, 568, 569, 570, 571}; - public static final int[] IDENTIFIER_B = {720, 721, 722, 723}; - public static final int[] CHANNEL_B = {724, 725, 730, 731, 732, 733, 734, 735, 740, 741, 742, 743}; - public static final int[] SYSTEM_SERVICE_CLASS_B = {744, 745, 750, 751, 752, 753, 754, 755}; - - private IBandIdentifier mIdentifierProviderA; - private IBandIdentifier mIdentifierProviderB; - - public SecondaryControlChannelBroadcast(LDU1Message message) - { - super(message); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.SECONDARY_CONTROL_CHANNEL_BROADCAST.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" SITE:" + getRFSubsystemID() + "-" + getSiteID()); - - sb.append(" CHAN A:" + getChannelA()); - - sb.append(" " + SystemService.toString(getSystemServiceClassA())); - - sb.append(" CHAN B:" + getChannelB()); - - sb.append(" " + SystemService.toString(getSystemServiceClassB())); - - return sb.toString(); - } - - public String getRFSubsystemID() - { - return mMessage.getHex(RFSS_ID, 2); - } - - public String getSiteID() - { - return mMessage.getHex(SITE_ID, 2); - } - - public int getIdentifierA() - { - return mMessage.getInt(IDENTIFIER_A); - } - - public int getChannelA() - { - return mMessage.getInt(CHANNEL_A); - } - - public String getChannelNumberA() - { - return getIdentifierA() + "-" + getChannelA(); - } - - public int getIdentifierB() - { - return mMessage.getInt(IDENTIFIER_B); - } - - public int getChannelB() - { - return mMessage.getInt(CHANNEL_B); - } - - public String getChannelNumberB() - { - return getIdentifierB() + "-" + getChannelB(); - } - - public int getSystemServiceClassA() - { - return mMessage.getInt(SYSTEM_SERVICE_CLASS_A); - } - - public int getSystemServiceClassB() - { - return mMessage.getInt(SYSTEM_SERVICE_CLASS_B); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getIdentifierA()) - { - mIdentifierProviderA = message; - } - if(identifier == getIdentifierB()) - { - mIdentifierProviderB = message; - } - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[2]; - - identifiers[0] = getIdentifierA(); - identifiers[1] = getIdentifierB(); - - return identifiers; - } - - public long getDownlinkFrequencyA() - { - if(mIdentifierProviderA != null) - { - return mIdentifierProviderA.getDownlinkFrequency(getChannelA()); - } - - return 0; - } - - public long getUplinkFrequencyA() - { - if(mIdentifierProviderA != null) - { - return mIdentifierProviderA.getUplinkFrequency(getChannelA()); - } - - return 0; - } - - public long getDownlinkFrequencyB() - { - if(mIdentifierProviderB != null) - { - return mIdentifierProviderB.getDownlinkFrequency(getChannelB()); - } - - return 0; - } - - public long getUplinkFrequencyB() - { - if(mIdentifierProviderB != null) - { - return mIdentifierProviderB.getUplinkFrequency(getChannelB()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/SecondaryControlChannelBroadcastExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/SecondaryControlChannelBroadcastExplicit.java deleted file mode 100644 index 408d651c..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/SecondaryControlChannelBroadcastExplicit.java +++ /dev/null @@ -1,157 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class SecondaryControlChannelBroadcastExplicit extends LDU1Message implements IdentifierReceiver -{ - public static final int[] RFSS_ID = {364, 365, 366, 367, 372, 373, 374, 375}; - public static final int[] SITE_ID = {376, 377, 382, 383, 384, 385, 386, 387}; - public static final int[] TRANSMIT_IDENTIFIER = {536, 537, 538, 539}; - public static final int[] TRANSMIT_CHANNEL = {540, 541, 546, 547, 548, 549, 550, 551, 556, 557, 558, 559}; - public static final int[] RECEIVE_IDENTIFIER = {560, 561, 566, 567}; - public static final int[] RECEIVE_CHANNEL = {568, 569, 570, 571, 720, 721, 722, 723, 724, 725, 730, 731}; - public static final int[] SYSTEM_SERVICE_CLASS = {732, 733, 734, 735, 740, 741, 742, 743}; - - private IBandIdentifier mTransmitIdentifierProvider; - private IBandIdentifier mReceiveIdentifierProvider; - - public SecondaryControlChannelBroadcastExplicit(LDU1Message message) - { - super(message); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" SITE:" + getRFSubsystemID() + "-" + getSiteID()); - - sb.append(" TRANSMIT:" + getTransmitChannel()); - - sb.append(" RECEIVE:" + getReceiveChannel()); - - sb.append(" " + SystemService.toString(getSystemServiceClass())); - - return sb.toString(); - } - - public String getRFSubsystemID() - { - return mMessage.getHex(RFSS_ID, 2); - } - - public String getSiteID() - { - return mMessage.getHex(SITE_ID, 2); - } - - public int getTransmitIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public int getTransmitChannelNumber() - { - return mMessage.getInt(TRANSMIT_CHANNEL); - } - - public String getTransmitChannel() - { - return getTransmitIdentifier() + "-" + getTransmitChannelNumber(); - } - - public int getReceiveIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public int getReceiveChannelNumber() - { - return mMessage.getInt(RECEIVE_CHANNEL); - } - - public String getReceiveChannel() - { - return getReceiveIdentifier() + "-" + getReceiveChannelNumber(); - } - - public int getSystemServiceClass() - { - return mMessage.getInt(SYSTEM_SERVICE_CLASS); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitIdentifier()) - { - mTransmitIdentifierProvider = message; - } - if(identifier == getReceiveIdentifier()) - { - mReceiveIdentifierProvider = message; - } - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[2]; - - identifiers[0] = getTransmitIdentifier(); - identifiers[1] = getReceiveIdentifier(); - - return identifiers; - } - - public long getDownlinkFrequency() - { - if(mTransmitIdentifierProvider != null) - { - return mTransmitIdentifierProvider.getDownlinkFrequency(getTransmitChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mReceiveIdentifierProvider != null) - { - return mReceiveIdentifierProvider.getUplinkFrequency(getReceiveChannelNumber()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/StatusQuery.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/StatusQuery.java deleted file mode 100644 index 7db8e146..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/StatusQuery.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class StatusQuery extends LDU1Message -{ - private final static Logger mLog = - LoggerFactory.getLogger( StatusQuery.class ); - public static final int[] TARGET_ADDRESS = { 536,537,538,539,540,541,546, - 547,548,549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 }; - public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730, - 731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 }; - - public StatusQuery( LDU1Message message ) - { - super( message ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.STATUS_QUERY.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " FROM:" + getSourceAddress() ); - - sb.append( " TO:" + getTargetAddress() ); - - return sb.toString(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/StatusUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/StatusUpdate.java deleted file mode 100644 index 5d7c878c..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/StatusUpdate.java +++ /dev/null @@ -1,64 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class StatusUpdate extends LDU1Message -{ - private final static Logger mLog = - LoggerFactory.getLogger( StatusUpdate.class ); - public static final int[] USER_STATUS = { 364,365,366,367,372,373,374,375 }; - public static final int[] UNIT_STATUS = { 376,377,382,383,384,385,386,387 }; - public static final int[] TARGET_ADDRESS = { 536,537,538,539,540,541,546, - 547,548,549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 }; - public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730, - 731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 }; - - public StatusUpdate( LDU1Message message ) - { - super( message ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.STATUS_UPDATE.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " STATUS USER:" + getUserStatus() ); - sb.append( " UNIT:" + getUnitStatus() ); - sb.append( " FROM:" + getSourceAddress() ); - sb.append( " TO:" + getTargetAddress() ); - - return sb.toString(); - } - - public String getUserStatus() - { - return mMessage.getHex( USER_STATUS, 2 ); - } - - public String getUnitStatus() - { - return mMessage.getHex( UNIT_STATUS, 2 ); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/SystemServiceBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/SystemServiceBroadcast.java deleted file mode 100644 index de530908..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/SystemServiceBroadcast.java +++ /dev/null @@ -1,63 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import io.github.dsheirer.module.decode.p25.reference.Service; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; - -public class SystemServiceBroadcast extends LDU1Message -{ - private final static Logger mLog = - LoggerFactory.getLogger( SystemServiceBroadcast.class ); - public static final int[] REQUEST_PRIORITY_LEVEL = { 384,385,386,387 }; - public static final int[] AVAILABLE_SERVICES = { 536,537,538,539,540,541, - 546,547,548,549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 }; - public static final int[] SUPPORTED_SERVICES = { 720,721,722,723,724,725, - 730,731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 }; - - public SystemServiceBroadcast( LDU1Message message ) - { - super( message ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.SYSTEM_SERVICE_BROADCAST.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " SERVICES AVAILABLE " ); - - sb.append( getAvailableServices() ); - - sb.append( " SUPPORTED " ); - - sb.append( getSupportedServices() ); - - return sb.toString(); - } - - public List getAvailableServices() - { - long bitmap = mMessage.getLong( AVAILABLE_SERVICES ); - - return Service.getServices( bitmap ); - } - - public List getSupportedServices() - { - long bitmap = mMessage.getLong( SUPPORTED_SERVICES ); - - return Service.getServices( bitmap ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/TelephoneInterconnectAnswerRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/TelephoneInterconnectAnswerRequest.java deleted file mode 100644 index 776f8ba6..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/TelephoneInterconnectAnswerRequest.java +++ /dev/null @@ -1,80 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.Digit; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; - -public class TelephoneInterconnectAnswerRequest extends LDU1Message -{ - private final static Logger mLog = LoggerFactory.getLogger( - TelephoneInterconnectAnswerRequest.class ); - - public static final int[] DIGIT_1 = { 364,365,366,367 }; - public static final int[] DIGIT_2 = { 372,373,374,375 }; - public static final int[] DIGIT_3 = { 376,377,382,383 }; - public static final int[] DIGIT_4 = { 384,385,386,387 }; - public static final int[] DIGIT_5 = { 536,537,538,539 }; - public static final int[] DIGIT_6 = { 540,541,546,547 }; - public static final int[] DIGIT_7 = { 548,549,550,551 }; - public static final int[] DIGIT_8 = { 556,557,558,559 }; - public static final int[] DIGIT_9 = { 560,561,566,567 }; - public static final int[] DIGIT_10 = { 568,569,570,571 }; - public static final int[] TARGET_ADDRESS = { 720,721,722,723,724,725,730, - 731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 }; - - public TelephoneInterconnectAnswerRequest( LDU1Message message ) - { - super( message ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.TELEPHONE_INTERCONNECT_ANSWER_REQUEST.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " TO:" ); - sb.append( getTargetAddress() ); - - sb.append( " TEL:" ); - sb.append( getTelephoneNumber() ); - - return sb.toString(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getTelephoneNumber() - { - List digits = new ArrayList(); - - digits.add( mMessage.getInt( DIGIT_1 ) ); - digits.add( mMessage.getInt( DIGIT_2 ) ); - digits.add( mMessage.getInt( DIGIT_3 ) ); - digits.add( mMessage.getInt( DIGIT_4 ) ); - digits.add( mMessage.getInt( DIGIT_5 ) ); - digits.add( mMessage.getInt( DIGIT_6 ) ); - digits.add( mMessage.getInt( DIGIT_7 ) ); - digits.add( mMessage.getInt( DIGIT_8 ) ); - digits.add( mMessage.getInt( DIGIT_9 ) ); - digits.add( mMessage.getInt( DIGIT_10 ) ); - - return Digit.decode( digits ); - } - -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/TelephoneInterconnectVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/TelephoneInterconnectVoiceChannelUser.java deleted file mode 100644 index 9753c197..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/TelephoneInterconnectVoiceChannelUser.java +++ /dev/null @@ -1,93 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class TelephoneInterconnectVoiceChannelUser extends LDU1Message -{ - private final static Logger mLog = - LoggerFactory.getLogger( TelephoneInterconnectVoiceChannelUser.class ); - - /* Service Options */ - public static final int EMERGENCY_FLAG = 376; - public static final int ENCRYPTED_CHANNEL_FLAG = 377; - public static final int DUPLEX_MODE = 382; - public static final int SESSION_MODE = 383; - public static final int[] CALL_TIMER = { 548,549,550,551,556,557,558,559, - 560,561,566,567,568,569,570,571 }; - public static final int[] ADDRESS = { 720,721,722,723,724,725,730,731,732, - 733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 }; - - public TelephoneInterconnectVoiceChannelUser( LDU1Message message ) - { - super( message ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - if( isEmergency() ) - { - sb.append( " EMERGENCY" ); - } - if( isEncryptedChannel() ) - { - sb.append( " ENCRYPTED CHANNEL" ); - } - - sb.append( " TIMER:" + getCallTimer() + " SECS" ); - - sb.append( " ADDR:" + getAddress() ); - - return sb.toString(); - } - - public boolean isEmergency() - { - return mMessage.get( EMERGENCY_FLAG ); - } - - public boolean isEncryptedChannel() - { - return mMessage.get( ENCRYPTED_CHANNEL_FLAG ); - } - - public DuplexMode getDuplexMode() - { - return mMessage.get( DUPLEX_MODE ) ? DuplexMode.FULL : DuplexMode.HALF; - } - - public SessionMode getSessionMode() - { - return mMessage.get( SESSION_MODE ) ? - SessionMode.CIRCUIT : SessionMode.PACKET; - } - - public String getAddress() - { - return mMessage.getHex( ADDRESS, 6 ); - } - - /** - * Call timer in seconds - */ - public int getCallTimer() - { - int units = mMessage.getInt( CALL_TIMER ); - - return (int)( units / 10 ); - } - -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/UnitAuthenticationCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/UnitAuthenticationCommand.java deleted file mode 100644 index cfd952f3..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/UnitAuthenticationCommand.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class UnitAuthenticationCommand extends LDU1Message -{ - private final static Logger mLog = - LoggerFactory.getLogger( UnitAuthenticationCommand.class ); - public static final int[] NETWORK_ID = { 364,365,366,367,372,373,374,375, - 376,377,382,383,384,385,386,387,536,537,538,539 }; - public static final int[] SYSTEM_ID = { 540,541,546,547,548,549,550,551,556, - 557,558,559 }; - public static final int[] TARGET_ID = { 560,561,566,567,568,569,570,571,720, - 721,722,723,724,725,730,731,732,733,734,735,740,741,742,743 }; - - public UnitAuthenticationCommand( LDU1Message message ) - { - super( message ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.UNIT_AUTHENTICATION_COMMAND.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " ADDRESS:" + getCompleteTargetAddress() ); - - return sb.toString(); - } - - public String getCompleteTargetAddress() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getNetworkID() ); - sb.append( ":" ); - sb.append( getSystemID() ); - sb.append( ":" ); - sb.append( getTargetID() ); - - return sb.toString(); - } - - public String getNetworkID() - { - return mMessage.getHex( NETWORK_ID, 5 ); - } - - public String getSystemID() - { - return mMessage.getHex( SYSTEM_ID, 3 ); - } - - public String getTargetID() - { - return mMessage.getHex( TARGET_ID, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/UnitRegistrationCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/UnitRegistrationCommand.java deleted file mode 100644 index 1ee90d23..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/UnitRegistrationCommand.java +++ /dev/null @@ -1,70 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class UnitRegistrationCommand extends LDU1Message -{ - private final static Logger mLog = - LoggerFactory.getLogger( UnitRegistrationCommand.class ); - public static final int[] NETWORK_ID = { 364,365,366,367,372,373,374,375, - 376,377,382,383,384,385,386,387,536,537,538,539 }; - public static final int[] SYSTEM_ID = { 540,541,546,547,548,549,550,551,556, - 557,558,559 }; - public static final int[] TARGET_ID = { 560,561,566,567,568,569,570,571,720, - 721,722,723,724,725,730,731,732,733,734,735,740,741,742,743 }; - - public UnitRegistrationCommand( LDU1Message message ) - { - super( message ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.UNIT_REGISTRATION_COMMAND.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " ADDRESS:" + getCompleteTargetAddress() ); - - return sb.toString(); - } - - public String getCompleteTargetAddress() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getNetworkID() ); - sb.append( ":" ); - sb.append( getSystemID() ); - sb.append( ":" ); - sb.append( getTargetID() ); - - return sb.toString(); - } - - - public String getNetworkID() - { - return mMessage.getHex( NETWORK_ID, 5 ); - } - - public String getSystemID() - { - return mMessage.getHex( SYSTEM_ID, 3 ); - } - - public String getTargetID() - { - return mMessage.getHex( TARGET_ID, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/UnitToUnitAnswerRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/UnitToUnitAnswerRequest.java deleted file mode 100644 index 45a1fdbc..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/UnitToUnitAnswerRequest.java +++ /dev/null @@ -1,87 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.P25Message; -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class UnitToUnitAnswerRequest extends LDU1Message -{ - private final static Logger mLog = - LoggerFactory.getLogger( UnitToUnitAnswerRequest.class ); - /* Service Options */ - public static final int EMERGENCY_FLAG = 364; - public static final int ENCRYPTED_CHANNEL_FLAG = 365; - public static final int DUPLEX_MODE = 366; - public static final int SESSION_MODE = 367; - public static final int[] TARGET_ADDRESS = { 536,537,538,539,540,541,546, - 547,548,549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 }; - public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730, - 731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 }; - - public UnitToUnitAnswerRequest( LDU1Message message ) - { - super( message ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.UNIT_TO_UNIT_ANSWER_REQUEST.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - if( isEmergency() ) - { - sb.append( " EMERGENCY" ); - } - if( isEncryptedChannel() ) - { - sb.append( " ENCRYPTED CHANNEL" ); - } - - sb.append( " FROM:" + getSourceAddress() ); - - sb.append( " TO:" + getTargetAddress() ); - - return sb.toString(); - } - - public boolean isEmergency() - { - return mMessage.get( EMERGENCY_FLAG ); - } - - public boolean isEncryptedChannel() - { - return mMessage.get( ENCRYPTED_CHANNEL_FLAG ); - } - - public P25Message.DuplexMode getDuplexMode() - { - return mMessage.get( DUPLEX_MODE ) ? P25Message.DuplexMode.FULL : P25Message.DuplexMode.HALF; - } - - public P25Message.SessionMode getSessionMode() - { - return mMessage.get( SESSION_MODE ) ? - P25Message.SessionMode.CIRCUIT : P25Message.SessionMode.PACKET; - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/UnitToUnitVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/UnitToUnitVoiceChannelUser.java deleted file mode 100644 index ab26df62..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/UnitToUnitVoiceChannelUser.java +++ /dev/null @@ -1,86 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.ldu.lc; - -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class UnitToUnitVoiceChannelUser extends LDU1Message -{ - private final static Logger mLog = - LoggerFactory.getLogger( UnitToUnitVoiceChannelUser.class ); - /* Service Options */ - public static final int EMERGENCY_FLAG = 376; - public static final int ENCRYPTED_CHANNEL_FLAG = 377; - public static final int DUPLEX_MODE = 382; - public static final int SESSION_MODE = 383; - public static final int[] TARGET_ADDRESS = { 536,537,538,539,540,541,546, - 547,548,549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 }; - public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730, - 731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 }; - - public UnitToUnitVoiceChannelUser( LDU1Message message ) - { - super( message ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.UNIT_TO_UNIT_VOICE_CHANNEL_USER.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - if( isEmergency() ) - { - sb.append( " EMERGENCY" ); - } - if( isEncryptedChannel() ) - { - sb.append( " ENCRYPTED CHANNEL" ); - } - - sb.append( " FROM:" + getSourceAddress() ); - - sb.append( " TO:" + getTargetAddress() ); - - return sb.toString(); - } - - public boolean isEmergency() - { - return mMessage.get( EMERGENCY_FLAG ); - } - - public boolean isEncryptedChannel() - { - return mMessage.get( ENCRYPTED_CHANNEL_FLAG ); - } - - public DuplexMode getDuplexMode() - { - return mMessage.get( DUPLEX_MODE ) ? DuplexMode.FULL : DuplexMode.HALF; - } - - public SessionMode getSessionMode() - { - return mMessage.get( SESSION_MODE ) ? - SessionMode.CIRCUIT : SessionMode.PACKET; - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/motorola/MotorolaOpcode_0.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/motorola/MotorolaOpcode_0.java deleted file mode 100644 index add7846d..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/ldu/lc/motorola/MotorolaOpcode_0.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.ldu.lc.motorola; - -import io.github.dsheirer.module.decode.p25.message.ldu.LDU1Message; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MotorolaOpcode_0 extends LDU1Message -{ - private final static Logger mLog = - LoggerFactory.getLogger( MotorolaOpcode_0.class ); - public static final int[] TARGET_ADDRESS = { 536,537,538,539,540,541,546, - 547,548,549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 }; - public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730, - 731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 }; - - public MotorolaOpcode_0( LDU1Message message ) - { - super( message ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.STATUS_QUERY.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " FROM:" + getSourceAddress() ); - - sb.append( " TO:" + getTargetAddress() ); - - return sb.toString(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUHeader.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUHeader.java new file mode 100644 index 00000000..40316f25 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUHeader.java @@ -0,0 +1,193 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.message.IBitErrorProvider; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.reference.Direction; +import io.github.dsheirer.module.decode.p25.reference.PDUFormat; +import io.github.dsheirer.module.decode.p25.reference.Vendor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * P25 Packet Data Unit header + */ +public class PDUHeader implements IBitErrorProvider +{ + private final static Logger mLog = LoggerFactory.getLogger(PDUHeader.class); + + public static final int CONFIRMATION_REQUIRED_INDICATOR = 1; + public static final int PACKET_DIRECTION_INDICATOR = 2; + public static final int[] PDU_FORMAT = {3, 4, 5, 6, 7}; + public static final int[] VENDOR_ID = {16, 17, 18, 19, 20, 21, 22, 23}; + public static final int[] LOGICAL_LINK_ID = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + public static final int[] BLOCKS_TO_FOLLOW = {49, 50, 51, 52, 53, 54, 55}; + public static final int[] PDU_CRC = {80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95}; + + protected boolean mValid; + private CorrectedBinaryMessage mMessage; + private Identifier mLLID; + + /** + * Constructs a PDU header. + * + * @param message + * @param passesCRC + */ + public PDUHeader(CorrectedBinaryMessage message, boolean passesCRC) + { + mMessage = message; + mValid = passesCRC; + } + + public CorrectedBinaryMessage getMessage() + { + return mMessage; + } + + /** + * Indicates if this header was correctly decoded and passed CCITT-16 CRC error check. + */ + public boolean isValid() + { + return mValid; + } + + /** + * Indicates if this PDU requires confirmation of receipt + */ + public boolean isConfirmationRequired() + { + return getMessage().get(CONFIRMATION_REQUIRED_INDICATOR); + } + + /** + * Direction of this message, inbound or outbound. + */ + public Direction getDirection() + { + return Direction.fromValue(getMessage().get(PACKET_DIRECTION_INDICATOR)); + } + + /** + * Indicates if this is an outbound (true) FNE -> SU, or an inbound (false) SU -> FNE packet. + */ + public boolean isOutbound() + { + return getDirection() == Direction.OUTBOUND; + } + + /** + * Packet Data Unit format + */ + public PDUFormat getFormat() + { + return getFormat(getMessage()); + } + + /** + * Determines the PDU format for the message + */ + public static PDUFormat getFormat(BinaryMessage binaryMessage) + { + return PDUFormat.fromValue(binaryMessage.getInt(PDU_FORMAT)); + } + + /** + * Number of bits processed to produce this header. + * + * @return 196 bits + */ + @Override + public int getBitsProcessedCount() + { + return 196; + } + + /** + * Number of bit errors detected and/or corrected + */ + @Override + public int getBitErrorsCount() + { + return getMessage().getCorrectedBitCount(); + } + + /** + * Vendor or manufacturer ID + */ + public Vendor getVendor() + { + return Vendor.fromValue(getMessage().getInt(VENDOR_ID)); + } + + /** + * Logical Link Identifier (ie TO radio identifier) + */ + public Identifier getLLID() + { + if(mLLID == null) + { + if(isOutbound()) + { + mLLID = APCO25ToTalkgroup.createIndividual(getMessage().getInt(LOGICAL_LINK_ID)); + } + else + { + mLLID = APCO25FromTalkgroup.createIndividual(getMessage().getInt(LOGICAL_LINK_ID)); + } + } + + return mLLID; + } + + /** + * Number of data blocks that follow this header + */ + public int getBlocksToFollowCount() + { + return getMessage().getInt(BLOCKS_TO_FOLLOW); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(!isValid()) + { + sb.append("***CRC-FAIL*** "); + } + + sb.append("PDU HEADER FORMAT:"); + sb.append(getFormat().getLabel()); + sb.append(isConfirmationRequired() ? " CONFIRMED" : " UNCONFIRMED"); + sb.append(" VENDOR:" + getVendor().getLabel()); + sb.append(isOutbound() ? "TO" : "FROM").append(" LLID").append(getLLID()); + + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUHeaderFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUHeaderFactory.java new file mode 100644 index 00000000..0d1379b5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUHeaderFactory.java @@ -0,0 +1,55 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.CRCP25; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCHeader; +import io.github.dsheirer.module.decode.p25.message.pdu.packet.PacketHeader; +import io.github.dsheirer.module.decode.p25.message.pdu.response.ResponseHeader; +import io.github.dsheirer.module.decode.p25.reference.PDUFormat; + +public class PDUHeaderFactory +{ + public static PDUHeader getPDUHeader(CorrectedBinaryMessage correctedBinaryMessage) + { + //CCITT-16 can detect and correct up to 1 bit error max - 2 bit errors indicates CRC-fail + int errorCount = CRCP25.correctCCITT80(correctedBinaryMessage, 0, 80); + boolean passesCRC = errorCount < 2; + + correctedBinaryMessage.incrementCorrectedBitCount(errorCount); + + PDUFormat format = PDUHeader.getFormat(correctedBinaryMessage); + + switch(format) + { + case ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL: + return new AMBTCHeader(correctedBinaryMessage, passesCRC); + case PACKET_DATA: + return new PacketHeader(correctedBinaryMessage, passesCRC); + case RESPONSE_PACKET_HEADER_FORMAT: + return new ResponseHeader(correctedBinaryMessage, passesCRC); + case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL: + default: + return new PDUHeader(correctedBinaryMessage, passesCRC); + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUMessage.java index 834cd96c..3089f718 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUMessage.java @@ -1,181 +1,216 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRC; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; import io.github.dsheirer.module.decode.p25.message.P25Message; -import io.github.dsheirer.module.decode.p25.message.tsbk.vendor.VendorOpcode; +import io.github.dsheirer.module.decode.p25.message.tsbk.Opcode; import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; import io.github.dsheirer.module.decode.p25.reference.PDUFormat; import io.github.dsheirer.module.decode.p25.reference.ServiceAccessPoint; import io.github.dsheirer.module.decode.p25.reference.Vendor; +import java.util.Collections; +import java.util.List; + public class PDUMessage extends P25Message { - public static final int CONFIRMATION_REQUIRED_INDICATOR = 65; - public static final int PACKET_DIRECTION_INDICATOR = 66; - public static final int[] FORMAT = { 67,68,69,70,71 }; - public static final int[] SAP_ID = { 74,75,76,77,78,79 }; - public static final int[] VENDOR_ID = { 80,81,82,83,84,85,86,87 }; - public static final int[] LOGICAL_LINK_ID = { 88,89,90,91,92,93,94,95,96,97, - 98,99,100,101,102,103,104,105,106,107,108,109,110,111 }; - public static final int[] BLOCKS_TO_FOLLOW = { 113,114,115,116,117,118,119 }; - public static final int[] PAD_OCTET_COUNT = { 123,124,125,126,127 }; - public static final int[] OPCODE = { 122,123,124,125,126,127 }; - public static final int[] DATA_HEADER_OFFSET = { 138,139,140,141,142,143 }; - public static final int[] PDU_CRC = { 144,145,146,147,148,149,150,151,152, - 153,154,155,156,157,158,159 }; + public static final int CONFIRMATION_REQUIRED_INDICATOR = 65; + public static final int PACKET_DIRECTION_INDICATOR = 66; + public static final int[] FORMAT = {67, 68, 69, 70, 71}; + public static final int[] SAP_ID = {74, 75, 76, 77, 78, 79}; + public static final int[] VENDOR_ID = {80, 81, 82, 83, 84, 85, 86, 87}; + public static final int[] LOGICAL_LINK_ID = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, + 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111}; + public static final int[] BLOCKS_TO_FOLLOW = {113, 114, 115, 116, 117, 118, 119}; + public static final int[] PAD_OCTET_COUNT = {123, 124, 125, 126, 127}; + public static final int[] OPCODE = {122, 123, 124, 125, 126, 127}; + public static final int[] DATA_HEADER_OFFSET = {138, 139, 140, 141, 142, 143}; + public static final int[] PDU_CRC = {144, 145, 146, 147, 148, 149, 150, 151, 152, + 153, 154, 155, 156, 157, 158, 159}; - public PDUMessage(BinaryMessage message, DataUnitID duid, AliasList aliasList ) + private Identifier mLLID; + + public PDUMessage(CorrectedBinaryMessage message, int nac, long timestamp) { - super( message, duid, aliasList ); + super(message, nac, timestamp); /* Setup a CRC array to hold the header CRC and the multi-block CRC */ - mCRC = new CRC[ 2 ]; - mCRC[ 0 ] = CRC.PASSED; +// mCRC = new CRC[ 2 ]; +// mCRC[ 0 ] = CRC.PASSED; } - - protected String getMessageStub() - { - StringBuilder sb = new StringBuilder(); - Vendor vendor = getVendor(); - - sb.append( "NAC:" ); - sb.append( getNAC() ); - sb.append( " " ); - - switch( getFormat() ) - { - case ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL: - sb.append( "ATSBK" ); - break; - case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL: - sb.append( "**** UNCONFIRMED MULTI-BLOCK TRUNKING CONTROL" ); - break; - default: - sb.append( getDUID().getLabel() ); - break; - } - - if( vendor == Vendor.STANDARD ) - { - sb.append( " " ); - sb.append( getOpcode().getLabel() ); - } - else - { - sb.append( " " ); - sb.append( vendor.getLabel() ); - } - - return sb.toString(); - } - - @Override - public String getMessage() + @Override + public DataUnitID getDUID() { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - switch( getFormat() ) - { - case ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL: - break; - case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL: - sb.append( " PAD OCTETS:" + getPadOctetCount() ); - sb.append( " DATA HDR OFFSET:" + getDataHeaderOffset() ); - break; - default: - } - - sb.append( " " ); - sb.append( getConfirmation() ); - sb.append( " " ); - sb.append( getDirection() ); - sb.append( " FMT:" ); - sb.append( getFormat().getLabel() ); - sb.append( " SAP:" ); - sb.append( getServiceAccessPoint().name() ); - sb.append( " VEND:" ); - sb.append( getVendor().getLabel() ); - sb.append( " LLID:" ); - sb.append( getLogicalLinkID() ); - sb.append( " BLKS TO FOLLOW:" ); - sb.append( getBlocksToFollowCount() ); - - return sb.toString(); + return DataUnitID.PACKET_DATA_UNIT; } - public String getConfirmation() - { - return mMessage.get( CONFIRMATION_REQUIRED_INDICATOR ) ? "CONFIRMED" : "UNCONFIRMED"; - } - - public String getDirection() - { - return mMessage.get( PACKET_DIRECTION_INDICATOR ) ? "OSP" : "ISP"; - } - - public boolean isOutbound() - { - return mMessage.get( PACKET_DIRECTION_INDICATOR ); - } - - public PDUFormat getFormat() - { - return PDUFormat.fromValue( mMessage.getInt( FORMAT ) ); - } - - public ServiceAccessPoint getServiceAccessPoint() - { - return ServiceAccessPoint.fromValue( mMessage.getInt( SAP_ID ) ); - } + protected String getMessageStub() + { + StringBuilder sb = new StringBuilder(); - public Vendor getVendor() - { - return Vendor.fromValue( mMessage.getInt( VENDOR_ID ) ); - } - - public String getLogicalLinkID() - { - return mMessage.getHex( LOGICAL_LINK_ID, 6 ); - } - - public int getBlocksToFollowCount() - { - return mMessage.getInt( BLOCKS_TO_FOLLOW ); - } - - public int getPadOctetCount() - { - return mMessage.getInt( PAD_OCTET_COUNT ); - } - - public Opcode getOpcode() - { - if( getFormat() == PDUFormat.ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL ) - { - return Opcode.fromValue( mMessage.getInt( OPCODE ) ); - } - - return Opcode.UNKNOWN; - } - - public VendorOpcode getVendorOpcode() - { - if( getFormat() == PDUFormat.ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL ) - { - return VendorOpcode.fromValue( mMessage.getInt( OPCODE ) ); - } - - return VendorOpcode.UNKNOWN; - } - - public int getDataHeaderOffset() - { - return mMessage.getInt( DATA_HEADER_OFFSET ); - } + Vendor vendor = getVendor(); + + sb.append("NAC:"); + sb.append(getNAC()); + sb.append(" "); + + switch(getFormat()) + { + case ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL: + sb.append("ATSBK"); + break; + case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL: + sb.append("**** UNCONFIRMED MULTI-BLOCK TRUNKING CONTROL"); + break; + default: + sb.append(getDUID().getLabel()); + break; + } + + if(vendor == Vendor.STANDARD) + { + sb.append(" "); + sb.append(getOpcode().getLabel()); + } + else + { + sb.append(" "); + sb.append(vendor.getLabel()); + } + + return sb.toString(); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append(getMessageStub()); + + switch(getFormat()) + { + case ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL: + break; + case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL: + sb.append(" PAD OCTETS:" + getPadOctetCount()); + sb.append(" DATA HDR OFFSET:" + getDataHeaderOffset()); + break; + default: + } + + sb.append(" "); + sb.append(getConfirmation()); + sb.append(" "); + sb.append(getDirection()); + sb.append(" FMT:"); + sb.append(getFormat().getLabel()); + sb.append(" SAP:"); + sb.append(getServiceAccessPoint().name()); + sb.append(" VEND:"); + sb.append(getVendor().getLabel()); + sb.append(" LLID:"); + sb.append(getLogicalLinkID()); + sb.append(" BLKS TO FOLLOW:"); + sb.append(getBlocksToFollowCount()); + + return sb.toString(); + } + + public String getConfirmation() + { + return getMessage().get(CONFIRMATION_REQUIRED_INDICATOR) ? "CONFIRMED" : "UNCONFIRMED"; + } + + public String getDirection() + { + return getMessage().get(PACKET_DIRECTION_INDICATOR) ? "OSP" : "ISP"; + } + + public boolean isOutbound() + { + return getMessage().get(PACKET_DIRECTION_INDICATOR); + } + + public PDUFormat getFormat() + { + return PDUFormat.fromValue(getMessage().getInt(FORMAT)); + } + + public ServiceAccessPoint getServiceAccessPoint() + { + return ServiceAccessPoint.fromValue(getMessage().getInt(SAP_ID)); + } + + public Vendor getVendor() + { + return Vendor.fromValue(getMessage().getInt(VENDOR_ID)); + } + + public Identifier getLogicalLinkID() + { + if(mLLID == null) + { + if(isOutbound()) + { + mLLID = APCO25ToTalkgroup.createIndividual(getMessage().getInt(LOGICAL_LINK_ID)); + } + else + { + mLLID = APCO25FromTalkgroup.createIndividual(getMessage().getInt(LOGICAL_LINK_ID)); + } + } + + return mLLID; + } + + public int getBlocksToFollowCount() + { + return getMessage().getInt(BLOCKS_TO_FOLLOW); + } + + public int getPadOctetCount() + { + return getMessage().getInt(PAD_OCTET_COUNT); + } + + public Opcode getOpcode() + { + if(getFormat() == PDUFormat.ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL) + { + return Opcode.fromValue(getMessage().getInt(OPCODE)); + } + + return Opcode.OSP_UNKNOWN; + } + + public int getDataHeaderOffset() + { + return getMessage().getInt(DATA_HEADER_OFFSET); + } + + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUMessageFactory.java index 4cea5c35..60858a7b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUMessageFactory.java @@ -1,131 +1,379 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu; import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.AdjacentStatusBroadcastExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.CallAlertExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.GroupAffiliationQueryExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.GroupAffiliationResponseExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.MessageUpdateExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.NetworkStatusBroadcastExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.ProtectionParameterBroadcast; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.RFSSStatusBroadcastExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.RoamingAddressUpdateExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.StatusQueryExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.StatusUpdateExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.control.UnitRegistrationResponseExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.data.GroupDataChannelGrantExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.data.IndividualDataChannelGrantExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.voice.GroupVoiceChannelGrantExplicit; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.voice.TelephoneInterconnectChannelGrantExplicit; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.voice.TelephoneInterconnectChannelGrantUpdateExplicit; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.voice.UnitToUnitAnswerRequestExplicit; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.voice.UnitToUnitVoiceChannelGrantExtended; -import io.github.dsheirer.module.decode.p25.message.pdu.osp.voice.UnitToUnitVoiceChannelGrantUpdateExtended; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.trellis.ViterbiDecoder_1_2_P25; +import io.github.dsheirer.module.decode.p25.P25Interleave; +import io.github.dsheirer.module.decode.p25.message.P25Message; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCHeader; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCAuthenticationQuery; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCAuthenticationResponse; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCCallAlertRequest; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCGroupAffiliationRequest; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCIndividualDataServiceRequest; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCLocationRegistrationRequest; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCMessageUpdateRequest; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCRoamingAddressRequest; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCStatusQueryRequest; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCStatusQueryResponse; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCStatusUpdateRequest; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCUnitAcknowledgeResponse; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCUnitToUnitVoiceServiceAnswerResponse; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp.AMBTCUnitToUnitVoiceServiceRequest; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCAdjacentStatusBroadcast; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCCallAlert; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCGroupAffiliationQuery; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCGroupAffiliationResponse; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCGroupDataChannelGrant; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCGroupVoiceChannelGrant; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCIndividualDataChannelGrant; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCMessageUpdate; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCProtectionParameterBroadcast; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCRoamingAddressResponse; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCRoamingAddressUpdate; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCStatusQuery; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCStatusUpdate; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCTelephoneInterconnectChannelGrant; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCTelephoneInterconnectChannelGrantUpdate; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCUnitRegistrationResponse; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCUnitToUnitAnswerRequest; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCUnitToUnitVoiceServiceChannelGrant; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCUnitToUnitVoiceServiceChannelGrantUpdate; +import io.github.dsheirer.module.decode.p25.message.pdu.block.ConfirmedDataBlock; +import io.github.dsheirer.module.decode.p25.message.pdu.block.DataBlock; +import io.github.dsheirer.module.decode.p25.message.pdu.block.UnconfirmedDataBlock; +import io.github.dsheirer.module.decode.p25.message.pdu.packet.PacketHeader; +import io.github.dsheirer.module.decode.p25.message.pdu.packet.PacketMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.packet.sndcp.SNDCPPacketMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.response.ResponseMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.umbtc.isp.UMBTCTelephoneInterconnectRequestExplicitDialing; +import io.github.dsheirer.module.decode.p25.message.tsbk.Opcode; import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; import io.github.dsheirer.module.decode.p25.reference.PDUFormat; -import io.github.dsheirer.module.decode.p25.reference.Vendor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.BitSet; public class PDUMessageFactory { - public static PDUMessage getMessage( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - PDUFormat format = PDUFormat.fromValue( - message.getInt( PDUMessage.FORMAT ) ); + private final static Logger mLog = LoggerFactory.getLogger(PDUMessageFactory.class); - switch( format ) - { - case ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL: - Vendor vendor = Vendor.fromValue( - message.getInt( PDUMessage.VENDOR_ID ) ); - - switch( vendor ) - { - case STANDARD: - Opcode opcode = Opcode.fromValue( - message.getInt( PDUMessage.OPCODE ) ); - - switch( opcode ) - { - case ADJACENT_STATUS_BROADCAST: - return new AdjacentStatusBroadcastExtended( - message, duid, aliasList ); - case CALL_ALERT: - return new CallAlertExtended( message, duid, - aliasList ); - case GROUP_AFFILIATION_QUERY: - return new GroupAffiliationQueryExtended( - message, duid, aliasList ); - case GROUP_AFFILIATION_RESPONSE: - return new GroupAffiliationResponseExtended( - message, duid, aliasList ); - case GROUP_DATA_CHANNEL_GRANT: - return new GroupDataChannelGrantExtended( - message, duid, aliasList ); - case GROUP_VOICE_CHANNEL_GRANT: - - return new GroupVoiceChannelGrantExplicit( - message, duid, aliasList ); - case INDIVIDUAL_DATA_CHANNEL_GRANT: - return new IndividualDataChannelGrantExtended( - message, duid, aliasList ); - case MESSAGE_UPDATE: - return new MessageUpdateExtended( message, duid, - aliasList ); - case NETWORK_STATUS_BROADCAST: - return new NetworkStatusBroadcastExtended( - message, duid, aliasList ); - case PROTECTION_PARAMETER_BROADCAST: - return new ProtectionParameterBroadcast( - message, duid, aliasList ); - case RFSS_STATUS_BROADCAST: - return new RFSSStatusBroadcastExtended( message, - duid, aliasList ); - case ROAMING_ADDRESS_UPDATE: - return new RoamingAddressUpdateExtended( - message, duid, aliasList ); - case STATUS_QUERY: - return new StatusQueryExtended( message, duid, - aliasList ); - case STATUS_UPDATE: - return new StatusUpdateExtended( message, duid, - aliasList ); - case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: - return new TelephoneInterconnectChannelGrantExplicit( - message, duid, aliasList ); - case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: - return new TelephoneInterconnectChannelGrantUpdateExplicit( - message, duid, aliasList ); - case UNIT_REGISTRATION_RESPONSE: - return new UnitRegistrationResponseExtended( - message, duid, aliasList ); - case UNIT_TO_UNIT_ANSWER_REQUEST: - return new UnitToUnitAnswerRequestExplicit( - message, duid, aliasList ); - case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: - return new UnitToUnitVoiceChannelGrantExtended( - message, duid, aliasList ); - case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: - return new UnitToUnitVoiceChannelGrantUpdateExtended( - message, duid, aliasList ); - default: - break; - } - case MOTOROLA: - break; - default: - break; - } - - return new PDUMessage( message, duid, aliasList ); - - case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL: - return new PDUMessage( message, duid, aliasList ); - default: - return new PDUMessage( message, duid, aliasList ); - } - } + private static final int[] BLOCK_0_UMBTC_OPCODE = {2, 3, 4, 5, 6, 7}; + private static final int PDU0_BEGIN = 0; + private static final int PDU0_END = 196; + + private static final ViterbiDecoder_1_2_P25 VITERBI_HALF_RATE_DECODER = new ViterbiDecoder_1_2_P25(); + + public static PDUSequence createPacketSequence(int nac, long timestamp, CorrectedBinaryMessage correctedBinaryMessage) + { + //Get deinterleaved header chunk + BitSet interleaved = correctedBinaryMessage.get(PDU0_BEGIN, PDU0_END); + CorrectedBinaryMessage deinterleaved = P25Interleave.deinterleaveChunk(P25Interleave.DATA_DEINTERLEAVE, interleaved); + + //Decode 1/2 rate trellis encoded PDU header + CorrectedBinaryMessage viterbiDecoded = VITERBI_HALF_RATE_DECODER.decode(deinterleaved); + + if(viterbiDecoded != null) + { + PDUHeader header = PDUHeaderFactory.getPDUHeader(viterbiDecoded); + return new PDUSequence(header, timestamp, nac); + } + + return null; + } + + /** + * Creates a packet sequence message from the packet sequence. + * + * @param pduSequence + * @return + */ + public static P25Message create(PDUSequence pduSequence, int nac, long timestamp) + { + switch(pduSequence.getHeader().getFormat()) + { + case ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL: + return createAMBTC(pduSequence, nac, timestamp); + case PACKET_DATA: + return createPacketData(pduSequence, nac, timestamp); + case RESPONSE_PACKET_HEADER_FORMAT: + return new ResponseMessage(pduSequence, nac, timestamp); + case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL: + return createUMBTC(pduSequence, nac, timestamp); + default: + return new PDUSequenceMessage(pduSequence, nac, timestamp); + } + } + + /** + * Creates a confirmed data block for a packet sequence + */ + public static DataBlock createConfirmedDataBlock(CorrectedBinaryMessage interleaved) + { + CorrectedBinaryMessage deinterleaved = P25Interleave.deinterleaveChunk(P25Interleave.DATA_DEINTERLEAVE, interleaved); + return new ConfirmedDataBlock(deinterleaved); + } + + /** + * Creates an unconfirmed data block for a packet sequence + */ + public static DataBlock createUnconfirmedDataBlock(CorrectedBinaryMessage interleaved) + { + CorrectedBinaryMessage deinterleaved = P25Interleave.deinterleaveChunk(P25Interleave.DATA_DEINTERLEAVE, interleaved); + return new UnconfirmedDataBlock(deinterleaved); + } + + /** + * Creates a packet data message + * + * @param pduSequence containing a packet data header and zero or more data blocks + * @param nac network access code + * @param timestamp of the packet sequence + * @return packet data message parser + */ + public static P25Message createPacketData(PDUSequence pduSequence, int nac, long timestamp) + { + PacketHeader packetHeader = (PacketHeader)pduSequence.getHeader(); + + switch(packetHeader.getServiceAccessPoint()) + { + case UNENCRYPTED_USER_DATA: + return new PacketMessage(pduSequence, nac, timestamp); + case SNDCP_PACKET_DATA_CONTROL: + return new SNDCPPacketMessage(pduSequence, nac, timestamp); + case PACKET_DATA: + return new PacketMessage(pduSequence, nac, timestamp); + case ADDRESS_RESOLUTION_PROTOCOL: + case CHANNEL_REASSIGNMENT: + case CIRCUIT_DATA: + case CIRCUIT_DATA_CONTROL: + case ENCRYPTED_KEY_MANAGEMENT_MESSAGE: + case ENCRYPTED_TRUNKING_CONTROL: + case ENCRYPTED_USER_DATA: + case EXTENDED_ADDRESS: + case MOBILE_RADIO_CONFIGURATION: + case MOBILE_RADIO_LOOPBACK: + case MOBILE_RADIO_PAGING: + case MOBILE_RADIO_STATISTICS: + case MOBILE_RADIO_OUT_OF_SERVICE: + case REGISTRATION_AND_AUTHORIZATION: + case SYSTEM_CONFIGURATION: + case UNENCRYPTED_KEY_MANAGEMENT_MESSAGE: + case UNENCRYPTED_TRUNKING_CONTROL: + case UNKNOWN: + default: + } + + return new PDUSequenceMessage(pduSequence, nac, timestamp); + } + + + /** + * Creates an alternate multi-block trunking control message + * + * @param pduSequence containing an AMBTC (PDU) header + * @param nac network access code + * @param timestamp of the packet sequence + * @return AMBTC message parser for the specific opcode + */ + public static P25Message createAMBTC(PDUSequence pduSequence, int nac, long timestamp) + { + AMBTCHeader ambtcHeader = (AMBTCHeader)pduSequence.getHeader(); + + switch(ambtcHeader.getOpcode()) + { + case ISP_AUTHENTICATION_QUERY_OBSOLETE: + return new AMBTCAuthenticationQuery(pduSequence, nac, timestamp); + case ISP_AUTHENTICATION_RESPONSE: + return new AMBTCAuthenticationResponse(pduSequence, nac, timestamp); + case ISP_CALL_ALERT_REQUEST: + return new AMBTCCallAlertRequest(pduSequence, nac, timestamp); + case ISP_GROUP_AFFILIATION_REQUEST: + return new AMBTCGroupAffiliationRequest(pduSequence, nac, timestamp); + case ISP_INDIVIDUAL_DATA_SERVICE_REQUEST: + return new AMBTCIndividualDataServiceRequest(pduSequence, nac, timestamp); + case ISP_LOCATION_REGISTRATION_REQUEST: + return new AMBTCLocationRegistrationRequest(pduSequence, nac, timestamp); + case ISP_MESSAGE_UPDATE_REQUEST: + return new AMBTCMessageUpdateRequest(pduSequence, nac, timestamp); + case ISP_ROAMING_ADDRESS_REQUEST: + return new AMBTCRoamingAddressRequest(pduSequence, nac, timestamp); + case ISP_STATUS_QUERY_REQUEST: + return new AMBTCStatusQueryRequest(pduSequence, nac, timestamp); + case ISP_STATUS_QUERY_RESPONSE: + return new AMBTCStatusQueryResponse(pduSequence, nac, timestamp); + case ISP_STATUS_UPDATE_REQUEST: + return new AMBTCStatusUpdateRequest(pduSequence, nac, timestamp); + case ISP_UNIT_ACKNOWLEDGE_RESPONSE: + return new AMBTCUnitAcknowledgeResponse(pduSequence, nac, timestamp); + case ISP_UNIT_TO_UNIT_VOICE_SERVICE_REQUEST: + return new AMBTCUnitToUnitVoiceServiceRequest(pduSequence, nac, timestamp); + case ISP_UNIT_TO_UNIT_ANSWER_RESPONSE: + return new AMBTCUnitToUnitVoiceServiceAnswerResponse(pduSequence, nac, timestamp); + + case OSP_ADJACENT_STATUS_BROADCAST: + return new AMBTCAdjacentStatusBroadcast(pduSequence, nac, timestamp); + case OSP_CALL_ALERT: + return new AMBTCCallAlert(pduSequence, nac, timestamp); + case OSP_GROUP_DATA_CHANNEL_GRANT: + return new AMBTCGroupDataChannelGrant(pduSequence, nac, timestamp); + case OSP_GROUP_AFFILIATION_QUERY: + return new AMBTCGroupAffiliationQuery(pduSequence, nac, timestamp); + case OSP_GROUP_AFFILIATION_RESPONSE: + return new AMBTCGroupAffiliationResponse(pduSequence, nac, timestamp); + case OSP_GROUP_VOICE_CHANNEL_GRANT: + return new AMBTCGroupVoiceChannelGrant(pduSequence, nac, timestamp); + case OSP_INDIVIDUAL_DATA_CHANNEL_GRANT: + return new AMBTCIndividualDataChannelGrant(pduSequence, nac, timestamp); + case OSP_MESSAGE_UPDATE: + return new AMBTCMessageUpdate(pduSequence, nac, timestamp); + case OSP_PROTECTION_PARAMETER_BROADCAST: + return new AMBTCProtectionParameterBroadcast(pduSequence, nac, timestamp); + case OSP_ROAMING_ADDRESS_UPDATE: + return new AMBTCRoamingAddressUpdate(pduSequence, nac, timestamp); + case OSP_ROAMING_ADDRESS_COMMAND: + return new AMBTCRoamingAddressResponse(pduSequence, nac, timestamp); + case OSP_STATUS_QUERY: + return new AMBTCStatusQuery(pduSequence, nac, timestamp); + case OSP_STATUS_UPDATE: + return new AMBTCStatusUpdate(pduSequence, nac, timestamp); + case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: + return new AMBTCTelephoneInterconnectChannelGrant(pduSequence, nac, timestamp); + case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: + return new AMBTCTelephoneInterconnectChannelGrantUpdate(pduSequence, nac, timestamp); + case OSP_UNIT_REGISTRATION_RESPONSE: + return new AMBTCUnitRegistrationResponse(pduSequence, nac, timestamp); + case OSP_UNIT_TO_UNIT_ANSWER_REQUEST: + return new AMBTCUnitToUnitAnswerRequest(pduSequence, nac, timestamp); + case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: + return new AMBTCUnitToUnitVoiceServiceChannelGrant(pduSequence, nac, timestamp); + case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: + return new AMBTCUnitToUnitVoiceServiceChannelGrantUpdate(pduSequence, nac, timestamp); + default: + return new PDUSequenceMessage(pduSequence, nac, timestamp); + } + } + + /** + * Creates an unconfirmed multi-block trunking control message + * + * @param pduSequence containing a UMBTC (PDU) header + * @param nac network access code + * @param timestamp of the packet sequence + * @return UMBTC message parser for the specific opcode + */ + public static P25Message createUMBTC(PDUSequence pduSequence, int nac, long timestamp) + { + Opcode opcode = Opcode.OSP_UNKNOWN; + + if(pduSequence.hasDataBlock(0)) + { + opcode = Opcode.fromValue(pduSequence.getDataBlock(0).getMessage().getInt(BLOCK_0_UMBTC_OPCODE), + pduSequence.getHeader().getDirection(), pduSequence.getHeader().getVendor()); + } + + switch(opcode) + { + case ISP_TELEPHONE_INTERCONNECT_EXPLICIT_DIAL_REQUEST: + return new UMBTCTelephoneInterconnectRequestExplicitDialing(pduSequence, nac, timestamp); + default: + return new PDUSequenceMessage(pduSequence, nac, timestamp); + } + } + + @Deprecated + public static PDUMessage getMessage(BinaryMessage message, DataUnitID duid, AliasList aliasList) + { + PDUFormat format = PDUFormat.fromValue(message.getInt(PDUMessage.FORMAT)); + +// switch(format) +// { +// case ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL: +// Vendor vendor = Vendor.fromValue( +// message.getInt(PDUMessage.VENDOR_ID)); +// +// switch(vendor) +// { +// case STANDARD: +// Opcode opcode = Opcode.fromValue(message.getInt(PDUMessage.OPCODE)); +// +// switch(opcode) +// { +// case OSP_ADJACENT_STATUS_BROADCAST: +// return new AdjacentStatusBroadcastExtended(message, duid, aliasList); +// case OSP_CALL_ALERT: +// return new CallAlertExtended(message, duid, aliasList); +// case OSP_GROUP_AFFILIATION_QUERY: +// return new GroupAffiliationQueryExtended(message, duid, aliasList); +// case OSP_GROUP_AFFILIATION_RESPONSE: +// return new GroupAffiliationResponseExtended(message, duid, aliasList); +// case OSP_GROUP_DATA_CHANNEL_GRANT: +// return new GroupDataChannelGrantExtended(message, duid, aliasList); +// case OSP_GROUP_VOICE_CHANNEL_GRANT: +// return new GroupVoiceChannelGrantExplicit(message, duid, aliasList); +// case OSP_INDIVIDUAL_DATA_CHANNEL_GRANT: +// return new IndividualDataChannelGrantExtended(message, duid, aliasList); +// case OSP_MESSAGE_UPDATE: +// return new MessageUpdateExtended(message, duid, aliasList); +// case OSP_NETWORK_STATUS_BROADCAST: +// return new NetworkStatusBroadcastExtended(message, duid, aliasList); +// case OSP_PROTECTION_PARAMETER_BROADCAST: +// return new ProtectionParameterBroadcast(message, duid, aliasList); +// case OSP_RFSS_STATUS_BROADCAST: +// return new RFSSStatusBroadcastExtended(message, +// duid, aliasList); +// case OSP_ROAMING_ADDRESS_UPDATE: +// return new RoamingAddressUpdateExtended(message, duid, aliasList); +// case OSP_STATUS_QUERY: +// return new StatusQueryExtended(message, duid, aliasList); +// case OSP_STATUS_UPDATE: +// return new StatusUpdateExtended(message, duid, +// aliasList); +// case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: +// return new TelephoneInterconnectChannelGrantExplicit(message, duid, aliasList); +// case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: +// return new TelephoneInterconnectChannelGrantUpdateExplicit(message, duid, aliasList); +// case OSP_UNIT_REGISTRATION_RESPONSE: +// return new UnitRegistrationResponseExtended(message, duid, aliasList); +// case OSP_UNIT_TO_UNIT_ANSWER_REQUEST: +// return new UnitToUnitAnswerRequestExplicit(message, duid, aliasList); +// case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: +// return new UnitToUnitVoiceChannelGrantExtended(message, duid, aliasList); +// case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: +// return new UnitToUnitVoiceChannelGrantUpdateExtended(message, duid, aliasList); +// default: +// break; +// } +// case MOTOROLA: +// break; +// default: +// break; +// } +// +// return new PDUMessage(message, duid, aliasList); +// +// case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL: +// return new PDUMessage(message, duid, aliasList); +// default: +// return new PDUMessage(message, duid, aliasList); +// } + return null; + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUSequence.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUSequence.java new file mode 100644 index 00000000..d22b253c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUSequence.java @@ -0,0 +1,161 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu; + +import io.github.dsheirer.message.IBitErrorProvider; +import io.github.dsheirer.module.decode.p25.message.pdu.block.DataBlock; + +import java.util.ArrayList; +import java.util.List; + +/** + * Packet Data Sequence comprised of a Packet Data Header and zero or more Data Blocks. + */ +public class PDUSequence implements IBitErrorProvider +{ + private long mTimestamp; + private int mNAC; + private PDUHeader mHeader; + private List mDataBlocks = new ArrayList<>(); + + public PDUSequence(PDUHeader pduHeader, long timestamp, int nac) + { + mHeader = pduHeader; + mTimestamp = timestamp; + mNAC = nac; + } + + /** + * Network Access Code (NAC) + */ + public int getNAC() + { + return mNAC; + } + + /** + * Timestamp when the header was transmitted + */ + public long getTimestamp() + { + return mTimestamp; + } + + /** + * Indicates if this message contains all of the data blocks specified by the header + */ + public boolean isComplete() + { + return getHeader().getBlocksToFollowCount() == mDataBlocks.size(); + } + + /** + * Adds the deinterleaved, corrected binary message to this sequence as a datablock. The data + * block is decoded according to the header confirmed/unconfirmed indicator. + * + * @param dataBlock for (un)confirmed data + */ + public void addDataBlock(DataBlock dataBlock) + { + mDataBlocks.add(dataBlock); + } + + public boolean hasDataBlock(int index) + { + return getDataBlock(index) != null; + } + + public DataBlock getDataBlock(int index) + { + if(index < mDataBlocks.size()) + { + return mDataBlocks.get(index); + } + + return null; + } + + /** + * Data blocks that follow the header + */ + public List getDataBlocks() + { + return mDataBlocks; + } + + /** + * Packet Data Unit Header + */ + public PDUHeader getHeader() + { + return mHeader; + } + + @Override + public int getBitsProcessedCount() + { + int processed = getHeader().getBitsProcessedCount(); + + for(DataBlock dataBlock : mDataBlocks) + { + processed += dataBlock.getBitsProcessedCount(); + } + + return processed; + } + + @Override + public int getBitErrorsCount() + { + int errorCount = getHeader().getBitErrorsCount(); + + for(DataBlock dataBlock : mDataBlocks) + { + errorCount += dataBlock.getBitErrorsCount(); + } + + return errorCount; + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append("NAC:").append(getNAC()); + + if(!isComplete()) + { + sb.append(" *INCOMPLETE - RECEIVED ").append(mDataBlocks.size()).append("/") + .append(getHeader().getBlocksToFollowCount()).append(" DATA BLOCKS"); + } + + sb.append(" ").append(getHeader().toString()); + + sb.append(" DATA BLOCKS:").append(mDataBlocks.size()); + + if(!mDataBlocks.isEmpty()) + { + sb.append(" MSG:"); + + for(DataBlock dataBlock: mDataBlocks) + { + sb.append(dataBlock.getMessage().toHexString()); + } + } + + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUSequenceMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUSequenceMessage.java new file mode 100644 index 00000000..e86b759f --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/PDUSequenceMessage.java @@ -0,0 +1,43 @@ +package io.github.dsheirer.module.decode.p25.message.pdu; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.message.P25Message; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.Collections; +import java.util.List; + +public class PDUSequenceMessage extends P25Message +{ + private PDUSequence mPDUSequence; + + public PDUSequenceMessage(PDUSequence PDUSequence, int nac, long timestamp) + { + super(null, nac, timestamp); + mPDUSequence = PDUSequence; + } + + public PDUSequence getPDUSequence() + { + return mPDUSequence; + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getPDUSequence().toString()); + return sb.toString(); + } + + @Override + public DataUnitID getDUID() + { + return DataUnitID.PACKET_DATA_UNIT; + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/UnitToUnitChannelGrantExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/UnitToUnitChannelGrantExtended.java deleted file mode 100644 index 3c72f0f6..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/UnitToUnitChannelGrantExtended.java +++ /dev/null @@ -1,220 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.pdu; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public abstract class UnitToUnitChannelGrantExtended extends PDUMessage implements IdentifierReceiver -{ - /* Service Options */ - public static final int EMERGENCY_FLAG = 128; - public static final int ENCRYPTED_CHANNEL_FLAG = 129; - public static final int DUPLEX_MODE = 130; - public static final int SESSION_MODE = 131; - - public static final int[] SOURCE_ADDRESS = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, - 105, 106, 107, 108, 109, 110, 111}; - public static final int[] SERVICE_OPTIONS = {128, 129, 130, 131, 132, 133, 134, 135}; - public static final int[] SOURCE_WACN = {160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, - 175, 176, 177, 178, 179}; - public static final int[] SOURCE_SYSTEM_ID = {180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191}; - public static final int[] SOURCE_ID = {192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, - 207, 208, 209, 210, 211, 212, 213, 214, 215}; - public static final int[] TARGET_ADDRESS = {216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, - 230, 231, 232, 233, 234, 235, 236, 237, 238, 239}; - public static final int[] TRANSMIT_IDENTIFIER = {240, 241, 242, 243}; - public static final int[] TRANSMIT_NUMBER = {244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255}; - public static final int[] RECEIVE_IDENTIFIER = {256, 257, 258, 259}; - public static final int[] RECEIVE_NUMBER = {260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271}; - public static final int[] MULTIPLE_BLOCK_CRC = {320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, - 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351}; - - private IBandIdentifier mTransmitIdentifierUpdate; - private IBandIdentifier mReceiveIdentifierUpdate; - - public UnitToUnitChannelGrantExtended(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU2(mMessage); - mCRC[1] = mMessage.getCRC(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - if(isEmergency()) - { - sb.append(" EMERGENCY"); - } - - sb.append(" FROM ADDR:"); - sb.append(getSourceAddress()); - - sb.append(" ID:"); - sb.append(getSourceID()); - - sb.append(" TO:"); - sb.append(getTargetAddress()); - - sb.append(" WACN:"); - sb.append(getSourceWACN()); - - sb.append(" SYS:"); - sb.append(getSourceSystemID()); - - sb.append(" CHAN DN:" + getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber()); - sb.append(" " + getDownlinkFrequency()); - - sb.append(" CHAN UP:" + getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber()); - sb.append(" " + getUplinkFrequency()); - - return sb.toString(); - } - - public boolean isEmergency() - { - return mMessage.get(EMERGENCY_FLAG); - } - - public boolean isEncrypted() - { - return mMessage.get(ENCRYPTED_CHANNEL_FLAG); - } - - public DuplexMode getDuplexMode() - { - return mMessage.get(DUPLEX_MODE) ? DuplexMode.FULL : DuplexMode.HALF; - } - - public SessionMode getSessionMode() - { - return mMessage.get(SESSION_MODE) ? - SessionMode.CIRCUIT : SessionMode.PACKET; - } - - public String getSourceAddress() - { - return mMessage.getHex(SOURCE_ADDRESS, 6); - } - - public String getSourceID() - { - return mMessage.getHex(SOURCE_ID, 6); - } - - public String getSourceWACN() - { - return mMessage.getHex(SOURCE_WACN, 5); - } - - public String getSourceSystemID() - { - return mMessage.getHex(SOURCE_SYSTEM_ID, 3); - } - - public String getTargetAddress() - { - return mMessage.getHex(TARGET_ADDRESS, 6); - } - - public int getTransmitChannelIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public int getTransmitChannelNumber() - { - return mMessage.getInt(TRANSMIT_NUMBER); - } - - public String getTransmitChannel() - { - return getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber(); - } - - public int getReceiveChannelIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public int getReceiveChannelNumber() - { - return mMessage.getInt(RECEIVE_NUMBER); - } - - public String getReceiveChannel() - { - return getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber(); - } - - public long getDownlinkFrequency() - { - if(mTransmitIdentifierUpdate != null) - { - return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mReceiveIdentifierUpdate != null) - { - return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber()); - } - - return 0; - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitChannelIdentifier()) - { - mTransmitIdentifierUpdate = message; - } - - if(identifier == getReceiveChannelIdentifier()) - { - mReceiveIdentifierUpdate = message; - } - } - - public int[] getIdentifiers() - { - int[] idens = new int[2]; - - idens[0] = getTransmitChannelIdentifier(); - idens[1] = getReceiveChannelIdentifier(); - - return idens; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/AMBTCHeader.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/AMBTCHeader.java new file mode 100644 index 00000000..3012291e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/AMBTCHeader.java @@ -0,0 +1,90 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.module.decode.p25.P25Utils; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUHeader; +import io.github.dsheirer.module.decode.p25.message.tsbk.Opcode; +import io.github.dsheirer.module.decode.p25.reference.ServiceAccessPoint; +import io.github.dsheirer.module.decode.p25.reference.Vendor; + +/** + * Alternage Multi-Block Trunking Control Header + */ +public class AMBTCHeader extends PDUHeader +{ + public static final int[] SAP_ID = {10, 11, 12, 13, 14, 15}; + public static final int[] OPCODE = {58, 59, 60, 61, 62, 63}; + public static final int[] DATA = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + + public AMBTCHeader(CorrectedBinaryMessage message, boolean passesCRC) + { + super(message, passesCRC); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(!isValid()) + { + sb.append("*CRC-FAIL*"); + } + + sb.append("AMBTC"); + + Vendor vendor = getVendor(); + + sb.append(" ").append(getOpcode().getLabel()); + + P25Utils.pad(sb, 22); + + if(vendor != Vendor.STANDARD) + { + sb.append(" VENDOR:").append(getVendor()); + } + + sb.append(" HDR:").append(getMessage().toHexString()); + return sb.toString(); + } + + /** + * Service Access Point (SAP) - determines the network service that will process this packet + */ + public ServiceAccessPoint getServiceAccessPoint() + { + return ServiceAccessPoint.fromValue(getMessage().getInt(SAP_ID)); + } + + public Opcode getOpcode() + { + return Opcode.fromValue(getMessage().getInt(OPCODE), getDirection(), getVendor()); + } + + /** + * 2 x Data Octets at the end of the header. + */ + public int getDataOctets() + { + return getMessage().getInt(DATA); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/AMBTCMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/AMBTCMessage.java new file mode 100644 index 00000000..8575c0c4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/AMBTCMessage.java @@ -0,0 +1,190 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.message.IBitErrorProvider; +import io.github.dsheirer.module.decode.p25.P25Utils; +import io.github.dsheirer.module.decode.p25.message.P25Message; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.block.DataBlock; +import io.github.dsheirer.module.decode.p25.message.pdu.block.UnconfirmedDataBlock; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.List; + +public abstract class AMBTCMessage extends P25Message implements IBitErrorProvider +{ + protected static final int[] HEADER_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47}; + + private PDUSequence mPDUSequence; + + public AMBTCMessage(PDUSequence PDUSequence, int nac, long timestamp) + { + super(nac, timestamp); + mPDUSequence = PDUSequence; + } + + @Override + protected String getMessageStub() + { + StringBuilder sb = new StringBuilder(); + sb.append(super.getMessageStub()); + sb.append(" ").append(getHeader().getOpcode()); + P25Utils.pad(sb, 30); + + return sb.toString(); + } + + public AMBTCHeader getHeader() + { + return (AMBTCHeader)getPDUSequence().getHeader(); + } + + public boolean hasDataBlock(int index) + { + return index < getPDUSequence().getDataBlocks().size(); + } + + public UnconfirmedDataBlock getDataBlock(int index) + { + if(index < getPDUSequence().getDataBlocks().size()) + { + DataBlock dataBlock = getPDUSequence().getDataBlocks().get(index); + + if(dataBlock instanceof UnconfirmedDataBlock) + { + return (UnconfirmedDataBlock)dataBlock; + } + } + + return null; + } + + + @Override + public DataUnitID getDUID() + { + return DataUnitID.ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL; + } + + /** + * List of identifiers provided by the message + */ + public abstract List getIdentifiers(); + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("NAC:").append(getNAC()); + + if(!getPDUSequence().isComplete()) + { + sb.append(" *INCOMPLETE - RECEIVED ").append(getPDUSequence().getDataBlocks().size()).append("/") + .append(getPDUSequence().getHeader().getBlocksToFollowCount()).append(" DATA BLOCKS"); + } + + sb.append(" ").append(getPDUSequence().getHeader().toString()); + + sb.append(" DATA BLOCKS:").append(getPDUSequence().getDataBlocks().size()); + + if(!getPDUSequence().getDataBlocks().isEmpty()) + { + sb.append(" MSG:"); + + for(DataBlock dataBlock: getPDUSequence().getDataBlocks()) + { + sb.append(dataBlock.getMessage().toHexString()); + } + } + + sb.append(" COMPLETE:").append(getMessage().toHexString()); + + return sb.toString(); + } + + public PDUSequence getPDUSequence() + { + return mPDUSequence; + } + + private void extractMessage() + { + //There are 16 bits in the header + int length = 16; + + int blockCount = getPDUSequence().getHeader().getBlocksToFollowCount(); + + //Each block provides 108 bits/12 bytes and the final block uses 32-bits for CRC + length += (blockCount * 96); + + CorrectedBinaryMessage consolidatedMessage = new CorrectedBinaryMessage(length); + + //Transfer 2 octets from header + AMBTCHeader header = (AMBTCHeader)getPDUSequence().getHeader(); + + int dataOctetsValue = header.getDataOctets(); + consolidatedMessage.load(0, 16, dataOctetsValue); + + if(getPDUSequence().isComplete()) + { + int offset = 16; + for(DataBlock dataBlock: getPDUSequence().getDataBlocks()) + { + if(dataBlock instanceof UnconfirmedDataBlock) + { + BinaryMessage dataBlockMessage = dataBlock.getMessage(); + + for(int x = 0; x < dataBlockMessage.size(); x++) + { + if(dataBlockMessage.get(x)) + { + consolidatedMessage.set(x + offset); + } + } + + offset += dataBlockMessage.size(); + } + } + } + else + { + setValid(false); + } + + setMessage(consolidatedMessage); + } + + @Override + public int getBitsProcessedCount() + { + return getPDUSequence().getBitsProcessedCount(); + } + + @Override + public int getBitErrorsCount() + { + return getPDUSequence().getBitErrorsCount(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCAuthenticationQuery.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCAuthenticationQuery.java new file mode 100644 index 00000000..f8dbfd4c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCAuthenticationQuery.java @@ -0,0 +1,147 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCAuthenticationQuery extends AMBTCMessage +{ + private static final int[] HEADER_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3}; + private static final int[] BLOCK_0_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_TARGET_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39}; + private static final int[] BLOCK_0_RESERVED = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + + private Identifier mWacn; + private Identifier mSystem; + private Identifier mSourceAddress; + private Identifier mTargetId; + private List mIdentifiers; + + public AMBTCAuthenticationQuery(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + if(getTargetId() != null) + { + sb.append(" TO:").append(getTargetId()); + } + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + return sb.toString(); + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + int value = getHeader().getMessage().getInt(HEADER_WACN); + value <<= 4; + value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); + mWacn = APCO25Wacn.create(value); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getTargetId() + { + if(mTargetId == null && hasDataBlock(0)) + { + mTargetId = APCO25ToTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(BLOCK_0_TARGET_ID)); + } + + return mTargetId; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + + if(getTargetId() != null) + { + mIdentifiers.add(getTargetId()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCAuthenticationResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCAuthenticationResponse.java new file mode 100644 index 00000000..3dd38c77 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCAuthenticationResponse.java @@ -0,0 +1,161 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCAuthenticationResponse extends AMBTCMessage +{ + private static final int[] HEADER_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3}; + private static final int[] BLOCK_0_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_SOURCE_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39}; + private static final int[] BLOCK_0_AUTHENTICATION_VALUE = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] BLOCK_1_AUTHENTICATION_VALUE = {0, 1, 2, 3}; + + private String mAuthenticationValue; + private Identifier mWacn; + private Identifier mSystem; + private Identifier mTargetAddress; + private Identifier mSourceId; + private List mIdentifiers; + + public AMBTCAuthenticationResponse(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceId()); + if(getSourceId() != null) + { + sb.append(" TO:").append(getTargetAddress()); + } + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + if(getAuthenticationValue() != null) + { + sb.append(" AUTHENTICATION:").append(getAuthenticationValue()); + } + return sb.toString(); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getHeader().getMessage().getInt(HEADER_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + int value = getHeader().getMessage().getInt(HEADER_WACN); + value <<= 4; + value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); + mWacn = APCO25Wacn.create(value); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getSourceId() + { + if(mSourceId == null && hasDataBlock(0)) + { + mSourceId = APCO25FromTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID)); + } + + return mSourceId; + } + + public String getAuthenticationValue() + { + if(mAuthenticationValue == null && hasDataBlock(0) && hasDataBlock(1)) + { + mAuthenticationValue = getDataBlock(0).getMessage().getHex(BLOCK_0_AUTHENTICATION_VALUE, 14) + + getDataBlock(1).getMessage().getHex(BLOCK_1_AUTHENTICATION_VALUE, 2); + } + + return mAuthenticationValue; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + + if(getTargetAddress() != null) + { + mIdentifiers.add(getTargetAddress()); + } + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getSourceId() != null) + { + mIdentifiers.add(getSourceId()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCCallAlertRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCCallAlertRequest.java new file mode 100644 index 00000000..900e7cf8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCCallAlertRequest.java @@ -0,0 +1,142 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCCallAlertRequest extends AMBTCMessage +{ + private static final int[] HEADER_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3}; + private static final int[] BLOCK_0_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_TARGET_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39}; + + private Identifier mWacn; + private Identifier mSystem; + private Identifier mTargetId; + private Identifier mSourceAddress; + private List mIdentifiers; + + public AMBTCCallAlertRequest(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + if(getTargetId() != null) + { + sb.append(" TO:").append(getTargetId()); + } + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + return sb.toString(); + } + + public Identifier getTargetId() + { + if(mTargetId == null) + { + mTargetId = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(BLOCK_0_TARGET_ID)); + } + + return mTargetId; + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + int value = getHeader().getMessage().getInt(HEADER_WACN); + value <<= 4; + value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); + mWacn = APCO25Wacn.create(value); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null && hasDataBlock(0)) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getTargetId() != null) + { + mIdentifiers.add(getTargetId()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCGroupAffiliationRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCGroupAffiliationRequest.java new file mode 100644 index 00000000..8ebe11b2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCGroupAffiliationRequest.java @@ -0,0 +1,144 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCGroupAffiliationRequest extends AMBTCMessage +{ + private static final int[] HEADER_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3}; + private static final int[] BLOCK_0_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_GROUP_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] RESERVED = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + + private Identifier mWacn; + private Identifier mSystem; + private Identifier mGroupId; + private Identifier mSourceAddress; + private List mIdentifiers; + + public AMBTCGroupAffiliationRequest(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + if(getSourceAddress() != null) + { + sb.append(" TO:").append(getGroupId()); + } + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + return sb.toString(); + } + + public Identifier getGroupId() + { + if(mGroupId == null) + { + mGroupId = APCO25ToTalkgroup.createGroup(getHeader().getMessage().getInt(BLOCK_0_GROUP_ID)); + } + + return mGroupId; + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + int value = getHeader().getMessage().getInt(HEADER_WACN); + value <<= 4; + value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); + mWacn = APCO25Wacn.create(value); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null && hasDataBlock(0)) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getGroupId() != null) + { + mIdentifiers.add(getGroupId()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCIndividualDataServiceRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCIndividualDataServiceRequest.java new file mode 100644 index 00000000..6f1240fd --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCIndividualDataServiceRequest.java @@ -0,0 +1,154 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCIndividualDataServiceRequest extends AMBTCMessage +{ + private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] HEADER_RESERVED = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; + private static final int[] BLOCK_0_SYSTEM = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] BLOCK_0_TARGET_ID = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] BLOCK_0_RESERVED = {56, 57, 58, 59, 60, 61, 62, 63}; + + private DataServiceOptions mServiceOptions; + private Identifier mWacn; + private Identifier mSystem; + private Identifier mSourceAddress; + private Identifier mTargetId; + private List mIdentifiers; + + public AMBTCIndividualDataServiceRequest(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + if(getTargetId() != null) + { + sb.append(" TO:").append(getTargetId()); + } + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + sb.append(" SERVICE OPTIONS:").append(getDataServiceOptions()); + return sb.toString(); + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + public DataServiceOptions getDataServiceOptions() + { + if(mServiceOptions == null) + { + mServiceOptions = new DataServiceOptions(getHeader().getMessage().getInt(HEADER_SERVICE_OPTIONS)); + } + + return mServiceOptions; + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + mWacn = APCO25Wacn.create(getDataBlock(0).getMessage().getInt(BLOCK_0_WACN)); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getTargetId() + { + if(mTargetId == null && hasDataBlock(0)) + { + mTargetId = APCO25ToTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(BLOCK_0_TARGET_ID)); + } + + return mTargetId; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getTargetId() != null) + { + mIdentifiers.add(getTargetId()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCLocationRegistrationRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCLocationRegistrationRequest.java new file mode 100644 index 00000000..4c44aa3b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCLocationRegistrationRequest.java @@ -0,0 +1,185 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25IdentifierTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCLocationRegistrationRequest extends AMBTCMessage +{ + private static final int[] HEADER_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3}; + private static final int[] BLOCK_0_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_SOURCE_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39}; + private static final int[] BLOCK_0_PREVIOUS_LRA = {40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] BLOCK_0_GROUP_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63}; + + private Identifier mWacn; + private Identifier mSystem; + private Identifier mSourceId; + private Identifier mSourceAddress; + private Identifier mGroupAddress; + private Identifier mPreviousLra; + private List mIdentifiers; + + public AMBTCLocationRegistrationRequest(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM ADDR:").append(getSourceAddress()); + if(getSourceId() != null) + { + sb.append(" FM ID:").append(getSourceId()); + } + if(getGroupAddress() != null) + { + sb.append(" TO:").append(getGroupAddress()); + } + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + if(getPreviousLra() != null) + { + sb.append(" PREVIOUS LRA:").append(getPreviousLra()); + } + return sb.toString(); + } + + public Identifier getGroupAddress() + { + if(mGroupAddress == null && hasDataBlock(0)) + { + mGroupAddress = APCO25ToTalkgroup.createGroup(getDataBlock(0).getMessage().getInt(BLOCK_0_GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + public Identifier getPreviousLra() + { + if(mPreviousLra == null && hasDataBlock(0)) + { + mPreviousLra = APCO25Lra.create(getDataBlock(0).getMessage().getInt(BLOCK_0_PREVIOUS_LRA)); + } + + return mPreviousLra; + } + + public Identifier getSourceId() + { + if(mSourceId == null) + { + mSourceId = APCO25IdentifierTalkgroup.createIndividual(getHeader().getMessage().getInt(BLOCK_0_SOURCE_ID)); + } + + return mSourceId; + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + int value = getHeader().getMessage().getInt(HEADER_WACN); + value <<= 4; + value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); + mWacn = APCO25Wacn.create(value); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null && hasDataBlock(0)) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getSourceId() != null) + { + mIdentifiers.add(getSourceId()); + } + if(getGroupAddress() != null) + { + mIdentifiers.add(getGroupAddress()); + } + if(getPreviousLra() != null) + { + mIdentifiers.add(getPreviousLra()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCMessageUpdateRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCMessageUpdateRequest.java new file mode 100644 index 00000000..d63b6ab8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCMessageUpdateRequest.java @@ -0,0 +1,152 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.message.APCO25ShortDataMessage; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCMessageUpdateRequest extends AMBTCMessage +{ + private static final int[] HEADER_MESSAGE = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; + private static final int[] BLOCK_0_SYSTEM = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] BLOCK_0_TARGET_ID = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] BLOCK_0_RESERVED = {56, 57, 58, 59, 60, 61, 62, 63}; + + private Identifier mShortDataMessage; + private Identifier mWacn; + private Identifier mSystem; + private Identifier mSourceAddress; + private Identifier mTargetId; + private List mIdentifiers; + + public AMBTCMessageUpdateRequest(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + if(getTargetId() != null) + { + sb.append(" TO:").append(getTargetId()); + } + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + sb.append(" SHORT DATA MSG:").append(getShortDataMessage()); + return sb.toString(); + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + public Identifier getShortDataMessage() + { + if(mShortDataMessage == null) + { + mShortDataMessage = APCO25ShortDataMessage.create(getHeader().getMessage().getInt(HEADER_MESSAGE)); + } + + return mShortDataMessage; + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + mWacn = APCO25Wacn.create(getDataBlock(0).getMessage().getInt(BLOCK_0_WACN)); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getTargetId() + { + if(mTargetId == null && hasDataBlock(0)) + { + mTargetId = APCO25ToTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(BLOCK_0_TARGET_ID)); + } + + return mTargetId; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getTargetId() != null) + { + mIdentifiers.add(getTargetId()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCRoamingAddressRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCRoamingAddressRequest.java new file mode 100644 index 00000000..9603b2ea --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCRoamingAddressRequest.java @@ -0,0 +1,141 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCRoamingAddressRequest extends AMBTCMessage +{ + private static final int[] HEADER_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3}; + private static final int[] BLOCK_0_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_TARGET_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39}; + + private Identifier mWacn; + private Identifier mSystem; + private Identifier mTargetId; + private Identifier mSourceAddress; + private List mIdentifiers; + + public AMBTCRoamingAddressRequest(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + if(getTargetId() != null) + { + sb.append(" TO:").append(getTargetId()); + } + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + return sb.toString(); + } + + public Identifier getTargetId() + { + if(mTargetId == null) + { + mTargetId = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(BLOCK_0_TARGET_ID)); + } + + return mTargetId; + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + int value = getHeader().getMessage().getInt(HEADER_WACN); + value <<= 4; + value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); + mWacn = APCO25Wacn.create(value); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null && hasDataBlock(0)) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getTargetId() != null) + { + mIdentifiers.add(getTargetId()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCStatusQueryRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCStatusQueryRequest.java new file mode 100644 index 00000000..8d48a553 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCStatusQueryRequest.java @@ -0,0 +1,144 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCStatusQueryRequest extends AMBTCMessage +{ + private static final int[] HEADER_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3}; + private static final int[] BLOCK_0_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_TARGET_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39}; + private static final int[] RESERVED = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63}; + + private Identifier mWacn; + private Identifier mSystem; + private Identifier mTargetId; + private Identifier mSourceAddress; + private List mIdentifiers; + + public AMBTCStatusQueryRequest(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + if(getSourceAddress() != null) + { + sb.append(" TO:").append(getTargetId()); + } + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + return sb.toString(); + } + + public Identifier getTargetId() + { + if(mTargetId == null) + { + mTargetId = APCO25ToTalkgroup.createIndividual(getHeader().getMessage().getInt(BLOCK_0_TARGET_ID)); + } + + return mTargetId; + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + int value = getHeader().getMessage().getInt(HEADER_WACN); + value <<= 4; + value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); + mWacn = APCO25Wacn.create(value); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null && hasDataBlock(0)) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getTargetId() != null) + { + mIdentifiers.add(getTargetId()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCStatusQueryResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCStatusQueryResponse.java new file mode 100644 index 00000000..a5ed93f6 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCStatusQueryResponse.java @@ -0,0 +1,165 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.status.APCO25Status; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCStatusQueryResponse extends AMBTCMessage +{ + private static final int[] HEADER_UNIT_STATUS = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] HEADER_USER_STATUS = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; + private static final int[] BLOCK_0_SYSTEM = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] BLOCK_0_TARGET_ID = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] BLOCK_0_RESERVED = {56, 57, 58, 59, 60, 61, 62, 63}; + + private Identifier mUnitStatus; + private Identifier mUserStatus; + private Identifier mWacn; + private Identifier mSystem; + private Identifier mSourceAddress; + private Identifier mTargetId; + private List mIdentifiers; + + public AMBTCStatusQueryResponse(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + if(getTargetId() != null) + { + sb.append(" TO:").append(getTargetId()); + } + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + sb.append(" UNIT STATUS:").append(getUnitStatus()); + sb.append(" USER STATUS:").append(getUserStatus()); + return sb.toString(); + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + public Identifier getUnitStatus() + { + if(mUnitStatus == null) + { + mUnitStatus = APCO25Status.createUnitStatus(getHeader().getMessage().getInt(HEADER_UNIT_STATUS)); + } + + return mUnitStatus; + } + + public Identifier getUserStatus() + { + if(mUserStatus == null) + { + mUserStatus = APCO25Status.createUserStatus(getHeader().getMessage().getInt(HEADER_USER_STATUS)); + } + + return mUserStatus; + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + mWacn = APCO25Wacn.create(getDataBlock(0).getMessage().getInt(BLOCK_0_WACN)); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getTargetId() + { + if(mTargetId == null && hasDataBlock(0)) + { + mTargetId = APCO25ToTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(BLOCK_0_TARGET_ID)); + } + + return mTargetId; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getTargetId() != null) + { + mIdentifiers.add(getTargetId()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCStatusUpdateRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCStatusUpdateRequest.java new file mode 100644 index 00000000..d072b1f7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCStatusUpdateRequest.java @@ -0,0 +1,165 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.status.APCO25Status; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCStatusUpdateRequest extends AMBTCMessage +{ + private static final int[] HEADER_UNIT_STATUS = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] HEADER_USER_STATUS = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; + private static final int[] BLOCK_0_SYSTEM = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] BLOCK_0_TARGET_ID = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] BLOCK_0_RESERVED = {56, 57, 58, 59, 60, 61, 62, 63}; + + private Identifier mUnitStatus; + private Identifier mUserStatus; + private Identifier mWacn; + private Identifier mSystem; + private Identifier mSourceAddress; + private Identifier mTargetId; + private List mIdentifiers; + + public AMBTCStatusUpdateRequest(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + if(getTargetId() != null) + { + sb.append(" TO:").append(getTargetId()); + } + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + sb.append(" UNIT STATUS:").append(getUnitStatus()); + sb.append(" USER STATUS:").append(getUserStatus()); + return sb.toString(); + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + public Identifier getUnitStatus() + { + if(mUnitStatus == null) + { + mUnitStatus = APCO25Status.createUnitStatus(getHeader().getMessage().getInt(HEADER_UNIT_STATUS)); + } + + return mUnitStatus; + } + + public Identifier getUserStatus() + { + if(mUserStatus == null) + { + mUserStatus = APCO25Status.createUserStatus(getHeader().getMessage().getInt(HEADER_USER_STATUS)); + } + + return mUserStatus; + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + mWacn = APCO25Wacn.create(getDataBlock(0).getMessage().getInt(BLOCK_0_WACN)); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getTargetId() + { + if(mTargetId == null && hasDataBlock(0)) + { + mTargetId = APCO25ToTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(BLOCK_0_TARGET_ID)); + } + + return mTargetId; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getTargetId() != null) + { + mIdentifiers.add(getTargetId()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCUnitAcknowledgeResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCUnitAcknowledgeResponse.java new file mode 100644 index 00000000..4bf86a54 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCUnitAcknowledgeResponse.java @@ -0,0 +1,159 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.message.tsbk.Opcode; +import io.github.dsheirer.module.decode.p25.reference.Direction; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCUnitAcknowledgeResponse extends AMBTCMessage +{ + private static final int[] ACKNOWLEDGED_SERVICE_OPCODE = {66, 67, 68, 69, 70, 71}; + private static final int[] HEADER_WACN = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; + private static final int[] BLOCK_0_SYSTEM = {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] BLOCK_0_TARGET_ID = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47}; + private static final int[] BLOCK_0_RESERVED = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + + private Opcode mAcknowledgedService; + private Identifier mWacn; + private Identifier mSystem; + private Identifier mSourceAddress; + private Identifier mTargetId; + private List mIdentifiers; + + public AMBTCUnitAcknowledgeResponse(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + if(getTargetId() != null) + { + sb.append(" TO:").append(getTargetId()); + } + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + sb.append(" ACK SERVICE:").append(getAcknowledgedService()); + return sb.toString(); + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + public Opcode getAcknowledgedService() + { + if(mAcknowledgedService == null) + { + mAcknowledgedService = Opcode.fromValue(getHeader().getMessage().getInt(ACKNOWLEDGED_SERVICE_OPCODE), + Direction.OUTBOUND, getHeader().getVendor()); + } + + return mAcknowledgedService; + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + int value = getHeader().getMessage().getInt(HEADER_WACN); + value <<= 12; + value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); + mWacn = APCO25Wacn.create(value); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getTargetId() + { + if(mTargetId == null && hasDataBlock(0)) + { + mTargetId = APCO25ToTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(BLOCK_0_TARGET_ID)); + } + + return mTargetId; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getTargetId() != null) + { + mIdentifiers.add(getTargetId()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCUnitToUnitVoiceServiceAnswerResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCUnitToUnitVoiceServiceAnswerResponse.java new file mode 100644 index 00000000..0dfd085f --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCUnitToUnitVoiceServiceAnswerResponse.java @@ -0,0 +1,168 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.reference.AnswerResponse; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCUnitToUnitVoiceServiceAnswerResponse extends AMBTCMessage +{ + private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] HEADER_ANSWER_RESPONSE = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_RESERVED = {0, 1, 2, 3, 4, 5, 6, 7}; + private static final int[] BLOCK_0_WACN = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27}; + private static final int[] BLOCK_0_SYSTEM = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] BLOCK_0_TARGET_ID = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63}; + + private VoiceServiceOptions mVoiceServiceOptions; + private AnswerResponse mAnswerResponse; + private Identifier mWacn; + private Identifier mSystem; + private Identifier mSourceAddress; + private Identifier mTargetId; + private List mIdentifiers; + + public AMBTCUnitToUnitVoiceServiceAnswerResponse(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + if(getTargetId() != null) + { + sb.append(" TO:").append(getTargetId()); + } + sb.append(" RESPONSE:").append(getAnswerResponse()); + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + return sb.toString(); + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + public AnswerResponse getAnswerResponse() + { + if(mAnswerResponse == null) + { + mAnswerResponse = AnswerResponse.fromValue(getHeader().getMessage().getInt(HEADER_ANSWER_RESPONSE)); + } + + return mAnswerResponse; + } + + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getHeader().getMessage().getInt(HEADER_SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + mWacn = APCO25Wacn.create(getDataBlock(0).getMessage().getInt(BLOCK_0_WACN)); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getTargetId() + { + if(mTargetId == null && hasDataBlock(0)) + { + mTargetId = APCO25ToTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(BLOCK_0_TARGET_ID)); + } + + return mTargetId; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getTargetId() != null) + { + mIdentifiers.add(getTargetId()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCUnitToUnitVoiceServiceRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCUnitToUnitVoiceServiceRequest.java new file mode 100644 index 00000000..d70f6396 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/isp/AMBTCUnitToUnitVoiceServiceRequest.java @@ -0,0 +1,155 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.isp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCUnitToUnitVoiceServiceRequest extends AMBTCMessage +{ + private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] HEADER_RESERVED = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_RESERVED = {0, 1, 2, 3, 4, 5, 6, 7}; + private static final int[] BLOCK_0_WACN = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27}; + private static final int[] BLOCK_0_SYSTEM = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] BLOCK_0_TARGET_ID = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63}; + + private VoiceServiceOptions mVoiceServiceOptions; + private Identifier mWacn; + private Identifier mSystem; + private Identifier mSourceAddress; + private Identifier mTargetId; + private List mIdentifiers; + + public AMBTCUnitToUnitVoiceServiceRequest(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + if(getTargetId() != null) + { + sb.append(" TO:").append(getTargetId()); + } + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + return sb.toString(); + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getHeader().getMessage().getInt(HEADER_SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + mWacn = APCO25Wacn.create(getDataBlock(0).getMessage().getInt(BLOCK_0_WACN)); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getTargetId() + { + if(mTargetId == null && hasDataBlock(0)) + { + mTargetId = APCO25ToTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(BLOCK_0_TARGET_ID)); + } + + return mTargetId; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getTargetId() != null) + { + mIdentifiers.add(getTargetId()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCAdjacentStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCAdjacentStatusBroadcast.java new file mode 100644 index 00000000..54bb9716 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCAdjacentStatusBroadcast.java @@ -0,0 +1,189 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.block.UnconfirmedDataBlock; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCAdjacentStatusBroadcast extends AMBTCMessage implements IFrequencyBandReceiver +{ + + private static final int[] HEADER_LRA = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] HEADER_SYSTEM = {36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] HEADER_RFSS = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] HEADER_SITE = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_DOWNLINK_FREQUENCY_BAND = {0, 1, 2, 3}; + private static final int[] BLOCK_0_DOWNLINK_CHANNEL_NUMBER = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_UPLINK_FREQUENCY_BAND = {16, 17, 18, 19}; + private static final int[] BLOCK_0_UPLINK_CHANNEL_NUMBER = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + + private Identifier mSystem; + private Identifier mLocationRegistrationArea; + private Identifier mRfss; + private Identifier mSite; + private IChannelDescriptor mChannel; + private List mIdentifiers; + private List mChannels; + + public AMBTCAdjacentStatusBroadcast(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + + setValid(hasDataBlock(0)); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" SYSTEM:").append(getSystem()); + sb.append(" LRA:").append(getLocationRegistrationArea()); + sb.append(" RFSS:").append(getRfss()); + sb.append(" SITE:").append(getSite()); + sb.append(" CHAN:").append(getChannel()); + return sb.toString(); + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getHeader().getMessage().getInt(HEADER_SYSTEM)); + } + + return mSystem; + } + + public Identifier getLocationRegistrationArea() + { + if(mLocationRegistrationArea == null) + { + mLocationRegistrationArea = APCO25Lra.create(getHeader().getMessage().getInt(HEADER_LRA)); + } + + return mLocationRegistrationArea; + } + + public Identifier getRfss() + { + if(mRfss == null) + { + mRfss = APCO25Rfss.create(getHeader().getMessage().getInt(HEADER_RFSS)); + } + + return mRfss; + } + + public Identifier getSite() + { + if(mSite == null) + { + mSite = APCO25Site.create(getHeader().getMessage().getInt(HEADER_SITE)); + } + + return mSite; + } + + public boolean isExtendedChannel() + { + return hasDataBlock(0) && + (getDataBlock(0).getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER) != + getDataBlock(0).getMessage().getInt(BLOCK_0_UPLINK_CHANNEL_NUMBER)); + } + + public IChannelDescriptor getChannel() + { + if(mChannel == null && hasDataBlock(0)) + { + if(hasDataBlock(0)) + { + UnconfirmedDataBlock block = getDataBlock(0); + + if(isExtendedChannel()) + { + mChannel = APCO25ExplicitChannel.create(block.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND), + block.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER), + block.getMessage().getInt(BLOCK_0_UPLINK_FREQUENCY_BAND), + block.getMessage().getInt(BLOCK_0_UPLINK_CHANNEL_NUMBER)); + } + else + { + mChannel = APCO25Channel.create(block.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND), + block.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER)); + } + } + else + { + mChannel = APCO25Channel.create(-1, 0); + } + } + + return mChannel; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getRfss() != null) + { + mIdentifiers.add(getRfss()); + } + if(getSite() != null) + { + mIdentifiers.add(getSite()); + } + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + if(mChannels == null) + { + mChannels = new ArrayList<>(); + + if(getChannel() != null) + { + mChannels.add(getChannel()); + } + } + + return mChannels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCCallAlert.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCCallAlert.java new file mode 100644 index 00000000..f623bc95 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCCallAlert.java @@ -0,0 +1,149 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.ArrayList; +import java.util.List; + +/** + * Call alert + */ +public class AMBTCCallAlert extends AMBTCMessage +{ + private static final int[] HEADER_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3}; + private static final int[] BLOCK_0_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_SOURCE_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39}; + + private Identifier mWacn; + private Identifier mSystem; + private Identifier mSourceId; + private Identifier mTargetAddress; + private List mIdentifiers; + + public AMBTCCallAlert(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + if(getSourceId() != null) + { + sb.append(" FM:").append(getSourceId()); + } + if(getTargetAddress() != null) + { + sb.append(" TO:").append(getTargetAddress()); + } + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + + return sb.toString(); + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + int value = getHeader().getMessage().getInt(HEADER_WACN); + value <<= 4; + value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); + mWacn = APCO25Wacn.create(value); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null && hasDataBlock(0)) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceId() + { + if(mSourceId == null && hasDataBlock(0)) + { + mSourceId = APCO25FromTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID)); + } + + return mSourceId; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getSourceId() != null) + { + mIdentifiers.add(getSourceId()); + } + if(getTargetAddress() != null) + { + mIdentifiers.add(getTargetAddress()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCGroupAffiliationQuery.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCGroupAffiliationQuery.java new file mode 100644 index 00000000..a44cf8e4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCGroupAffiliationQuery.java @@ -0,0 +1,149 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.ArrayList; +import java.util.List; + +/** + * Group affiliation query + */ +public class AMBTCGroupAffiliationQuery extends AMBTCMessage +{ + private static final int[] HEADER_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3}; + private static final int[] BLOCK_0_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_SOURCE_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39}; + + private Identifier mWacn; + private Identifier mSystem; + private Identifier mSourceId; + private Identifier mTargetAddress; + private List mIdentifiers; + + public AMBTCGroupAffiliationQuery(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + if(getSourceId() != null) + { + sb.append(" FM:").append(getSourceId()); + } + if(getTargetAddress() != null) + { + sb.append(" TO:").append(getTargetAddress()); + } + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + + return sb.toString(); + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + int value = getHeader().getMessage().getInt(HEADER_WACN); + value <<= 4; + value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); + mWacn = APCO25Wacn.create(value); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null && hasDataBlock(0)) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceId() + { + if(mSourceId == null && hasDataBlock(0)) + { + mSourceId = APCO25FromTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID)); + } + + return mSourceId; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getSourceId() != null) + { + mIdentifiers.add(getSourceId()); + } + if(getTargetAddress() != null) + { + mIdentifiers.add(getTargetAddress()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCGroupAffiliationResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCGroupAffiliationResponse.java new file mode 100644 index 00000000..4c4e640e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCGroupAffiliationResponse.java @@ -0,0 +1,241 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25AnnouncementTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.ArrayList; +import java.util.List; + +/** + * Group affiliation response + */ +public class AMBTCGroupAffiliationResponse extends AMBTCMessage +{ + private static final int[] HEADER_SOURCE_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_SOURCE_WACN = {0, 1, 2, 3}; + private static final int[] BLOCK_0_SOURCE_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_SOURCE_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39}; + private static final int[] BLOCK_0_GROUP_WACN = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59}; + private static final int[] BLOCK_0_GROUP_SYSTEM = {60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] BLOCK_0_GROUP_ID = {72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87}; + private static final int[] BLOCK_0_ANNOUNCEMENT_GROUP_ID = {88, 89, 90, 91, 92, 93, 94, 95}; + private static final int[] BLOCK_1_ANNOUNCEMENT_GROUP_ID = {0, 1, 2, 3, 4, 5, 6, 7}; + + private Identifier mTargetAddress; + private Identifier mSourceWacn; + private Identifier mSourceSystem; + private Identifier mSourceId; + private Identifier mGroupWacn; + private Identifier mGroupSystem; + private Identifier mGroupId; + private Identifier mAnnouncementGroupId; + private List mIdentifiers; + + public AMBTCGroupAffiliationResponse(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + if(getTargetAddress() != null) + { + sb.append(" TO:").append(getTargetAddress()); + } + if(getSourceWacn() != null) + { + sb.append(" FM WACN:").append(getSourceWacn()); + } + if(getSourceSystem() != null) + { + sb.append(" FM SYSTEM:").append(getSourceSystem()); + } + if(getSourceId() != null) + { + sb.append(" FM ID:").append(getSourceId()); + } + if(getGroupWacn() != null) + { + sb.append(" GRP WACN:").append(getGroupWacn()); + } + if(getGroupSystem() != null) + { + sb.append(" GRP SYSTEM:").append(getGroupSystem()); + } + if(getGroupId() != null) + { + sb.append(" GRP ID:").append(getGroupId()); + } + if(getAnnouncementGroupId() != null) + { + sb.append(" ANNOUNCEMENT GRP:").append(getAnnouncementGroupId()); + } + + return sb.toString(); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null && hasDataBlock(0)) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceWacn() + { + if(mSourceWacn == null && hasDataBlock(0)) + { + int value = getHeader().getMessage().getInt(HEADER_SOURCE_WACN); + value <<= 4; + value += getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_WACN); + mSourceWacn = APCO25Wacn.create(value); + } + + return mSourceWacn; + } + + public Identifier getSourceSystem() + { + if(mSourceSystem == null && hasDataBlock(0)) + { + mSourceSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_SYSTEM)); + } + + return mSourceSystem; + } + + public Identifier getSourceId() + { + if(mSourceId == null && hasDataBlock(0)) + { + mSourceId = APCO25FromTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID)); + } + + return mSourceId; + } + + public Identifier getGroupWacn() + { + if(mGroupWacn == null && hasDataBlock(0)) + { + mGroupWacn = APCO25Wacn.create(getDataBlock(0).getMessage().getInt(BLOCK_0_GROUP_WACN)); + } + + return mGroupWacn; + } + + public Identifier getGroupSystem() + { + if(mGroupSystem == null && hasDataBlock(0)) + { + mGroupSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_GROUP_SYSTEM)); + } + + return mGroupSystem; + } + + public Identifier getGroupId() + { + if(mGroupId == null && hasDataBlock(0)) + { + mGroupId = APCO25ToTalkgroup.createGroup(getDataBlock(0).getMessage().getInt(BLOCK_0_GROUP_ID)); + } + + return mGroupId; + } + + public Identifier getAnnouncementGroupId() + { + if(mAnnouncementGroupId == null && hasDataBlock(0) && hasDataBlock(1)) + { + int id = getDataBlock(0).getMessage().getInt(BLOCK_0_ANNOUNCEMENT_GROUP_ID); + id <<= 8; + id += getDataBlock(1).getMessage().getInt(BLOCK_1_ANNOUNCEMENT_GROUP_ID); + + mAnnouncementGroupId = APCO25AnnouncementTalkgroup.create(id); + } + + return mAnnouncementGroupId; + } + + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getTargetAddress() != null) + { + mIdentifiers.add(getTargetAddress()); + } + if(getSourceWacn() != null) + { + mIdentifiers.add(getSourceWacn()); + } + if(getSourceSystem() != null) + { + mIdentifiers.add(getSourceSystem()); + } + if(getSourceId() != null) + { + mIdentifiers.add(getSourceId()); + } + if(getTargetAddress() != null) + { + mIdentifiers.add(getTargetAddress()); + } + if(getGroupWacn() != null) + { + mIdentifiers.add(getGroupWacn()); + } + if(getGroupSystem() != null) + { + mIdentifiers.add(getGroupSystem()); + } + if(getGroupId() != null) + { + mIdentifiers.add(getGroupId()); + } + if(getAnnouncementGroupId() != null) + { + mIdentifiers.add(getAnnouncementGroupId()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCGroupDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCGroupDataChannelGrant.java new file mode 100644 index 00000000..2e0a9490 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCGroupDataChannelGrant.java @@ -0,0 +1,166 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.block.UnconfirmedDataBlock; +import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCGroupDataChannelGrant extends AMBTCMessage implements IFrequencyBandReceiver +{ + private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] HEADER_RESERVED = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_RESERVED = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_DOWNLINK_FREQUENCY_BAND = {16, 17, 18, 19}; + private static final int[] BLOCK_0_DOWNLINK_CHANNEL_NUMBER = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] BLOCK_0_UPLINK_FREQUENCY_BAND = {32, 33, 34, 35}; + private static final int[] BLOCK_0_UPLINK_CHANNEL_NUMBER = {36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] BLOCK_0_GROUP_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + + private DataServiceOptions mDataServiceOptions; + private APCO25Channel mChannel; + private Identifier mSourceAddress; + private Identifier mGroupAddress; + private List mIdentifiers; + private List mChannels; + + public AMBTCGroupDataChannelGrant(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getGroupAddress()); + sb.append(" CHAN:").append(getChannel()); + sb.append(" SERVICE OPTIONS:").append(getDataServiceOptions()); + return sb.toString(); + } + + public DataServiceOptions getDataServiceOptions() + { + if(mDataServiceOptions == null) + { + mDataServiceOptions = new DataServiceOptions(getHeader().getMessage().getInt(HEADER_SERVICE_OPTIONS)); + } + + return mDataServiceOptions; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + public Identifier getGroupAddress() + { + if(mGroupAddress == null && hasDataBlock(0)) + { + mGroupAddress = APCO25ToTalkgroup.createGroup(getDataBlock(0).getMessage().getInt(BLOCK_0_GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + public boolean isExtendedChannel() + { + return hasDataBlock(0) && + (getDataBlock(0).getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER) != + getDataBlock(0).getMessage().getInt(BLOCK_0_UPLINK_CHANNEL_NUMBER)); + } + + public APCO25Channel getChannel() + { + if(mChannel == null && hasDataBlock(0)) + { + UnconfirmedDataBlock block = getDataBlock(0); + + if(isExtendedChannel()) + { + mChannel = APCO25ExplicitChannel.create(block.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND), + block.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER), + block.getMessage().getInt(BLOCK_0_UPLINK_FREQUENCY_BAND), + block.getMessage().getInt(BLOCK_0_UPLINK_CHANNEL_NUMBER)); + } + else + { + mChannel = APCO25Channel.create(block.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND), + block.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER)); + } + } + + return mChannel; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + if(getGroupAddress() != null) + { + mIdentifiers.add(getGroupAddress()); + } + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + if(mChannels == null) + { + mChannels = new ArrayList<>(); + + if(getChannel() != null) + { + mChannels.add(getChannel()); + } + } + + return mChannels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCGroupVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCGroupVoiceChannelGrant.java new file mode 100644 index 00000000..fe116501 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCGroupVoiceChannelGrant.java @@ -0,0 +1,174 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.block.UnconfirmedDataBlock; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCGroupVoiceChannelGrant extends AMBTCMessage implements IFrequencyBandReceiver +{ + private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] HEADER_RESERVED = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_RESERVED = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_DOWNLINK_FREQUENCY_BAND = {16, 17, 18, 19}; + private static final int[] BLOCK_0_DOWNLINK_CHANNEL_NUMBER = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] BLOCK_0_UPLINK_FREQUENCY_BAND = {32, 33, 34, 35}; + private static final int[] BLOCK_0_UPLINK_CHANNEL_NUMBER = {36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] BLOCK_0_GROUP_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + + private VoiceServiceOptions mVoiceServiceOptions; + private APCO25Channel mChannel; + private Identifier mSourceAddress; + private Identifier mGroupAddress; + private List mIdentifiers; + private List mChannels; + + public AMBTCGroupVoiceChannelGrant(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getGroupAddress()); + sb.append(" CHAN:").append(getChannel()); + sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + return sb.toString(); + } + + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getHeader().getMessage().getInt(HEADER_SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + public Identifier getGroupAddress() + { + if(mGroupAddress == null && hasDataBlock(0)) + { + mGroupAddress = APCO25ToTalkgroup.createGroup(getDataBlock(0).getMessage().getInt(BLOCK_0_GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + public boolean isExtendedChannel() + { + return hasDataBlock(0) && + (getDataBlock(0).getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER) != + getDataBlock(0).getMessage().getInt(BLOCK_0_UPLINK_CHANNEL_NUMBER)); + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + if(hasDataBlock(0)) + { + UnconfirmedDataBlock block = getDataBlock(0); + + if(isExtendedChannel()) + { + mChannel = APCO25ExplicitChannel.create(block.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND), + block.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER), + block.getMessage().getInt(BLOCK_0_UPLINK_FREQUENCY_BAND), + block.getMessage().getInt(BLOCK_0_UPLINK_CHANNEL_NUMBER)); + } + else + { + mChannel = APCO25Channel.create(block.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND), + block.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER)); + } + } + + if(mChannel == null) + { + mChannel = APCO25Channel.create(-1, 0); + } + } + + return mChannel; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + if(getGroupAddress() != null) + { + mIdentifiers.add(getGroupAddress()); + } + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + if(mChannels == null) + { + mChannels = new ArrayList<>(); + + if(getChannel() != null) + { + mChannels.add(getChannel()); + } + } + + return mChannels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCIndividualDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCIndividualDataChannelGrant.java new file mode 100644 index 00000000..07583c65 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCIndividualDataChannelGrant.java @@ -0,0 +1,236 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.block.UnconfirmedDataBlock; +import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCIndividualDataChannelGrant extends AMBTCMessage implements IFrequencyBandReceiver +{ + private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] HEADER_RESERVED = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; + private static final int[] BLOCK_0_SYSTEM = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] BLOCK_0_SOURCE_ID = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] BLOCK_0_TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_DOWNLINK_FREQUENCY_BAND = {80, 81, 82, 83}; + private static final int[] BLOCK_0_DOWNLINK_CHANNEL_NUMBER = {84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95}; + private static final int[] BLOCK_1_UPLINK_FREQUENCY_BAND = {0, 1, 2, 3}; + private static final int[] BLOCK_1_UPLINK_CHANNEL_NUMBER = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + + private DataServiceOptions mDataServiceOptions; + private Identifier mWacn; + private Identifier mSystem; + private Identifier mSourceAddress; + private Identifier mSourceId; + private Identifier mTargetAddress; + private List mIdentifiers; + private APCO25Channel mChannel; + private List mChannels; + + public AMBTCIndividualDataChannelGrant(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + if(getSourceId() != null) + { + sb.append(" FM:").append(getSourceId()); + } + sb.append(" TO:").append(getTargetAddress()); + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + sb.append(" SERVICE OPTIONS:").append(getDataServiceOptions()); + return sb.toString(); + } + + public DataServiceOptions getDataServiceOptions() + { + if(mDataServiceOptions == null) + { + mDataServiceOptions = new DataServiceOptions(getHeader().getMessage().getInt(HEADER_SERVICE_OPTIONS)); + } + + return mDataServiceOptions; + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + mWacn = APCO25Wacn.create(getDataBlock(0).getMessage().getInt(BLOCK_0_WACN)); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + public Identifier getSourceId() + { + if(mSourceId == null && hasDataBlock(0)) + { + mSourceId = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(BLOCK_0_SOURCE_ID)); + } + + return mSourceId; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null && hasDataBlock(0)) + { + mTargetAddress = APCO25ToTalkgroup.createGroup(getDataBlock(0).getMessage().getInt(BLOCK_0_TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + if(getTargetAddress() != null) + { + mIdentifiers.add(getTargetAddress()); + } + if(getSourceId() != null) + { + mIdentifiers.add(getSourceId()); + } + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + } + + return mIdentifiers; + } + + public boolean isExtendedChannel() + { + return hasDataBlock(0) && + (getDataBlock(0).getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER) != + getDataBlock(0).getMessage().getInt(BLOCK_1_UPLINK_CHANNEL_NUMBER)); + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + if(hasDataBlock(0)) + { + UnconfirmedDataBlock block0 = getDataBlock(0); + + if(isExtendedChannel() && hasDataBlock(1)) + { + UnconfirmedDataBlock block1 = getDataBlock(1); + mChannel = APCO25ExplicitChannel.create(block0.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND), + block0.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER), + block1.getMessage().getInt(BLOCK_1_UPLINK_FREQUENCY_BAND), + block1.getMessage().getInt(BLOCK_1_UPLINK_CHANNEL_NUMBER)); + } + else + { + mChannel = APCO25Channel.create(block0.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND), + block0.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER)); + } + } + + if(mChannel == null) + { + mChannel = APCO25Channel.create(-1, 0); + } + } + + return mChannel; + } + + @Override + public List getChannels() + { + if(mChannels == null) + { + mChannels = new ArrayList<>(); + + if(getChannel() != null) + { + mChannels.add(getChannel()); + } + } + + return mChannels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCMessageUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCMessageUpdate.java new file mode 100644 index 00000000..ccf2e62d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCMessageUpdate.java @@ -0,0 +1,170 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.message.APCO25ShortDataMessage; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.ArrayList; +import java.util.List; + +/** + * Message update + */ +public class AMBTCMessageUpdate extends AMBTCMessage +{ + private static final int[] HEADER_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3}; + private static final int[] BLOCK_0_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_SOURCE_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39}; + private static final int[] BLOCK_0_SDM = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + + private Identifier mWacn; + private Identifier mSystem; + private Identifier mSourceId; + private Identifier mTargetAddress; + private Identifier mShortDataMessage; + private List mIdentifiers; + + public AMBTCMessageUpdate(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + if(getSourceId() != null) + { + sb.append(" FM:").append(getSourceId()); + } + if(getTargetAddress() != null) + { + sb.append(" TO:").append(getTargetAddress()); + } + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + if(getShortDataMessage() != null) + { + sb.append(" SHORT DATA MESSAGE:").append(getShortDataMessage()); + } + + return sb.toString(); + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + int value = getHeader().getMessage().getInt(HEADER_WACN); + value <<= 4; + value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); + mWacn = APCO25Wacn.create(value); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null && hasDataBlock(0)) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceId() + { + if(mSourceId == null && hasDataBlock(0)) + { + mSourceId = APCO25FromTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID)); + } + + return mSourceId; + } + + public Identifier getShortDataMessage() + { + if(mShortDataMessage == null && hasDataBlock(0)) + { + mShortDataMessage = APCO25ShortDataMessage.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SDM)); + } + + return mShortDataMessage; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getSourceId() != null) + { + mIdentifiers.add(getSourceId()); + } + if(getTargetAddress() != null) + { + mIdentifiers.add(getTargetAddress()); + } + if(getShortDataMessage() != null) + { + mIdentifiers.add(getShortDataMessage()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCProtectionParameterBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCProtectionParameterBroadcast.java new file mode 100644 index 00000000..bd51e2e9 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCProtectionParameterBroadcast.java @@ -0,0 +1,146 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.encryption.APCO25EncryptionKey; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.ArrayList; +import java.util.List; + +/** + * Protection (encryption) parameter broadcast + */ +public class AMBTCProtectionParameterBroadcast extends AMBTCMessage +{ + private static final int[] HEADER_ALGORITHM_ID = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_KEY_ID = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_INBOUND_MESSAGE_INDICATOR_1 = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] BLOCK_0_INBOUND_MESSAGE_INDICATOR_2 = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, + 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87}; + private static final int[] BLOCK_0_OUTBOUND_MESSAGE_INDICATOR_1 = {88, 89, 90, 91, 92, 93, 94, 95}; + private static final int[] BLOCK_1_OUTBOUND_MESSAGE_INDICATOR_2 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] BLOCK_1_OUTBOUND_MESSAGE_INDICATOR_3 = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + + private Identifier mTargetAddress; + private Identifier mEncryptionKey; + private String mInboundMessageIndicator; + private String mOutboundMessageIndicator; + private List mIdentifiers; + + public AMBTCProtectionParameterBroadcast(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + if(getTargetAddress() != null) + { + sb.append(" TO:").append(getTargetAddress()); + } + if(getEncryptionKey() != null) + { + sb.append(" ENCRYPTION KEY:").append(getEncryptionKey()); + } + if(getInboundMessageIndicator() != null) + { + sb.append(" INBOUND MI:").append(getInboundMessageIndicator()); + } + if(getOutboundMessageIndicator() != null) + { + sb.append(" OUTBOUND MI:").append(getOutboundMessageIndicator()); + } + + return sb.toString(); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null && hasDataBlock(0)) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getEncryptionKey() + { + if(mEncryptionKey == null && hasDataBlock(0)) + { + mEncryptionKey = APCO25EncryptionKey.create(getHeader().getMessage().getInt(HEADER_ALGORITHM_ID), + getDataBlock(0).getMessage().getInt(BLOCK_0_KEY_ID)); + } + + return mEncryptionKey; + } + + public String getInboundMessageIndicator() + { + if(mInboundMessageIndicator == null && hasDataBlock(0)) + { + mInboundMessageIndicator = getDataBlock(0).getMessage().getHex(BLOCK_0_INBOUND_MESSAGE_INDICATOR_1, 10) + + getDataBlock(0).getMessage().getHex(BLOCK_0_INBOUND_MESSAGE_INDICATOR_2, 8); + } + + return mInboundMessageIndicator; + } + + public String getOutboundMessageIndicator() + { + if(mOutboundMessageIndicator == null && hasDataBlock(0) && hasDataBlock(1)) + { + mOutboundMessageIndicator = getDataBlock(0).getMessage().getHex(BLOCK_0_OUTBOUND_MESSAGE_INDICATOR_1, 2) + + getDataBlock(1).getMessage().getHex(BLOCK_1_OUTBOUND_MESSAGE_INDICATOR_2, 8) + + getDataBlock(1).getMessage().getHex(BLOCK_1_OUTBOUND_MESSAGE_INDICATOR_3, 8); + } + + return mOutboundMessageIndicator; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getTargetAddress() != null) + { + mIdentifiers.add(getTargetAddress()); + } + if(getEncryptionKey() != null) + { + mIdentifiers.add(getEncryptionKey()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCRoamingAddressResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCRoamingAddressResponse.java new file mode 100644 index 00000000..4afb047f --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCRoamingAddressResponse.java @@ -0,0 +1,169 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.ArrayList; +import java.util.List; + +/** + * Roaming address update message. Assigns WACN and SYSTEM combinations to a target address. + * + * Note: this is a variable block length message containing between 2 and 8 possible combination + * values. This class only parses out the first WACN an SYSTEM combination. Further parsing + * support is required to fully extract the additional combinations, but they are basically stacked + * one after another in each of the subsequent data blocks with the final data block containing the + * 4 CRC octets. + */ +public class AMBTCRoamingAddressResponse extends AMBTCMessage +{ + private static final int LAST_MESSAGE_FLAG = 64; + private static final int[] HEADER_MSN = {68, 69, 70, 71}; + private static final int[] HEADER_WACN_A = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN_A = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; + private static final int[] BLOCK_0_SYSTEM_A = {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; + + private Identifier mWacn; + private Identifier mSystem; + private Identifier mTargetAddress; + private List mIdentifiers; + + public AMBTCRoamingAddressResponse(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + if(getTargetAddress() != null) + { + sb.append(" TO:").append(getTargetAddress()); + } + if(getWacn() != null) + { + sb.append(" WACN A:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM A:").append(getSystem()); + } + sb.append(" MSN:").append(getMessageSequenceNumber()); + if(isLastMessage()) + { + sb.append(" (FINAL)"); + } + + sb.append(" TOTAL WACN/SYSTEM COMBOS IN MESSAGE:").append(getWacnCount()); + + return sb.toString(); + } + + private int getWacnCount() + { + int dataBlockCount = getPDUSequence().getDataBlocks().size(); + + switch(dataBlockCount) + { + case 1: + return 2; + case 2: + return 5; + case 3: + return 8; + default: + return 2; + } + } + + public boolean isLastMessage() + { + return getHeader().getMessage().get(LAST_MESSAGE_FLAG); + } + + private int getMessageSequenceNumber() + { + return getHeader().getMessage().getInt(HEADER_MSN); + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + int value = getHeader().getMessage().getInt(HEADER_WACN_A); + value <<= 12; + value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN_A); + mWacn = APCO25Wacn.create(value); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM_A)); + } + + return mSystem; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null && hasDataBlock(0)) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getTargetAddress() != null) + { + mIdentifiers.add(getTargetAddress()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCRoamingAddressUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCRoamingAddressUpdate.java new file mode 100644 index 00000000..a3faaea9 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCRoamingAddressUpdate.java @@ -0,0 +1,169 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.ArrayList; +import java.util.List; + +/** + * Roaming address update message. Assigns WACN and SYSTEM combinations to a target address. + * + * Note: this is a variable block length message containing between 2 and 8 possible combination + * values. This class only parses out the first WACN an SYSTEM combination. Further parsing + * support is required to fully extract the additional combinations, but they are basically stacked + * one after another in each of the subsequent data blocks with the final data block containing the + * 4 CRC octets. + */ +public class AMBTCRoamingAddressUpdate extends AMBTCMessage +{ + private static final int LAST_MESSAGE_FLAG = 64; + private static final int[] HEADER_MSN = {68, 69, 70, 71}; + private static final int[] HEADER_WACN_A = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN_A = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; + private static final int[] BLOCK_0_SYSTEM_A = {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; + + private Identifier mWacn; + private Identifier mSystem; + private Identifier mTargetAddress; + private List mIdentifiers; + + public AMBTCRoamingAddressUpdate(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + if(getTargetAddress() != null) + { + sb.append(" TO:").append(getTargetAddress()); + } + if(getWacn() != null) + { + sb.append(" WACN A:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM A:").append(getSystem()); + } + sb.append(" MSN:").append(getMessageSequenceNumber()); + if(isLastMessage()) + { + sb.append(" (FINAL)"); + } + + sb.append(" TOTAL WACN/SYSTEM COMBOS IN MESSAGE:").append(getWacnCount()); + + return sb.toString(); + } + + private int getWacnCount() + { + int dataBlockCount = getPDUSequence().getDataBlocks().size(); + + switch(dataBlockCount) + { + case 1: + return 2; + case 2: + return 5; + case 3: + return 8; + default: + return 2; + } + } + + public boolean isLastMessage() + { + return getHeader().getMessage().get(LAST_MESSAGE_FLAG); + } + + private int getMessageSequenceNumber() + { + return getHeader().getMessage().getInt(HEADER_MSN); + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + int value = getHeader().getMessage().getInt(HEADER_WACN_A); + value <<= 12; + value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN_A); + mWacn = APCO25Wacn.create(value); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM_A)); + } + + return mSystem; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null && hasDataBlock(0)) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getTargetAddress() != null) + { + mIdentifiers.add(getTargetAddress()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCStatusQuery.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCStatusQuery.java new file mode 100644 index 00000000..fe300c24 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCStatusQuery.java @@ -0,0 +1,149 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.ArrayList; +import java.util.List; + +/** + * Status Query + */ +public class AMBTCStatusQuery extends AMBTCMessage +{ + private static final int[] HEADER_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3}; + private static final int[] BLOCK_0_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_SOURCE_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39}; + + private Identifier mWacn; + private Identifier mSystem; + private Identifier mSourceId; + private Identifier mTargetAddress; + private List mIdentifiers; + + public AMBTCStatusQuery(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + if(getSourceId() != null) + { + sb.append(" FM:").append(getSourceId()); + } + if(getTargetAddress() != null) + { + sb.append(" TO:").append(getTargetAddress()); + } + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + + return sb.toString(); + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + int value = getHeader().getMessage().getInt(HEADER_WACN); + value <<= 4; + value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); + mWacn = APCO25Wacn.create(value); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null && hasDataBlock(0)) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceId() + { + if(mSourceId == null && hasDataBlock(0)) + { + mSourceId = APCO25FromTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID)); + } + + return mSourceId; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getSourceId() != null) + { + mIdentifiers.add(getSourceId()); + } + if(getTargetAddress() != null) + { + mIdentifiers.add(getTargetAddress()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCStatusUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCStatusUpdate.java new file mode 100644 index 00000000..23b6e522 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCStatusUpdate.java @@ -0,0 +1,182 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.status.APCO25Status; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; + +import java.util.ArrayList; +import java.util.List; + +/** + * Status Query + */ +public class AMBTCStatusUpdate extends AMBTCMessage +{ + private static final int[] HEADER_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3}; + private static final int[] BLOCK_0_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_SOURCE_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39}; + private static final int[] BLOCK_0_UNIT_STATUS = {40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] BLOCK_0_USER_STATUS = {48, 49, 50, 51, 52, 53, 54, 55}; + + private Identifier mWacn; + private Identifier mSystem; + private Identifier mSourceId; + private Identifier mTargetAddress; + private Identifier mUnitStatus; + private Identifier mUserStatus; + private List mIdentifiers; + + public AMBTCStatusUpdate(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + if(getTargetAddress() != null) + { + sb.append(" TO:").append(getTargetAddress()); + } + if(getSourceId() != null) + { + sb.append(" FM:").append(getSourceId()); + } + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + if(getUnitStatus() != null) + { + sb.append(" UNIT STATUS:").append(getUnitStatus()); + } + if(getUserStatus() != null) + { + sb.append(" USER STATUS:").append(getUserStatus()); + } + + return sb.toString(); + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + int value = getHeader().getMessage().getInt(HEADER_WACN); + value <<= 4; + value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); + mWacn = APCO25Wacn.create(value); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null && hasDataBlock(0)) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceId() + { + if(mSourceId == null && hasDataBlock(0)) + { + mSourceId = APCO25FromTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID)); + } + + return mSourceId; + } + + public Identifier getUnitStatus() + { + if(mUnitStatus == null && hasDataBlock(0)) + { + mUnitStatus = APCO25Status.createUnitStatus(getDataBlock(0).getMessage().getInt(BLOCK_0_UNIT_STATUS)); + } + + return mUnitStatus; + } + + public Identifier getUserStatus() + { + if(mUserStatus == null && hasDataBlock(0)) + { + mUserStatus = APCO25Status.createUserStatus(getDataBlock(0).getMessage().getInt(BLOCK_0_USER_STATUS)); + } + + return mUserStatus; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getSourceId() != null) + { + mIdentifiers.add(getSourceId()); + } + if(getTargetAddress() != null) + { + mIdentifiers.add(getTargetAddress()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCTelephoneInterconnectChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCTelephoneInterconnectChannelGrant.java new file mode 100644 index 00000000..46467cd8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCTelephoneInterconnectChannelGrant.java @@ -0,0 +1,171 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.block.UnconfirmedDataBlock; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCTelephoneInterconnectChannelGrant extends AMBTCMessage implements IFrequencyBandReceiver +{ + private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] HEADER_RESERVED = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_DOWNLINK_FREQUENCY_BAND = {0, 1, 2, 3}; + private static final int[] BLOCK_0_DOWNLINK_CHANNEL_NUMBER = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_UPLINK_FREQUENCY_BAND = {16, 17, 18, 19}; + private static final int[] BLOCK_0_UPLINK_CHANNEL_NUMBER = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] BLOCK_0_CALL_TIMER = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] BLOCK_0_RESERVED = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + + private VoiceServiceOptions mVoiceServiceOptions; + private APCO25Channel mChannel; + private Identifier mAnyAddress; + private List mIdentifiers; + private List mChannels; + + public AMBTCTelephoneInterconnectChannelGrant(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" ADDR:").append(getAnyAddress()); + if(getChannel() != null) + { + sb.append(" CHAN:").append(getChannel()); + } + sb.append(" CALL TIMER:").append(getCallTimer()).append("ms"); + sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + return sb.toString(); + } + + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getHeader().getMessage().getInt(HEADER_SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + public long getCallTimer() + { + if(hasDataBlock(0)) + { + return getDataBlock(0).getMessage().getInt(BLOCK_0_CALL_TIMER) * 100; //milliseconds + } + + return 0; + } + + public Identifier getAnyAddress() + { + if(mAnyAddress == null) + { + mAnyAddress = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(HEADER_ADDRESS)); + } + + return mAnyAddress; + } + + public boolean isExtendedChannel() + { + return hasDataBlock(0) && + (getDataBlock(0).getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER) != + getDataBlock(0).getMessage().getInt(BLOCK_0_UPLINK_CHANNEL_NUMBER)); + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + if(hasDataBlock(0)) + { + UnconfirmedDataBlock block = getDataBlock(0); + + if(isExtendedChannel()) + { + mChannel = APCO25ExplicitChannel.create(block.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND), + block.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER), + block.getMessage().getInt(BLOCK_0_UPLINK_FREQUENCY_BAND), + block.getMessage().getInt(BLOCK_0_UPLINK_CHANNEL_NUMBER)); + } + else + { + mChannel = APCO25Channel.create(block.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND), + block.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER)); + } + } + + if(mChannel == null) + { + mChannel = APCO25Channel.create(-1, 0); + } + } + + return mChannel; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getAnyAddress() != null) + { + mIdentifiers.add(getAnyAddress()); + } + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + if(mChannels == null) + { + mChannels = new ArrayList<>(); + + if(getChannel() != null) + { + mChannels.add(getChannel()); + } + } + + return mChannels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCTelephoneInterconnectChannelGrantUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCTelephoneInterconnectChannelGrantUpdate.java new file mode 100644 index 00000000..a54b7623 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCTelephoneInterconnectChannelGrantUpdate.java @@ -0,0 +1,171 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.block.UnconfirmedDataBlock; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCTelephoneInterconnectChannelGrantUpdate extends AMBTCMessage implements IFrequencyBandReceiver +{ + private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] HEADER_RESERVED = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_DOWNLINK_FREQUENCY_BAND = {0, 1, 2, 3}; + private static final int[] BLOCK_0_DOWNLINK_CHANNEL_NUMBER = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_UPLINK_FREQUENCY_BAND = {16, 17, 18, 19}; + private static final int[] BLOCK_0_UPLINK_CHANNEL_NUMBER = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] BLOCK_0_CALL_TIMER = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] BLOCK_0_RESERVED = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + + private VoiceServiceOptions mVoiceServiceOptions; + private APCO25Channel mChannel; + private Identifier mAnyAddress; + private List mIdentifiers; + private List mChannels; + + public AMBTCTelephoneInterconnectChannelGrantUpdate(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" ADDR:").append(getAnyAddress()); + if(getChannel() != null) + { + sb.append(" CHAN:").append(getChannel()); + } + sb.append(" CALL TIMER:").append(getCallTimer()).append("ms"); + sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + return sb.toString(); + } + + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getHeader().getMessage().getInt(HEADER_SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + public long getCallTimer() + { + if(hasDataBlock(0)) + { + return getDataBlock(0).getMessage().getInt(BLOCK_0_CALL_TIMER) * 100; //milliseconds + } + + return 0; + } + + public Identifier getAnyAddress() + { + if(mAnyAddress == null) + { + mAnyAddress = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(HEADER_ADDRESS)); + } + + return mAnyAddress; + } + + public boolean isExtendedChannel() + { + return hasDataBlock(0) && + (getDataBlock(0).getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER) != + getDataBlock(0).getMessage().getInt(BLOCK_0_UPLINK_CHANNEL_NUMBER)); + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + if(hasDataBlock(0)) + { + UnconfirmedDataBlock block = getDataBlock(0); + + if(isExtendedChannel()) + { + mChannel = APCO25ExplicitChannel.create(block.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND), + block.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER), + block.getMessage().getInt(BLOCK_0_UPLINK_FREQUENCY_BAND), + block.getMessage().getInt(BLOCK_0_UPLINK_CHANNEL_NUMBER)); + } + else + { + mChannel = APCO25Channel.create(block.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND), + block.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER)); + } + } + + if(mChannel == null) + { + mChannel = APCO25Channel.create(-1, 0); + } + } + + return mChannel; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getAnyAddress() != null) + { + mIdentifiers.add(getAnyAddress()); + } + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + if(mChannels == null) + { + mChannels = new ArrayList<>(); + + if(getChannel() != null) + { + mChannels.add(getChannel()); + } + } + + return mChannels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCUnitRegistrationResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCUnitRegistrationResponse.java new file mode 100644 index 00000000..dfccbb5a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCUnitRegistrationResponse.java @@ -0,0 +1,183 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.reference.Response; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit registration response - when the source unit does not match the WACN id for the current site + */ +public class AMBTCUnitRegistrationResponse extends AMBTCMessage +{ + private static final int[] HEADER_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3}; + private static final int[] BLOCK_0_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_SOURCE_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39}; + private static final int[] BLOCK_0_SOURCE_ADDRESS = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] BLOCK_0_RESPONSE = {70, 71}; + + private Response mResponse; + private Identifier mTargetAddress; + private Identifier mWacn; + private Identifier mSystem; + private Identifier mSourceId; + private Identifier mSourceAddress; + private List mIdentifiers; + + public AMBTCUnitRegistrationResponse(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + if(getTargetAddress() != null) + { + sb.append(" TO:").append(getTargetAddress()); + } + if(getSourceId() != null) + { + sb.append(" FM ID:").append(getSourceId()); + } + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + if(getSourceAddress() != null) + { + sb.append(" FM ADDRESS:").append(getSourceAddress()); + } + + return sb.toString(); + } + + public Response getResponse() + { + if(mResponse == null && hasDataBlock(0)) + { + mResponse = Response.fromValue(getDataBlock(0).getMessage().getInt(BLOCK_0_RESPONSE)); + } + + return mResponse; + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + int value = getHeader().getMessage().getInt(HEADER_WACN); + value <<= 4; + value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); + mWacn = APCO25Wacn.create(value); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null && hasDataBlock(0)) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceId() + { + if(mSourceId == null && hasDataBlock(0)) + { + mSourceId = APCO25FromTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID)); + } + + return mSourceId; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null && hasDataBlock(0)) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + if(getSourceId() != null) + { + mIdentifiers.add(getSourceId()); + } + if(getTargetAddress() != null) + { + mIdentifiers.add(getTargetAddress()); + } + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCUnitToUnitAnswerRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCUnitToUnitAnswerRequest.java new file mode 100644 index 00000000..432c4cce --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCUnitToUnitAnswerRequest.java @@ -0,0 +1,154 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCUnitToUnitAnswerRequest extends AMBTCMessage +{ + private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] HEADER_RESERVED = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_RESERVED = {0, 1, 2, 3, 4, 5, 6, 7}; + private static final int[] BLOCK_0_WACN = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}; + private static final int[] BLOCK_0_SYSTEM = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] BLOCK_0_SOURCE_ID = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63}; + + private VoiceServiceOptions mVoiceServiceOptions; + private Identifier mWacn; + private Identifier mSystem; + private Identifier mSourceId; + private Identifier mTargetAddress; + private List mIdentifiers; + + public AMBTCUnitToUnitAnswerRequest(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + if(getSourceId() != null) + { + sb.append(" FM:").append(getSourceId()); + } + sb.append(" TO:").append(getTargetAddress()); + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + return sb.toString(); + } + + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getHeader().getMessage().getInt(HEADER_SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + mWacn = APCO25Wacn.create(getDataBlock(0).getMessage().getInt(BLOCK_0_WACN)); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getSourceId() + { + if(mSourceId == null) + { + mSourceId = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(BLOCK_0_SOURCE_ID)); + } + + return mSourceId; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null && hasDataBlock(0)) + { + mTargetAddress = APCO25ToTalkgroup.createGroup(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getTargetAddress() != null) + { + mIdentifiers.add(getTargetAddress()); + } + if(getSourceId() != null) + { + mIdentifiers.add(getSourceId()); + } + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCUnitToUnitVoiceServiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCUnitToUnitVoiceServiceChannelGrant.java new file mode 100644 index 00000000..b76327b9 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCUnitToUnitVoiceServiceChannelGrant.java @@ -0,0 +1,236 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.block.UnconfirmedDataBlock; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCUnitToUnitVoiceServiceChannelGrant extends AMBTCMessage implements IFrequencyBandReceiver +{ + private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] HEADER_RESERVED = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; + private static final int[] BLOCK_0_SYSTEM = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] BLOCK_0_SOURCE_ID = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] BLOCK_0_TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_DOWNLINK_FREQUENCY_BAND = {80, 81, 82, 83}; + private static final int[] BLOCK_0_DOWNLINK_CHANNEL_NUMBER = {84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95}; + private static final int[] BLOCK_1_UPLINK_FREQUENCY_BAND = {0, 1, 2, 3}; + private static final int[] BLOCK_1_UPLINK_CHANNEL_NUMBER = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + + private VoiceServiceOptions mVoiceServiceOptions; + private Identifier mWacn; + private Identifier mSystem; + private Identifier mSourceAddress; + private Identifier mSourceId; + private Identifier mTargetAddress; + private List mIdentifiers; + private APCO25Channel mChannel; + private List mChannels; + + public AMBTCUnitToUnitVoiceServiceChannelGrant(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + if(getSourceId() != null) + { + sb.append(" FM:").append(getSourceId()); + } + sb.append(" TO:").append(getTargetAddress()); + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + return sb.toString(); + } + + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getHeader().getMessage().getInt(HEADER_SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + mWacn = APCO25Wacn.create(getDataBlock(0).getMessage().getInt(BLOCK_0_WACN)); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + public Identifier getSourceId() + { + if(mSourceId == null && hasDataBlock(0)) + { + mSourceId = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(BLOCK_0_SOURCE_ID)); + } + + return mSourceId; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null && hasDataBlock(0)) + { + mTargetAddress = APCO25ToTalkgroup.createGroup(getDataBlock(0).getMessage().getInt(BLOCK_0_TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + if(getTargetAddress() != null) + { + mIdentifiers.add(getTargetAddress()); + } + if(getSourceId() != null) + { + mIdentifiers.add(getSourceId()); + } + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + } + + return mIdentifiers; + } + + public boolean isExtendedChannel() + { + return hasDataBlock(0) && + (getDataBlock(0).getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER) != + getDataBlock(0).getMessage().getInt(BLOCK_1_UPLINK_CHANNEL_NUMBER)); + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + if(hasDataBlock(0)) + { + UnconfirmedDataBlock block0 = getDataBlock(0); + + if(isExtendedChannel() && hasDataBlock(1)) + { + UnconfirmedDataBlock block1 = getDataBlock(1); + mChannel = APCO25ExplicitChannel.create(block0.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND), + block0.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER), + block1.getMessage().getInt(BLOCK_1_UPLINK_FREQUENCY_BAND), + block1.getMessage().getInt(BLOCK_1_UPLINK_CHANNEL_NUMBER)); + } + else + { + mChannel = APCO25Channel.create(block0.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND), + block0.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER)); + } + } + + if(mChannel == null) + { + mChannel = APCO25Channel.create(-1, 0); + } + } + + return mChannel; + } + + @Override + public List getChannels() + { + if(mChannels == null) + { + mChannels = new ArrayList<>(); + + if(getChannel() != null) + { + mChannels.add(getChannel()); + } + } + + return mChannels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCUnitToUnitVoiceServiceChannelGrantUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCUnitToUnitVoiceServiceChannelGrantUpdate.java new file mode 100644 index 00000000..72b197bc --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/ambtc/osp/AMBTCUnitToUnitVoiceServiceChannelGrantUpdate.java @@ -0,0 +1,237 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.block.UnconfirmedDataBlock; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +public class AMBTCUnitToUnitVoiceServiceChannelGrantUpdate extends AMBTCMessage implements IFrequencyBandReceiver +{ + private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] HEADER_RESERVED = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_WACN = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; + private static final int[] BLOCK_0_SYSTEM = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] BLOCK_0_SOURCE_ID = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] BLOCK_0_TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_DOWNLINK_FREQUENCY_BAND = {80, 81, 82, 83}; + private static final int[] BLOCK_0_DOWNLINK_CHANNEL_NUMBER = {84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95}; + private static final int[] BLOCK_1_UPLINK_FREQUENCY_BAND = {0, 1, 2, 3}; + private static final int[] BLOCK_1_UPLINK_CHANNEL_NUMBER = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + + private VoiceServiceOptions mVoiceServiceOptions; + private Identifier mWacn; + private Identifier mSystem; + private Identifier mSourceAddress; + private Identifier mSourceId; + private Identifier mTargetAddress; + private List mIdentifiers; + private APCO25Channel mChannel; + private List mChannels; + + public AMBTCUnitToUnitVoiceServiceChannelGrantUpdate(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + if(getSourceId() != null) + { + sb.append(" FM:").append(getSourceId()); + } + sb.append(" TO:").append(getTargetAddress()); + if(getWacn() != null) + { + sb.append(" WACN:").append(getWacn()); + } + if(getSystem() != null) + { + sb.append(" SYSTEM:").append(getSystem()); + } + sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + return sb.toString(); + } + + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getHeader().getMessage().getInt(HEADER_SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + mWacn = APCO25Wacn.create(getDataBlock(0).getMessage().getInt(BLOCK_0_WACN)); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null && hasDataBlock(0)) + { + mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + } + + return mSystem; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + public Identifier getSourceId() + { + if(mSourceId == null && hasDataBlock(0)) + { + mSourceId = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(BLOCK_0_SOURCE_ID)); + } + + return mSourceId; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null && hasDataBlock(0)) + { + mTargetAddress = APCO25ToTalkgroup.createGroup(getDataBlock(0).getMessage().getInt(BLOCK_0_TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + if(getTargetAddress() != null) + { + mIdentifiers.add(getTargetAddress()); + } + if(getSourceId() != null) + { + mIdentifiers.add(getSourceId()); + } + if(getWacn() != null) + { + mIdentifiers.add(getWacn()); + } + if(getSystem() != null) + { + mIdentifiers.add(getSystem()); + } + } + + return mIdentifiers; + } + + public boolean isExtendedChannel() + { + return hasDataBlock(0) && + (getDataBlock(0).getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER) != + getDataBlock(0).getMessage().getInt(BLOCK_1_UPLINK_CHANNEL_NUMBER)); + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + if(hasDataBlock(0)) + { + UnconfirmedDataBlock block0 = getDataBlock(0); + + if(isExtendedChannel() && hasDataBlock(1)) + { + UnconfirmedDataBlock block1 = getDataBlock(1); + + mChannel = APCO25ExplicitChannel.create(block0.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND), + block0.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER), + block1.getMessage().getInt(BLOCK_1_UPLINK_FREQUENCY_BAND), + block1.getMessage().getInt(BLOCK_1_UPLINK_CHANNEL_NUMBER)); + } + else + { + mChannel = APCO25Channel.create(block0.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND), + block0.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER)); + } + } + + if(mChannel == null) + { + mChannel = APCO25Channel.create(-1, 0); + } + } + + return mChannel; + } + + @Override + public List getChannels() + { + if(mChannels == null) + { + mChannels = new ArrayList<>(); + + if(getChannel() != null) + { + mChannels.add(getChannel()); + } + } + + return mChannels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/block/ConfirmedDataBlock.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/block/ConfirmedDataBlock.java new file mode 100644 index 00000000..2b7cd345 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/block/ConfirmedDataBlock.java @@ -0,0 +1,89 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.block; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.CRC; +import io.github.dsheirer.edac.CRCP25; +import io.github.dsheirer.edac.trellis.ViterbiDecoder_3_4_P25; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ConfirmedDataBlock extends DataBlock +{ + private final static Logger mLog = LoggerFactory.getLogger(ConfirmedDataBlock.class); + + public static final int[] SEQUENCE_NUMBER = {0, 1, 2, 3, 4, 5, 6}; + public static final int PAYLOAD_START = 16; + public static final int PAYLOAD_END = 144; + + private static final ViterbiDecoder_3_4_P25 VITERBI_THREE_QUARTER_RATE_DECODER = new ViterbiDecoder_3_4_P25(); + private CorrectedBinaryMessage mDecodedMessage; + private boolean mValid; + + public ConfirmedDataBlock(CorrectedBinaryMessage correctedBinaryMessage) + { + mDecodedMessage = VITERBI_THREE_QUARTER_RATE_DECODER.decode(correctedBinaryMessage); + mDecodedMessage.incrementCorrectedBitCount(correctedBinaryMessage.getCorrectedBitCount()); + + checkCRC(); + } + + /** + * Sequence number for the data block. + * + * @return sequence number between 0 - 127, or -1 if there was an error during decoding. + */ + public int getSequenceNumber() + { + if(mDecodedMessage != null) + { + return mDecodedMessage.getInt(SEQUENCE_NUMBER); + } + + return -1; + } + + private void checkCRC() + { + CRC crc = CRCP25.checkCRC9(mDecodedMessage, 0); + mValid = (crc == CRC.PASSED || crc == CRC.CORRECTED); + } + + @Override + public BinaryMessage getMessage() + { + return mDecodedMessage.getSubMessage(PAYLOAD_START, PAYLOAD_END); + } + + @Override + public int getBitErrorsCount() + { + return mDecodedMessage.getCorrectedBitCount(); + } + + @Override + public boolean isValid() + { + return mValid; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/block/DataBlock.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/block/DataBlock.java new file mode 100644 index 00000000..0e72a806 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/block/DataBlock.java @@ -0,0 +1,55 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.block; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.message.IBitErrorProvider; + +/** + * Packet Data Unit data block. + */ +public abstract class DataBlock implements IBitErrorProvider +{ + /** + * Decoded binary message payload. + */ + public abstract BinaryMessage getMessage(); + + /** + * Number of bits that were processed for this data block (196). + */ + @Override + public int getBitsProcessedCount() + { + //Interleaved message length of a PDU packet is 196 bits + 6 status bits + return 196; + } + + /** + * Number of bit errors detected during the decoding process + */ + @Override + public abstract int getBitErrorsCount(); + + /** + * Indicates if the data block passes any block level error correction + */ + public abstract boolean isValid(); +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/block/UnconfirmedDataBlock.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/block/UnconfirmedDataBlock.java new file mode 100644 index 00000000..91421131 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/block/UnconfirmedDataBlock.java @@ -0,0 +1,71 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.block; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.trellis.ViterbiDecoder_1_2_P25; + +/** + * P25 Unconfirmed Data block that uses 1/2 rate trellis coding. + */ +public class UnconfirmedDataBlock extends DataBlock +{ + private static final ViterbiDecoder_1_2_P25 VITERBI_HALF_RATE_DECODER = new ViterbiDecoder_1_2_P25(); + private CorrectedBinaryMessage mDecodedMessage; + + /** + * Constructs an unconfirmed data block from the deinterleaved message. + * @param correctedBinaryMessage containing deinterleaved 196-bit data block. + */ + public UnconfirmedDataBlock(CorrectedBinaryMessage correctedBinaryMessage) + { + mDecodedMessage = VITERBI_HALF_RATE_DECODER.decode(correctedBinaryMessage); + mDecodedMessage.incrementCorrectedBitCount(correctedBinaryMessage.getCorrectedBitCount()); + } + + /** + * Message payload + */ + @Override + public CorrectedBinaryMessage getMessage() + { + return mDecodedMessage; + } + + /** + * Number of bit errors detected/corrected during viterbi decoding. + */ + @Override + public int getBitErrorsCount() + { + return mDecodedMessage.getCorrectedBitCount(); + } + + /** + * Indicates if this data block passes any block-level CRC. + * @return true always for unconfirmed data blocks. + */ + @Override + public boolean isValid() + { + return true; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/PDUConfirmedMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/PDUConfirmedMessage.java deleted file mode 100644 index 41f362ea..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/PDUConfirmedMessage.java +++ /dev/null @@ -1,207 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.confirmed; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRC; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.PDUType; -import io.github.dsheirer.module.decode.p25.reference.Vendor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class PDUConfirmedMessage extends PDUMessage -{ - public final static Logger mLog = - LoggerFactory.getLogger( PDUConfirmedMessage.class ); - - public static final int SEQUENCE_RESET_FLAG = 128; //SYN - public static final int[] PACKET_SEQUENCE_NUMBER = { 129,130,131 }; - public static final int FINAL_FRAGMENT_FLAG = 132; - public static final int[] FRAGMENT_SEQUENCE_NUMBER = { 133,134,135 }; - public static final int[] PDU_TYPE = { 176,177,178,179 }; - - public PDUConfirmedMessage( BinaryMessage message, AliasList aliasList ) - { - super( message, DataUnitID.PDUC, aliasList ); - - checkCRC(); - } - - /** - * Constructor to support subclassing of this message. Initial construction - * of the message enables CRC checks to be calculated and message type to - * be inspected so that we can construct an accurate subclass message, - * without having to recalculate CRC checksums. - */ - protected PDUConfirmedMessage( PDUConfirmedMessage message ) - { - super( message.getSourceMessage(), DataUnitID.PDUC, message.getAliasList() ); - mCRC = message.getCRCResults(); - } - - private void checkCRC() - { - int blocks = getBlocksToFollowCount(); - - /* The NID and Header have already passed CRC */ - mCRC = new CRC[ 2 + blocks ]; - mCRC[ 0 ] = CRC.PASSED; - mCRC[ 1 ] = CRC.PASSED; - - for( int x = 0; x < getBlocksToFollowCount(); x++ ) - { - /* Data blocks start at 160 and every 144 thereafter */ - mCRC[ x + 2 ] = CRCP25.checkCRC9( mMessage, 160 + ( x * 144 ) ); - } - - //TODO: check the final 4-byte end of block sequence CRC - } - - /* Override for now */ - public boolean isValid() - { - return true; - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "[" + this.getClass() + "]" ); - sb.append( "NAC:" ); - sb.append( getNAC() ); - sb.append( " " ); - sb.append( getDUID().getLabel() ); - sb.append( " " ); - sb.append( getDirection() ); - sb.append( " CRC[" ); - sb.append( getErrorStatus() ); - sb.append( "] LLID:" ); - sb.append( getLogicalLinkID() ); - - sb.append( " " ); - sb.append( getFormat().getLabel() ); - sb.append( " SAP:" ); - sb.append( getServiceAccessPoint().getLabel() ); - - if( getVendor() != Vendor.STANDARD ) - { - sb.append( " VEND:" ); - sb.append( getVendor().getLabel() ); - } - - sb.append( " PACKET #" ); - sb.append( getPacketSequenceNumber() ); - - if( isFinalFragment() && getFragmentSequenceNumber() == 0 ) - { - sb.append( ".C" ); - } - else - { - sb.append( "." ); - sb.append( getFragmentSequenceNumber() ); - - if( isFinalFragment() ) - { - sb.append( "C" ); - } - } - - sb.append( " BLOCKS:" ); - sb.append( getBlocksToFollowCount() ); - - sb.append( " " ); - sb.append( mMessage.toString() ); - - return sb.toString(); - } - - /** - * Packet Data Unit Type - */ - public PDUType getPDUType() - { - return PDUType.fromValue( mMessage.getInt( PDU_TYPE ), isOutbound() ); - } - - - - /** - * Number of bytes/octets contained in the message, exclusive of the data - * header. - */ - public int getOctetCount() - { - /* 16 octets per block, and 12 octets in final block */ - int count = getBlocksToFollowCount() * 16 - 4; - - int padding = getPadOctetCount(); - - /* Hack: motorola sndcp packet data control - when it says there are 15 - * pad octets and only 1 data block of 12 data octets, that means there - * are more pad octets than available data and they appear to suppress - * the final padded data block containing the 12 pad octets. The data - * block count should be 2, but it says 1, so they're either not sending - * the final block, or there is an error in the block count */ - if( padding > count ) - { - count -= ( padding - 12 ); - } - else - { - count -= padding; - } - - - count -= getDataHeaderOffset(); - - return count; - } - - /** - * Indicates that the receiver should accept this packet, despite the - * apparent out-of-order packet and fragment sequence numbering. This - * indicates that something happened to the synchronization between - * the tower and the radio and now the tower commands the radio to reset - * the sequence numbers and accept this packet/fragment instead of discarding - * it as a duplicate. - */ - public boolean isSequenceReset() - { - return mMessage.get( SEQUENCE_RESET_FLAG ); - } - - /** - * Packet sequence number. Numbers run from 0 to 7 and will continually - * roll-over if there are more than 8 total packets. - */ - public int getPacketSequenceNumber() - { - return mMessage.getInt( PACKET_SEQUENCE_NUMBER ); - } - - /** - * Indicates that this packet fragment is the final fragment - */ - public boolean isFinalFragment() - { - return mMessage.get( FINAL_FRAGMENT_FLAG ); - } - - /** - * Packet fragment sequence number - */ - public int getFragmentSequenceNumber() - { - return mMessage.getInt( FRAGMENT_SEQUENCE_NUMBER ); - } - - public String toString() - { - return getMessage(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/PDUConfirmedMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/PDUConfirmedMessageFactory.java deleted file mode 100644 index 0459cfa3..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/PDUConfirmedMessageFactory.java +++ /dev/null @@ -1,79 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.confirmed; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class PDUConfirmedMessageFactory -{ - public final static Logger mLog = - LoggerFactory.getLogger( PDUConfirmedMessage.class ); - - public static PDUConfirmedMessage getMessage( PDUConfirmedMessage message ) - { - switch( message.getServiceAccessPoint() ) - { - case ADDRESS_RESOLUTION_PROTOCOL: - break; - case CHANNEL_REASSIGNMENT: - break; - case CIRCUIT_DATA: - break; - case CIRCUIT_DATA_CONTROL: - break; - case ENCRYPTED_KEY_MANAGEMENT_MESSAGE: - break; - case ENCRYPTED_TRUNKING_CONTROL: - break; - case ENCRYPTED_USER_DATA: - break; - case EXTENDED_ADDRESS: - break; - case MR_CONFIGURATION: - break; - case MR_LOOPBACK: - break; - case MR_OUT_OF_SERVICE: - break; - case MR_PAGING: - break; - case MR_STATISTICS: - break; - case PACKET_DATA: - break; - case REGISTRATION_AND_AUTHORIZATION: - break; - /* Deliberate fall-through */ - case SNDCP_PACKET_DATA_CONTROL: - case UNENCRYPTED_USER_DATA: - switch( message.getPDUType() ) - { - case SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT: - return new SNDCPActivateTDSContextAccept( message ); - case SNDCP_ACTIVATE_TDS_CONTEXT_REJECT: - return new SNDCPActivateTDSContextReject( message ); - case SNDCP_ACTIVATE_TDS_CONTEXT_REQUEST: - return new SNDCPActivateTDSContextRequest( message ); - case SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT: - case SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST: - return new SNDCPDeactivateTDSContext( message ); - case SNDCP_RF_CONFIRMED_DATA: - return new SNDCPUserData( message ); - case SNDCP_RF_UNCONFIRMED_DATA: - break; - default: - break; - } - break; - case SYSTEM_CONFIGURATION: - break; - case UNENCRYPTED_KEY_MANAGEMENT_MESSAGE: - break; - case UNENCRYPTED_TRUNKING_CONTROL: - break; - default: - - } - - return message; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/PDUTypeUnknown.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/PDUTypeUnknown.java deleted file mode 100644 index 26b04ce2..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/PDUTypeUnknown.java +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.confirmed; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class PDUTypeUnknown extends PDUConfirmedMessage -{ - public final static Logger mLog = - LoggerFactory.getLogger( PDUTypeUnknown.class ); - - public PDUTypeUnknown( PDUConfirmedMessage message ) - { - super( message ); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "NAC:" ); - sb.append( getNAC() ); - sb.append( " LLID:" ); - sb.append( getLogicalLinkID() ); - sb.append( " PACKET DATA UNIT CONFIRMED - " ); - sb.append( getPDUType().getLabel() ); - sb.append( " CRC[" ); - sb.append( getErrorStatus() ); - sb.append( "]" ); - - sb.append( " PACKET #" ); - sb.append( getPacketSequenceNumber() ); - - if( isFinalFragment() && getFragmentSequenceNumber() == 0 ) - { - sb.append( ".C" ); - } - else - { - sb.append( "." ); - sb.append( getFragmentSequenceNumber() ); - - if( isFinalFragment() ) - { - sb.append( "C" ); - } - } - - sb.append( " " ); - - sb.append( mMessage.toString() ); - - return sb.toString(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/PacketData.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/PacketData.java deleted file mode 100644 index c07f556b..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/PacketData.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.confirmed; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class PacketData extends PDUConfirmedMessage -{ - public final static Logger mLog = - LoggerFactory.getLogger( PacketData.class ); - - public static final int DATA_BLOCK_START = 176; - - public PacketData( BinaryMessage message, AliasList aliasList ) - { - super( message, aliasList ); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "NAC:" ); - sb.append( getNAC() ); - sb.append( " PDUC LLID:" ); - sb.append( getLogicalLinkID() ); - - - sb.append( " PACKET DATA" ); - sb.append( " CRC[" ); - sb.append( getErrorStatus() ); - sb.append( "]" ); - - if( !mMessage.get( FINAL_FRAGMENT_FLAG ) ) - { - sb.append( " RESENDING" ); - } - - sb.append( " PACKET #" ); - sb.append( getPacketSequenceNumber() ); - - if( isFinalFragment() && getFragmentSequenceNumber() == 0 ) - { - sb.append( ".C" ); - } - else - { - sb.append( "." ); - sb.append( getFragmentSequenceNumber() ); - - if( isFinalFragment() ) - { - sb.append( "C" ); - } - } - - sb.append( " " ); - - sb.append( mMessage.toString() ); - - return sb.toString(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/SNDCPActivateTDSContextAccept.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/SNDCPActivateTDSContextAccept.java deleted file mode 100644 index 11ed37b6..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/SNDCPActivateTDSContextAccept.java +++ /dev/null @@ -1,242 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.confirmed; - -import io.github.dsheirer.module.decode.p25.reference.MDPConfigurationOption; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class SNDCPActivateTDSContextAccept extends PDUConfirmedMessage -{ - public final static Logger mLog = - LoggerFactory.getLogger( SNDCPActivateTDSContextAccept.class ); - - /* SN-Activate TDS Context Accept */ - public static final int[] NSAPI = { 180,181,182,183 }; - public static final int[] PDUPM = { 184,185,186,187 }; - public static final int[] READY = { 188,189,190,191 }; - public static final int[] STANDBY = { 192,193,194,195 }; - public static final int[] NAT = { 196,197,198,199 }; - public static final int[] IP_1 = { 200,201,202,203,204,205,206,207 }; - public static final int[] IP_2 = { 208,209,210,211,212,213,214,215 }; - public static final int[] IP_3 = { 216,217,218,219,220,221,222,223 }; - public static final int[] IP_4 = { 224,225,226,227,228,229,230,231 }; - public static final int[] IPHC = { 232,233,234,235,236,237,238,239 }; - public static final int[] TCPSS = { 240,241,242,243 }; - public static final int[] UDPSS = { 244,245,246,247 }; - public static final int[] MTU = { 248,249,250,251 }; - public static final int[] UDPC = { 252,253,254,255 }; - public static final int[] MDPCO = { 256,257,258,259,260,261,262,263 }; - public static final int[] DATA_ACCESS_CONTROL = { 264,265,266,267,268, - 269,270,271,272,273,274,275,276,277,278,279 }; - - public SNDCPActivateTDSContextAccept( PDUConfirmedMessage message ) - { - super( message ); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "NAC:" ); - sb.append( getNAC() ); - sb.append( " PDUC LLID:" ); - sb.append( getLogicalLinkID() ); - sb.append( " ACCEPT SNDCP PACKET DATA ACTIVATE " ); - sb.append( getNetworkAddressType() ); - sb.append( " " ); - sb.append( getIPAddress() ); - sb.append( " NSAPI:" ); - sb.append( getNSAPI() ); - sb.append( " MTU:" ); - sb.append( getMaximumTransmissionUnit() ); - sb.append( " CRC[" ); - sb.append( getErrorStatus() ); - sb.append( "]" ); - - sb.append( " PACKET #" ); - sb.append( getPacketSequenceNumber() ); - - if( isFinalFragment() && getFragmentSequenceNumber() == 0 ) - { - sb.append( ".C" ); - } - else - { - sb.append( "." ); - sb.append( getFragmentSequenceNumber() ); - - if( isFinalFragment() ) - { - sb.append( "C" ); - } - } - - sb.append( " " ); - - return sb.toString(); - } - - /** - * Network Service Access Point Identifier - up to 14 NSAPI's can be - * allocated to the mobile with each NSAPI to be used for a specific - * protocol layer. - */ - public int getNSAPI() - { - return mMessage.getInt( NSAPI ); - } - - public int getPDUPriorityMaximum() - { - return mMessage.getInt( PDUPM ); - } - - public String getReadyTimer() - { - switch( mMessage.getInt( READY ) ) - { - case 1: - return "1 Second"; - case 2: - return "2 Seconds"; - case 3: - return "4 Seconds"; - case 4: - return "6 Seconds"; - case 5: - return "8 Seconds"; - case 6: - return "10 Seconds"; - case 7: - return "15 Seconds"; - case 8: - return "20 Seconds"; - case 9: - return "25 Seconds"; - case 10: - return "30 Seconds"; - case 11: - return "60 Seconds"; - case 12: - return "120 Seconds"; - case 13: - return "180 Seconds"; - case 14: - return "300 Seconds"; - case 15: - return "Always in Ready"; - } - - return "UNKNOWN"; - } - - - public String getStandbyTimer() - { - switch( mMessage.getInt( STANDBY ) ) - { - case 1: - return "10 Seconds"; - case 2: - return "30 Seconds"; - case 3: - return "1 Minutes"; - case 4: - return "5 Minutes"; - case 5: - return "10 Minutes"; - case 6: - return "30 Minutes"; - case 7: - return "1 Hour"; - case 8: - return "2 Hours"; - case 9: - return "4 Hours"; - case 10: - return "8 Hours"; - case 11: - return "12 Hours"; - case 12: - return "24 Hours"; - case 13: - return "48 Hours"; - case 14: - return "72 Hours"; - case 15: - return "Always in Ready"; - } - - return "Unknown"; - } - - public String getNetworkAddressType() - { - return mMessage.getInt( NAT ) == 0 ? "IPV4 STATIC" : "IPV4 DYNAMIC"; - } - - public String getIPAddress() - { - StringBuilder sb = new StringBuilder(); - - sb.append( mMessage.getInt( IP_1 ) ); - sb.append( "." ); - sb.append( mMessage.getInt( IP_2 ) ); - sb.append( "." ); - sb.append( mMessage.getInt( IP_3 ) ); - sb.append( "." ); - sb.append( mMessage.getInt( IP_4 ) ); - - return sb.toString(); - } - - public boolean hasIPHeaderCompression() - { - return mMessage.getInt( IPHC ) == 1; - } - - public boolean hasUserDataPayloadCompression() - { - return mMessage.getInt( UDPC ) == 1; - } - - public int getTCPStateSlots() - { - return mMessage.getInt( TCPSS ); - } - - public int getUDPStateSlots() - { - return mMessage.getInt( UDPSS ); - } - - public String getMaximumTransmissionUnit() - { - int mtu = mMessage.getInt( MTU ); - - switch( mtu ) - { - case 1: - return "296 BYTES"; - case 2: - return "510 BYTES"; - case 3: - return "1020 BYTES"; - case 4: - return "1500 BYTES"; - default: - return "UNK-" + mtu; - } - } - - public MDPConfigurationOption getMDPConfigurationOption() - { - return MDPConfigurationOption.fromValue( mMessage.getInt( MDPCO ) ); - } - - public String getDataAccessControl() - { - return mMessage.getHex( DATA_ACCESS_CONTROL, 4 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/SNDCPActivateTDSContextReject.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/SNDCPActivateTDSContextReject.java deleted file mode 100644 index ecc52b37..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/SNDCPActivateTDSContextReject.java +++ /dev/null @@ -1,53 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.confirmed; - -import io.github.dsheirer.module.decode.p25.reference.SNDCPActivationRejectReason; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class SNDCPActivateTDSContextReject extends PDUConfirmedMessage -{ - public final static Logger mLog = - LoggerFactory.getLogger( SNDCPActivateTDSContextReject.class ); - - public static final int[] NSAPI = { 180,181,182,183 }; - public static final int[] REJECT_CODE = { 184,185,186,187,188,189,190,191 }; - - public SNDCPActivateTDSContextReject( PDUConfirmedMessage message ) - { - super( message ); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "NAC:" ); - sb.append( getNAC() ); - sb.append( " PDUC LLID:" ); - sb.append( getLogicalLinkID() ); - sb.append( " REJECT SNDCP PACKET DATA ACTIVATE - REASON:" ); - sb.append( getReason().getLabel() ); - sb.append( " CRC[" ); - sb.append( getErrorStatus() ); - sb.append( "] " ); - sb.append( mMessage.toString() ); - - return sb.toString(); - } - - /** - * Network Service Access Point Identifier - up to 14 NSAPI's can be - * allocated to the mobile with each NSAPI to be used for a specific - * protocol layer. - */ - public int getNSAPI() - { - return mMessage.getInt( NSAPI ); - } - - public SNDCPActivationRejectReason getReason() - { - return SNDCPActivationRejectReason.fromValue( mMessage.getInt( REJECT_CODE ) ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/SNDCPActivateTDSContextRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/SNDCPActivateTDSContextRequest.java deleted file mode 100644 index ea6e53bd..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/SNDCPActivateTDSContextRequest.java +++ /dev/null @@ -1,148 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.confirmed; - -import io.github.dsheirer.module.decode.p25.reference.MDPConfigurationOption; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class SNDCPActivateTDSContextRequest extends PDUConfirmedMessage -{ - public final static Logger mLog = - LoggerFactory.getLogger( SNDCPActivateTDSContextRequest.class ); - - public static final int[] SNDCP_VERSION = { 180,181,182,183 }; - public static final int[] NSAPI = { 184,185,186,187 }; - public static final int[] NAT = { 188,189,190,191 }; - public static final int[] IP_1 = { 192,193,194,195,196,197,198,199 }; - public static final int[] IP_2 = { 200,201,202,203,204,205,206,207 }; - public static final int[] IP_3 = { 208,209,210,211,212,213,214,215 }; - public static final int[] IP_4 = { 216,217,218,219,220,221,222,223 }; - public static final int[] DSUT = { 224,225,226,227 }; - public static final int[] UDPC = { 228,229,230,231 }; - public static final int[] IPHC = { 232,233,234,235,236,237,238,239 }; - public static final int[] TCPSS = { 240,241,242,243 }; - public static final int[] UDPSS = { 244,245,246,247 }; - public static final int[] MDPCO = { 248,249,250,251,252,253,254,255 }; - - public SNDCPActivateTDSContextRequest( PDUConfirmedMessage message ) - { - super( message ); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "NAC:" ); - sb.append( getNAC() ); - sb.append( " PDUC LLID:" ); - sb.append( getLogicalLinkID() ); - sb.append( " REQUEST SNDCP PACKET DATA ACTIVATE " ); - sb.append( getNetworkAddressType() ); - sb.append( " " ); - sb.append( getIPAddress() ); - sb.append( " NSAPI:" ); - sb.append( getNSAPI() ); - sb.append( " CRC[" ); - sb.append( getErrorStatus() ); - sb.append( "]" ); - - sb.append( " PACKET #" ); - sb.append( getPacketSequenceNumber() ); - - if( isFinalFragment() && getFragmentSequenceNumber() == 0 ) - { - sb.append( ".C" ); - } - else - { - sb.append( "." ); - sb.append( getFragmentSequenceNumber() ); - - if( isFinalFragment() ) - { - sb.append( "C" ); - } - } - - sb.append( " " ); - - return sb.toString(); - } - - /** - * SNDCP Version: 1 = P25 SNDCP Version 1 - */ - public int getSNDCPVersion() - { - return mMessage.getInt( SNDCP_VERSION ); - } - - /** - * Network Service Access Point Identifier - up to 14 NSAPI's can be - * allocated to the mobile with each NSAPI to be used for a specific - * protocol layer. - */ - public int getNSAPI() - { - return mMessage.getInt( NSAPI ); - } - - public String getNetworkAddressType() - { - return mMessage.getInt( NAT ) == 0 ? "IPV4 STATIC" : "IPV4 DYNAMIC"; - } - - public String getIPAddress() - { - StringBuilder sb = new StringBuilder(); - - sb.append( mMessage.getInt( IP_1 ) ); - sb.append( "." ); - sb.append( mMessage.getInt( IP_2 ) ); - sb.append( "." ); - sb.append( mMessage.getInt( IP_3 ) ); - sb.append( "." ); - sb.append( mMessage.getInt( IP_4 ) ); - - return sb.toString(); - } - - public String getDataSubscriberUnitType() - { - switch( mMessage.getInt( DSUT ) ) - { - case 0: - return "DATA ONLY MRC"; - case 1: - return "DATA AND VOICE MRC"; - } - - return "UNKNOWN"; - } - - public boolean hasIPHeaderCompression() - { - return mMessage.getInt( IPHC ) == 1; - } - - public boolean hasUserDataPayloadCompression() - { - return mMessage.getInt( UDPC ) == 1; - } - - public int getTCPStateSlots() - { - return mMessage.getInt( TCPSS ); - } - - public int getUDPStateSlots() - { - return mMessage.getInt( UDPSS ); - } - - public MDPConfigurationOption getMDPConfigurationOption() - { - return MDPConfigurationOption.fromValue( mMessage.getInt( MDPCO ) ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/SNDCPDeactivateTDSContext.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/SNDCPDeactivateTDSContext.java deleted file mode 100644 index 62310317..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/SNDCPDeactivateTDSContext.java +++ /dev/null @@ -1,59 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.confirmed; - -import io.github.dsheirer.module.decode.p25.reference.PDUType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class SNDCPDeactivateTDSContext extends PDUConfirmedMessage -{ - public final static Logger mLog = - LoggerFactory.getLogger( SNDCPDeactivateTDSContext.class ); - - public static final int[] NSAPI = { 180,181,182,183 }; - public static final int[] DEACTIVATION_TYPE = { 184,185,186,187,188,189, - 190,191 }; - - public SNDCPDeactivateTDSContext( PDUConfirmedMessage message ) - { - super( message ); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "NAC:" ); - sb.append( getNAC() ); - sb.append( " PDUC LLID:" ); - sb.append( getLogicalLinkID() ); - sb.append( " REQUEST SNDCP PACKET DATA DEACTIVATE " ); - sb.append( getDeactivationType() ); - sb.append( " CRC[" ); - sb.append( getErrorStatus() ); - sb.append( "] " ); - - return sb.toString(); - } - - /** - * Network Service Access Point Identifier - up to 14 NSAPI's can be - * allocated to the mobile with each NSAPI to be used for a specific - * protocol layer. - */ - public int getNSAPI() - { - return mMessage.getInt( NSAPI ); - } - - public String getDeactivationType() - { - if( getPDUType() == PDUType.SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST ) - { - return mMessage.getInt( DEACTIVATION_TYPE ) == 0 ? - "ALL NSAPIS" : "THIS NSAPI"; - } - - return null; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/SNDCPUserData.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/SNDCPUserData.java deleted file mode 100644 index 5ba57e42..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/confirmed/SNDCPUserData.java +++ /dev/null @@ -1,355 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.confirmed; - -import io.github.dsheirer.module.decode.p25.reference.IPHeaderCompression; -import io.github.dsheirer.module.decode.p25.reference.IPProtocol; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class SNDCPUserData extends PDUConfirmedMessage -{ - public final static Logger mLog = - LoggerFactory.getLogger( SNDCPUserData.class ); - - public static final int DATA_BLOCK_START = 176; - - public static final int[] NSAPI = { 180,181,182,183 }; - public static final int[] PCOMP = { 184,185,186,187 }; - public static final int[] DCOMP = { 188,189,190,191 }; - - /* IP Packet Header */ - public static final int[] IP_VERSION = { 192,193,194,195 }; - public static final int[] IHL = { 196,197,198,199 }; - public static final int[] DSCP = { 200,201,202,203,204,205 }; - public static final int[] ECN = { 206,207 }; - public static final int[] TOTAL_LENGTH = { 208,209,210,211,212,213,214,215, - 216,217,218,219,220,221,222,223 }; - public static final int[] IDENTIFICATION = { 224,225,226,227,228,229,230,231, - 232,233,234,235,236,237,238,239 }; - public static final int[] FLAGS = { 240,241,242 }; - public static final int[] FRAGMENT_OFFSET = { 243,244,245,246,247,248,249, - 250,251,252,253,254,255 }; - public static final int[] TTL = { 256,257,258,259,260,261,262,263 }; - public static final int[] PROTOCOL = { 264,265,266,267,268,269,270,271 }; - public static final int[] HEADER_CHECKSUM = { 272,273,274,275,276,277,278, - 279,280,281,282,283,284,285,286,287 }; - public static final int[] SOURCE_IP_1 = { 288,289,290,291,292,293,294,295 }; - public static final int[] SOURCE_IP_2 = { 296,297,298,299,300,301,302,303 }; - public static final int[] BLOCK_SERIAL_2 = { 304,305,306,307,308,309,310 }; - public static final int[] BLOCK_CRC9_2 = { 311,312,313,314,315,316,317,318, - 319 }; - public static final int[] SOURCE_IP_3 = { 320,321,322,323,324,325,326,327 }; - public static final int[] SOURCE_IP_4 = { 328,329,330,331,332,333,334,335 }; - public static final int[] DESTINATION_IP_1 = { 336,337,338,339,340,341,342,343 }; - public static final int[] DESTINATION_IP_2 = { 344,345,346,347,348,349,350,351 }; - public static final int[] DESTINATION_IP_3 = { 352,353,354,355,356,357,358,359 }; - public static final int[] DESTINATION_IP_4 = { 360,361,362,363,364,365,366,367 }; - - public static final int IP_DATA_START = 368; - - /* User Datagram Protocol */ - public static final int[] UDP_SOURCE_PORT = { 368,369,370,371,372,373,374, - 375 }; - public static final int[] UDP_DESTINATION_PORT = { 376,377,378,379,380,381, - 382,383 }; - public static final int[] UDP_LENGTH = { 384,385,386,387,388,389,390,391, - 392,393,394,395,396,397,398,399 }; - public static final int[] UDP_CHECKSUM = { 400,401,402,403,404,405,406,407, - 408,409,410,411,412,413,414,415 }; - public static final int UDP_DATA_START = 416; - - public SNDCPUserData( PDUConfirmedMessage message ) - { - super( message ); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "NAC:" ); - sb.append( getNAC() ); - sb.append( " PDUC LLID:" ); - sb.append( getLogicalLinkID() ); - sb.append( " SNDCP NSAPI:" ); - sb.append( getNSAPI() ); - sb.append( " " ); - IPProtocol protocol = getIPProtocol(); - - sb.append( protocol.getLabel() ); - sb.append( "/IP" ); - - sb.append( " FM:" ); - sb.append( getSourceIPAddress() ); - - if( protocol == IPProtocol.UDP ) - { - sb.append( ":" ); - sb.append( getUDPSourcePort() ); - } - - sb.append( " TO:" ); - sb.append( getDestinationIPAddress() ); - - if( protocol == IPProtocol.UDP ) - { - sb.append( ":" ); - sb.append( getUDPDestinationPort() ); - } - - sb.append( " DATA:" ); - sb.append( getPayload() ); - - sb.append( " CRC[" ); - sb.append( getErrorStatus() ); - sb.append( "]" ); - - sb.append( " PACKET #" ); - sb.append( getPacketSequenceNumber() ); - - if( isFinalFragment() && getFragmentSequenceNumber() == 0 ) - { - sb.append( ".C" ); - } - else - { - sb.append( "." ); - sb.append( getFragmentSequenceNumber() ); - - if( isFinalFragment() ) - { - sb.append( "C" ); - } - } - - sb.append( " " ); - - sb.append( mMessage.toString() ); - - return sb.toString(); - } - - /** - * Network Service Access Point Identifier - up to 14 NSAPI's can be - * allocated to the mobile with each NSAPI to be used for a specific - * protocol layer. - */ - public int getNSAPI() - { - return mMessage.getInt( NSAPI ); - } - - public IPHeaderCompression getIPHeaderCompressionState() - { - return IPHeaderCompression.fromValue( mMessage.getInt( PCOMP ) ); - } - - /** - * No enum values were defined for this - */ - public boolean hasUserDataCompression() - { - return mMessage.getInt( DCOMP ) > 0; - } - - /** - * IP Version - should always be 4 (IPV4) - */ - public int getIPVersion() - { - return mMessage.getInt( IP_VERSION ); - } - - /** - * IP packet header length in 32-bit words - */ - public int getInternetHeaderLength() - { - return mMessage.getInt( IHL ); - } - - /** - * IP packet DSCP - */ - public int getDifferentiatedServicesCodePoint() - { - return mMessage.getInt( DSCP ); - } - - /** - * IP packet ECN - */ - public int getExplicitCongestionNotification() - { - return mMessage.getInt( ECN ); - } - - /** - * IP packet size in bytes, including header and data - */ - public int getTotalLength() - { - return mMessage.getInt( TOTAL_LENGTH ); - } - - /** - * IP packet identification - */ - public int getIdentification() - { - return mMessage.getInt( IDENTIFICATION ); - } - - /** - * IP packet flags. 0-Reserved, 1-Don't Fragment, 2-More Fragments - */ - public int getFlags() - { - return mMessage.getInt( FLAGS ); - } - - public boolean isFragment() - { - return getFlags() > 0; - } - - /** - * IP packet - identifies the offset value in 8-byte blocks (64-bit blocks) - * for this packet fragment from the beginning. - */ - public int getFragmentOffset() - { - return mMessage.getInt( FRAGMENT_OFFSET ); - } - - /** - * IP packet time (hops) to live - */ - public int getTimeToLive() - { - return mMessage.getInt( TTL ); - } - - /** - * Identifies the IP protocol carried by the IP packet - */ - public IPProtocol getIPProtocol() - { - return IPProtocol.fromValue( mMessage.getInt( PROTOCOL ) ); - } - - /** - * Source IPV4 address - */ - public String getSourceIPAddress() - { - StringBuilder sb = new StringBuilder(); - - sb.append( mMessage.getInt( SOURCE_IP_1 ) ); - sb.append( "." ); - sb.append( mMessage.getInt( SOURCE_IP_2 ) ); - sb.append( "." ); - sb.append( mMessage.getInt( SOURCE_IP_3 ) ); - sb.append( "." ); - sb.append( mMessage.getInt( SOURCE_IP_4 ) ); - - return sb.toString(); - } - - /** - * Destination IPV4 address - */ - public String getDestinationIPAddress() - { - StringBuilder sb = new StringBuilder(); - - sb.append( mMessage.getInt( DESTINATION_IP_1 ) ); - sb.append( "." ); - sb.append( mMessage.getInt( DESTINATION_IP_2 ) ); - sb.append( "." ); - sb.append( mMessage.getInt( DESTINATION_IP_3 ) ); - sb.append( "." ); - sb.append( mMessage.getInt( DESTINATION_IP_4 ) ); - - return sb.toString(); - } - - /** - * UDP source port - */ - public int getUDPSourcePort() - { - return mMessage.getInt( UDP_SOURCE_PORT ); - } - - /** - * UDP destination port - */ - public int getUDPDestinationPort() - { - return mMessage.getInt( UDP_DESTINATION_PORT ); - } - - /** - * UDP data payload size in bytes - */ - public int getUDPDataLength() - { - return mMessage.getInt( UDP_LENGTH ); - } - /** - * Hex string representation of the packet data, including packet header - * data if present. - */ - public String getPayload() - { - StringBuilder sb = new StringBuilder(); - - /* Adjust for PDU header (2 bytes) plus IP Packet header (20 bytes) */ - int start = 22; - - if( getIPProtocol() == IPProtocol.UDP ) - { - /* Adjust for UDP header (8 bytes) */ - start = 30; - } - - int spacingCounter = 0; - - /* Append octets inserting a space between each 32-bit value */ - int octets = getOctetCount() + getDataHeaderOffset(); - - for( int x = start; x < octets; x++ ) - { - sb.append( getPacketOctet( x ) ); - - spacingCounter++; - - if( spacingCounter >= 4 ) - { - sb.append( " " ); - spacingCounter = 0; - } - } - - return sb.toString(); - } - - /** - * Returns the octet identified by the 0-indexed 'octet' argument location. - * If there is a Data Header Offset, the 0 index points to the first octet - * of the data header. Otherwise, the 0 index points to the first octet - * of the packet. You must account for the data header offset when - * specifying the octet. - */ - public String getPacketOctet( int octet ) - { - int block = (int)( octet / 16 ); - int offset = octet % 16; - - int start = DATA_BLOCK_START + ( block * 144 ) + ( offset * 8 ); - - return mMessage.getHex( start, start + 7, 2 ); - } - -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/AdjacentStatusBroadcastExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/AdjacentStatusBroadcastExtended.java deleted file mode 100644 index 661c2f64..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/AdjacentStatusBroadcastExtended.java +++ /dev/null @@ -1,208 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.pdu.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.module.decode.p25.message.IAdjacentSite; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class AdjacentStatusBroadcastExtended extends PDUMessage implements IdentifierReceiver, IAdjacentSite -{ - public static final int[] LRA = {88, 89, 90, 91, 92, 93, 94, 95}; - public static final int[] SYSTEM_ID = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111}; - public static final int[] RF_SUBSYSTEM_ID = {128, 129, 130, 131, 132, 133, 134, 135}; - public static final int[] SITE_ID = {136, 137, 138, 139, 140, 141, 142, 143}; - public static final int[] TRANSMIT_IDENTIFIER = {160, 161, 162, 163}; - public static final int[] TRANSMIT_CHANNEL = {164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175}; - public static final int[] RECEIVE_IDENTIFIER = {176, 177, 178, 179}; - public static final int[] RECEIVE_CHANNEL = {180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191}; - public static final int[] SYSTEM_SERVICE_CLASS = {192, 193, 194, 195, 196, 197, 198, 199}; - public static final int[] MULTIPLE_BLOCK_CRC = {224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, - 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255}; - - private IBandIdentifier mTransmitIdentifierProvider; - private IBandIdentifier mReceiveIdentifierProvider; - - public AdjacentStatusBroadcastExtended(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU1(mMessage); - mCRC[1] = mMessage.getCRC(); - } - - @Override - public String getEventType() - { - return Opcode.ADJACENT_STATUS_BROADCAST.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" LRA:" + getLRA()); - - sb.append(" SYS:" + getSystemID()); - - sb.append(" RFSS:" + getRFSS()); - - sb.append(" SITE:" + getSiteID()); - - sb.append(" CTRL CHAN:" + getTransmitIdentifier() + "-" + getTransmitChannel()); - - sb.append(" DN:" + getDownlinkFrequency()); - - sb.append(" " + getReceiveIdentifier() + "-" + getReceiveChannel()); - - sb.append(" UP:" + getUplinkFrequency()); - - sb.append(" SYS SVC CLASS:" + getSystemServiceClass()); - - return sb.toString(); - } - - public String getUniqueID() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getSystemID()); - sb.append(":"); - sb.append(getRFSS()); - sb.append(":"); - sb.append(getSiteID()); - - return sb.toString(); - } - - public String getSystemID() - { - return mMessage.getHex(SYSTEM_ID, 3); - } - - public String getSiteID() - { - return mMessage.getHex(SITE_ID, 2); - } - - public int getTransmitIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public int getTransmitChannel() - { - return mMessage.getInt(TRANSMIT_CHANNEL); - } - - public int getReceiveIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public int getReceiveChannel() - { - return mMessage.getInt(RECEIVE_CHANNEL); - } - - public long getDownlinkFrequency() - { - if(mTransmitIdentifierProvider != null) - { - return mTransmitIdentifierProvider.getDownlinkFrequency(getTransmitChannel()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mReceiveIdentifierProvider != null) - { - return mReceiveIdentifierProvider.getUplinkFrequency(getReceiveChannel()); - } - - return 0; - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitIdentifier()) - { - mTransmitIdentifierProvider = message; - } - - if(identifier == getReceiveIdentifier()) - { - mReceiveIdentifierProvider = message; - } - } - - public int[] getIdentifiers() - { - int[] idens = new int[2]; - - idens[0] = getTransmitIdentifier(); - idens[1] = getReceiveIdentifier(); - - return idens; - } - - @Override - public String getRFSS() - { - return mMessage.getHex(RF_SUBSYSTEM_ID, 2); - } - - @Override - public String getLRA() - { - return mMessage.getHex(LRA, 2); - } - - @Override - public String getSystemServiceClass() - { - return SystemService.toString(mMessage.getInt(SYSTEM_SERVICE_CLASS)); - } - - @Override - public String getDownlinkChannel() - { - return getTransmitIdentifier() + "-" + getTransmitChannel(); - } - - @Override - public String getUplinkChannel() - { - return getReceiveIdentifier() + "-" + getReceiveChannel(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/CallAlertExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/CallAlertExtended.java deleted file mode 100644 index c212df94..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/CallAlertExtended.java +++ /dev/null @@ -1,80 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class CallAlertExtended extends PDUMessage -{ - public static final int[] TARGET_ADDRESS = { 88,89,90,91,92,93,94,95,96,97, - 98,99,100,101,102,103,104,105,106,107,108,109,110,111 }; - public static final int[] WACN = { 128,129,130,131,132,133,134,135,136,137, - 138,139,140,141,142,143,160,161,162,163 }; - public static final int[] SYSTEM_ID = { 164,165,166,167,168,169,170,171,172, - 173,174,175 }; - public static final int[] SOURCE_ID = { 176,177,178,179,180,181,182,183,184, - 185,186,187,188,189,190,191,192,193,194,195,196,197,198,199 }; - public static final int[] MULTIPLE_BLOCK_CRC = { 224,225,226,227,228,229, - 230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247, - 248,249,250,251,252,253,254,255 }; - - public CallAlertExtended(BinaryMessage message, DataUnitID duid, AliasList aliasList ) - { - super( message, duid, aliasList ); - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU1( mMessage ); - mCRC[ 1 ] = mMessage.getCRC(); - } - - @Override - public String getEventType() - { - return Opcode.CALL_ALERT.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " FROM:" ); - sb.append( getSourceID() ); - - sb.append( " TO:" ); - sb.append( getTargetAddress() ); - - sb.append( " WACN:" ); - sb.append( getWACN() ); - - sb.append( " SYS:" ); - sb.append( getSystemID() ); - - return sb.toString(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getWACN() - { - return mMessage.getHex( WACN, 5 ); - } - - public String getSystemID() - { - return mMessage.getHex( SYSTEM_ID, 3 ); - } - - public String getSourceID() - { - return mMessage.getHex( SOURCE_ID, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/GroupAffiliationQueryExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/GroupAffiliationQueryExtended.java deleted file mode 100644 index f4d203ae..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/GroupAffiliationQueryExtended.java +++ /dev/null @@ -1,80 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class GroupAffiliationQueryExtended extends PDUMessage -{ - public static final int[] TARGET_ADDRESS = { 88,89,90,91,92,93,94,95,96,97, - 98,99,100,101,102,103,104,105,106,107,108,109,110,111 }; - public static final int[] WACN = { 128,129,130,131,132,133,134,135,136,137, - 138,139,140,141,142,143,160,161,162,163 }; - public static final int[] SYSTEM_ID = { 164,165,166,167,168,169,170,171,172, - 173,174,175 }; - public static final int[] SOURCE_ID = { 176,177,178,179,180,181,182,183,184, - 185,186,187,188,189,190,191,192,193,194,195,196,197,198,199 }; - public static final int[] MULTIPLE_BLOCK_CRC = { 224,225,226,227,228,229, - 230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247, - 248,249,250,251,252,253,254,255 }; - - public GroupAffiliationQueryExtended(BinaryMessage message, DataUnitID duid, AliasList aliasList ) - { - super( message, duid, aliasList ); - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU1( mMessage ); - mCRC[ 1 ] = mMessage.getCRC(); - } - - @Override - public String getEventType() - { - return Opcode.GROUP_AFFILIATION_QUERY.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " FROM:" ); - sb.append( getSourceID() ); - - sb.append( " TO:" ); - sb.append( getTargetAddress() ); - - sb.append( " WACN:" ); - sb.append( getWACN() ); - - sb.append( " SYS:" ); - sb.append( getSystemID() ); - - return sb.toString(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getWACN() - { - return mMessage.getHex( WACN, 5 ); - } - - public String getSystemID() - { - return mMessage.getHex( SYSTEM_ID, 3 ); - } - - public String getSourceID() - { - return mMessage.getHex( SOURCE_ID, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/GroupAffiliationResponseExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/GroupAffiliationResponseExtended.java deleted file mode 100644 index 42d95566..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/GroupAffiliationResponseExtended.java +++ /dev/null @@ -1,131 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; -import io.github.dsheirer.module.decode.p25.reference.Response; - -public class GroupAffiliationResponseExtended extends PDUMessage -{ - public static final int[] TARGET_ADDRESS = { 88,89,90,91,92,93,94,95,96,97, - 98,99,100,101,102,103,104,105,106,107,108,109,110,111 }; - public static final int[] SOURCE_WACN = { 128,129,130,131,132,133,134,135, - 136,137,138,139,140,141,142,143,160,161,162,163 }; - public static final int[] SOURCE_SYSTEM_ID = { 164,165,166,167,168,169,170, - 171,172,173,174,175 }; - public static final int[] SOURCE_ID = { 176,177,178,179,180,181,182,183,184, - 185,186,187,188,189,190,191,192,193,194,195,196,197,198,199 }; - public static final int[] GROUP_WACN = { 200,201,202,203,204,205,206,207, - 208,209,210,211,212,213,214,215,216,217,218,219 }; - public static final int[] GROUP_SYSTEM_ID = { 220,221,222,223,224,225,226, - 227,228,229,230,231 }; - public static final int[] GROUP_ID = { 232,233,234,235,236,237,238,239,240, - 241,242,243,244,245,246,247 }; - public static final int[] ANNOUNCEMENT_GROUP_ID = { 248,249,250,251,252,253, - 254,255,256,257,258,259,260,261,262,263 }; - public static final int[] GROUP_AFFILIATION_RESPONSE_VALUE = { 270,271 }; - public static final int[] MULTIPLE_BLOCK_CRC = { 320,321,322,323,324,325, - 326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342, - 343,344,345,346,347,348,349,350,351 }; - - public GroupAffiliationResponseExtended(BinaryMessage message, DataUnitID duid, AliasList aliasList ) - { - super( message, duid, aliasList ); - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU1( mMessage ); - mCRC[ 1 ] = mMessage.getCRC(); - } - - @Override - public String getEventType() - { - return Opcode.GROUP_AFFILIATION_RESPONSE.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( "AFFILIATION:" ); - sb.append( getResponse().name() ); - - sb.append( " TO:" ); - sb.append( getTargetAddress() ); - - sb.append( " FROM:" ); - sb.append( getSourceID() ); - - sb.append( " WACN:" ); - sb.append( getSourceWACN() ); - - sb.append( " SYS:" ); - sb.append( getSourceSystemID() ); - - sb.append( " GROUP:" ); - sb.append( getGroupID() ); - - sb.append( " WACN:" ); - sb.append( getGroupWACN() ); - - sb.append( " SYS:" ); - sb.append( getGroupSystemID() ); - - sb.append( " ANNOUNCEMENT GROUP:" ); - sb.append( getAnnouncementGroupID() ); - - return sb.toString(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getSourceWACN() - { - return mMessage.getHex( SOURCE_WACN, 5 ); - } - - public String getSourceSystemID() - { - return mMessage.getHex( SOURCE_SYSTEM_ID, 3 ); - } - - public String getSourceID() - { - return mMessage.getHex( SOURCE_ID, 6 ); - } - - public String getGroupWACN() - { - return mMessage.getHex( GROUP_WACN, 5 ); - } - - public String getGroupSystemID() - { - return mMessage.getHex( GROUP_SYSTEM_ID, 3 ); - } - - public String getGroupID() - { - return mMessage.getHex( GROUP_ID, 4 ); - } - - public String getAnnouncementGroupID() - { - return mMessage.getHex( GROUP_ID, 4 ); - } - - public Response getResponse() - { - return Response.fromValue( - mMessage.getInt( GROUP_AFFILIATION_RESPONSE_VALUE ) ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/MessageUpdateExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/MessageUpdateExtended.java deleted file mode 100644 index 44a8e6d5..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/MessageUpdateExtended.java +++ /dev/null @@ -1,90 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class MessageUpdateExtended extends PDUMessage -{ - public static final int[] TARGET_ADDRESS = { 88,89,90,91,92,93,94,95,96,97, - 98,99,100,101,102,103,104,105,106,107,108,109,110,111 }; - public static final int[] SOURCE_WACN = { 128,129,130,131,132,133,134,135,136,137, - 138,139,140,141,142,143,160,161,162,163 }; - public static final int[] SOURCE_SYSTEM_ID = { 164,165,166,167,168,169,170,171,172, - 173,174,175 }; - public static final int[] SOURCE_ID = { 176,177,178,179,180,181,182,183,184, - 185,186,187,188,189,190,191,192,193,194,195,196,197,198,199 }; - public static final int[] SHORT_DATA_MESSAGE = { 200,201,202,203,204,205, - 206,207,208,209,210,211,212,213,214,215 }; - public static final int[] MULTIPLE_BLOCK_CRC = { 224,225,226,227,228,229, - 230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247, - 248,249,250,251,252,253,254,255 }; - - public MessageUpdateExtended(BinaryMessage message, DataUnitID duid, AliasList aliasList ) - { - super( message, duid, aliasList ); - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU1( mMessage ); - mCRC[ 1 ] = mMessage.getCRC(); - } - - @Override - public String getEventType() - { - return Opcode.CALL_ALERT.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " TO:" ); - sb.append( getTargetAddress() ); - - sb.append( " MSG:" ); - sb.append( getShortDataMessage() ); - - sb.append( " FROM:" ); - sb.append( getSourceID() ); - - sb.append( " WACN:" ); - sb.append( getSourceWACN() ); - - sb.append( " SYS:" ); - sb.append( getSourceSystemID() ); - - return sb.toString(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getSourceWACN() - { - return mMessage.getHex( SOURCE_WACN, 5 ); - } - - public String getSourceSystemID() - { - return mMessage.getHex( SOURCE_SYSTEM_ID, 3 ); - } - - public String getSourceID() - { - return mMessage.getHex( SOURCE_ID, 6 ); - } - - public String getShortDataMessage() - { - return mMessage.getHex( SHORT_DATA_MESSAGE, 4 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/NetworkStatusBroadcastExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/NetworkStatusBroadcastExtended.java deleted file mode 100644 index 1e17466a..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/NetworkStatusBroadcastExtended.java +++ /dev/null @@ -1,181 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.pdu.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; -import io.github.dsheirer.module.decode.p25.reference.P25NetworkCallsign; - -public class NetworkStatusBroadcastExtended extends PDUMessage implements IdentifierReceiver -{ - public static final int[] LRA = {88, 89, 90, 91, 92, 93, 94, 95}; - public static final int[] SYSTEM_ID = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111}; - public static final int[] WACN = {160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, - 176, 177, 178, 179}; - public static final int[] TRANSMIT_IDENTIFIER = {184, 185, 186, 187}; - public static final int[] TRANSMIT_CHANNEL = {188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199}; - public static final int[] RECEIVE_IDENTIFIER = {200, 201, 202, 203}; - public static final int[] RECEIVE_CHANNEL = {204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215}; - public static final int[] SYSTEM_SERVICE_CLASS = {216, 217, 218, 219, 220, 221, 222, 223}; - public static final int[] MULTIPLE_BLOCK_CRC = {224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, - 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255}; - - private IBandIdentifier mTransmitIdentifierUpdate; - private IBandIdentifier mReceiveIdentifierUpdate; - - public NetworkStatusBroadcastExtended(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU1(mMessage); - mCRC[1] = mMessage.getCRC(); - } - - @Override - public String getEventType() - { - return Opcode.NETWORK_STATUS_BROADCAST.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" LRA:" + getLocationRegistrationArea()); - - sb.append(" WACN:" + getWACN()); - - sb.append(" SYS:" + getSystemID()); - - sb.append(" CTRL CHAN:" + getReceiveIdentifier() + "-" + getReceiveChannel()); - - sb.append(" DN:" + getDownlinkFrequency()); - - sb.append(" " + getTransmitIdentifier() + "-" + getTransmitChannel()); - - sb.append(" UP:" + getUplinkFrequency()); - - sb.append(" SYS SVC CLASS:" + - SystemService.toString(getSystemServiceClass())); - - return sb.toString(); - } - - - public String getLocationRegistrationArea() - { - return mMessage.getHex(LRA, 2); - } - - public String getSystemID() - { - return mMessage.getHex(SYSTEM_ID, 3); - } - - public String getWACN() - { - return mMessage.getHex(WACN, 5); - } - - public String getNetworkCallsign() - { - return P25NetworkCallsign.getCallsign(mMessage.getInt(WACN), - mMessage.getInt(SYSTEM_ID)); - } - - public int getTransmitIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public int getTransmitChannel() - { - return mMessage.getInt(TRANSMIT_CHANNEL); - } - - public int getReceiveIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public int getReceiveChannel() - { - return mMessage.getInt(RECEIVE_CHANNEL); - } - - public int getSystemServiceClass() - { - return mMessage.getInt(SYSTEM_SERVICE_CLASS); - } - - public long getDownlinkFrequency() - { - if(mTransmitIdentifierUpdate != null) - { - return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannel()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mReceiveIdentifierUpdate != null) - { - return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannel()); - } - - return 0; - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitIdentifier()) - { - mTransmitIdentifierUpdate = message; - } - - if(identifier == getReceiveIdentifier()) - { - mReceiveIdentifierUpdate = message; - } - } - - public int[] getIdentifiers() - { - int[] idens = new int[2]; - - idens[0] = getTransmitIdentifier(); - idens[1] = getReceiveIdentifier(); - - return idens; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/ProtectionParameterBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/ProtectionParameterBroadcast.java deleted file mode 100644 index e0325000..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/ProtectionParameterBroadcast.java +++ /dev/null @@ -1,113 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Encryption; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class ProtectionParameterBroadcast extends PDUMessage -{ - public static final int[] TARGET_ADDRESS = { 88,89,90,91,92,93,94,95,96,97, - 98,99,100,101,102,103,104,105,106,107,108,109,110,111 }; - public static final int[] ENCRYPTION_TYPE = { 128,129,130,131,132,133,134, - 135 }; - public static final int[] ALGORITHM_ID = { 136,137,138,139,140,141,142,143 }; - public static final int[] KEY_ID = { 160,161,162,163,164,165,166,167,168, - 169,170,171,172,173,174,175 }; - public static final int[] INBOUND_INITIALIZATION_VECTOR_A = { 176,177,178, - 179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196, - 197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214, - 215 }; - public static final int[] INBOUND_INITIALIZATION_VECTOR_B = { 216,217,218, - 219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236, - 237,238,239,240,241,242,243,244,245,246,247 }; - public static final int[] OUTBOUND_INITIALIZATION_VECTOR_A = { 248,249,250, - 251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268, - 269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286, - 287 }; - public static final int[] OUTBOUND_INITIALIZATION_VECTOR_B = { 288,289,290, - 291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308, - 309,310,311,312,313,314,315,316,317,318,319 }; - public static final int[] MULTIPLE_BLOCK_CRC = { 320,321,322,323,324,325, - 326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342, - 343,344,345,346,347,348,349,350,351 }; - - public ProtectionParameterBroadcast(BinaryMessage message, - DataUnitID duid, AliasList aliasList ) - { - super( message, duid, aliasList ); - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU1( mMessage ); - mCRC[ 1 ] = mMessage.getCRC(); - } - - @Override - public String getEventType() - { - return Opcode.PROTECTION_PARAMETER_BROADCAST.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " TO:" ); - sb.append( getTargetAddress() ); - - sb.append( " ENCRYPTION:" ); - sb.append( getEncryptionType().name() ); - - sb.append( " ALGORITHM ID:" ); - sb.append( getAlgorithmID() ); - - sb.append( " KEY ID:" ); - sb.append( getKeyID() ); - - sb.append( " INBOUND IV:" ); - sb.append( getInboundInitializationVector() ); - - sb.append( " OUTBOUND IV:" ); - sb.append( getOutboundInitializationVector() ); - - return sb.toString(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public Encryption getEncryptionType() - { - return Encryption.fromValue( mMessage.getInt( ENCRYPTION_TYPE ) ); - } - - public String getAlgorithmID() - { - return mMessage.getHex( ALGORITHM_ID, 2 ); - } - - public String getKeyID() - { - return mMessage.getHex( KEY_ID, 4 ); - } - - public String getInboundInitializationVector() - { - return mMessage.getHex( INBOUND_INITIALIZATION_VECTOR_A, 10 ) + - mMessage.getHex( INBOUND_INITIALIZATION_VECTOR_B, 8 ); - } - - public String getOutboundInitializationVector() - { - return mMessage.getHex( OUTBOUND_INITIALIZATION_VECTOR_A, 10 ) + - mMessage.getHex( OUTBOUND_INITIALIZATION_VECTOR_B, 8 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/RFSSStatusBroadcastExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/RFSSStatusBroadcastExtended.java deleted file mode 100644 index b97ecb66..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/RFSSStatusBroadcastExtended.java +++ /dev/null @@ -1,185 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.pdu.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class RFSSStatusBroadcastExtended extends PDUMessage implements IdentifierReceiver -{ - public static final int[] LRA = {88, 89, 90, 91, 92, 93, 94, 95}; - public static final int ACTIVE_NETWORK_CONNECTION_INDICATOR = 99; - public static final int[] SYSTEM_ID = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111}; - public static final int[] RFSS_ID = {160, 161, 162, 163, 164, 165, 166, 167}; - public static final int[] SITE_ID = {168, 169, 170, 171, 172, 173, 174, 175}; - public static final int[] TRANSMIT_IDENTIFIER = {184, 185, 186, 187}; - public static final int[] TRANSMIT_CHANNEL = {188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199}; - public static final int[] RECEIVE_IDENTIFIER = {200, 201, 202, 203}; - public static final int[] RECEIVE_CHANNEL = {204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215}; - public static final int[] SYSTEM_SERVICE_CLASS = {216, 217, 218, 219, 220, 221, 222, 223}; - public static final int[] MULTIPLE_BLOCK_CRC = {224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, - 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255}; - - private IBandIdentifier mTransmitIdentifierUpdate; - private IBandIdentifier mReceiveIdentifierUpdate; - - public RFSSStatusBroadcastExtended(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU1(mMessage); - mCRC[1] = mMessage.getCRC(); - } - - @Override - public String getEventType() - { - return Opcode.NETWORK_STATUS_BROADCAST.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" LRA:" + getLocationRegistrationArea()); - - sb.append(" SYS:" + getSystemID()); - - sb.append(" SITE:" + getRFSubsystemID() + "-" + getSiteID()); - - sb.append(" CTRL CHAN:" + getReceiveIdentifier() + "-" + getReceiveChannel()); - - sb.append(" DN:" + getDownlinkFrequency()); - - sb.append(" " + getTransmitIdentifier() + "-" + getTransmitChannel()); - - sb.append(" UP:" + getUplinkFrequency()); - - sb.append(" SYS SVC CLASS:" + - SystemService.toString(getSystemServiceClass())); - - return sb.toString(); - } - - - public String getLocationRegistrationArea() - { - return mMessage.getHex(LRA, 2); - } - - public boolean hasActiveNetworkConnection() - { - return mMessage.get(ACTIVE_NETWORK_CONNECTION_INDICATOR); - } - - public String getSystemID() - { - return mMessage.getHex(SYSTEM_ID, 3); - } - - public String getRFSubsystemID() - { - return mMessage.getHex(RFSS_ID, 2); - } - - public String getSiteID() - { - return mMessage.getHex(SITE_ID, 2); - } - - public int getTransmitIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public int getTransmitChannel() - { - return mMessage.getInt(TRANSMIT_CHANNEL); - } - - public int getReceiveIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public int getReceiveChannel() - { - return mMessage.getInt(RECEIVE_CHANNEL); - } - - public int getSystemServiceClass() - { - return mMessage.getInt(SYSTEM_SERVICE_CLASS); - } - - public long getDownlinkFrequency() - { - if(mTransmitIdentifierUpdate != null) - { - return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannel()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mReceiveIdentifierUpdate != null) - { - return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannel()); - } - - return 0; - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitIdentifier()) - { - mTransmitIdentifierUpdate = message; - } - - if(identifier == getReceiveIdentifier()) - { - mReceiveIdentifierUpdate = message; - } - } - - public int[] getIdentifiers() - { - int[] idens = new int[2]; - - idens[0] = getTransmitIdentifier(); - idens[1] = getReceiveIdentifier(); - - return idens; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/RoamingAddressUpdateExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/RoamingAddressUpdateExtended.java deleted file mode 100644 index c60184e0..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/RoamingAddressUpdateExtended.java +++ /dev/null @@ -1,269 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.module.decode.p25.P25MessageFramer; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class RoamingAddressUpdateExtended extends PDUMessage -{ - public static final int[] TARGET_ADDRESS = { 88,89,90,91,92,93,94,95,96,97, - 98,99,100,101,102,103,104,105,106,107,108,109,110,111 }; - public static final int LAST_MESSAGE_INDICATOR = 128; - public static final int[] MESSAGE_SEQUENCE_NUMBER = { 132,133,134,135 }; - public static final int[] WACN_A = { 136,137,138,139,140,141,142,143,160, - 161,162,163,164,165,166,167,168,169,170,171 }; - public static final int[] SYSTEM_ID_A = { 172,173,174,175,176,177,178,179, - 180,181,182,183 }; - public static final int[] SOURCE_ID_FORMAT_1 = { 184,185,186,187,188,189, - 190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207 }; - public static final int[] WACN_B = { 184,185,186,187,188,189,190,191,192, - 193,194,195,196,197,198,199,200,201,202,203 }; - public static final int[] SYSTEM_ID_B = { 204,205,206,207,208,209,210,211, - 212,213,214,215 }; - public static final int[] WACN_C = { 216,217,218,219,220,221,222,223,224, - 225,226,227,228,229,230,231,232,233,234,235 }; - public static final int[] SYSTEM_ID_C = { 236,237,238,239,240,241,242,243, - 244,245,246,247 }; - public static final int[] WACN_D = { 248,249,250,251,252,253,254,255,256, - 257,258,259,260,261,262,263,264,265,266,267 }; - public static final int[] SYSTEM_ID_D = { 268,269,270,271,272,273,274,275, - 276,277,278,279 }; - public static final int[] SOURCE_ID_FORMAT_2 = { 280,281,282,283,284,285, - 286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303 }; - public static final int[] WACN_E = { 280,281,282,283,284,285, - 286,287,288,289,290,291,292,293,294,295,296,297,298,299 }; - public static final int[] SYSTEM_ID_E = { 300,301,302,303,304,305,306,307, - 308,309,310,311 }; - public static final int[] WACN_F = { 312,313,314,315,316,317,318,319,320, - 321,322,323,324,325,326,327,328,329,330,331 }; - public static final int[] SYSTEM_ID_F = { 332,333,334,335,336,337,338,339, - 340,341,342,343 }; - public static final int[] WACN_G = { 344,345,346,347,348,349,350,351,352, - 353,354,355,356,357,358,359,360,361,362,363 }; - public static final int[] SYSTEM_ID_G = { 364,365,366,367,368,369,370,371, - 372,373,374,375 }; - public static final int[] SOURCE_ID_FORMAT_3 = { 376,377,378,379,380,381, - 382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399 }; - public static final int[] MULTIPLE_BLOCK_CRC_FORMAT_1 = { 224,225,226,227, - 228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245, - 246,247,248,249,250,251,252,253,254,255 }; - public static final int[] MULTIPLE_BLOCK_CRC_FORMAT_2 = { 320,321,322,323, - 324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341, - 342,343,344,345,346,347,348,349,350,351 }; - public static final int[] MULTIPLE_BLOCK_CRC_FORMAT_3 = { 416,417,418,419, - 420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437, - 438,439,440,441,442,443,444,445,446,447 }; - - private enum Format{ FORMAT_1, FORMAT_2, FORMAT_3 }; - - private Format mFormat; - - public RoamingAddressUpdateExtended(BinaryMessage message, - DataUnitID duid, AliasList aliasList ) - { - super( message, duid, aliasList ); - - if( mMessage.size() == P25MessageFramer.PDU2_BEGIN ) - { - mFormat = Format.FORMAT_1; - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU1( mMessage ); - mCRC[ 1 ] = mMessage.getCRC(); - } - else if( mMessage.size() == P25MessageFramer.PDU3_BEGIN ) - { - mFormat = Format.FORMAT_2; - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU2( mMessage ); - mCRC[ 1 ] = mMessage.getCRC(); - } - else if( mMessage.size() == P25MessageFramer.PDU3_DECODED_END ) - { - mFormat = Format.FORMAT_3; - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU3( mMessage ); - mCRC[ 1 ] = mMessage.getCRC(); - } - else - { - mFormat = Format.FORMAT_1; - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU1( mMessage ); - mCRC[ 1 ] = mMessage.getCRC(); - } - } - - @Override - public String getEventType() - { - return Opcode.ROAMING_ADDRESS_UPDATE.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " ROAMING ADDRESS STACK FROM:" ); - sb.append( getSourceID() ); - - if( isLastBlock() ) - { - sb.append( " LAST BLOCK" ); - } - - sb.append( "MSG SEQ:" ); - sb.append( getMessageSequenceNumber() ); - - sb.append( " TO:" ); - sb.append( getTargetAddress() ); - - sb.append( " WACN A:" + getWACNA() ); - sb.append( " SYS A:" + getSystemIDA() ); - - if( isFormat2() ) - { - sb.append( " WACN B:" + getWACNB() ); - sb.append( " SYS B:" + getSystemIDB() ); - sb.append( " WACN C:" + getWACNC() ); - sb.append( " SYS C:" + getSystemIDC() ); - sb.append( " WACN D:" + getWACND() ); - sb.append( " SYS D:" + getSystemIDD() ); - } - - if( isFormat3() ) - { - sb.append( " WACN E:" + getWACNE() ); - sb.append( " SYS E:" + getSystemIDE() ); - sb.append( " WACN F:" + getWACNF() ); - sb.append( " SYS F:" + getSystemIDF() ); - sb.append( " WACN G:" + getWACNG() ); - sb.append( " SYS G:" + getSystemIDG() ); - } - - return sb.toString(); - } - - public boolean isLastBlock() - { - return mMessage.get( LAST_MESSAGE_INDICATOR ); - } - - public int getMessageSequenceNumber() - { - return mMessage.getInt( MESSAGE_SEQUENCE_NUMBER ); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getWACNA() - { - return mMessage.getHex( WACN_A, 5 ); - } - - public String getSystemIDA() - { - return mMessage.getHex( SYSTEM_ID_A, 3 ); - } - - public String getWACNB() - { - return isFormat2() ? mMessage.getHex( WACN_B, 5 ) : null; - } - - public String getSystemIDB() - { - return isFormat2() ? mMessage.getHex( SYSTEM_ID_B, 3 ) : null; - } - - public String getWACNC() - { - return isFormat2() ? mMessage.getHex( WACN_C, 5 ) : null; - } - - public String getSystemIDC() - { - return isFormat2() ? mMessage.getHex( SYSTEM_ID_C, 3 ) : null; - } - - public String getWACND() - { - return isFormat2() ? mMessage.getHex( WACN_D, 5 ) : null; - } - - public String getSystemIDD() - { - return isFormat2() ? mMessage.getHex( SYSTEM_ID_D, 3 ) : null; - } - - public String getWACNE() - { - return isFormat3() ? mMessage.getHex( WACN_E, 5 ) : null; - } - - public String getSystemIDE() - { - return isFormat3() ? mMessage.getHex( SYSTEM_ID_E, 3 ) : null; - } - - public String getWACNF() - { - return isFormat3() ? mMessage.getHex( WACN_F, 5 ) : null; - } - - public String getSystemIDF() - { - return isFormat3() ? mMessage.getHex( SYSTEM_ID_F, 3 ) : null; - } - - public String getWACNG() - { - return isFormat3() ? mMessage.getHex( WACN_G, 5 ) : null; - } - - public String getSystemIDG() - { - return isFormat3() ? mMessage.getHex( SYSTEM_ID_G, 3 ) : null; - } - - public String getSourceID() - { - switch( mFormat ) - { - case FORMAT_1: - return mMessage.getHex( SOURCE_ID_FORMAT_1, 6 ); - case FORMAT_2: - return mMessage.getHex( SOURCE_ID_FORMAT_2, 6 ); - case FORMAT_3: - return mMessage.getHex( SOURCE_ID_FORMAT_2, 6 ); - } - - return null; - } - - public boolean isFormat2() - { - return mFormat != Format.FORMAT_3; - } - - public boolean isFormat3() - { - return mFormat == Format.FORMAT_3; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/StatusQueryExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/StatusQueryExtended.java deleted file mode 100644 index df3a3dc0..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/StatusQueryExtended.java +++ /dev/null @@ -1,81 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class StatusQueryExtended extends PDUMessage -{ - public static final int[] TARGET_ADDRESS = { 88,89,90,91,92,93,94,95,96,97, - 98,99,100,101,102,103,104,105,106,107,108,109,110,111 }; - public static final int[] SOURCE_WACN = { 128,129,130,131,132,133,134,135,136,137, - 138,139,140,141,142,143,160,161,162,163 }; - public static final int[] SOURCE_SYSTEM_ID = { 164,165,166,167,168,169,170,171,172, - 173,174,175 }; - public static final int[] SOURCE_ID = { 176,177,178,179,180,181,182,183,184, - 185,186,187,188,189,190,191,192,193,194,195,196,197,198,199 }; - public static final int[] MULTIPLE_BLOCK_CRC = { 224,225,226,227,228,229, - 230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247, - 248,249,250,251,252,253,254,255 }; - - public StatusQueryExtended( BinaryMessage message, - DataUnitID duid, AliasList aliasList ) - { - super( message, duid, aliasList ); - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU1( mMessage ); - mCRC[ 1 ] = mMessage.getCRC(); - } - - @Override - public String getEventType() - { - return Opcode.STATUS_QUERY.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " TO:" ); - sb.append( getTargetAddress() ); - - sb.append( " FROM:" ); - sb.append( getSourceID() ); - - sb.append( " WACN:" ); - sb.append( getSourceWACN() ); - - sb.append( " SYS:" ); - sb.append( getSourceSystemID() ); - - return sb.toString(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getSourceWACN() - { - return mMessage.getHex( SOURCE_WACN, 5 ); - } - - public String getSourceSystemID() - { - return mMessage.getHex( SOURCE_SYSTEM_ID, 3 ); - } - - public String getSourceID() - { - return mMessage.getHex( SOURCE_ID, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/StatusUpdateExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/StatusUpdateExtended.java deleted file mode 100644 index 280f9dfe..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/StatusUpdateExtended.java +++ /dev/null @@ -1,97 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class StatusUpdateExtended extends PDUMessage -{ - public static final int[] TARGET_ADDRESS = { 88,89,90,91,92,93,94,95,96,97, - 98,99,100,101,102,103,104,105,106,107,108,109,110,111 }; - public static final int[] SOURCE_WACN = { 128,129,130,131,132,133,134,135,136,137, - 138,139,140,141,142,143,160,161,162,163 }; - public static final int[] SOURCE_SYSTEM_ID = { 164,165,166,167,168,169,170,171,172, - 173,174,175 }; - public static final int[] SOURCE_ID = { 176,177,178,179,180,181,182,183,184, - 185,186,187,188,189,190,191,192,193,194,195,196,197,198,199 }; - public static final int[] UNIT_STATUS = { 200,201,202,203,204,205,206,207 }; - public static final int[] USER_STATUS = { 208,209,210,211,212,213,214,215 }; - public static final int[] MULTIPLE_BLOCK_CRC = { 224,225,226,227,228,229, - 230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247, - 248,249,250,251,252,253,254,255 }; - - public StatusUpdateExtended( BinaryMessage message, - DataUnitID duid, AliasList aliasList ) - { - super( message, duid, aliasList ); - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU1( mMessage ); - mCRC[ 1 ] = mMessage.getCRC(); - } - - @Override - public String getEventType() - { - return Opcode.STATUS_UPDATE.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " TO:" ); - sb.append( getTargetAddress() ); - - sb.append( " FROM:" ); - sb.append( getSourceID() ); - - sb.append( " WACN:" ); - sb.append( getSourceWACN() ); - - sb.append( " SYS:" ); - sb.append( getSourceSystemID() ); - - sb.append( " STATUS USER:" + getUserStatus() ); - sb.append( " UNIT:" + getUnitStatus() ); - - return sb.toString(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getSourceWACN() - { - return mMessage.getHex( SOURCE_WACN, 5 ); - } - - public String getSourceSystemID() - { - return mMessage.getHex( SOURCE_SYSTEM_ID, 3 ); - } - - public String getSourceID() - { - return mMessage.getHex( SOURCE_ID, 6 ); - } - - public String getUserStatus() - { - return mMessage.getHex( USER_STATUS, 2 ); - } - - public String getUnitStatus() - { - return mMessage.getHex( UNIT_STATUS, 2 ); - } - -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/UnitRegistrationResponseExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/UnitRegistrationResponseExtended.java deleted file mode 100644 index b35a680b..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/control/UnitRegistrationResponseExtended.java +++ /dev/null @@ -1,99 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; -import io.github.dsheirer.module.decode.p25.reference.Response; - -public class UnitRegistrationResponseExtended extends PDUMessage -{ - public static final int[] ASSIGNED_SOURCE_ADDRESS = { 88,89,90,91,92, - 93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111 }; - public static final int[] WACN = { 128,129,130,131,132,133,134,135, - 136,137,138,139,140,141,142,143,160,161,162,163 }; - public static final int[] SYSTEM_ID = { 164,165,166,167,168,169,170, - 171,172,173,174,175 }; - public static final int[] SOURCE_ID = { 176,177,178,179,180,181,182,183,184, - 185,186,187,188,189,190,191,192,193,194,195,196,197,198,199 }; - public static final int[] SOURCE_ADDRESS = { 200,201,202,203,204,205,206, - 207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223 }; - public static final int[] UNIT_REGISTRATION_RESPONSE_VALUE = { 230,231 }; - public static final int[] MULTIPLE_BLOCK_CRC = { 320,321,322,323,324,325, - 326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342, - 343,344,345,346,347,348,349,350,351 }; - - public UnitRegistrationResponseExtended( BinaryMessage message, - DataUnitID duid, AliasList aliasList ) - { - super( message, duid, aliasList ); - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU1( mMessage ); - mCRC[ 1 ] = mMessage.getCRC(); - } - - @Override - public String getEventType() - { - return Opcode.UNIT_REGISTRATION_RESPONSE.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( "REGISTRATION:" ); - sb.append( getResponse().name() ); - - sb.append( " SRC ID:" ); - sb.append( getSourceID() ); - - sb.append( " ADDR:" ); - sb.append( getSourceAddress() ); - - sb.append( " WACN:" ); - sb.append( getWACN() ); - - sb.append( " SYS:" ); - sb.append( getSystemID() ); - - return sb.toString(); - } - - public String getAssignedSourceAddress() - { - return mMessage.getHex( ASSIGNED_SOURCE_ADDRESS, 6 ); - } - - public String getWACN() - { - return mMessage.getHex( WACN, 5 ); - } - - public String getSystemID() - { - return mMessage.getHex( SYSTEM_ID, 3 ); - } - - public String getSourceID() - { - return mMessage.getHex( SOURCE_ID, 6 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } - - public Response getResponse() - { - return Response.fromValue( - mMessage.getInt( UNIT_REGISTRATION_RESPONSE_VALUE ) ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/data/GroupDataChannelGrantExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/data/GroupDataChannelGrantExtended.java deleted file mode 100644 index 0913a06a..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/data/GroupDataChannelGrantExtended.java +++ /dev/null @@ -1,199 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.pdu.osp.data; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class GroupDataChannelGrantExtended extends PDUMessage implements IdentifierReceiver -{ - /* Service Options */ - public static final int EMERGENCY_FLAG = 128; - public static final int ENCRYPTED_CHANNEL_FLAG = 129; - public static final int DUPLEX_MODE = 130; - public static final int SESSION_MODE = 131; - - public static final int[] SOURCE_ADDRESS = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, - 104, 105, 106, 107, 108, 109, 110, 111}; - public static final int[] SERVICE_OPTIONS = {128, 129, 130, 131, 132, 133, 134, 135}; - public static final int[] TRANSMIT_IDENTIFIER = {160, 161, 162, 163}; - public static final int[] TRANSMIT_NUMBER = {164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175}; - public static final int[] RECEIVE_IDENTIFIER = {176, 177, 178, 179}; - public static final int[] RECEIVE_NUMBER = {180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191}; - public static final int[] GROUP_ADDRESS = {192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, - 206, 207}; - public static final int[] MULTIPLE_BLOCK_CRC = {224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, - 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255}; - - private IBandIdentifier mTransmitIdentifierUpdate; - private IBandIdentifier mReceiveIdentifierUpdate; - - public GroupDataChannelGrantExtended(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU1(mMessage); - mCRC[1] = mMessage.getCRC(); - } - - @Override - public String getEventType() - { - return Opcode.TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - if(isEmergency()) - { - sb.append(" EMERGENCY"); - } - - sb.append(" FROM:"); - sb.append(getSourceAddress()); - - sb.append(" TO:"); - sb.append(getGroupAddress()); - - sb.append(" CHAN DN:" + getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber()); - sb.append(" " + getDownlinkFrequency()); - - sb.append(" CHAN UP:" + getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber()); - sb.append(" " + getUplinkFrequency()); - - return sb.toString(); - } - - public boolean isEmergency() - { - return mMessage.get(EMERGENCY_FLAG); - } - - public boolean isEncrypted() - { - return mMessage.get(ENCRYPTED_CHANNEL_FLAG); - } - - public DuplexMode getDuplexMode() - { - return mMessage.get(DUPLEX_MODE) ? DuplexMode.FULL : DuplexMode.HALF; - } - - public SessionMode getSessionMode() - { - return mMessage.get(SESSION_MODE) ? - SessionMode.CIRCUIT : SessionMode.PACKET; - } - - public String getSourceAddress() - { - return mMessage.getHex(SOURCE_ADDRESS, 6); - } - - public String getGroupAddress() - { - return mMessage.getHex(GROUP_ADDRESS, 4); - } - - public int getTransmitChannelIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public int getTransmitChannelNumber() - { - return mMessage.getInt(TRANSMIT_NUMBER); - } - - public String getTransmitChannel() - { - return getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber(); - } - - public int getReceiveChannelIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public int getReceiveChannelNumber() - { - return mMessage.getInt(RECEIVE_NUMBER); - } - - public String getReceiveChannel() - { - return getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber(); - } - - public long getDownlinkFrequency() - { - if(mTransmitIdentifierUpdate != null) - { - return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mReceiveIdentifierUpdate != null) - { - return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber()); - } - - return 0; - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitChannelIdentifier()) - { - mTransmitIdentifierUpdate = message; - } - - if(identifier == getReceiveChannelIdentifier()) - { - mReceiveIdentifierUpdate = message; - } - } - - public int[] getIdentifiers() - { - int[] idens = new int[2]; - - idens[0] = getTransmitChannelIdentifier(); - idens[1] = getReceiveChannelIdentifier(); - - return idens; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/data/IndividualDataChannelGrantExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/data/IndividualDataChannelGrantExtended.java deleted file mode 100644 index 512cb2a2..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/data/IndividualDataChannelGrantExtended.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.osp.data; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.pdu.UnitToUnitChannelGrantExtended; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class IndividualDataChannelGrantExtended extends UnitToUnitChannelGrantExtended implements IdentifierReceiver -{ - public IndividualDataChannelGrantExtended(BinaryMessage message, DataUnitID duid, AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.INDIVIDUAL_DATA_CHANNEL_GRANT.getDescription(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/voice/GroupVoiceChannelGrantExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/voice/GroupVoiceChannelGrantExplicit.java deleted file mode 100644 index a5f64ad3..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/voice/GroupVoiceChannelGrantExplicit.java +++ /dev/null @@ -1,199 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.pdu.osp.voice; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class GroupVoiceChannelGrantExplicit extends PDUMessage implements IdentifierReceiver -{ - /* Service Options */ - public static final int EMERGENCY_FLAG = 128; - public static final int ENCRYPTED_CHANNEL_FLAG = 129; - public static final int DUPLEX_MODE = 130; - public static final int SESSION_MODE = 131; - - public static final int[] SOURCE_ADDRESS = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, - 105, 106, 107, 108, 109, 110, 111}; - public static final int[] SERVICE_OPTIONS = {128, 129, 130, 131, 132, 133, 134, 135}; - public static final int[] TRANSMIT_IDENTIFIER = {176, 177, 178, 179}; - public static final int[] TRANSMIT_NUMBER = {180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191}; - public static final int[] RECEIVE_IDENTIFIER = {192, 193, 194, 195}; - public static final int[] RECEIVE_NUMBER = {196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207}; - public static final int[] GROUP_ADDRESS = {208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, - 222, 223}; - public static final int[] MULTIPLE_BLOCK_CRC = {224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, - 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255}; - - private IBandIdentifier mTransmitIdentifierUpdate; - private IBandIdentifier mReceiveIdentifierUpdate; - - public GroupVoiceChannelGrantExplicit(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU1(mMessage); - mCRC[1] = mMessage.getCRC(); - } - - @Override - public String getEventType() - { - return Opcode.GROUP_VOICE_CHANNEL_GRANT.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - if(isEmergency()) - { - sb.append(" EMERGENCY"); - } - - sb.append(" FROM:"); - sb.append(getSourceAddress()); - - sb.append(" GRP:"); - sb.append(getGroupAddress()); - - sb.append(" CHAN DN:" + getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber()); - sb.append(" " + getDownlinkFrequency()); - - sb.append(" CHAN UP:" + getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber()); - sb.append(" " + getUplinkFrequency()); - - return sb.toString(); - } - - public boolean isEmergency() - { - return mMessage.get(EMERGENCY_FLAG); - } - - public boolean isEncrypted() - { - return mMessage.get(ENCRYPTED_CHANNEL_FLAG); - } - - public DuplexMode getDuplexMode() - { - return mMessage.get(DUPLEX_MODE) ? DuplexMode.FULL : DuplexMode.HALF; - } - - public SessionMode getSessionMode() - { - return mMessage.get(SESSION_MODE) ? - SessionMode.CIRCUIT : SessionMode.PACKET; - } - - public String getSourceAddress() - { - return mMessage.getHex(SOURCE_ADDRESS, 6); - } - - public String getGroupAddress() - { - return mMessage.getHex(GROUP_ADDRESS, 4); - } - - public int getTransmitChannelIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public int getTransmitChannelNumber() - { - return mMessage.getInt(TRANSMIT_NUMBER); - } - - public String getTransmitChannel() - { - return getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber(); - } - - public int getReceiveChannelIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public int getReceiveChannelNumber() - { - return mMessage.getInt(RECEIVE_NUMBER); - } - - public String getReceiveChannel() - { - return getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber(); - } - - public long getDownlinkFrequency() - { - if(mTransmitIdentifierUpdate != null) - { - return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mReceiveIdentifierUpdate != null) - { - return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber()); - } - - return 0; - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitChannelIdentifier()) - { - mTransmitIdentifierUpdate = message; - } - - if(identifier == getReceiveChannelIdentifier()) - { - mReceiveIdentifierUpdate = message; - } - } - - public int[] getIdentifiers() - { - int[] idens = new int[2]; - - idens[0] = getTransmitChannelIdentifier(); - idens[1] = getReceiveChannelIdentifier(); - - return idens; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/voice/TelephoneInterconnectChannelGrantExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/voice/TelephoneInterconnectChannelGrantExplicit.java deleted file mode 100644 index b3b3880d..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/voice/TelephoneInterconnectChannelGrantExplicit.java +++ /dev/null @@ -1,206 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.pdu.osp.voice; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.P25Message; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -import java.util.Date; - -public class TelephoneInterconnectChannelGrantExplicit extends PDUMessage implements IdentifierReceiver -{ - /* Service Options */ - public static final int EMERGENCY_FLAG = 128; - public static final int ENCRYPTED_CHANNEL_FLAG = 129; - public static final int DUPLEX_MODE = 130; - public static final int SESSION_MODE = 131; - - public static final int[] ADDRESS = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, - 106, 107, 108, 109, 110, 111}; - public static final int[] SERVICE_OPTIONS = {128, 129, 130, 131, 132, 133, 134, 135}; - public static final int[] TRANSMIT_IDENTIFIER = {160, 161, 162, 163}; - public static final int[] TRANSMIT_NUMBER = {164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175}; - public static final int[] RECEIVE_IDENTIFIER = {176, 177, 178, 179}; - public static final int[] RECEIVE_NUMBER = {180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191}; - public static final int[] CALL_TIMER = {192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207}; - public static final int[] MULTIPLE_BLOCK_CRC = {224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, - 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255}; - - private IBandIdentifier mTransmitIdentifierUpdate; - private IBandIdentifier mReceiveIdentifierUpdate; - - public TelephoneInterconnectChannelGrantExplicit(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU1(mMessage); - mCRC[1] = mMessage.getCRC(); - } - - @Override - public String getEventType() - { - return Opcode.TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - if(isEmergency()) - { - sb.append(" EMERGENCY"); - } - - sb.append(" ADDR:"); - sb.append(getAddress()); - - sb.append(" CALL TIMER:"); - sb.append(mTimeDurationFormat.format(new Date(getCallTimer()))); - - sb.append(" CHAN DN:" + getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber()); - sb.append(" " + getDownlinkFrequency()); - - sb.append(" CHAN UP:" + getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber()); - sb.append(" " + getUplinkFrequency()); - - return sb.toString(); - } - - public boolean isEmergency() - { - return mMessage.get(EMERGENCY_FLAG); - } - - public boolean isEncrypted() - { - return mMessage.get(ENCRYPTED_CHANNEL_FLAG); - } - - public P25Message.DuplexMode getDuplexMode() - { - return mMessage.get(DUPLEX_MODE) ? P25Message.DuplexMode.FULL : P25Message.DuplexMode.HALF; - } - - public P25Message.SessionMode getSessionMode() - { - return mMessage.get(SESSION_MODE) ? - P25Message.SessionMode.CIRCUIT : P25Message.SessionMode.PACKET; - } - - public String getAddress() - { - return mMessage.getHex(ADDRESS, 6); - } - - /* - * Call timer in milliseconds - */ - public long getCallTimer() - { - int timer = mMessage.getInt(CALL_TIMER); - - return timer / 100; - } - - public int getTransmitChannelIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public int getTransmitChannelNumber() - { - return mMessage.getInt(TRANSMIT_NUMBER); - } - - public String getTransmitChannel() - { - return getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber(); - } - - public int getReceiveChannelIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public int getReceiveChannelNumber() - { - return mMessage.getInt(RECEIVE_NUMBER); - } - - public String getReceiveChannel() - { - return getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber(); - } - - public long getDownlinkFrequency() - { - if(mTransmitIdentifierUpdate != null) - { - return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mReceiveIdentifierUpdate != null) - { - return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber()); - } - - return 0; - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitChannelIdentifier()) - { - mTransmitIdentifierUpdate = message; - } - - if(identifier == getReceiveChannelIdentifier()) - { - mReceiveIdentifierUpdate = message; - } - } - - public int[] getIdentifiers() - { - int[] idens = new int[2]; - - idens[0] = getTransmitChannelIdentifier(); - idens[1] = getReceiveChannelIdentifier(); - - return idens; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/voice/TelephoneInterconnectChannelGrantUpdateExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/voice/TelephoneInterconnectChannelGrantUpdateExplicit.java deleted file mode 100644 index 587565ec..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/voice/TelephoneInterconnectChannelGrantUpdateExplicit.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.osp.voice; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class TelephoneInterconnectChannelGrantUpdateExplicit - extends TelephoneInterconnectChannelGrantExplicit -{ - public TelephoneInterconnectChannelGrantUpdateExplicit(BinaryMessage message, - DataUnitID duid, AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE.getDescription(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/voice/UnitToUnitAnswerRequestExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/voice/UnitToUnitAnswerRequestExplicit.java deleted file mode 100644 index 39e028c3..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/voice/UnitToUnitAnswerRequestExplicit.java +++ /dev/null @@ -1,112 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.osp.voice; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.module.decode.p25.message.P25Message; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class UnitToUnitAnswerRequestExplicit extends PDUMessage -{ - /* Service Options */ - public static final int EMERGENCY_FLAG = 128; - public static final int ENCRYPTED_CHANNEL_FLAG = 129; - public static final int DUPLEX_MODE = 130; - public static final int SESSION_MODE = 131; - - public static final int[] TARGET_ADDRESS = { 88,89,90,91,92,93,94,95,96,97, - 98,99,100,101,102,103,104,105,106,107,108,109,110,111 }; - public static final int[] WACN = { 168,169,170,171,172,173,174,175,176,177, - 178,179,180,181,182,183,184,185,186,187 }; - public static final int[] SYSTEM_ID = { 188,189,190,191,192,193,194,195,196, - 197,198,199 }; - public static final int[] SOURCE_ID = { 200,201,202,203,204,205,206,207,208, - 209,210,211,212,213,214,215,216,217,218,219,220,221,222,223 }; - public static final int[] MULTIPLE_BLOCK_CRC = { 224,225,226,227,228,229, - 230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247, - 248,249,250,251,252,253,254,255 }; - - public UnitToUnitAnswerRequestExplicit(BinaryMessage message, - DataUnitID duid, AliasList aliasList ) - { - super( message, duid, aliasList ); - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU1( mMessage ); - mCRC[ 1 ] = mMessage.getCRC(); - } - - @Override - public String getEventType() - { - return Opcode.UNIT_TO_UNIT_ANSWER_REQUEST.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - if( isEmergency() ) - { - sb.append( " EMERGENCY" ); - } - - sb.append( " FROM:" ); - sb.append( getSourceID() ); - - sb.append( " TO:" ); - sb.append( getTargetAddress() ); - - sb.append( " WACN:" + getWACN() ); - - sb.append( " SYS:" + getSystemID() ); - - return sb.toString(); - } - - public boolean isEmergency() - { - return mMessage.get( EMERGENCY_FLAG ); - } - - public boolean isEncryptedChannel() - { - return mMessage.get( ENCRYPTED_CHANNEL_FLAG ); - } - - public P25Message.DuplexMode getDuplexMode() - { - return mMessage.get( DUPLEX_MODE ) ? P25Message.DuplexMode.FULL : P25Message.DuplexMode.HALF; - } - - public P25Message.SessionMode getSessionMode() - { - return mMessage.get( SESSION_MODE ) ? - P25Message.SessionMode.CIRCUIT : P25Message.SessionMode.PACKET; - } - - public String getSystemID() - { - return mMessage.getHex( SYSTEM_ID, 3 ); - } - - public String getWACN() - { - return mMessage.getHex( WACN, 5 ); - } - - public String getSourceID() - { - return mMessage.getHex( SOURCE_ID, 6 ); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/voice/UnitToUnitVoiceChannelGrantExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/voice/UnitToUnitVoiceChannelGrantExtended.java deleted file mode 100644 index 81d30f74..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/voice/UnitToUnitVoiceChannelGrantExtended.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.pdu.osp.voice; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.pdu.UnitToUnitChannelGrantExtended; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class UnitToUnitVoiceChannelGrantExtended - extends UnitToUnitChannelGrantExtended - implements IdentifierReceiver -{ - public UnitToUnitVoiceChannelGrantExtended( BinaryMessage message, - DataUnitID duid, AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.UNIT_TO_UNIT_VOICE_CHANNEL_GRANT.getDescription(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/voice/UnitToUnitVoiceChannelGrantUpdateExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/voice/UnitToUnitVoiceChannelGrantUpdateExtended.java deleted file mode 100644 index d981c610..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/osp/voice/UnitToUnitVoiceChannelGrantUpdateExtended.java +++ /dev/null @@ -1,228 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.pdu.osp.voice; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRCP25; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class UnitToUnitVoiceChannelGrantUpdateExtended extends PDUMessage implements IdentifierReceiver -{ - /* Service Options */ - public static final int EMERGENCY_FLAG = 128; - public static final int ENCRYPTED_CHANNEL_FLAG = 129; - public static final int DUPLEX_MODE = 130; - public static final int SESSION_MODE = 131; - - public static final int[] SOURCE_ADDRESS = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, - 105, 106, 107, 108, 109, 110, 111}; - public static final int[] SERVICE_OPTIONS = {128, 129, 130, 131, 132, 133, 134, 135}; - public static final int[] SOURCE_WACN = {160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, - 175, 176, 177, 178, 179}; - public static final int[] SOURCE_SYSTEM_ID = {180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191}; - public static final int[] SOURCE_ID = {192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, - 207, 208, 209, 210, 211, 212, 213, 214, 215}; - public static final int[] TARGET_ADDRESS = {216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, - 230, 231, 232, 233, 234, 235, 236, 237, 238, 239}; - public static final int[] TRANSMIT_IDENTIFIER = {240, 241, 242, 243}; - public static final int[] TRANSMIT_NUMBER = {244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255}; - public static final int[] RECEIVE_IDENTIFIER = {256, 257, 258, 259}; - public static final int[] RECEIVE_NUMBER = {260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271}; - public static final int[] MULTIPLE_BLOCK_CRC = {320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, - 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351}; - - private IBandIdentifier mTransmitIdentifierUpdate; - private IBandIdentifier mReceiveIdentifierUpdate; - - public UnitToUnitVoiceChannelGrantUpdateExtended(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - - /* Header block is already error detected/corrected - perform error - * detection correction on the intermediate and final data blocks */ - mMessage = CRCP25.correctPDU2(mMessage); - mCRC[1] = mMessage.getCRC(); - } - - @Override - public String getEventType() - { - return Opcode.UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - if(isEmergency()) - { - sb.append(" EMERGENCY"); - } - - sb.append(" FROM ADDR:"); - sb.append(getSourceAddress()); - - sb.append(" ID:"); - sb.append(getSourceID()); - - sb.append(" TO:"); - sb.append(getTargetAddress()); - - sb.append(" WACN:"); - sb.append(getSourceWACN()); - - sb.append(" SYS:"); - sb.append(getSourceSystemID()); - - sb.append(" CHAN DN:" + getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber()); - sb.append(" " + getDownlinkFrequency()); - - sb.append(" CHAN UP:" + getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber()); - sb.append(" " + getUplinkFrequency()); - - return sb.toString(); - } - - public boolean isEmergency() - { - return mMessage.get(EMERGENCY_FLAG); - } - - public boolean isEncrypted() - { - return mMessage.get(ENCRYPTED_CHANNEL_FLAG); - } - - public DuplexMode getDuplexMode() - { - return mMessage.get(DUPLEX_MODE) ? DuplexMode.FULL : DuplexMode.HALF; - } - - public SessionMode getSessionMode() - { - return mMessage.get(SESSION_MODE) ? - SessionMode.CIRCUIT : SessionMode.PACKET; - } - - public String getSourceAddress() - { - return mMessage.getHex(SOURCE_ADDRESS, 6); - } - - public String getSourceID() - { - return mMessage.getHex(SOURCE_ID, 6); - } - - public String getSourceWACN() - { - return mMessage.getHex(SOURCE_WACN, 5); - } - - public String getSourceSystemID() - { - return mMessage.getHex(SOURCE_SYSTEM_ID, 3); - } - - public String getTargetAddress() - { - return mMessage.getHex(TARGET_ADDRESS, 6); - } - - public int getTransmitChannelIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public int getTransmitChannelNumber() - { - return mMessage.getInt(TRANSMIT_NUMBER); - } - - public String getTransmitChannel() - { - return getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber(); - } - - public int getReceiveChannelIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public int getReceiveChannelNumber() - { - return mMessage.getInt(RECEIVE_NUMBER); - } - - public String getReceiveChannel() - { - return getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber(); - } - - public long getDownlinkFrequency() - { - if(mTransmitIdentifierUpdate != null) - { - return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mReceiveIdentifierUpdate != null) - { - return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber()); - } - - return 0; - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitChannelIdentifier()) - { - mTransmitIdentifierUpdate = message; - } - - if(identifier == getReceiveChannelIdentifier()) - { - mReceiveIdentifierUpdate = message; - } - } - - public int[] getIdentifiers() - { - int[] idens = new int[2]; - - idens[0] = getTransmitChannelIdentifier(); - idens[1] = getReceiveChannelIdentifier(); - - return idens; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/PacketHeader.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/PacketHeader.java new file mode 100644 index 00000000..f2ae0131 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/PacketHeader.java @@ -0,0 +1,128 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.packet; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUHeader; +import io.github.dsheirer.module.decode.p25.reference.ServiceAccessPoint; + +public class PacketHeader extends PDUHeader +{ + public static final int[] SAP_ID = {10, 11, 12, 13, 14, 15}; + public static final int FULL_MESSAGE_FLAG = 48; + public static final int[] PAD_OCTET_COUNT = {59, 60, 61, 62, 63}; + public static final int SYNCHRONIZE_FLAG = 64; + public static final int[] PACKET_SEQUENCE_NUMBER = {65,66}; + public static final int[] MESSAGE_FRAGMENT_SEQUENCE_NUMBER = {67,68,69}; + public static final int[] DATA_HEADER_OFFSET = {74, 75, 76, 77, 78, 79}; + + public PacketHeader(CorrectedBinaryMessage message, boolean passesCRC) + { + super(message, passesCRC); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(!isValid()) + { + sb.append(" *CRC-FAIL*"); + } + + sb.append(isConfirmationRequired() ? " CONFIRMED" : " UNCONFIRMED"); + sb.append(" ").append(getDirection()); + sb.append(" FMT:").append(getFormat().getLabel()); + sb.append(" SAP:").append(getServiceAccessPoint().name()); + sb.append(" VEND:").append(getVendor().getLabel()); + sb.append(isOutbound() ? "TO" : "FROM").append(" LLID:").append(getLLID()); + sb.append(" BLKS TO FOLLOW:").append(getBlocksToFollowCount()); + + if(isSynchronize()) + { + sb.append(" PACKET:").append(getPacketSequenceNumber()); + sb.append(" MSG FRAGMENT:").append(getMessageFragmentSequenceNumber()); + } + + sb.append(" PAD OCTETS:").append(getPadOctetCount()); + sb.append(" HEADER OFFSET:").append(getDataHeaderOffset()); + + return sb.toString(); + } + + /** + * Service Access Point (SAP) - determines the network service that will process this packet + */ + public ServiceAccessPoint getServiceAccessPoint() + { + return ServiceAccessPoint.fromValue(getMessage().getInt(SAP_ID)); + } + + /** + * Indicates if this is the first time this packet header has been transmitted. A value of false + * indicates this is a retransmitted packet header. + */ + public boolean isFullMessage() + { + return getMessage().get(FULL_MESSAGE_FLAG); + } + + /** + * Number of octets (bytes) that are appended to the end of the packet to make a full final block + */ + public int getPadOctetCount() + { + return getMessage().getInt(PAD_OCTET_COUNT); + } + + /** + * Indicates that this header contains packet and message fragment sequence numbers + */ + public boolean isSynchronize() + { + return getMessage().get(SYNCHRONIZE_FLAG); + } + + /** + * Packet sequence number for correct ordering by the receiver. + */ + public int getPacketSequenceNumber() + { + return getMessage().getInt(PACKET_SEQUENCE_NUMBER); + } + + /** + * Message fragment sequence number for correct ordering by the receiver + */ + public int getMessageFragmentSequenceNumber() + { + return getMessage().getInt(MESSAGE_FRAGMENT_SEQUENCE_NUMBER); + } + + /** + * Number of bytes in the data portion of the packet that are dedicated to the header. This + * offset is used to identify the start of the data portion of the message. + */ + public int getDataHeaderOffset() + { + return getMessage().getInt(DATA_HEADER_OFFSET); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/PacketMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/PacketMessage.java new file mode 100644 index 00000000..1252f355 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/PacketMessage.java @@ -0,0 +1,267 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.packet; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.ip.IPacket; +import io.github.dsheirer.module.decode.ip.PacketMessageFactory; +import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet; +import io.github.dsheirer.module.decode.p25.message.P25Message; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.block.ConfirmedDataBlock; +import io.github.dsheirer.module.decode.p25.message.pdu.block.DataBlock; +import io.github.dsheirer.module.decode.p25.message.pdu.packet.sndcp.SNDCPPacketHeader; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.IPHeaderCompression; +import io.github.dsheirer.module.decode.p25.reference.UDPHeaderCompression; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Packet Data Unit (PDU) sequence containing IP packet data. + */ +public class PacketMessage extends P25Message +{ + private final static Logger mLog = LoggerFactory.getLogger(PacketMessage.class); + + private PDUSequence mPDUSequence; + private BinaryMessage mPayload; + private BinaryMessage mPacketMessage; + private SNDCPPacketHeader mSNDCPPacketHeader; + private IPacket mPacket; + + public PacketMessage(PDUSequence PDUSequence, int nac, long timestamp) + { + super(null, nac, timestamp); + mPDUSequence = PDUSequence; + } + + /** + * Packet Data Unit (PDU) header and data block(s) for this message + */ + public PDUSequence getPDUSequence() + { + return mPDUSequence; + } + + /** + * Packet header from the packet sequence. + */ + public PacketHeader getHeader() + { + return (PacketHeader)getPDUSequence().getHeader(); + } + + public SNDCPPacketHeader getSNDCPPacketHeader() + { + if(mSNDCPPacketHeader == null) + { + if(getPDUSequence().isComplete() && getHeader().getDataHeaderOffset() == 2) + { + if(getPDUSequence().getDataBlocks().size() >= 1) + { + BinaryMessage message = getPDUSequence().getDataBlocks().get(0).getMessage().getSubMessage(0, 16); + mSNDCPPacketHeader = new SNDCPPacketHeader(message, getHeader().isOutbound()); + } + else + { + mLog.debug("***************** 2 byte SNDCP header with no data blocks header: " + getPDUSequence().getHeader().getMessage().toHexString()); + mSNDCPPacketHeader = new SNDCPPacketHeader(getHeader().isOutbound()); + } + } + else + { + mSNDCPPacketHeader = new SNDCPPacketHeader(getHeader().isOutbound()); + } + } + + return mSNDCPPacketHeader; + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + if(!getPDUSequence().isComplete()) + { + sb.append(" *INCOMPLETE - RECEIVED ").append(getPDUSequence().getDataBlocks().size()).append("/") + .append(getPDUSequence().getHeader().getBlocksToFollowCount()).append(" DATA BLOCKS"); + } + + if(getPacket() instanceof IPV4Packet) + { + sb.append(" LLID:").append(getHeader().getLLID()); + + SNDCPPacketHeader sndcpPacketHeader = getSNDCPPacketHeader(); + sb.append(" NSAPI:").append(sndcpPacketHeader.getNSAPI()); + + if(sndcpPacketHeader.getIPHeaderCompression() != IPHeaderCompression.NONE) + { + sb.append("IP HEADER COMPRESSION:").append(sndcpPacketHeader.getIPHeaderCompression()); + } + if(sndcpPacketHeader.getUDPHeaderCompression() != UDPHeaderCompression.NONE) + { + sb.append("UDP HEADER COMPRESSION:").append(sndcpPacketHeader.getUDPHeaderCompression()); + } + + sb.append(" ").append(getPacket()); + } + else + { + sb.append(" ").append(getPDUSequence().getHeader().toString()); + + if(getHeader().isConfirmationRequired()) + { + sb.append(" DATA BLOCKS ").append(getDataBlockSequenceNumbers()); + } + else + { + sb.append(" DATA BLOCKS:").append(getPDUSequence().getDataBlocks().size()); + } + + if(!getPDUSequence().getDataBlocks().isEmpty()) + { + sb.append(" MSG:"); + + for(DataBlock dataBlock : getPDUSequence().getDataBlocks()) + { + sb.append(dataBlock.getMessage().toHexString()); + } + } + + sb.append(" PAYLOAD:").append(getPayloadMessage().toHexString()); + sb.append(" ").append(getPacket()); + } + return sb.toString(); + } + + public List getDataBlockSequenceNumbers() + { + List numbers = new ArrayList<>(); + + for(DataBlock dataBlock: getPDUSequence().getDataBlocks()) + { + if(dataBlock instanceof ConfirmedDataBlock) + { + int sequenceNumber = ((ConfirmedDataBlock)dataBlock).getSequenceNumber(); + + if(sequenceNumber != -1) + { + numbers.add(sequenceNumber); + } + } + } + + return numbers; + } + + public IPacket getPacket() + { + if(mPacket == null) + { + mPacket = PacketMessageFactory.create(getSNDCPPacketHeader(), getPacketMessage(), 0); + } + + return mPacket; + } + + public BinaryMessage getPacketMessage() + { + if(mPacketMessage == null) + { + int start = getHeader().getDataHeaderOffset() * 8; + + if(getPayloadMessage().size() >= start + 32) + { + int end = getPayloadMessage().size() - 32; //CRC + end -= (getHeader().getPadOctetCount() * 8); + if(end > start) + { + mPacketMessage = getPayloadMessage().getSubMessage(start, end); + } + } + + if(mPacketMessage == null) + { + mPacketMessage = new BinaryMessage(0); + } + } + + return mPacketMessage; + } + + /** + * Combined binary payload of the data blocks including any header offset data and the 32-bit packet CRC. + * Note: for confirmed packets, this payload does not include the data block sequence number and block CRC data. + * Data blocks are assumed to be received in the correct order and no attempt is made to fill in for + * any missing data blocks. + */ + public BinaryMessage getPayloadMessage() + { + if(mPayload == null) + { + int octetCount = 0; + + if(getHeader().isConfirmationRequired()) + { + //Confirmed 3/4 rate encoded data blocks + octetCount += (16 * getHeader().getBlocksToFollowCount()); + } + else + { + //Unconfirmed 1/2 rate encoded data blocks + octetCount += (12 * getHeader().getBlocksToFollowCount()); + } + + mPayload = new BinaryMessage(octetCount * 8); + + int pointer = 0; + + List dataBlocks = getPDUSequence().getDataBlocks(); + + for(DataBlock dataBlock : dataBlocks) + { + BinaryMessage blockPayload = dataBlock.getMessage(); + mPayload.load(pointer, blockPayload); + pointer += blockPayload.size(); + } + } + + return mPayload; + } + + @Override + public DataUnitID getDUID() + { + return DataUnitID.IP_PACKET_DATA; + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/ActivateTdsContextAccept.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/ActivateTdsContextAccept.java new file mode 100644 index 00000000..b69eadb4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/ActivateTdsContextAccept.java @@ -0,0 +1,232 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.packet.sndcp; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.ipv4.APCO25IpAddress; +import io.github.dsheirer.module.decode.p25.reference.MDPConfigurationOption; +import io.github.dsheirer.module.decode.p25.reference.MaximumTransmitUnit; +import io.github.dsheirer.module.decode.p25.reference.NetworkAddressType; +import io.github.dsheirer.module.decode.p25.reference.PDUPriorityMaximumType; +import io.github.dsheirer.module.decode.p25.reference.ReadyTimer; +import io.github.dsheirer.module.decode.p25.reference.StandbyTimer; + +import java.util.ArrayList; +import java.util.List; + +/** + * Activate Trunking Data Service (TDS) Context Request + */ +public class ActivateTdsContextAccept extends SNDCPMessage +{ + private static final int[] NSAPI = {4, 5, 6, 7}; + private static final int[] PDUPM = {8, 9, 10, 11}; + private static final int[] READY_TIMER = {12, 13, 14, 15}; + private static final int[] STANDBY_TIMER = {16, 17, 18, 19}; + private static final int[] NAT = {20, 21, 22, 23}; + private static final int[] IPV4_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int TCP_IP_HEADER_COMPRESSION = 56; + private static final int[] IP_HEADER_COMPRESSION_BITMAP = {56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] TCPSS = {64, 65, 66, 67}; + private static final int[] UDPSS = {68, 69, 70, 71}; + private static final int[] MTU = {72, 73, 74, 75}; + private static final int[] UDP_IP_HEADER_COMPRESSION_BITMAP = {76, 77, 78, 79}; + private static final int[] MDPCO = {80, 81, 82, 83, 84, 85, 86, 87}; + private static final int[] DATA_ACCESS_CONTROL = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103}; + + private Identifier mIPAddress; + private List mIdentifiers; + + /** + * Constructs an SNDCP message parser instance. + * + * @param message containing the binary sequence + * @param outbound where true is outbound (from repeater) and false is inbound (from mobile) + */ + public ActivateTdsContextAccept(BinaryMessage message, boolean outbound) + { + super(message, outbound); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(super.toString()); + sb.append(" NSAPI:").append(getNSAPI()); + if(hasIPAddress()) + { + sb.append(" ADDRESS:").append(getIPAddress()); + sb.append(" TYPE:").append(getNetworkAddressType()); + } + sb.append(" READY TIMER:").append(getReadyTimer().getSeconds()).append("s"); + sb.append(" STANDBY TIMER:").append(getStandbyTimer().getSeconds()).append("s"); + sb.append(" MTU:").append(getMaximumTransmitUnit().getByteCount()).append("B"); + sb.append(" AUTHORIZED GROUPS:").append(getDataAccessGroups()); + return sb.toString(); + } + + /** + * Network Service Access Point Identifier + */ + public int getNSAPI() + { + return getMessage().getInt(NSAPI); + } + + /** + * Maximum priority that can be used with PDUs for this context, to support differentiated + * quality of service. + */ + public PDUPriorityMaximumType getPDUMaximumPriority() + { + return PDUPriorityMaximumType.fromValue(getMessage().getInt(PDUPM)); + } + + public ReadyTimer getReadyTimer() + { + return ReadyTimer.fromValue(getMessage().getInt(READY_TIMER)); + } + + public StandbyTimer getStandbyTimer() + { + return StandbyTimer.fromValue(getMessage().getInt(STANDBY_TIMER)); + } + + public MaximumTransmitUnit getMaximumTransmitUnit() + { + return MaximumTransmitUnit.fromValue(getMessage().getInt(MTU)); + } + + /** + * Network Address Type + */ + public NetworkAddressType getNetworkAddressType() + { + return NetworkAddressType.fromValue(getMessage().getInt(NAT)); + } + + public boolean hasIPAddress() + { + NetworkAddressType nat = getNetworkAddressType(); + + return nat == NetworkAddressType.IPV4_STATIC_ADDRESS || + nat == NetworkAddressType.IPV4_DYNAMIC_ADDRESS; + } + + public Identifier getIPAddress() + { + if(mIPAddress == null && hasIPAddress()) + { + if(isOutbound()) + { + mIPAddress = APCO25IpAddress.createTo(getMessage().getInt(IPV4_ADDRESS)); + } + else + { + mIPAddress = APCO25IpAddress.createFrom(getMessage().getInt(IPV4_ADDRESS)); + } + } + + return mIPAddress; + } + + /** + * Data Access Control Groups. Indicates which (predefined) groups have access to the SNDCP context. + */ + public List getDataAccessGroups() + { + List groups = new ArrayList<>(); + for(int index : DATA_ACCESS_CONTROL) + { + if(getMessage().get(index)) + { + groups.add(index - 87); + } + } + + return groups; + } + /** + * Indicates if TCP/IP header compression is either being requested or is in use + */ + public boolean isTCPIPHeaderCompression() + { + return getMessage().get(TCP_IP_HEADER_COMPRESSION); + } + + /** + * Indicates if any form of IP header compression is being used/requested. + */ + public boolean isIPHeaderCompression() + { + return getMessage().getInt(IP_HEADER_COMPRESSION_BITMAP) > 0; + } + + /** + * Number of concurrent TCP/IP compressed connections when TCP/IP header compression is being used. See RFC 1144 + */ + public int getTCPIPStateSlots() + { + return getMessage().getInt(TCPSS); + } + + /** + * Number of concurrent UDP/IP compressed connnections when UDP/IP header compression is being used. + */ + public int getUDPIPStateSlots() + { + return getMessage().getInt(UDPSS); + } + + /** + * Indicates if any form of UDP/IP header compression is being used/requested. + */ + public boolean isUDPHeaderCompression() + { + return getMessage().getInt(UDP_IP_HEADER_COMPRESSION_BITMAP) > 0; + } + + /** + * MDP Configuration Option; + */ + public MDPConfigurationOption getMDPConfigurationOptions() + { + return MDPConfigurationOption.fromValue(getMessage().getInt(MDPCO)); + } + + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + + if(hasIPAddress()) + { + mIdentifiers.add(getIPAddress()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/ActivateTdsContextReject.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/ActivateTdsContextReject.java new file mode 100644 index 00000000..7afedc6a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/ActivateTdsContextReject.java @@ -0,0 +1,77 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.packet.sndcp; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.reference.RejectReason; + +import java.util.Collections; +import java.util.List; + +/** + * Activate Trunking Data Service (TDS) Context Reject + */ +public class ActivateTdsContextReject extends SNDCPMessage +{ + private static final int[] NSAPI = {4, 5, 6, 7}; + private static final int[] REJECT_REASON = {8, 9, 10, 11, 12, 13, 14, 15}; + + /** + * Constructs an SNDCP message parser instance. + * + * @param message containing the binary sequence + * @param outbound where true is outbound (from repeater) and false is inbound (from mobile) + */ + public ActivateTdsContextReject(BinaryMessage message, boolean outbound) + { + super(message, outbound); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(super.toString()); + sb.append(" NSAPI:").append(getNSAPI()); + sb.append(" REASON:").append(getRejectReason()); + return sb.toString(); + } + + /** + * Network Service Access Point Identifier + */ + public int getNSAPI() + { + return getMessage().getInt(NSAPI); + } + + + public RejectReason getRejectReason() + { + return RejectReason.fromValue(getMessage().getInt(REJECT_REASON)); + } + + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/ActivateTdsContextRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/ActivateTdsContextRequest.java new file mode 100644 index 00000000..19dd5bdd --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/ActivateTdsContextRequest.java @@ -0,0 +1,200 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.packet.sndcp; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.ipv4.APCO25IpAddress; +import io.github.dsheirer.module.decode.p25.reference.DataSubscriberUnitType; +import io.github.dsheirer.module.decode.p25.reference.MDPConfigurationOption; +import io.github.dsheirer.module.decode.p25.reference.NetworkAddressType; + +import java.util.ArrayList; +import java.util.List; + +/** + * Activate Trunking Data Service (TDS) Context Request + */ +public class ActivateTdsContextRequest extends SNDCPMessage +{ + private static final int[] VERSION = {4, 5, 6, 7}; + private static final int[] NSAPI = {8, 9, 10, 11}; + private static final int[] NAT = {12, 13, 14, 15}; + private static final int[] IPV4_ADDRESS = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] DSUT = {48, 49, 50, 51}; + private static final int[] UDP_IP_HEADER_COMPRESSION_BITMAP = {52, 53, 54, 55}; + private static final int[] IP_HEADER_COMPRESSION_BITMAP = {56, 57, 58, 59, 60, 61, 62, 63}; + private static final int TCP_IP_HEADER_COMPRESSION = 56; + private static final int[] TCPSS = {64, 65, 66, 67}; + private static final int[] UDPSS = {68, 69, 70, 71}; + private static final int[] MDPCO = {72, 73, 74, 75, 76, 77, 78, 79}; + + private Identifier mIPAddress; + private List mIdentifiers; + + /** + * Constructs an SNDCP message parser instance. + * + * @param message containing the binary sequence + * @param outbound where true is outbound (from repeater) and false is inbound (from mobile) + */ + public ActivateTdsContextRequest(BinaryMessage message, boolean outbound) + { + super(message, outbound); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getPDUType()); + if(hasIPAddress()) + { + sb.append(" IP:").append(getIPAddress()); + sb.append(" TYPE:").append(getNetworkAddressType()); + } + + sb.append(" NSAPI:").append(getNSAPI()); + + return sb.toString(); + } + + /** + * SNDCP Version Number + */ + public int getVersion() + { + return getMessage().getInt(VERSION); + } + + /** + * Network Service Access Point Identifier + */ + public int getNSAPI() + { + return getMessage().getInt(NSAPI); + } + + /** + * Network Address Type + */ + public NetworkAddressType getNetworkAddressType() + { + return NetworkAddressType.fromValue(getMessage().getInt(NAT)); + } + + public boolean hasIPAddress() + { + NetworkAddressType nat = getNetworkAddressType(); + + return nat == NetworkAddressType.IPV4_STATIC_ADDRESS || + nat == NetworkAddressType.IPV4_DYNAMIC_ADDRESS; + } + + public Identifier getIPAddress() + { + if(mIPAddress == null && hasIPAddress()) + { + if(isOutbound()) + { + mIPAddress = APCO25IpAddress.createTo(getMessage().getInt(IPV4_ADDRESS)); + } + else + { + mIPAddress = APCO25IpAddress.createFrom(getMessage().getInt(IPV4_ADDRESS)); + } + } + + return mIPAddress; + } + + /** + * Identifies the type of user equipment + */ + public DataSubscriberUnitType getDataSubsciberUnitType() + { + return DataSubscriberUnitType.fromValue(getMessage().getInt(DSUT)); + } + + /** + * Indicates if TCP/IP header compression is either being requested or is in use + */ + public boolean isTCPIPHeaderCompression() + { + return getMessage().get(TCP_IP_HEADER_COMPRESSION); + } + + /** + * Indicates if any form of IP header compression is being used/requested. + */ + public boolean isIPHeaderCompression() + { + return getMessage().getInt(IP_HEADER_COMPRESSION_BITMAP) > 0; + } + + /** + * Number of concurrent TCP/IP compressed connections when TCP/IP header compression is being used. See RFC 1144 + */ + public int getTCPIPStateSlots() + { + return getMessage().getInt(TCPSS); + } + + /** + * Number of concurrent UDP/IP compressed connnections when UDP/IP header compression is being used. + */ + public int getUDPIPStateSlots() + { + return getMessage().getInt(UDPSS); + } + + /** + * Indicates if any form of UDP/IP header compression is being used/requested. + */ + public boolean isUDPHeaderCompression() + { + return getMessage().getInt(UDP_IP_HEADER_COMPRESSION_BITMAP) > 0; + } + + /** + * MDP Configuration Option; + */ + public MDPConfigurationOption getMDPConfigurationOptions() + { + return MDPConfigurationOption.fromValue(getMessage().getInt(MDPCO)); + } + + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + + if(hasIPAddress()) + { + mIdentifiers.add(getIPAddress()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/DeActivateTdsContextRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/DeActivateTdsContextRequest.java new file mode 100644 index 00000000..2d86fc38 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/DeActivateTdsContextRequest.java @@ -0,0 +1,98 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.packet.sndcp; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * De-Activate Trunking Data Service (TDS) Context Request + */ +public class DeActivateTdsContextRequest extends SNDCPMessage +{ + private static final int[] NSAPI = {4, 5, 6, 7}; + private static final int[] DEACTIVATION_TYPE = {8, 9, 10, 11, 12, 13, 14, 15}; + + /** + * Constructs an SNDCP message parser instance. + * + * @param message containing the binary sequence + * @param outbound where true is outbound (from repeater) and false is inbound (from mobile) + */ + public DeActivateTdsContextRequest(BinaryMessage message, boolean outbound) + { + super(message, outbound); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(super.toString()); + sb.append(" DEACTIVATE NSAPIs ").append(getDeactivateNSAPIs()); + return sb.toString(); + } + + /** + * Network Service Access Point Identifier + */ + public int getNSAPI() + { + return getMessage().getInt(NSAPI); + } + + /** + * List of NSAPIs to deactivate for the mobile subscriber + */ + public List getDeactivateNSAPIs() + { + int type = getMessage().getInt(DEACTIVATION_TYPE); + + if(type == 0) + { + List allNSAPIs = new ArrayList<>(); + + for(int x = 0; x < 16; x++) + { + allNSAPIs.add(x); + } + + return allNSAPIs; + } + else if(type == 1) + { + List thisNSAPI = new ArrayList<>(); + thisNSAPI.add(getNSAPI()); + return thisNSAPI; + } + + return Collections.EMPTY_LIST; + } + + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/SNDCPMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/SNDCPMessage.java new file mode 100644 index 00000000..adfef6e8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/SNDCPMessage.java @@ -0,0 +1,92 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.packet.sndcp; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.module.decode.p25.reference.PDUType; + +/** + * Subnetwork Dependent Convergence Protocol (SNDCP) base message parser + */ +public class SNDCPMessage +{ + private static final int[] PDU_TYPE = {0, 1, 2, 3}; + + private BinaryMessage mMessage; + private boolean mOutbound; + + /** + * Constructs an SNDCP message parser instance. + * + * @param message containing the binary sequence + * @param outbound where true is outbound (from repeater) and false is inbound (from mobile) + */ + public SNDCPMessage(BinaryMessage message, boolean outbound) + { + mMessage = message; + mOutbound = outbound; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getPDUType()); + return sb.toString(); + } + + /** + * Binary message sequence + */ + public BinaryMessage getMessage() + { + return mMessage; + } + + /** + * Indicates if this is an outbound (versus inbound) SNDCP message + */ + public boolean isOutbound() + { + return mOutbound; + } + + /** + * Indicates the type of SNDCP packet contained in this message + */ + public PDUType getPDUType() + { + return getPDUType(getMessage(), isOutbound()); + } + + /** + * Determines PDU type of the message + * + * @param message containing a PDU type field + * @param outbound where true is outbound and false is inbound + * @return pdu type + */ + public static PDUType getPDUType(BinaryMessage message, boolean outbound) + { + return PDUType.fromValue(message.getInt(PDU_TYPE), outbound); + } + +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/SNDCPMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/SNDCPMessageFactory.java new file mode 100644 index 00000000..ed4c633e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/SNDCPMessageFactory.java @@ -0,0 +1,58 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.packet.sndcp; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.module.decode.p25.reference.PDUType; + +public class SNDCPMessageFactory +{ + public static SNDCPMessage create(BinaryMessage binaryMessage, boolean outbound) + { + PDUType pduType = SNDCPMessage.getPDUType(binaryMessage, outbound); + + switch(pduType) + { + case OUTBOUND_SNDCP_RF_UNCONFIRMED_DATA: + //Note: (un)confirmed data type is processed by the PDUMessageFactory as a packet message + case OUTBOUND_SNDCP_RF_CONFIRMED_DATA: + //Note: (un)confirmed data type is processed by the PDUMessageFactory as a packet message + case INBOUND_SNDCP_RF_CONFIRMED_DATA: + //Note: (un)confirmed data type is processed by the PDUMessageFactory as a packet message + case OUTBOUND_SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT: + return new ActivateTdsContextAccept(binaryMessage, outbound); + case INBOUND_SNDCP_ACTIVATE_TDS_CONTEXT_REQUEST: + return new ActivateTdsContextRequest(binaryMessage, outbound); + case OUTBOUND_SNDCP_ACTIVATE_TDS_CONTEXT_REJECT: + return new ActivateTdsContextReject(binaryMessage, outbound); + case OUTBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST: + return new DeActivateTdsContextRequest(binaryMessage, outbound); + case OUTBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT: + case INBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT: + case INBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST: + + case OUTBOUND_UNKNOWN: + case INBOUND_UNKNOWN: + default: + return new SNDCPMessage(binaryMessage, outbound); + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/SNDCPPacketHeader.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/SNDCPPacketHeader.java new file mode 100644 index 00000000..f191f0f3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/SNDCPPacketHeader.java @@ -0,0 +1,120 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.packet.sndcp; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.module.decode.ip.Header; +import io.github.dsheirer.module.decode.p25.reference.IPHeaderCompression; +import io.github.dsheirer.module.decode.p25.reference.PDUType; +import io.github.dsheirer.module.decode.p25.reference.UDPHeaderCompression; + +/** + * SNDCP header for a packet message. This header contains IP and UDP compression indicators. + * + * Note: this header can be constructed with no data to ensure that it is never null for the case + * of packet messages that do not contain an SNDCP header. + */ +public class SNDCPPacketHeader extends Header +{ + private static final int[] PDU_TYPE = {0, 1, 2, 3}; + private static final int[] NSAPI = {4, 5, 6, 7}; + private static final int[] PACKET_HEADER_COMPRESSION = {8, 9, 10, 11}; + private static final int[] DATAGRAM_HEADER_COMPRESSION = {12, 13, 14, 15}; + + private boolean mOutboundMessage; + + public SNDCPPacketHeader(BinaryMessage message, boolean outboundMessage) + { + super(message, 0); + mOutboundMessage = outboundMessage; + } + + @Override + public int getLength() + { + return getMessage().size(); + } + + /** + * Constructs an empty header with no data. + */ + public SNDCPPacketHeader(boolean outboundMessage) + { + this(new BinaryMessage(0), outboundMessage); + } + + public boolean isOutboundMessage() + { + return mOutboundMessage; + } + + /** + * Indicates if this header has data or if it is an empty header + */ + public boolean hasData() + { + return getLength() == 16; + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(hasData()) + { + sb.append(getPDUType()); + sb.append(" NSAPI:").append(getNSAPI()); + sb.append(" IP COMPRESSION:").append(getIPHeaderCompression()); + sb.append(" UDP COMPRESSION:").append(getUDPHeaderCompression()); + } + else + { + sb.append("NO SNDCP HEADER"); + } + + return sb.toString(); + } + + public PDUType getPDUType() + { + if(hasData()) + { + return PDUType.fromValue(getMessage().getInt(PDU_TYPE), isOutboundMessage()); + } + + return isOutboundMessage() ? PDUType.OUTBOUND_UNKNOWN : PDUType.INBOUND_UNKNOWN; + } + + public int getNSAPI() + { + return getMessage().getInt(NSAPI); + } + + public IPHeaderCompression getIPHeaderCompression() + { + return IPHeaderCompression.fromValue(getMessage().getInt(PACKET_HEADER_COMPRESSION)); + } + + public UDPHeaderCompression getUDPHeaderCompression() + { + return UDPHeaderCompression.fromValue(getMessage().getInt(DATAGRAM_HEADER_COMPRESSION)); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/SNDCPPacketMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/SNDCPPacketMessage.java new file mode 100644 index 00000000..aba6e2f1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/packet/sndcp/SNDCPPacketMessage.java @@ -0,0 +1,70 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.packet.sndcp; + +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.packet.PacketMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +public class SNDCPPacketMessage extends PacketMessage +{ + private SNDCPMessage mSNDCPMessage; + + public SNDCPPacketMessage(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + + sb.append(" LLID:").append(getHeader().getLLID()); + if(getPDUSequence().isComplete()) + { + sb.append(" ").append(getSNDCPMessage().toString()); + sb.append(" MSG:").append(getPacketMessage().toHexString()); + } + else + { + sb.append(" ***INCOMPLETE DATA BLOCKS RECEIVED***"); + } + + return sb.toString(); + } + + @Override + public DataUnitID getDUID() + { + return DataUnitID.SUBNETWORK_DEPENDENT_CONVERGENCE_PROTOCOL; + } + + public SNDCPMessage getSNDCPMessage() + { + if(mSNDCPMessage == null) + { + mSNDCPMessage = SNDCPMessageFactory.create(getPacketMessage(), getPDUSequence().getHeader().isOutbound()); + } + + return mSNDCPMessage; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/response/ResponseHeader.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/response/ResponseHeader.java new file mode 100644 index 00000000..4958ec9e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/response/ResponseHeader.java @@ -0,0 +1,92 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.response; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUHeader; +import io.github.dsheirer.module.decode.p25.reference.PacketResponse; +import io.github.dsheirer.module.decode.p25.reference.Vendor; + +public class ResponseHeader extends PDUHeader +{ + public static final int[] RESPONSE = {8,9,10,11,12,13,14,15}; + public static final int SOURCE_LLID_FLAG = 48; + public static final int[] FROM_LOGICAL_LINK_ID = {56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79}; + + public ResponseHeader(CorrectedBinaryMessage message, boolean passesCRC) + { + super(message, passesCRC); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(!isValid()) + { + sb.append("***CRC-FAIL*** "); + } + + sb.append("PDU RESPONSE"); + + if(hasSourceLLID()) + { + sb.append(" FROM:").append(getFromLogicalLinkID()); + } + + sb.append(" TO:").append(getLLID()); + + sb.append(" ").append(getResponse()); + + Vendor vendor = getVendor(); + + if(vendor != Vendor.STANDARD) + { + sb.append(" VENDOR:").append(getVendor()); + } + + return sb.toString(); + } + + /** + * Packet response message + */ + public PacketResponse getResponse() + { + return PacketResponse.fromValue(getMessage().getInt(RESPONSE)); + } + + /** + * Indicates if this header contains a source (from) LLID + */ + public boolean hasSourceLLID() + { + return !getMessage().get(SOURCE_LLID_FLAG); + } + + /** + * Source Logical Link Identifier (ie FROM radio identifier) + */ + public String getFromLogicalLinkID() + { + return getMessage().getHex(FROM_LOGICAL_LINK_ID, 6); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/response/ResponseMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/response/ResponseMessage.java new file mode 100644 index 00000000..41fd3465 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/response/ResponseMessage.java @@ -0,0 +1,157 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.response; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequenceMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.block.DataBlock; +import io.github.dsheirer.module.decode.p25.reference.PacketResponse; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ResponseMessage extends PDUSequenceMessage +{ + public ResponseMessage(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + private ResponseHeader getHeader() + { + return (ResponseHeader)getPDUSequence().getHeader(); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append("NAC:").append(getNAC()); + + sb.append(" ").append(getHeader().toString()); + + if(getPDUSequence().isComplete()) + { + if(getHeader().getResponse() == PacketResponse.SELECTIVE_RETRY) + { + sb.append(" - RESEND BLOCKS ").append(getMissingBlockNumbers()); + } + else + { + if(getPDUSequence().getDataBlocks().size() > 0) + { + sb.append(" BLOCKS TO FOLLOW:").append(getPDUSequence().getDataBlocks().size()); + + sb.append(" DATA BLOCKS:").append(getPDUSequence().getDataBlocks().size()); + + if(!getPDUSequence().getDataBlocks().isEmpty()) + { + sb.append(" MSG:"); + + for(DataBlock dataBlock: getPDUSequence().getDataBlocks()) + { + sb.append(dataBlock.getMessage().toHexString()); + } + } + } + } + } + else + { + sb.append(" *INCOMPLETE - RECEIVED ").append(getPDUSequence().getDataBlocks().size()).append("/") + .append(getHeader().getBlocksToFollowCount()).append(" DATA BLOCKS"); + } + + + return sb.toString(); + } + + /** + * Identifies missing block numbers for Selective Retry type response messages. + */ + public List getMissingBlockNumbers() + { + int dataBlockCount = getPDUSequence().getDataBlocks().size(); + + if(0 < dataBlockCount && dataBlockCount <= 2) + { + List missingBlockNumbers = new ArrayList<>(); + + int blockOffset = 0; + + for(DataBlock dataBlock: getPDUSequence().getDataBlocks()) + { + missingBlockNumbers.addAll(getMissingBlockNumbers(dataBlock, blockOffset)) ; + + blockOffset += 64; + } + + Collections.sort(missingBlockNumbers); + return missingBlockNumbers; + } + + return Collections.EMPTY_LIST; + } + + /** + * Identifies the missing block numbers for a selective retry type response message from the + * appended data block. Up to 127 (max block size for a PDU) missing blocks can be identified + * across 2 data blocks with each data block holding a maximum of 64 block indices. + * + * The format for the missing block indices is arranged in a super easy (not) to parse format: + * + * 7 6 5 4 3 2 1 0 + * 15 14 13 12 ... + * ... + * 63 62 61 60 59 58 57 54 + * + * @param dataBlock + * @param offset + * @return + */ + private List getMissingBlockNumbers(DataBlock dataBlock, int offset) + { + List missingBlockNumbers = new ArrayList<>(); + + int blockIndex = 7; + + BinaryMessage message = dataBlock.getMessage(); + + for(int index = 0; index < 64; index++) + { + if(!message.get(index)) + { + missingBlockNumbers.add(blockIndex + offset); + } + + blockIndex--; + + if((blockIndex ^ 8) == 0) + { + blockIndex += 8; + } + } + + return missingBlockNumbers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/umbtc/UMBTCHeader.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/umbtc/UMBTCHeader.java new file mode 100644 index 00000000..57f237bf --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/umbtc/UMBTCHeader.java @@ -0,0 +1,92 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.umbtc; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.module.decode.p25.P25Utils; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUHeader; +import io.github.dsheirer.module.decode.p25.reference.ServiceAccessPoint; +import io.github.dsheirer.module.decode.p25.reference.Vendor; + +/** + * Unconfirmed Multi-Block Trunking Control Header + */ +public class UMBTCHeader extends PDUHeader +{ + public static final int[] SAP_ID = {10, 11, 12, 13, 14, 15}; + public static final int[] PAD_OCTET_COUNT = {59, 60, 61, 62, 63}; + public static final int[] RESERVED = {64, 65, 66, 67, 68, 69, 70, 71}; + public static final int[] DATA_HEADER_OFFSET = {74, 75, 76, 77, 78, 79}; + + public UMBTCHeader(CorrectedBinaryMessage message, boolean passesCRC) + { + super(message, passesCRC); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(!isValid()) + { + sb.append("*CRC-FAIL*"); + } + + sb.append("UMBTC"); + + Vendor vendor = getVendor(); + + P25Utils.pad(sb, 22); + + if(vendor != Vendor.STANDARD) + { + sb.append(" VENDOR:").append(getVendor()); + } + + sb.append(" HDR:").append(getMessage().toHexString()); + return sb.toString(); + } + + /** + * Service Access Point (SAP) - determines the network service that will process this packet + */ + public ServiceAccessPoint getServiceAccessPoint() + { + return ServiceAccessPoint.fromValue(getMessage().getInt(SAP_ID)); + } + + /** + * Number of octets (bytes) that are appended to the end of the packet to make a full final block + */ + public int getPadOctetCount() + { + return getMessage().getInt(PAD_OCTET_COUNT); + } + + /** + * Number of bits to where the header + * @return + */ + public int getDataHeaderOffset() + { + return getMessage().getInt(DATA_HEADER_OFFSET); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/umbtc/UMBTCMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/umbtc/UMBTCMessage.java new file mode 100644 index 00000000..534e0c62 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/umbtc/UMBTCMessage.java @@ -0,0 +1,149 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.umbtc; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.message.IBitErrorProvider; +import io.github.dsheirer.module.decode.p25.P25Utils; +import io.github.dsheirer.module.decode.p25.message.P25Message; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.block.DataBlock; +import io.github.dsheirer.module.decode.p25.message.pdu.block.UnconfirmedDataBlock; +import io.github.dsheirer.module.decode.p25.message.tsbk.Opcode; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.List; + +public abstract class UMBTCMessage extends P25Message implements IBitErrorProvider +{ + protected static final int[] HEADER_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] BLOCK_0_OPCODE = {2, 3, 4, 5, 6, 7}; + + private PDUSequence mPDUSequence; + + public UMBTCMessage(PDUSequence PDUSequence, int nac, long timestamp) + { + super(nac, timestamp); + mPDUSequence = PDUSequence; + } + + @Override + protected String getMessageStub() + { + StringBuilder sb = new StringBuilder(); + sb.append(super.getMessageStub()); + sb.append(" ").append(getOpcode()); + P25Utils.pad(sb, 30); + + return sb.toString(); + } + + public UMBTCHeader getHeader() + { + return (UMBTCHeader)getPDUSequence().getHeader(); + } + + public Opcode getOpcode() + { + if(hasDataBlock(0)) + { + return Opcode.fromValue(getDataBlock(0).getMessage().getInt(BLOCK_0_OPCODE), + getHeader().getDirection(), getHeader().getVendor()); + } + + return Opcode.OSP_UNKNOWN; + } + + public boolean hasDataBlock(int index) + { + return getDataBlock(index) != null; + } + + public UnconfirmedDataBlock getDataBlock(int index) + { + DataBlock dataBlock = getPDUSequence().getDataBlocks().get(index); + + if(dataBlock instanceof UnconfirmedDataBlock) + { + return (UnconfirmedDataBlock)dataBlock; + } + + return null; + } + + + @Override + public DataUnitID getDUID() + { + return DataUnitID.UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL; + } + + /** + * List of identifiers provided by the message + */ + public abstract List getIdentifiers(); + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("NAC:").append(getNAC()); + + if(!getPDUSequence().isComplete()) + { + sb.append(" *INCOMPLETE - RECEIVED ").append(getPDUSequence().getDataBlocks().size()).append("/") + .append(getPDUSequence().getHeader().getBlocksToFollowCount()).append(" DATA BLOCKS"); + } + + sb.append(" ").append(getPDUSequence().getHeader().toString()); + + sb.append(" DATA BLOCKS:").append(getPDUSequence().getDataBlocks().size()); + + if(!getPDUSequence().getDataBlocks().isEmpty()) + { + sb.append(" MSG:"); + + for(DataBlock dataBlock : getPDUSequence().getDataBlocks()) + { + sb.append(dataBlock.getMessage().toHexString()); + } + } + + return sb.toString(); + } + + public PDUSequence getPDUSequence() + { + return mPDUSequence; + } + + @Override + public int getBitsProcessedCount() + { + return getPDUSequence().getBitsProcessedCount(); + } + + @Override + public int getBitErrorsCount() + { + return getPDUSequence().getBitErrorsCount(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/umbtc/isp/UMBTCTelephoneInterconnectRequestExplicitDialing.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/umbtc/isp/UMBTCTelephoneInterconnectRequestExplicitDialing.java new file mode 100644 index 00000000..9cc67e26 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/pdu/umbtc/isp/UMBTCTelephoneInterconnectRequestExplicitDialing.java @@ -0,0 +1,151 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.pdu.umbtc.isp; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.message.IBitErrorProvider; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.telephone.APCO25TelephoneNumber; +import io.github.dsheirer.module.decode.p25.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.message.pdu.umbtc.UMBTCMessage; +import io.github.dsheirer.module.decode.p25.reference.Digit; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +public class UMBTCTelephoneInterconnectRequestExplicitDialing extends UMBTCMessage implements IBitErrorProvider +{ + private static final int[] BLOCK_0_DIGIT_COUNT = {8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + + private VoiceServiceOptions mVoiceServiceOptions; + private Identifier mSourceAddress; + private Identifier mTelephoneNumber; + private List mIdentifiers; + + public UMBTCTelephoneInterconnectRequestExplicitDialing(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(super.getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + if(getTelephoneNumber() != null) + { + sb.append(" TO:").append(getTelephoneNumber()); + } + if(getVoiceServiceOptions() != null) + { + sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + } + + return sb.toString(); + } + + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null && hasDataBlock(0)) + { + ; + } + { + mVoiceServiceOptions = new VoiceServiceOptions(getDataBlock(0).getMessage().getInt(BLOCK_0_SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getHeader().getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + public Identifier getTelephoneNumber() + { + if(mTelephoneNumber == null && hasDataBlock(0)) + { + int digitCount = getDataBlock(0).getMessage().getInt(BLOCK_0_DIGIT_COUNT); + List digits = new ArrayList<>(); + + for(int x = 1; x <= digitCount; x++) + { + digits.add(getDigit(x)); + } + + mTelephoneNumber = APCO25TelephoneNumber.createTo(Digit.decode(digits)); + } + + return mTelephoneNumber; + } + + /** + * Returns the specified digit. + * + * @param digit in range 1 - 34 + * @return specified digit or Digit.UNKNOWN + */ + private int getDigit(int digit) + { + if(digit <= 18) //Block 0 + { + if(hasDataBlock(0)) + { + int startIndex = 24 + ((digit - 1) * 4); + return getDataBlock(0).getMessage().getInt(startIndex, startIndex + 3); + } + } + else if(digit <= 34) //Block 1 + { + if(hasDataBlock(1)) + { + int startIndex = 0 + ((digit - 19) * 4); + return getDataBlock(0).getMessage().getInt(startIndex, startIndex + 3); + } + } + + return -1; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSourceAddress()); + if(getTelephoneNumber() != null) + { + mIdentifiers.add(getTelephoneNumber()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/TDULinkControlMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/TDULinkControlMessage.java new file mode 100644 index 00000000..3361da52 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/TDULinkControlMessage.java @@ -0,0 +1,199 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tdu; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.edac.Golay24; +import io.github.dsheirer.edac.ReedSolomon_63_47_17; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.P25Message; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWordFactory; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.Collections; +import java.util.List; + +public class TDULinkControlMessage extends P25Message implements IFrequencyBandReceiver +{ + public static final int[] LC_HEX_0 = {0, 1, 2, 3, 4, 5}; + public static final int[] LC_HEX_1 = {6, 7, 8, 9, 10, 11}; + public static final int[] LC_HEX_2 = {24, 25, 26, 27, 28, 29}; + public static final int[] LC_HEX_3 = {30, 31, 32, 33, 34, 35}; + public static final int[] LC_HEX_4 = {48, 49, 50, 51, 52, 53}; + public static final int[] LC_HEX_5 = {54, 55, 56, 57, 58, 59}; + public static final int[] LC_HEX_6 = {72, 73, 74, 75, 76, 77}; + public static final int[] LC_HEX_7 = {78, 79, 80, 81, 82, 83}; + public static final int[] LC_HEX_8 = {96, 97, 98, 99, 100, 101}; + public static final int[] LC_HEX_9 = {102, 103, 104, 105, 106, 107}; + public static final int[] LC_HEX_10 = {120, 121, 122, 123, 124, 125}; + public static final int[] LC_HEX_11 = {126, 127, 128, 129, 130, 131}; + public static final int[] RS_HEX_0 = {144, 145, 146, 147, 148, 149}; + public static final int[] RS_HEX_1 = {150, 151, 152, 153, 154, 155}; + public static final int[] RS_HEX_2 = {168, 169, 170, 171, 172, 173}; + public static final int[] RS_HEX_3 = {174, 175, 176, 177, 178, 179}; + public static final int[] RS_HEX_4 = {192, 193, 194, 195, 196, 197}; + public static final int[] RS_HEX_5 = {198, 199, 200, 201, 202, 203}; + public static final int[] RS_HEX_6 = {216, 217, 218, 219, 220, 221}; + public static final int[] RS_HEX_7 = {222, 223, 224, 225, 226, 227}; + public static final int[] RS_HEX_8 = {240, 241, 242, 243, 244, 245}; + public static final int[] RS_HEX_9 = {246, 247, 248, 249, 250, 251}; + public static final int[] RS_HEX_10 = {264, 265, 266, 267, 268, 269}; + public static final int[] RS_HEX_11 = {270, 271, 272, 273, 274, 275}; + + private LinkControlWord mLinkControlWord; + + public TDULinkControlMessage(CorrectedBinaryMessage message, int nac, long timestamp) + { + super(message, nac, timestamp); + } + + @Override + public DataUnitID getDUID() + { + return DataUnitID.TERMINATOR_DATA_UNIT_LINK_CONTROL; + } + + public LinkControlWord getLinkControlWord() + { + if(mLinkControlWord == null) + { + createLinkControlWord(); + } + + return mLinkControlWord; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append(getMessageStub()); + sb.append(" ").append(getLinkControlWord()); + + return sb.toString(); + } + + /** + * Checks and repairs the 12 x 24-bit golay(24,12,8) codewords and then + * checks and repairs the 24 x 6-bit link control and reed-solomon + * RS(24,12,13) parity codewords + */ + private void createLinkControlWord() + { + //Check the Golay codewords + int codewordPointer = 0; + + while(codewordPointer < getMessage().size()) + { + int corrected = Golay24.checkAndCorrect(getMessage(), codewordPointer); + codewordPointer += 24; + } + + //Check the Reed-Solomon parity bits. The RS decoder expects the link control data and reed solomon parity hex + // codewords in reverse order. The RS(24,12,13) code used by P25 removes the left-hand 47 data hex words, so we + // replace them with zeros. */ + int[] input = new int[63]; + int[] output = new int[63]; + + input[0] = getMessage().getInt(RS_HEX_11); + input[1] = getMessage().getInt(RS_HEX_10); + input[2] = getMessage().getInt(RS_HEX_9); + input[3] = getMessage().getInt(RS_HEX_8); + input[4] = getMessage().getInt(RS_HEX_7); + input[5] = getMessage().getInt(RS_HEX_6); + input[6] = getMessage().getInt(RS_HEX_5); + input[7] = getMessage().getInt(RS_HEX_4); + input[8] = getMessage().getInt(RS_HEX_3); + input[9] = getMessage().getInt(RS_HEX_2); + input[10] = getMessage().getInt(RS_HEX_1); + input[11] = getMessage().getInt(RS_HEX_0); + input[12] = getMessage().getInt(LC_HEX_11); + input[13] = getMessage().getInt(LC_HEX_10); + input[14] = getMessage().getInt(LC_HEX_9); + input[15] = getMessage().getInt(LC_HEX_8); + input[16] = getMessage().getInt(LC_HEX_7); + input[17] = getMessage().getInt(LC_HEX_6); + input[18] = getMessage().getInt(LC_HEX_5); + input[19] = getMessage().getInt(LC_HEX_4); + input[20] = getMessage().getInt(LC_HEX_3); + input[21] = getMessage().getInt(LC_HEX_2); + input[22] = getMessage().getInt(LC_HEX_1); + input[23] = getMessage().getInt(LC_HEX_0); + /* indexes 24 - 62 are defaulted to zero */ + + //Reed-Solomon(24,12,13) code protects the link control word. Maximum correctable errors are: 6 + ReedSolomon_63_47_17 reedSolomon_63_47_17 = new ReedSolomon_63_47_17(6); + + boolean irrecoverableErrors = reedSolomon_63_47_17.decode(input, output); + + //Transfer error corrected output to a new binary message + BinaryMessage binaryMessage = new BinaryMessage(72); + + int pointer = 0; + + for(int x = 23; x >= 12; x--) + { + if(output[x] != -1) + { + binaryMessage.load(pointer, 6, output[x]); + } + + pointer += 6; + } + + mLinkControlWord = LinkControlWordFactory.create(binaryMessage); + + if(irrecoverableErrors) + { + mLinkControlWord.setValid(false); + } + + //If we corrected any bit errors, update the original message with the bit error count + for(int x = 0; x < 23; x++) + { + if(output[x] != input[x]) + { + getMessage().incrementCorrectedBitCount(Integer.bitCount((output[x] ^ input[x]))); + } + } + } + + @Override + public List getChannels() + { + if(getLinkControlWord().isValid() && getLinkControlWord() instanceof IFrequencyBandReceiver) + { + return ((IFrequencyBandReceiver)getLinkControlWord()).getChannels(); + } + + return Collections.EMPTY_LIST; + } + + @Override + public List getIdentifiers() + { + return getLinkControlWord().getIdentifiers(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/TDUMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/TDUMessage.java index 9bc63985..a0274c34 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/TDUMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/TDUMessage.java @@ -1,54 +1,52 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tdu; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRC; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.message.P25Message; import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import java.util.Collections; +import java.util.List; + public class TDUMessage extends P25Message { - public TDUMessage( BinaryMessage message, DataUnitID duid, - AliasList aliasList ) + public TDUMessage(CorrectedBinaryMessage message, int nac, long timestamp) { - super( message, duid, aliasList ); - - /* NID CRC is checked in the message framer, thus a constructed message - * means it passed the CRC */ - mCRC = new CRC[ 1 ]; - mCRC[ 0 ] = CRC.PASSED; + super(message, nac, timestamp); } - @Override - public String getMessage() + @Override + public DataUnitID getDUID() { - StringBuilder sb = new StringBuilder(); - - sb.append( "NAC:" ); - sb.append( getNAC() ); - sb.append( " " ); - sb.append( getDUID().getLabel() ); - - sb.append( " TERMINATOR DATA UNIT" ); - - return sb.toString(); + return DataUnitID.TERMINATOR_DATA_UNIT; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + return sb.toString(); + } + + public List getIdentifiers() + { + return Collections.EMPTY_LIST; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/AdjacentSiteStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/AdjacentSiteStatusBroadcast.java deleted file mode 100644 index 45aee039..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/AdjacentSiteStatusBroadcast.java +++ /dev/null @@ -1,170 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.message.IAdjacentSite; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class AdjacentSiteStatusBroadcast extends TDULinkControlMessage implements IdentifierReceiver, IAdjacentSite -{ - public static final int[] LRA = {72, 73, 74, 75, 88, 89, 90, 91}; - public static final int[] SYSTEM_ID = {96, 97, 98, 99, 112, 113, 114, 115, 116, 117, 118, 119}; - public static final int[] RFSS_ID = {120, 121, 122, 123, 136, 137, 138, 139}; - public static final int[] SITE_ID = {140, 141, 142, 143, 144, 145, 146, 147}; - public static final int[] IDENTIFIER = {160, 161, 162, 163}; - public static final int[] CHANNEL = {164, 165, 166, 167, 168, 169, 170, 171, 184, 185, 186, 187}; - public static final int[] SYSTEM_SERVICE_CLASS = {188, 189, 190, 191, 192, 193, 194, 195}; - - private IBandIdentifier mIdentifierUpdate; - - public AdjacentSiteStatusBroadcast(TDULinkControlMessage source) - { - super(source); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.ADJACENT_SITE_STATUS_BROADCAST.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" LRA:" + getLRA()); - - sb.append(" SYS:" + getSystemID()); - - sb.append(" SITE:" + getRFSS() + "-" + getSiteID()); - - sb.append(" CHAN:" + getChannel()); - - sb.append(" " + getSystemServiceClass()); - - return sb.toString(); - } - - public String getUniqueID() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getSystemID()); - sb.append(":"); - sb.append(getRFSS()); - sb.append(":"); - sb.append(getSiteID()); - - return sb.toString(); - } - - public String getLRA() - { - return mMessage.getHex(LRA, 2); - } - - public String getSystemID() - { - return mMessage.getHex(SYSTEM_ID, 3); - } - - public String getRFSS() - { - return mMessage.getHex(RFSS_ID, 2); - } - - public String getSiteID() - { - return mMessage.getHex(SITE_ID, 2); - } - - public int getIdentifier() - { - return mMessage.getInt(IDENTIFIER); - } - - public String getChannel() - { - return getIdentifier() + "-" + getChannelNumber(); - } - - @Override - public String getDownlinkChannel() - { - return getChannel(); - } - - @Override - public String getUplinkChannel() - { - return getChannel(); - } - - public int getChannelNumber() - { - return mMessage.getInt(CHANNEL); - } - - public String getSystemServiceClass() - { - return SystemService.toString(mMessage.getInt(SYSTEM_SERVICE_CLASS)); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - mIdentifierUpdate = message; - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[1]; - - identifiers[0] = getIdentifier(); - - return identifiers; - } - - public long getDownlinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getDownlinkFrequency(getChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getUplinkFrequency(getChannelNumber()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/AdjacentSiteStatusBroadcastExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/AdjacentSiteStatusBroadcastExplicit.java deleted file mode 100644 index 669d58d8..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/AdjacentSiteStatusBroadcastExplicit.java +++ /dev/null @@ -1,184 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.message.IAdjacentSite; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class AdjacentSiteStatusBroadcastExplicit extends TDULinkControlMessage implements IdentifierReceiver, IAdjacentSite -{ - public static final int[] LRA = {72, 73, 74, 75, 88, 89, 90, 91}; - public static final int[] TRANSMIT_IDENTIFIER = {92, 93, 94, 95}; - public static final int[] TRANSMIT_CHANNEL = {96, 97, 98, 99, 112, 113, 114, 115, - 116, 117, 118, 119}; - public static final int[] RFSS_ID = {120, 121, 122, 123, 136, 137, 138, 139}; - public static final int[] SITE_ID = {140, 141, 142, 143, 144, 145, 146, 147}; - - public static final int[] RECEIVE_IDENTIFIER = {160, 161, 162, 163}; - public static final int[] RECEIVE_CHANNEL = {164, 165, 166, 167, 168, 169, 170, - 171, 184, 185, 186, 187}; - - private IBandIdentifier mTransmitBand; - private IBandIdentifier mReceiveBand; - - public AdjacentSiteStatusBroadcastExplicit(TDULinkControlMessage source) - { - super(source); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" LRA:" + getLRA()); - - sb.append(" SITE:" + getRFSS() + "-" + getSiteID()); - - sb.append(" DNLINK:" + getDownlinkChannel()); - - sb.append(" UPLINK:" + getUplinkChannel()); - - return sb.toString(); - } - - public String getUniqueID() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getSystemID()); - sb.append(":"); - sb.append(getRFSS()); - sb.append(":"); - sb.append(getSiteID()); - - return sb.toString(); - } - - public String getLRA() - { - return mMessage.getHex(LRA, 2); - } - - public String getRFSS() - { - return mMessage.getHex(RFSS_ID, 2); - } - - public String getSystemID() - { - return "***"; - } - - public String getSiteID() - { - return mMessage.getHex(SITE_ID, 2); - } - - @Override - public String getSystemServiceClass() - { - return "[]"; - } - - public int getTransmitIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public int getTransmitChannel() - { - return mMessage.getInt(TRANSMIT_CHANNEL); - } - - public String getDownlinkChannel() - { - return getTransmitIdentifier() + "-" + getTransmitChannel(); - } - - public int getReceiveIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public int getReceiveChannel() - { - return mMessage.getInt(RECEIVE_CHANNEL); - } - - public String getUplinkChannel() - { - return getReceiveIdentifier() + "-" + getReceiveChannel(); - } - - public long getDownlinkFrequency() - { - if(mTransmitBand != null) - { - return mTransmitBand.getDownlinkFrequency(getTransmitChannel()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mReceiveBand != null) - { - return mReceiveBand.getUplinkFrequency(getReceiveChannel()); - } - - return 0; - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitIdentifier()) - { - mTransmitBand = message; - } - - if(identifier == getReceiveIdentifier()) - { - mReceiveBand = message; - } - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[2]; - - identifiers[0] = getTransmitIdentifier(); - identifiers[1] = getReceiveIdentifier(); - - return identifiers; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/CallAlert.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/CallAlert.java deleted file mode 100644 index 60d288e6..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/CallAlert.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class CallAlert extends TDULinkControlMessage -{ - public static final int[] TARGET_ADDRESS = { 112,113,114,115,116,117,118, - 119,120,121,122,123,136,137,138,139,140,141,142,143,144,145,146,147 }; - public static final int[] SOURCE_ADDRESS = { 160,161,162,163,164,165,166, - 167,168,169,170,171,184,185,186,187,188,189,190,190,192,193,194,195 }; - - public CallAlert( TDULinkControlMessage source ) - { - super( source ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.CALL_ALERT.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " SRC ADDR: " + getSourceAddress() ); - sb.append( " TGT ADDR: " + getTargetAddress() ); - - return sb.toString(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/CallTermination.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/CallTermination.java deleted file mode 100644 index 721af006..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/CallTermination.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class CallTermination extends TDULinkControlMessage -{ - public static final int[] SOURCE_ADDRESS = { 160,161,162,163,164,165,166, - 167,168,169,170,171,184,185,186,187,188,189,190,191,192,193,194,195 }; - - public CallTermination( TDULinkControlMessage source ) - { - super( source ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " BY:" + getSourceAddress() ); - - return sb.toString(); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/ChannelIdentifierUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/ChannelIdentifierUpdate.java deleted file mode 100644 index 647838d5..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/ChannelIdentifierUpdate.java +++ /dev/null @@ -1,117 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class ChannelIdentifierUpdate extends TDULinkControlMessage implements IBandIdentifier -{ - public static final int[] IDENTIFIER = {72, 73, 74, 75}; - public static final int[] BANDWIDTH = {88, 89, 90, 91, 92, 93, 94, 95, 96}; - public static final int[] TRANSMIT_OFFSET = {97, 98, 99, 112, 113, 114, 115, 116, - 117}; - public static final int[] CHANNEL_SPACING = {118, 119, 120, 121, 122, 123, 136, - 137, 138, 139}; - public static final int[] BASE_FREQUENCY = {140, 141, 142, 143, 144, 145, 146, - 147, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 184, 185, 186, - 187, 188, 189, 190, 191, 192, 193, 194, 195}; - - public ChannelIdentifierUpdate(TDULinkControlMessage source) - { - super(source); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.CHANNEL_IDENTIFIER_UPDATE.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" IDEN:" + getIdentifier()); - - sb.append(" BASE:" + getBaseFrequency()); - - sb.append(" BW:" + getBandwidth()); - - sb.append(" SPACING:" + getChannelSpacing()); - - sb.append(" OFFSET:" + getTransmitOffset()); - - return sb.toString(); - } - - @Override - public int getIdentifier() - { - return mMessage.getInt(IDENTIFIER); - } - - /** - * Channel bandwidth in hertz - */ - @Override - public int getBandwidth() - { - return mMessage.getInt(BANDWIDTH) * 125; - } - - @Override - public long getChannelSpacing() - { - return mMessage.getLong(CHANNEL_SPACING) * 125l; - } - - @Override - public long getBaseFrequency() - { - return mMessage.getLong(BASE_FREQUENCY) * 5l; - } - - @Override - public long getTransmitOffset() - { - return -1 * mMessage.getLong(TRANSMIT_OFFSET) * 250000l; - } - - @Override - public long getDownlinkFrequency(int channelNumber) - { - return getBaseFrequency() + (channelNumber * getChannelSpacing()); - } - - @Override - public long getUplinkFrequency(int channelNumber) - { - return getDownlinkFrequency(channelNumber) + getTransmitOffset(); - } - - @Override - public boolean isTDMA() - { - return false; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/ChannelIdentifierUpdateExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/ChannelIdentifierUpdateExplicit.java deleted file mode 100644 index 7858ec13..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/ChannelIdentifierUpdateExplicit.java +++ /dev/null @@ -1,114 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class ChannelIdentifierUpdateExplicit extends TDULinkControlMessage implements IBandIdentifier -{ - public static final int[] IDENTIFIER = {72, 73, 74, 75}; - public static final int[] BANDWIDTH = {88, 89, 90, 91}; - public static final int[] TRANSMIT_OFFSET = {92, 93, 94, 95, 96, 97, 98, 99, 112, 113, 114, 115, 116, 117}; - public static final int[] CHANNEL_SPACING = {118, 119, 120, 121, 122, 123, 136, 137, 138, 139}; - public static final int[] BASE_FREQUENCY = {140, 141, 142, 143, 144, 145, 146, 147, 160, 161, 162, 163, 164, 165, - 166, 167, 168, 169, 170, 171, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195}; - - public ChannelIdentifierUpdateExplicit(TDULinkControlMessage source) - { - super(source); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.CHANNEL_IDENTIFIER_UPDATE_EXPLICIT.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" IDEN:" + getIdentifier()); - - sb.append(" BASE:" + getBaseFrequency()); - - sb.append(" BW:" + getBandwidth()); - - sb.append(" SPACING:" + getChannelSpacing()); - - sb.append(" OFFSET:" + getTransmitOffset()); - - return sb.toString(); - } - - @Override - public int getIdentifier() - { - return mMessage.getInt(IDENTIFIER); - } - - /** - * Channel bandwidth in hertz - */ - @Override - public int getBandwidth() - { - return mMessage.getInt(BANDWIDTH) * 125; - } - - @Override - public long getChannelSpacing() - { - return mMessage.getLong(CHANNEL_SPACING) * 125l; - } - - @Override - public long getBaseFrequency() - { - return mMessage.getLong(BASE_FREQUENCY) * 5l; - } - - @Override - public long getTransmitOffset() - { - return -1 * mMessage.getLong(TRANSMIT_OFFSET) * 250000l; - } - - @Override - public long getDownlinkFrequency(int channelNumber) - { - return getBaseFrequency() + (channelNumber * getChannelSpacing()); - } - - @Override - public long getUplinkFrequency(int channelNumber) - { - return getDownlinkFrequency(channelNumber) + getTransmitOffset(); - } - - @Override - public boolean isTDMA() - { - return false; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/ExtendedFunctionCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/ExtendedFunctionCommand.java deleted file mode 100644 index 3d8dd601..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/ExtendedFunctionCommand.java +++ /dev/null @@ -1,85 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.reference.ExtendedFunction; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ExtendedFunctionCommand extends TDULinkControlMessage -{ - private final static Logger mLog = - LoggerFactory.getLogger( ExtendedFunctionCommand.class ); - public static final int[] EXTENDED_FUNCTION = { 72,73,74,75,88,89,90,91,92, - 93,94,95,96,97,98,99 }; - public static final int[] ARGUMENT = { 112,113,114,115,116,117,118,119,120, - 121,122,123,136,137,138,139,140,141,142,143,144,145,145,147 }; - public static final int[] TARGET_ADDRESS = { 160,161,162,163,164,165,166, - 167,168,169,170,171,184,185,186,187,188,189,190,190,192,193,194,195 }; - - public ExtendedFunctionCommand( TDULinkControlMessage source ) - { - super( source ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.EXTENDED_FUNCTION_COMMAND.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " EXTENDED FUNCTION:" + getExtendedFunction().getLabel() ); - - sb.append( " ARGUMENT: " + getTargetAddress() ); - - sb.append( " TGT ADDR: " + getTargetAddress() ); - - return sb.toString(); - } - - public ExtendedFunction getExtendedFunction() - { - return ExtendedFunction.fromValue( mMessage.getInt( EXTENDED_FUNCTION ) ); - } - - public String getArgument() - { - return mMessage.getHex( ARGUMENT, 6 ); - } - - public String getSourceAddress() - { - switch( getExtendedFunction() ) - { - case RADIO_CHECK: - case RADIO_CHECK_ACK: - case RADIO_DETACH: - case RADIO_DETACH_ACK: - case RADIO_INHIBIT: - case RADIO_INHIBIT_ACK: - case RADIO_UNINHIBIT: - case RADIO_UNINHIBIT_ACK: - return getArgument(); - default: - break; - } - - return null; - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - @Override - public String getToID() - { - return getTargetAddress(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/GroupAffiliationQuery.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/GroupAffiliationQuery.java deleted file mode 100644 index aafe1d3b..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/GroupAffiliationQuery.java +++ /dev/null @@ -1,50 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class GroupAffiliationQuery extends TDULinkControlMessage -{ - private final static Logger mLog = - LoggerFactory.getLogger( GroupAffiliationQuery.class ); - public static final int[] TARGET_ADDRESS = { 112,113,114,115,116,117,118, - 119,120,121,122,123,136,137,138,139,140,141,142,143,144,145,146,147 }; - public static final int[] SOURCE_ADDRESS = { 160,161,162,163,164,165,166, - 167,168,169,170,171,184,185,186,187,188,189,190,191,192,193,194,195 }; - - public GroupAffiliationQuery( TDULinkControlMessage source ) - { - super( source ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.GROUP_AFFILIATION_QUERY.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " FROM:" + getSourceAddress() ); - - sb.append( " TO:" + getTargetAddress() ); - - return sb.toString(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/GroupVoiceChannelUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/GroupVoiceChannelUpdate.java deleted file mode 100644 index c350c3da..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/GroupVoiceChannelUpdate.java +++ /dev/null @@ -1,205 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -import java.util.Set; -import java.util.TreeSet; - -public class GroupVoiceChannelUpdate extends TDULinkControlMessage implements IdentifierReceiver -{ - public static final int[] IDENTIFIER_A = {72, 73, 74, 75}; - public static final int[] CHANNEL_A = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99}; - public static final int[] GROUP_ADDRESS_A = {112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 136, 137, 138, 139}; - public static final int[] IDENTIFIER_B = {140, 141, 142, 143}; - public static final int[] CHANNEL_B = {144, 145, 146, 147, 160, 161, 162, 163, 164, 165, 166, 167}; - public static final int[] GROUP_ADDRESS_B = {168, 169, 170, 171, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195}; - - private IBandIdentifier mIdentifierUpdateA; - private IBandIdentifier mIdentifierUpdateB; - - public GroupVoiceChannelUpdate(TDULinkControlMessage source) - { - super(source); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.GROUP_VOICE_CHANNEL_UPDATE.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" CHAN A:" + getChannelA()); - - sb.append(" GRP A:" + getGroupAddressA()); - - if(hasChannelB()) - { - sb.append(" CHAN B:" + getChannelA()); - - sb.append(" GRP B:" + getGroupAddressA()); - } - - return sb.toString(); - } - - public int getChannelIdentifierA() - { - return mMessage.getInt(IDENTIFIER_A); - } - - public int getChannelNumberA() - { - return mMessage.getInt(CHANNEL_A); - } - - public String getChannelA() - { - return getChannelIdentifierA() + "-" + getChannelNumberA(); - } - - public int getChannelIdentifierB() - { - return mMessage.getInt(IDENTIFIER_B); - } - - public int getChannelNumberB() - { - return mMessage.getInt(CHANNEL_B); - } - - public String getChannelB() - { - return getChannelIdentifierB() + "-" + getChannelNumberB(); - } - - public boolean hasChannelB() - { - return getChannelNumberB() != 0; - } - - public String getGroupAddressA() - { - return mMessage.getHex(GROUP_ADDRESS_A, 4); - } - - public String getGroupAddressB() - { - return mMessage.getHex(GROUP_ADDRESS_B, 4); - } - - /** - * Returns a sorted list of addresses contained in the A and B fields - * - * @return - */ - public Set getAddresses() - { - Set addresses = new TreeSet(); - - addresses.add(getGroupAddressA()); - addresses.add(getGroupAddressB()); - - return addresses; - } - - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getChannelIdentifierA()) - { - mIdentifierUpdateA = message; - } - if(identifier == getChannelIdentifierB()) - { - mIdentifierUpdateB = message; - } - } - - @Override - public int[] getIdentifiers() - { - if(hasChannelB()) - { - int[] identifiers = new int[2]; - identifiers[0] = getChannelIdentifierA(); - identifiers[1] = getChannelIdentifierB(); - - return identifiers; - } - else - { - int[] identifiers = new int[1]; - identifiers[0] = getChannelIdentifierA(); - - return identifiers; - } - } - - public long getDownlinkFrequencyA() - { - if(mIdentifierUpdateA != null) - { - return mIdentifierUpdateA.getDownlinkFrequency(getChannelNumberA()); - } - - return 0; - } - - public long getUplinkFrequencyA() - { - if(mIdentifierUpdateA != null) - { - return mIdentifierUpdateA.getUplinkFrequency(getChannelNumberA()); - } - - return 0; - } - - public long getDownlinkFrequencyB() - { - if(mIdentifierUpdateB != null) - { - return mIdentifierUpdateB.getDownlinkFrequency(getChannelNumberB()); - } - - return 0; - } - - public long getUplinkFrequencyB() - { - if(mIdentifierUpdateB != null) - { - return mIdentifierUpdateB.getUplinkFrequency(getChannelNumberB()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/GroupVoiceChannelUpdateExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/GroupVoiceChannelUpdateExplicit.java deleted file mode 100644 index 55a6fb8d..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/GroupVoiceChannelUpdateExplicit.java +++ /dev/null @@ -1,176 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class GroupVoiceChannelUpdateExplicit extends TDULinkControlMessage - implements IdentifierReceiver -{ - private final static Logger mLog = - LoggerFactory.getLogger(GroupVoiceChannelUpdateExplicit.class); - - public static final int EMERGENCY_FLAG = 92; - public static final int ENCRYPTED_CHANNEL_FLAG = 93; - public static final int DUPLEX_MODE = 94; - public static final int SESSION_MODE = 95; - public static final int[] GROUP_ADDRESS = {112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 136, 137, 138, 139}; - public static final int[] TRANSMIT_IDENTIFIER = {140, 141, 142, 143}; - public static final int[] TRANSMIT_CHANNEL = {144, 145, 146, 147, 160, 161, 162, 163, 164, 165, 166, 167}; - public static final int[] RECEIVE_IDENTIFIER = {168, 169, 170, 171}; - public static final int[] RECEIVE_CHANNEL = {184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199}; - - private IBandIdentifier mTransmitIdentifierUpdate; - private IBandIdentifier mReceiveIdentifierUpdate; - - public GroupVoiceChannelUpdateExplicit(TDULinkControlMessage source) - { - super(source); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - if(isEmergency()) - { - sb.append(" EMERGENCY"); - } - if(isEncryptedChannel()) - { - sb.append(" ENCRYPTED CHANNEL"); - } - - - return sb.toString(); - } - - public boolean isEmergency() - { - return mMessage.get(EMERGENCY_FLAG); - } - - public boolean isEncryptedChannel() - { - return mMessage.get(ENCRYPTED_CHANNEL_FLAG); - } - - public DuplexMode getDuplexMode() - { - return mMessage.get(DUPLEX_MODE) ? DuplexMode.FULL : DuplexMode.HALF; - } - - public SessionMode getSessionMode() - { - return mMessage.get(SESSION_MODE) ? - SessionMode.CIRCUIT : SessionMode.PACKET; - } - - public int getTransmitChannelIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public int getTransmitChannelNumber() - { - return mMessage.getInt(TRANSMIT_CHANNEL); - } - - public String getTransmitChannel() - { - return getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber(); - } - - public int getReceiveChannelIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public int getReceiveChannelNumber() - { - return mMessage.getInt(RECEIVE_CHANNEL); - } - - public String getReceiveChannel() - { - return getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber(); - } - - public String getGroupAddress() - { - return mMessage.getHex(GROUP_ADDRESS, 4); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitChannelIdentifier()) - { - mTransmitIdentifierUpdate = message; - } - if(identifier == getReceiveChannelIdentifier()) - { - mReceiveIdentifierUpdate = message; - } - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[2]; - - identifiers[0] = getTransmitChannelIdentifier(); - identifiers[1] = getReceiveChannelIdentifier(); - - return identifiers; - } - - public long getDownlinkFrequency() - { - if(mTransmitIdentifierUpdate != null) - { - return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mReceiveIdentifierUpdate != null) - { - return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/GroupVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/GroupVoiceChannelUser.java deleted file mode 100644 index daa3ab0a..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/GroupVoiceChannelUser.java +++ /dev/null @@ -1,85 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class GroupVoiceChannelUser extends TDULinkControlMessage -{ - private final static Logger mLog = - LoggerFactory.getLogger( GroupVoiceChannelUser.class ); - /* Service Options */ - public static final int EMERGENCY_FLAG = 92; - public static final int ENCRYPTED_CHANNEL_FLAG = 93; - public static final int DUPLEX_MODE = 94; - public static final int SESSION_MODE = 95; - public static final int[] GROUP_ADDRESS = { 120,121,122,123,136,137,138,139, - 140,141,142,143,144,145,146,147 }; - public static final int[] SOURCE_ADDRESS = { 160,161,162,163,164,165,166, - 167,168,169,170,171,184,185,186,187,188,189,190,191,192,193,194,195 }; - - public GroupVoiceChannelUser( TDULinkControlMessage source ) - { - super( source ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.GROUP_VOICE_CHANNEL_USER.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - if( isEmergency() ) - { - sb.append( " EMERGENCY" ); - } - if( isEncryptedChannel() ) - { - sb.append( " ENCRYPTED CHANNEL" ); - } - - sb.append( " FROM:" + getSourceAddress() ); - - sb.append( " TO:" + getGroupAddress() ); - - return sb.toString(); - } - - public boolean isEmergency() - { - return mMessage.get( EMERGENCY_FLAG ); - } - - public boolean isEncryptedChannel() - { - return mMessage.get( ENCRYPTED_CHANNEL_FLAG ); - } - - public DuplexMode getDuplexMode() - { - return mMessage.get( DUPLEX_MODE ) ? DuplexMode.FULL : DuplexMode.HALF; - } - - public SessionMode getSessionMode() - { - return mMessage.get( SESSION_MODE ) ? - SessionMode.CIRCUIT : SessionMode.PACKET; - } - - public String getGroupAddress() - { - return mMessage.getHex( GROUP_ADDRESS, 4 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/MessageUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/MessageUpdate.java deleted file mode 100644 index 39bc6607..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/MessageUpdate.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MessageUpdate extends TDULinkControlMessage -{ - private final static Logger mLog = - LoggerFactory.getLogger( MessageUpdate.class ); - public static final int[] SHORT_DATA_MESSAGE = { 72,73,74,75,88,89,90,91,92, - 93,94,95,96,97,98,99 }; - public static final int[] TARGET_ADDRESS = { 112,113,114,115,116,117,118, - 119,120,121,122,123,136,137,138,139,140,141,142,143,144,145,146,147 }; - public static final int[] SOURCE_ADDRESS = { 160,161,162,163,164,165,166, - 167,168,169,170,171,184,185,186,187,188,189,190,190,192,193,194,195 }; - - public MessageUpdate( TDULinkControlMessage source ) - { - super( source ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.MESSAGE_UPDATE.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " MSG:" + getShortDataMessage() ); - sb.append( " SRC ADDR: " + getSourceAddress() ); - sb.append( " TGT ADDR: " + getTargetAddress() ); - - return sb.toString(); - } - - public String getShortDataMessage() - { - return mMessage.getHex( SHORT_DATA_MESSAGE, 4 ); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/NetworkStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/NetworkStatusBroadcast.java deleted file mode 100644 index 1d7cf390..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/NetworkStatusBroadcast.java +++ /dev/null @@ -1,128 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import io.github.dsheirer.module.decode.p25.reference.Service; - -public class NetworkStatusBroadcast extends TDULinkControlMessage implements IdentifierReceiver -{ - public static final int[] WACN = {72, 73, 74, 75, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 112, 113, 114, - 115, 116, 117, 118, 119, 120, 121, 122, 123}; - public static final int[] SYSTEM = {136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147}; - public static final int[] IDENTIFIER = {160, 161, 162, 163}; - public static final int[] CHANNEL = {164, 165, 166, 167, 168, 169, 170, 171, 184, 185, 186, 187}; - public static final int[] SYSTEM_SERVICE_CLASS = {188, 189, 190, 191, 192, 193, 194, 195}; - - private IBandIdentifier mIdentifierUpdate; - - public NetworkStatusBroadcast(TDULinkControlMessage source) - { - super(source); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.NETWORK_STATUS_BROADCAST.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" NETWORK:" + getWACN()); - sb.append(" SYS:" + getSystem()); - sb.append(" CHAN:" + getChannel()); - sb.append(" " + Service.getServices(getSystemServiceClass()).toString()); - - return sb.toString(); - } - - public String getWACN() - { - return mMessage.getHex(WACN, 5); - } - - public String getSystem() - { - return mMessage.getHex(SYSTEM, 3); - } - - public int getIdentifier() - { - return mMessage.getInt(IDENTIFIER); - } - - public int getChannelNumber() - { - return mMessage.getInt(CHANNEL); - } - - public String getChannel() - { - return getIdentifier() + "-" + getChannelNumber(); - } - - public int getSystemServiceClass() - { - return mMessage.getInt(SYSTEM_SERVICE_CLASS); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - mIdentifierUpdate = message; - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[1]; - - identifiers[0] = getIdentifier(); - - return identifiers; - } - - public long getDownlinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getDownlinkFrequency(getChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getUplinkFrequency(getChannelNumber()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/NetworkStatusBroadcastExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/NetworkStatusBroadcastExplicit.java deleted file mode 100644 index 16b51973..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/NetworkStatusBroadcastExplicit.java +++ /dev/null @@ -1,146 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class NetworkStatusBroadcastExplicit extends TDULinkControlMessage implements IdentifierReceiver -{ - public static final int[] WACN = {72, 73, 74, 75, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 112, 113, 114, 115}; - public static final int[] SYSTEM = {116, 117, 118, 119, 120, 121, 122, 123, 136, 137, 138, 139}; - public static final int[] TRANSMIT_IDENTIFIER = {140, 141, 142, 143}; - public static final int[] TRANSMIT_CHANNEL = {144, 145, 146, 147, 160, 161, 162, 163, 164, 165, 166, 167}; - public static final int[] RECEIVE_IDENTIFIER = {168, 169, 170, 171}; - public static final int[] RECEIVE_CHANNEL = {184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195}; - - private IBandIdentifier mTransmitIdentifierUpdate; - private IBandIdentifier mReceiveIdentifierUpdate; - - public NetworkStatusBroadcastExplicit(TDULinkControlMessage source) - { - super(source); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.NETWORK_STATUS_BROADCAST_EXPLICIT.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" NETWORK:" + getWACN()); - sb.append(" SYS:" + getSystem()); - sb.append(" TRANSMIT:" + getTransmitChannel()); - sb.append(" RECEIVE:" + getReceiveChannel()); - - return sb.toString(); - } - - public String getWACN() - { - return mMessage.getHex(WACN, 5); - } - - public String getSystem() - { - return mMessage.getHex(SYSTEM, 3); - } - - public int getTransmitIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public int getTransmitChannelNumber() - { - return mMessage.getInt(TRANSMIT_CHANNEL); - } - - public String getTransmitChannel() - { - return getTransmitIdentifier() + "-" + getTransmitChannelNumber(); - } - - public int getReceiveIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public int getReceiveChannelNumber() - { - return mMessage.getInt(RECEIVE_CHANNEL); - } - - public String getReceiveChannel() - { - return getReceiveIdentifier() + "-" + getReceiveChannelNumber(); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitIdentifier()) - { - mTransmitIdentifierUpdate = message; - } - if(identifier == getReceiveIdentifier()) - { - mReceiveIdentifierUpdate = message; - } - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[2]; - - identifiers[0] = getTransmitIdentifier(); - identifiers[1] = getReceiveIdentifier(); - - return identifiers; - } - - public long getDownlinkFrequency() - { - if(mTransmitIdentifierUpdate != null) - { - return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mReceiveIdentifierUpdate != null) - { - return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/ProtectionParameterBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/ProtectionParameterBroadcast.java deleted file mode 100644 index 2343bf2b..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/ProtectionParameterBroadcast.java +++ /dev/null @@ -1,57 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.reference.Encryption; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ProtectionParameterBroadcast extends TDULinkControlMessage -{ - private final static Logger mLog = - LoggerFactory.getLogger( ProtectionParameterBroadcast.class ); - - public final static int[] ALGORITHM_ID = { 112,113,114,115,116,117,118,119 }; - public final static int[] KEY_ID = { 120,121,122,123,136,137,138,139,140, - 141,142,143,144,145,146,147 }; - public static final int[] TARGET_ADDRESS = { 160,161,162,163,164,165,166, - 167,168,169,170,171,184,185,186,187,188,189,190,190,192,193,194,195 }; - - public ProtectionParameterBroadcast( TDULinkControlMessage source ) - { - super( source ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.PROTECTION_PARAMETER_BROADCAST.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " ENCRYPTION:" + getEncryption().name() ); - sb.append( " KEY:" + getEncryptionKey() ); - sb.append( " ADDRESSS:" + getTargetAddress() ); - - return sb.toString(); - } - - public Encryption getEncryption() - { - return Encryption.fromValue( mMessage.getInt( ALGORITHM_ID ) ); - } - - public int getEncryptionKey() - { - return mMessage.getInt( KEY_ID ); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/RFSSStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/RFSSStatusBroadcast.java deleted file mode 100644 index 8e556b7d..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/RFSSStatusBroadcast.java +++ /dev/null @@ -1,146 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class RFSSStatusBroadcast extends TDULinkControlMessage implements IdentifierReceiver -{ - public static final int[] LRA = {72, 73, 74, 75, 88, 89, 90, 91}; - public static final int[] SYSTEM_ID = {96, 97, 98, 99, 112, 113, 114, 115, 116, 117, 118, 119}; - public static final int[] RFSS_ID = {120, 121, 122, 123, 136, 137, 138, 139}; - public static final int[] SITE_ID = {140, 141, 142, 143, 144, 145, 146, 147}; - public static final int[] IDENTIFIER = {160, 161, 162, 163}; - public static final int[] CHANNEL = {164, 165, 166, 167, 168, 169, 170, 171, 184, 185, 186, 187}; - public static final int[] SYSTEM_SERVICE_CLASS = {188, 189, 190, 191, 192, 193, 194, 195}; - - private IBandIdentifier mIdentifierUpdate; - - public RFSSStatusBroadcast(TDULinkControlMessage source) - { - super(source); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.RFSS_STATUS_BROADCAST.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" LRA:" + getLocationRegistrationArea()); - - sb.append(" SYS:" + getSystem()); - - sb.append(" SITE:" + getRFSubsystemID() + "-" + getSiteID()); - - sb.append(" CHAN:" + getChannelNumber()); - - sb.append(" " + SystemService.toString(getSystemServiceClass())); - - sb.append(" " + mMessage.toString()); - - return sb.toString(); - } - - public String getLocationRegistrationArea() - { - return mMessage.getHex(LRA, 2); - } - - public String getSystem() - { - return mMessage.getHex(SYSTEM_ID, 3); - } - - public String getRFSubsystemID() - { - return mMessage.getHex(RFSS_ID, 2); - } - - public String getSiteID() - { - return mMessage.getHex(SITE_ID, 2); - } - - public int getIdentifier() - { - return mMessage.getInt(IDENTIFIER); - } - - public String getChannel() - { - return getIdentifier() + "-" + getChannelNumber(); - } - - public int getChannelNumber() - { - return mMessage.getInt(CHANNEL); - } - - public int getSystemServiceClass() - { - return mMessage.getInt(SYSTEM_SERVICE_CLASS); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - mIdentifierUpdate = message; - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[1]; - - identifiers[0] = getIdentifier(); - - return identifiers; - } - - public long getDownlinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getDownlinkFrequency(getChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getUplinkFrequency(getChannelNumber()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/RFSSStatusBroadcastExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/RFSSStatusBroadcastExplicit.java deleted file mode 100644 index 40fcdf77..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/RFSSStatusBroadcastExplicit.java +++ /dev/null @@ -1,171 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class RFSSStatusBroadcastExplicit extends TDULinkControlMessage implements IdentifierReceiver -{ - private final static Logger mLog = LoggerFactory.getLogger(RFSSStatusBroadcastExplicit.class); - - public static final int[] LRA = {72, 73, 74, 75, 88, 89, 90, 91}; - public static final int[] RECEIVE_IDENTIFIER = {92, 93, 94, 95}; - public static final int[] RECEIVE_CHANNEL = {96, 97, 98, 99, 112, 113, 114, 115, 116, 117, 118, 119}; - public static final int[] RFSS_ID = {120, 121, 122, 123, 136, 137, 138, 139}; - public static final int[] SITE_ID = {140, 141, 142, 143, 144, 145, 146, 147}; - public static final int[] TRANSMIT_IDENTIFIER = {160, 161, 162, 163}; - public static final int[] TRANSMIT_CHANNEL = {164, 165, 166, 167, 168, 169, 170, 171, 184, 185, 186, 187}; - public static final int[] SYSTEM_SERVICE_CLASS = {188, 189, 190, 191, 192, 193, 194, 195}; - - private IBandIdentifier mTransmitIdentifierUpdate; - private IBandIdentifier mReceiveIdentifierUpdate; - - public RFSSStatusBroadcastExplicit(TDULinkControlMessage source) - { - super(source); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.RFSS_STATUS_BROADCAST_EXPLICIT.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" LRA:" + getLocationRegistrationArea()); - - sb.append(" SITE:" + getRFSubsystemID() + "-" + getSiteID()); - - sb.append(" TRANSMIT:" + getTransmitChannelNumber()); - - sb.append(" RECEIVE:" + getReceiveChannelNumber()); - - sb.append(" " + SystemService.toString(getSystemServiceClass())); - - sb.append(" " + mMessage.toString()); - - return sb.toString(); - } - - public String getLocationRegistrationArea() - { - return mMessage.getHex(LRA, 2); - } - - public String getRFSubsystemID() - { - return mMessage.getHex(RFSS_ID, 2); - } - - public String getSiteID() - { - return mMessage.getHex(SITE_ID, 2); - } - - public int getTransmitIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public String getTransmitChannel() - { - return getTransmitIdentifier() + "-" + getTransmitChannelNumber(); - } - - public int getTransmitChannelNumber() - { - return mMessage.getInt(TRANSMIT_CHANNEL); - } - - public int getReceiveIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public String getReceiveChannel() - { - return getReceiveIdentifier() + "-" + getReceiveChannelNumber(); - } - - public int getReceiveChannelNumber() - { - return mMessage.getInt(RECEIVE_CHANNEL); - } - - public int getSystemServiceClass() - { - return mMessage.getInt(SYSTEM_SERVICE_CLASS); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitIdentifier()) - { - mTransmitIdentifierUpdate = message; - } - - if(identifier == getReceiveIdentifier()) - { - mReceiveIdentifierUpdate = message; - } - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[2]; - - identifiers[0] = getTransmitIdentifier(); - identifiers[1] = getReceiveIdentifier(); - - return identifiers; - } - - public long getDownlinkFrequency() - { - if(mTransmitIdentifierUpdate != null) - { - return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mReceiveIdentifierUpdate != null) - { - return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/SecondaryControlChannelBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/SecondaryControlChannelBroadcast.java deleted file mode 100644 index 988eb294..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/SecondaryControlChannelBroadcast.java +++ /dev/null @@ -1,184 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class SecondaryControlChannelBroadcast extends TDULinkControlMessage implements IdentifierReceiver -{ - public static final int[] RFSS_ID = {72, 73, 74, 75, 88, 89, 90, 91}; - public static final int[] SITE_ID = {92, 93, 94, 95, 96, 97, 98, 99}; - public static final int[] IDENTIFIER_A = {112, 113, 114, 115}; - public static final int[] CHANNEL_A = {116, 117, 118, 119, 120, 121, 122, 123, 136, 137, 138, 139}; - public static final int[] SYSTEM_SERVICE_CLASS_A = {140, 141, 142, 143, 144, 145, 146, 147}; - public static final int[] IDENTIFIER_B = {160, 161, 162, 163}; - public static final int[] CHANNEL_B = {164, 165, 166, 167, 168, 169, 170, 171, 184, 185, 186, 187}; - public static final int[] SYSTEM_SERVICE_CLASS_B = {188, 189, 190, 191, 192, 193, 194, 195}; - - private IBandIdentifier mIdentifierProviderA; - private IBandIdentifier mIdentifierProviderB; - - public SecondaryControlChannelBroadcast(TDULinkControlMessage source) - { - super(source); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.SECONDARY_CONTROL_CHANNEL_BROADCAST.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" SITE:" + getRFSubsystemID() + "-" + getSiteID()); - - sb.append(" CHAN A:" + getChannelA()); - - sb.append(" " + SystemService.toString(getSystemServiceClassA())); - - sb.append(" CHAN B:" + getChannelB()); - - sb.append(" " + SystemService.toString(getSystemServiceClassB())); - - return sb.toString(); - } - - public String getRFSubsystemID() - { - return mMessage.getHex(RFSS_ID, 2); - } - - public String getSiteID() - { - return mMessage.getHex(SITE_ID, 2); - } - - public int getIdentifierA() - { - return mMessage.getInt(IDENTIFIER_A); - } - - public int getChannelA() - { - return mMessage.getInt(CHANNEL_A); - } - - public String getChannelNumberA() - { - return getIdentifierA() + "-" + getChannelA(); - } - - public int getIdentifierB() - { - return mMessage.getInt(IDENTIFIER_B); - } - - public int getChannelB() - { - return mMessage.getInt(CHANNEL_B); - } - - public String getChannelNumberB() - { - return getIdentifierB() + "-" + getChannelB(); - } - - public int getSystemServiceClassA() - { - return mMessage.getInt(SYSTEM_SERVICE_CLASS_A); - } - - public int getSystemServiceClassB() - { - return mMessage.getInt(SYSTEM_SERVICE_CLASS_B); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getIdentifierA()) - { - mIdentifierProviderA = message; - } - if(identifier == getIdentifierB()) - { - mIdentifierProviderB = message; - } - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[2]; - - identifiers[0] = getIdentifierA(); - identifiers[1] = getIdentifierB(); - - return identifiers; - } - - public long getDownlinkFrequencyA() - { - if(mIdentifierProviderA != null) - { - return mIdentifierProviderA.getDownlinkFrequency(getChannelA()); - } - - return 0; - } - - public long getUplinkFrequencyA() - { - if(mIdentifierProviderA != null) - { - return mIdentifierProviderA.getUplinkFrequency(getChannelA()); - } - - return 0; - } - - public long getDownlinkFrequencyB() - { - if(mIdentifierProviderB != null) - { - return mIdentifierProviderB.getDownlinkFrequency(getChannelB()); - } - - return 0; - } - - public long getUplinkFrequencyB() - { - if(mIdentifierProviderB != null) - { - return mIdentifierProviderB.getUplinkFrequency(getChannelB()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/SecondaryControlChannelBroadcastExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/SecondaryControlChannelBroadcastExplicit.java deleted file mode 100644 index 705880bf..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/SecondaryControlChannelBroadcastExplicit.java +++ /dev/null @@ -1,156 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemService; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; - -public class SecondaryControlChannelBroadcastExplicit extends TDULinkControlMessage implements IdentifierReceiver -{ - public static final int[] RFSS_ID = {72, 73, 74, 75, 88, 89, 90, 91}; - public static final int[] SITE_ID = {92, 93, 94, 95, 96, 97, 98, 99}; - public static final int[] TRANSMIT_IDENTIFIER = {112, 113, 114, 115}; - public static final int[] TRANSMIT_CHANNEL = {116, 117, 118, 119, 120, 121, 122, 123, 136, 137, 138, 139}; - public static final int[] RECEIVE_IDENTIFIER = {140, 141, 142, 143}; - public static final int[] RECEIVE_CHANNEL = {144, 145, 146, 147, 160, 161, 162, 163, 164, 165, 166, 167}; - public static final int[] SYSTEM_SERVICE_CLASS = {168, 169, 170, 171, 184, 185, 186, 187}; - - private IBandIdentifier mTransmitIdentifierProvider; - private IBandIdentifier mReceiveIdentifierProvider; - - public SecondaryControlChannelBroadcastExplicit(TDULinkControlMessage source) - { - super(source); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" SITE:" + getRFSubsystemID() + "-" + getSiteID()); - - sb.append(" TRANSMIT:" + getTransmitChannel()); - - sb.append(" RECEIVE:" + getReceiveChannel()); - - sb.append(" " + SystemService.toString(getSystemServiceClass())); - - return sb.toString(); - } - - public String getRFSubsystemID() - { - return mMessage.getHex(RFSS_ID, 2); - } - - public String getSiteID() - { - return mMessage.getHex(SITE_ID, 2); - } - - public int getTransmitIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public int getTransmitChannelNumber() - { - return mMessage.getInt(TRANSMIT_CHANNEL); - } - - public String getTransmitChannel() - { - return getTransmitIdentifier() + "-" + getTransmitChannelNumber(); - } - - public int getReceiveIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public int getReceiveChannelNumber() - { - return mMessage.getInt(RECEIVE_CHANNEL); - } - - public String getReceiveChannel() - { - return getReceiveIdentifier() + "-" + getReceiveChannelNumber(); - } - - public int getSystemServiceClass() - { - return mMessage.getInt(SYSTEM_SERVICE_CLASS); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitIdentifier()) - { - mTransmitIdentifierProvider = message; - } - if(identifier == getReceiveIdentifier()) - { - mReceiveIdentifierProvider = message; - } - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[2]; - - identifiers[0] = getTransmitIdentifier(); - identifiers[1] = getReceiveIdentifier(); - - return identifiers; - } - - public long getDownlinkFrequency() - { - if(mTransmitIdentifierProvider != null) - { - return mTransmitIdentifierProvider.getDownlinkFrequency(getTransmitChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mReceiveIdentifierProvider != null) - { - return mReceiveIdentifierProvider.getUplinkFrequency(getReceiveChannelNumber()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/StatusQuery.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/StatusQuery.java deleted file mode 100644 index 0c4acefd..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/StatusQuery.java +++ /dev/null @@ -1,50 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class StatusQuery extends TDULinkControlMessage -{ - private final static Logger mLog = - LoggerFactory.getLogger( StatusQuery.class ); - public static final int[] TARGET_ADDRESS = { 112,113,114,115,116,117,118, - 119,120,121,122,123,136,137,138,139,140,141,142,143,144,145,146,147 }; - public static final int[] SOURCE_ADDRESS = { 160,161,162,163,164,165,166, - 167,168,169,170,171,184,185,186,187,188,189,190,191,192,193,194,195 }; - - public StatusQuery( TDULinkControlMessage source ) - { - super( source ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.STATUS_QUERY.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " FROM:" + getSourceAddress() ); - - sb.append( " TO:" + getTargetAddress() ); - - return sb.toString(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/StatusUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/StatusUpdate.java deleted file mode 100644 index 239aa5de..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/StatusUpdate.java +++ /dev/null @@ -1,63 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class StatusUpdate extends TDULinkControlMessage -{ - private final static Logger mLog = - LoggerFactory.getLogger( StatusUpdate.class ); - public static final int[] USER_STATUS = { 80,81,82,83,84,85,86,87 }; - public static final int[] UNIT_STATUS = { 88,89,90,91,92,93,94,95 }; - public static final int[] TARGET_ADDRESS = { 112,113,114,115,116,117,118, - 119,120,121,122,123,136,137,138,139,140,141,142,143,144,145,146,147 }; - public static final int[] SOURCE_ADDRESS = { 160,161,162,163,164,165,166, - 167,168,169,170,171,184,185,186,187,188,189,190,191,192,193,194,195 }; - - public StatusUpdate( TDULinkControlMessage source ) - { - super( source ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.STATUS_UPDATE.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " STATUS USER:" + getUserStatus() ); - sb.append( " UNIT:" + getUnitStatus() ); - sb.append( " FROM:" + getSourceAddress() ); - sb.append( " TO:" + getTargetAddress() ); - - return sb.toString(); - } - - public String getUserStatus() - { - return mMessage.getHex( USER_STATUS, 2 ); - } - - public String getUnitStatus() - { - return mMessage.getHex( UNIT_STATUS, 2 ); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/SystemServiceBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/SystemServiceBroadcast.java deleted file mode 100644 index 31d781a3..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/SystemServiceBroadcast.java +++ /dev/null @@ -1,62 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import io.github.dsheirer.module.decode.p25.reference.Service; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; - -public class SystemServiceBroadcast extends TDULinkControlMessage -{ - private final static Logger mLog = - LoggerFactory.getLogger( SystemServiceBroadcast.class ); - public static final int[] REQUEST_PRIORITY_LEVEL = { 96,97,98,99 }; - public static final int[] AVAILABLE_SERVICES = { 112,113,114,115,116,117,118, - 119,120,121,122,123,136,137,138,139,140,141,142,143,144,145,146,147 }; - public static final int[] SUPPORTED_SERVICES = { 160,161,162,163,164,165, - 166,167,168,169,170,171,184,185,186,187,188,189,190,191,192,193,194,195 }; - - public SystemServiceBroadcast( TDULinkControlMessage source ) - { - super( source ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.SYSTEM_SERVICE_BROADCAST.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " SERVICES AVAILABLE " ); - - sb.append( getAvailableServices() ); - - sb.append( " SUPPORTED " ); - - sb.append( getSupportedServices() ); - - return sb.toString(); - } - - public List getAvailableServices() - { - long bitmap = mMessage.getLong( AVAILABLE_SERVICES ); - - return Service.getServices( bitmap ); - } - - public List getSupportedServices() - { - long bitmap = mMessage.getLong( SUPPORTED_SERVICES ); - - return Service.getServices( bitmap ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/TDULCMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/TDULCMessageFactory.java deleted file mode 100644 index 8e40d0a1..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/TDULCMessageFactory.java +++ /dev/null @@ -1,95 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.reference.Vendor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - -public class TDULCMessageFactory -{ - private final static Logger mLog = - LoggerFactory.getLogger( TDULCMessageFactory.class ); - - /** - * Constructs a new sub-class of the TDULC message if the opcode is recognized - * or returns the original message. - */ - public static TDULinkControlMessage getMessage( TDULinkControlMessage message ) - { - if( message.isImplicitFormat() || message.getVendor() == Vendor.STANDARD ) - { - switch( message.getOpcode() ) - { - case ADJACENT_SITE_STATUS_BROADCAST: - return new AdjacentSiteStatusBroadcast( message ); - case ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT: - return new AdjacentSiteStatusBroadcastExplicit( message ); - case CALL_ALERT: - return new CallAlert( message ); - case CALL_TERMINATION_OR_CANCELLATION: - return new CallTermination( message ); - case CHANNEL_IDENTIFIER_UPDATE: - return new ChannelIdentifierUpdate( message ); - case CHANNEL_IDENTIFIER_UPDATE_EXPLICIT: - return new ChannelIdentifierUpdateExplicit( message ); - case EXTENDED_FUNCTION_COMMAND: - return new ExtendedFunctionCommand( message ); - case GROUP_AFFILIATION_QUERY: - return new GroupAffiliationQuery( message ); - case GROUP_VOICE_CHANNEL_UPDATE: - return new GroupVoiceChannelUpdate( message ); - case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT: - return new GroupVoiceChannelUpdateExplicit( message ); - case GROUP_VOICE_CHANNEL_USER: - return new GroupVoiceChannelUser( message ); - case MESSAGE_UPDATE: - return new MessageUpdate( message ); - case NETWORK_STATUS_BROADCAST: - return new NetworkStatusBroadcast( message ); - case NETWORK_STATUS_BROADCAST_EXPLICIT: - return new NetworkStatusBroadcastExplicit( message ); - case PROTECTION_PARAMETER_BROADCAST: - return new ProtectionParameterBroadcast( message ); - case RFSS_STATUS_BROADCAST: - return new RFSSStatusBroadcast( message ); - case RFSS_STATUS_BROADCAST_EXPLICIT: - return new RFSSStatusBroadcastExplicit( message ); - case SECONDARY_CONTROL_CHANNEL_BROADCAST: - return new SecondaryControlChannelBroadcast( message ); - case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: - return new SecondaryControlChannelBroadcastExplicit( message ); - case STATUS_QUERY: - return new StatusQuery( message ); - case STATUS_UPDATE: - return new StatusUpdate( message ); - case SYSTEM_SERVICE_BROADCAST: - return new SystemServiceBroadcast( message ); - case TELEPHONE_INTERCONNECT_ANSWER_REQUEST: - return new TelephoneInterconnectAnswerRequest( message ); - case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER: - return new TelephoneInterconnectVoiceChannelUser( message ); - case UNIT_AUTHENTICATION_COMMAND: - return new UnitAuthenticationCommand( message ); - case UNIT_REGISTRATION_COMMAND: - return new UnitRegistrationCommand( message ); - case UNIT_TO_UNIT_ANSWER_REQUEST: - return new UnitToUnitAnswerRequest( message ); - case UNIT_TO_UNIT_VOICE_CHANNEL_USER: - return new UnitToUnitVoiceChannelUser( message ); - default: - break; - } - } - else - { - switch( message.getVendor() ) - { - case MOTOROLA: - default: - break; - } - } - - return message; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/TDULinkControlMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/TDULinkControlMessage.java deleted file mode 100644 index 1429e2d6..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/TDULinkControlMessage.java +++ /dev/null @@ -1,282 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRC; -import io.github.dsheirer.edac.Golay24; -import io.github.dsheirer.edac.ReedSolomon_63_47_17; -import io.github.dsheirer.module.decode.p25.message.P25Message; -import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.MotorolaOpcode; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import io.github.dsheirer.module.decode.p25.reference.Vendor; -import io.github.dsheirer.module.decode.p25.reference.VendorLinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class TDULinkControlMessage extends P25Message -{ - public final static Logger mLog = LoggerFactory.getLogger( TDULinkControlMessage.class ); - - public static final int[] LC_HEX_0 = { 64,65,66,67,68,69 }; - public static final int[] LC_HEX_1 = { 70,71,72,73,74,75 }; - public static final int[] LC_HEX_2 = { 88,89,90,91,92,93 }; - public static final int[] LC_HEX_3 = { 94,95,96,97,98,99 }; - public static final int[] LC_HEX_4 = { 112,113,114,115,116,117 }; - public static final int[] LC_HEX_5 = { 118,119,120,121,122,123 }; - public static final int[] LC_HEX_6 = { 136,137,138,139,140,141 }; - public static final int[] LC_HEX_7 = { 142,143,144,145,146,147 }; - public static final int[] LC_HEX_8 = { 160,161,162,163,164,165 }; - public static final int[] LC_HEX_9 = { 166,167,168,169,170,171 }; - public static final int[] LC_HEX_10 = { 184,185,186,187,188,189 }; - public static final int[] LC_HEX_11 = { 190,191,192,193,194,195 }; - public static final int[] RS_HEX_0 = { 208,209,210,211,212,213 }; - public static final int[] RS_HEX_1 = { 214,215,216,217,218,219 }; - public static final int[] RS_HEX_2 = { 232,233,234,235,236,237 }; - public static final int[] RS_HEX_3 = { 238,239,240,241,242,243 }; - public static final int[] RS_HEX_4 = { 256,257,258,259,260,261 }; - public static final int[] RS_HEX_5 = { 262,263,264,265,266,267 }; - public static final int[] RS_HEX_6 = { 280,281,282,283,284,285 }; - public static final int[] RS_HEX_7 = { 286,287,288,289,290,291 }; - public static final int[] RS_HEX_8 = { 304,305,306,307,308,309 }; - public static final int[] RS_HEX_9 = { 310,311,312,313,314,315 }; - public static final int[] RS_HEX_10 = { 328,329,330,331,332,333 }; - public static final int[] RS_HEX_11 = { 334,335,336,337,338,339 }; - - public static final int ENCRYPTION_FLAG = 64; - public static final int IMPLICIT_VENDOR_FLAG = 65; - public static final int[] OPCODE = { 66,67,68,69,70,71 }; - public static final int[] VENDOR = { 72,73,74,75,88,89,90,91 }; - - /* Reed-Solomon(24,12,13) code protects the link control word. Maximum - * correctable errors are: Hamming Distance(13) / 2 = 6 */ - public static final ReedSolomon_63_47_17 mReedSolomonDecoder = - new ReedSolomon_63_47_17( 6 ); - - public TDULinkControlMessage( BinaryMessage message, DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - - /* NID CRC is checked in the message framer so a constructed message - * means it passed the CRC */ - mCRC = new CRC[ 3 ]; - mCRC[ 0 ] = CRC.PASSED; - - checkCRC(); - } - - /** - * Constructs a TDULinkControl message from an existing message and bypasses - * the CRC calculation. - */ - protected TDULinkControlMessage( TDULinkControlMessage message ) - { - super( message.getSourceMessage(), DataUnitID.TDULC, message.getAliasList() ); - - mCRC = message.getCRC(); - } - - public CRC[] getCRC() - { - return mCRC; - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - sb.append( " " ); - sb.append( mMessage.toString() ); - - return sb.toString(); - } - - public String getMessageStub() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "NAC:" ); - sb.append( getNAC() ); - sb.append( " " ); - sb.append( getDUID().getLabel() ); - sb.append( " " ); - - if( isEncrypted() ) - { - sb.append( "ENCRYPTED" ); - } - else - { - if( isImplicitFormat() || getVendor() == Vendor.STANDARD ) - { - sb.append( getOpcode().getLabel() ); - } - else - { - sb.append( "VENDOR:" + getVendor().getLabel() + " " ); - sb.append( getVendorOpcode().getLabel() ); - } - } - - return sb.toString(); - } - - public boolean isEncrypted() - { - return mMessage.get( ENCRYPTION_FLAG ); - } - - public boolean isImplicitFormat() - { - return mMessage.get( IMPLICIT_VENDOR_FLAG ); - } - - /** - * Standard opcodes when the Vendor is STANDARD - */ - public LinkControlOpcode getOpcode() - { - return LinkControlOpcode.fromValue( mMessage.getInt( OPCODE ) ); - } - - /** - * Returns a generic vendor-neutral opcode. Use this when there is a non - * standard vendor code. - */ - public VendorLinkControlOpcode getVendorOpcode() - { - return VendorLinkControlOpcode.fromValue( mMessage.getInt( OPCODE ) ); - } - - /** - * If the vendor is motorola, returns the Motorola OPCODE for this message - */ - public MotorolaOpcode getMotorolaOpcode() - { - return MotorolaOpcode.fromValue( mMessage.getInt( OPCODE ) ); - } - - public Vendor getVendor() - { - if( isImplicitFormat() ) - { - return Vendor.STANDARD; - } - - return Vendor.fromValue( mMessage.getInt( VENDOR ) ); - } - - - /** - * Checks and repairs the 12 x 24-bit golay(24,12,8) codewords and then - * checks and repairs the 24 x 6-bit link control and reed-solomon - * RS(24,12,13) parity codewords - */ - private void checkCRC() - { - /* Check the Golay codewords */ - int x = 64; - - mCRC[ 1 ] = CRC.PASSED; - - while( x < mMessage.size() ) - { - mMessage = Golay24.checkAndCorrect( mMessage, x ); - - if( !( mMessage.getCRC() == CRC.PASSED ) ) - { - mCRC[ 1 ] = CRC.FAILED_CRC; - } - - x += 24; - } - - /* Check the Reed-Solomon parity bits. The RS decoder expects the link - * control data and reed solomon parity hex codewords in reverse order. - * The RS(24,12,13) code used by P25 removes the left-hand 47 data hex - * words, so we replace them with zeros. */ - int[] input = new int [63]; - int[] output = new int [63]; - - input[ 0 ] = mMessage.getInt( RS_HEX_11 ); - input[ 1 ] = mMessage.getInt( RS_HEX_10 ); - input[ 2 ] = mMessage.getInt( RS_HEX_9 ); - input[ 3 ] = mMessage.getInt( RS_HEX_8 ); - input[ 4 ] = mMessage.getInt( RS_HEX_7 ); - input[ 5 ] = mMessage.getInt( RS_HEX_6 ); - input[ 6 ] = mMessage.getInt( RS_HEX_5 ); - input[ 7 ] = mMessage.getInt( RS_HEX_4 ); - input[ 8 ] = mMessage.getInt( RS_HEX_3 ); - input[ 9 ] = mMessage.getInt( RS_HEX_2 ); - input[ 10 ] = mMessage.getInt( RS_HEX_1 ); - input[ 11 ] = mMessage.getInt( RS_HEX_0 ); - input[ 12 ] = mMessage.getInt( LC_HEX_11 ); - input[ 13 ] = mMessage.getInt( LC_HEX_10 ); - input[ 14 ] = mMessage.getInt( LC_HEX_9 ); - input[ 15 ] = mMessage.getInt( LC_HEX_8 ); - input[ 16 ] = mMessage.getInt( LC_HEX_7 ); - input[ 17 ] = mMessage.getInt( LC_HEX_6 ); - input[ 18 ] = mMessage.getInt( LC_HEX_5 ); - input[ 19 ] = mMessage.getInt( LC_HEX_4 ); - input[ 20 ] = mMessage.getInt( LC_HEX_3 ); - input[ 21 ] = mMessage.getInt( LC_HEX_2 ); - input[ 22 ] = mMessage.getInt( LC_HEX_1 ); - input[ 23 ] = mMessage.getInt( LC_HEX_0 ); - /* indexes 24 - 62 are defaulted to zero */ - - boolean irrecoverableErrors = mReedSolomonDecoder.decode( input, output ); - - if( irrecoverableErrors ) - { - mCRC[ 2 ] = CRC.FAILED_CRC; - } - else - { - mCRC[ 2 ] = CRC.PASSED; - - /* Only fix the link control data hex codewords */ - repairHexCodeword( input, output, 12, LC_HEX_11 ); - repairHexCodeword( input, output, 13, LC_HEX_10 ); - repairHexCodeword( input, output, 14, LC_HEX_9 ); - repairHexCodeword( input, output, 15, LC_HEX_8 ); - repairHexCodeword( input, output, 16, LC_HEX_7 ); - repairHexCodeword( input, output, 17, LC_HEX_6 ); - repairHexCodeword( input, output, 18, LC_HEX_5 ); - repairHexCodeword( input, output, 19, LC_HEX_4 ); - repairHexCodeword( input, output, 20, LC_HEX_3 ); - repairHexCodeword( input, output, 21, LC_HEX_2 ); - repairHexCodeword( input, output, 22, LC_HEX_1 ); - repairHexCodeword( input, output, 23, LC_HEX_0 ); - - /* If the golay check failed, mark it as corrected too */ - if( mCRC[ 1 ] == CRC.FAILED_CRC ) - { - mCRC[ 1 ] = CRC.CORRECTED; - } - } - } - - private void repairHexCodeword( int[] input, int[] output, int index, int[] indexSet ) - { - if( input[ index ] != output[ index ] ) - { - mMessage.load( indexSet[ 0 ], 6, output[ index ] ); - mCRC[ 2 ] = CRC.CORRECTED; - } - } - - /** - * Indicates if the message passed the golay and reed-solomon parity checks - * or was corrected. - */ - public boolean isValid() - { - // mCRC[ 0 ] - always passes (otherwise message wouldn't be constructed ) - // mCRC[ 1 ] - golay - always passes or corrected if mCRC[2] is valid - - return mCRC[ 2 ] == CRC.PASSED || mCRC[ 2 ] == CRC.CORRECTED; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/TelephoneInterconnectAnswerRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/TelephoneInterconnectAnswerRequest.java deleted file mode 100644 index 503b1ed3..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/TelephoneInterconnectAnswerRequest.java +++ /dev/null @@ -1,79 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.reference.Digit; -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; - -public class TelephoneInterconnectAnswerRequest extends TDULinkControlMessage -{ - private final static Logger mLog = LoggerFactory.getLogger( TelephoneInterconnectAnswerRequest.class ); - - public static final int[] DIGIT_1 = { 72,73,74,75 }; - public static final int[] DIGIT_2 = { 88,89,90,91 }; - public static final int[] DIGIT_3 = { 92,93,94,95 }; - public static final int[] DIGIT_4 = { 96,97,98,99 }; - public static final int[] DIGIT_5 = { 112,113,114,115 }; - public static final int[] DIGIT_6 = { 116,117,118,119 }; - public static final int[] DIGIT_7 = { 120,121,122,123 }; - public static final int[] DIGIT_8 = { 136,137,138,139 }; - public static final int[] DIGIT_9 = { 140,141,142,143 }; - public static final int[] DIGIT_10 = { 144,145,146,147 }; - - public static final int[] TARGET_ADDRESS = { 160,161,162,163,164,165,166, - 167,168,169,170,171,184,185,186,187,188,189,190,191,192,193,194,195 }; - - public TelephoneInterconnectAnswerRequest( TDULinkControlMessage source ) - { - super( source ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.TELEPHONE_INTERCONNECT_ANSWER_REQUEST.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " TO:" ); - sb.append( getTargetAddress() ); - - sb.append( " TEL:" ); - sb.append( getTelephoneNumber() ); - - return sb.toString(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getTelephoneNumber() - { - List digits = new ArrayList(); - - digits.add( mMessage.getInt( DIGIT_1 ) ); - digits.add( mMessage.getInt( DIGIT_2 ) ); - digits.add( mMessage.getInt( DIGIT_3 ) ); - digits.add( mMessage.getInt( DIGIT_4 ) ); - digits.add( mMessage.getInt( DIGIT_5 ) ); - digits.add( mMessage.getInt( DIGIT_6 ) ); - digits.add( mMessage.getInt( DIGIT_7 ) ); - digits.add( mMessage.getInt( DIGIT_8 ) ); - digits.add( mMessage.getInt( DIGIT_9 ) ); - digits.add( mMessage.getInt( DIGIT_10 ) ); - - return Digit.decode( digits ); - } - -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/TelephoneInterconnectVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/TelephoneInterconnectVoiceChannelUser.java deleted file mode 100644 index b969b3cc..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/TelephoneInterconnectVoiceChannelUser.java +++ /dev/null @@ -1,92 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class TelephoneInterconnectVoiceChannelUser extends TDULinkControlMessage -{ - private final static Logger mLog = - LoggerFactory.getLogger( TelephoneInterconnectVoiceChannelUser.class ); - /* Service Options */ - public static final int EMERGENCY_FLAG = 92; - public static final int ENCRYPTED_CHANNEL_FLAG = 93; - public static final int DUPLEX_MODE = 94; - public static final int SESSION_MODE = 95; - - public static final int[] CALL_TIMER = { 120,121,122,123,136,137,138,139, - 140,141,142,143,144,145,146,147 }; - public static final int[] ADDRESS = { 160,161,162,163,164,165,166, - 167,168,169,170,171,184,185,186,187,188,189,190,191,192,193,194,195 }; - - public TelephoneInterconnectVoiceChannelUser( TDULinkControlMessage source ) - { - super( source ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - if( isEmergency() ) - { - sb.append( " EMERGENCY" ); - } - if( isEncryptedChannel() ) - { - sb.append( " ENCRYPTED CHANNEL" ); - } - - sb.append( " TIMER:" + getCallTimer() + " SECS" ); - - sb.append( " ADDR:" + getAddress() ); - - return sb.toString(); - } - - public boolean isEmergency() - { - return mMessage.get( EMERGENCY_FLAG ); - } - - public boolean isEncryptedChannel() - { - return mMessage.get( ENCRYPTED_CHANNEL_FLAG ); - } - - public DuplexMode getDuplexMode() - { - return mMessage.get( DUPLEX_MODE ) ? DuplexMode.FULL : DuplexMode.HALF; - } - - public SessionMode getSessionMode() - { - return mMessage.get( SESSION_MODE ) ? - SessionMode.CIRCUIT : SessionMode.PACKET; - } - - public String getAddress() - { - return mMessage.getHex( ADDRESS, 6 ); - } - - /** - * Call timer in seconds - */ - public int getCallTimer() - { - int units = mMessage.getInt( CALL_TIMER ); - - return (int)( units / 10 ); - } - -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/UnitAuthenticationCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/UnitAuthenticationCommand.java deleted file mode 100644 index 6a458d26..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/UnitAuthenticationCommand.java +++ /dev/null @@ -1,70 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class UnitAuthenticationCommand extends TDULinkControlMessage -{ - private final static Logger mLog = - LoggerFactory.getLogger( UnitAuthenticationCommand.class ); - public static final int[] NETWORK_ID = { 72,73,74,75,88,89,90,91,92,93,94, - 95,96,97,98,99,112,113,114,115 }; - public static final int[] SYSTEM_ID = { 116,117,118,119,120,121,122,123, - 136,137,138,139 }; - /* ICD says this is a source id, but this is a command messages, so this - * must be a target id */ - public static final int[] TARGET_ID = { 140,141,142,143,144,145,146,147, - 160,161,162,163,164,165,166,167,168,169,170,171,184,185,186,187 }; - - public UnitAuthenticationCommand( TDULinkControlMessage source ) - { - super( source ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.UNIT_AUTHENTICATION_COMMAND.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " ADDRESS:" + getCompleteTargetAddress() ); - - return sb.toString(); - } - - public String getCompleteTargetAddress() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getNetworkID() ); - sb.append( ":" ); - sb.append( getSystemID() ); - sb.append( ":" ); - sb.append( getTargetID() ); - - return sb.toString(); - } - - public String getNetworkID() - { - return mMessage.getHex( NETWORK_ID, 5 ); - } - - public String getSystemID() - { - return mMessage.getHex( SYSTEM_ID, 3 ); - } - - public String getTargetID() - { - return mMessage.getHex( TARGET_ID, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/UnitRegistrationCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/UnitRegistrationCommand.java deleted file mode 100644 index 145f237d..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/UnitRegistrationCommand.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class UnitRegistrationCommand extends TDULinkControlMessage -{ - private final static Logger mLog = - LoggerFactory.getLogger( UnitRegistrationCommand.class ); - public static final int[] NETWORK_ID = { 72,73,74,75,88,89,90,91,92,93,94, - 95,96,97,98,99,112,113,114,115 }; - public static final int[] SYSTEM_ID = { 116,117,118,119,120,121,122,123, - 136,137,138,139 }; - /* ICD calls this source, but should be target address */ - public static final int[] TARGET_ID = { 140,141,142,143,144,145,146,147, - 160,161,162,163,164,165,166,167,168,169,170,171,184,185,186,187 }; - - public UnitRegistrationCommand( TDULinkControlMessage source ) - { - super( source ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.UNIT_REGISTRATION_COMMAND.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " ADDRESS:" + getCompleteTargetAddress() ); - - return sb.toString(); - } - - public String getCompleteTargetAddress() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getNetworkID() ); - sb.append( ":" ); - sb.append( getSystemID() ); - sb.append( ":" ); - sb.append( getTargetID() ); - - return sb.toString(); - } - - public String getNetworkID() - { - return mMessage.getHex( NETWORK_ID, 5 ); - } - - public String getSystemID() - { - return mMessage.getHex( SYSTEM_ID, 3 ); - } - - public String getTargetID() - { - return mMessage.getHex( TARGET_ID, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/UnitToUnitAnswerRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/UnitToUnitAnswerRequest.java deleted file mode 100644 index e6cd06d7..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/UnitToUnitAnswerRequest.java +++ /dev/null @@ -1,85 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class UnitToUnitAnswerRequest extends TDULinkControlMessage -{ - private final static Logger mLog = - LoggerFactory.getLogger( UnitToUnitAnswerRequest.class ); - /* Service Options */ - public static final int EMERGENCY_FLAG = 92; - public static final int ENCRYPTED_CHANNEL_FLAG = 93; - public static final int DUPLEX_MODE = 94; - public static final int SESSION_MODE = 95; - public static final int[] TARGET_ADDRESS = { 112,113,114,115,116,117,118, - 119,120,121,122,123,136,137,138,139,140,141,142,143,144,145,146,147 }; - public static final int[] SOURCE_ADDRESS = { 160,161,162,163,164,165,166, - 167,168,169,170,171,184,185,186,187,188,189,190,191,192,193,194,195 }; - - public UnitToUnitAnswerRequest( TDULinkControlMessage source ) - { - super( source ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.UNIT_TO_UNIT_ANSWER_REQUEST.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - if( isEmergency() ) - { - sb.append( " EMERGENCY" ); - } - if( isEncryptedChannel() ) - { - sb.append( " ENCRYPTED CHANNEL" ); - } - - sb.append( " FROM:" + getSourceAddress() ); - - sb.append( " TO:" + getTargetAddress() ); - - return sb.toString(); - } - - public boolean isEmergency() - { - return mMessage.get( EMERGENCY_FLAG ); - } - - public boolean isEncryptedChannel() - { - return mMessage.get( ENCRYPTED_CHANNEL_FLAG ); - } - - public DuplexMode getDuplexMode() - { - return mMessage.get( DUPLEX_MODE ) ? DuplexMode.FULL : DuplexMode.HALF; - } - - public SessionMode getSessionMode() - { - return mMessage.get( SESSION_MODE ) ? - SessionMode.CIRCUIT : SessionMode.PACKET; - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/UnitToUnitVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/UnitToUnitVoiceChannelUser.java deleted file mode 100644 index 7789e094..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tdu/lc/UnitToUnitVoiceChannelUser.java +++ /dev/null @@ -1,85 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tdu.lc; - -import io.github.dsheirer.module.decode.p25.reference.LinkControlOpcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class UnitToUnitVoiceChannelUser extends TDULinkControlMessage -{ - private final static Logger mLog = - LoggerFactory.getLogger( UnitToUnitVoiceChannelUser.class ); - /* Service Options */ - public static final int EMERGENCY_FLAG = 92; - public static final int ENCRYPTED_CHANNEL_FLAG = 93; - public static final int DUPLEX_MODE = 94; - public static final int SESSION_MODE = 95; - public static final int[] TARGET_ADDRESS = { 112,113,114,115,116,117,118, - 119,120,121,122,123,136,137,138,139,140,141,142,143,144,145,146,147 }; - public static final int[] SOURCE_ADDRESS = { 160,161,162,163,164,165,166, - 167,168,169,170,171,184,185,186,187,188,189,190,191,192,193,194,195 }; - - public UnitToUnitVoiceChannelUser( TDULinkControlMessage source ) - { - super( source ); - } - - @Override - public String getEventType() - { - return LinkControlOpcode.UNIT_TO_UNIT_VOICE_CHANNEL_USER.getDescription(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - if( isEmergency() ) - { - sb.append( " EMERGENCY" ); - } - if( isEncryptedChannel() ) - { - sb.append( " ENCRYPTED CHANNEL" ); - } - - sb.append( " FROM:" + getSourceAddress() ); - - sb.append( " TO:" + getTargetAddress() ); - - return sb.toString(); - } - - public boolean isEmergency() - { - return mMessage.get( EMERGENCY_FLAG ); - } - - public boolean isEncryptedChannel() - { - return mMessage.get( ENCRYPTED_CHANNEL_FLAG ); - } - - public DuplexMode getDuplexMode() - { - return mMessage.get( DUPLEX_MODE ) ? DuplexMode.FULL : DuplexMode.HALF; - } - - public SessionMode getSessionMode() - { - return mMessage.get( SESSION_MODE ) ? - SessionMode.CIRCUIT : SessionMode.PACKET; - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/ChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/ChannelGrant.java deleted file mode 100644 index 9fad0b82..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/ChannelGrant.java +++ /dev/null @@ -1,110 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tsbk; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public abstract class ChannelGrant extends ServiceMessage implements IdentifierReceiver -{ - public static final int[] PRIORITY = {85, 86, 87}; - public static final int[] CHANNEL_IDENTIFIER = {88, 89, 90, 91}; - public static final int[] CHANNEL_NUMBER = {92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103}; - - private IBandIdentifier mIdentifierUpdate; - - public ChannelGrant(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - } - - /** - * Indicates if this channel grant is for a TDMA channel - */ - public boolean isTDMAChannel() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.isTDMA(); - } - - return false; - } - - /** - * 1 = Lowest, 4 = Default, 7 = Highest - */ - public int getPriority() - { - return mMessage.getInt(PRIORITY); - } - - public int getChannelIdentifier() - { - return mMessage.getInt(CHANNEL_IDENTIFIER); - } - - public int getChannelNumber() - { - return mMessage.getInt(CHANNEL_NUMBER); - } - - public String getChannel() - { - return getChannelIdentifier() + "-" + getChannelNumber(); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - mIdentifierUpdate = message; - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[1]; - identifiers[0] = getChannelIdentifier(); - - return identifiers; - } - - public long getDownlinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getDownlinkFrequency(getChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getUplinkFrequency(getChannelNumber()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/GroupChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/GroupChannelGrant.java deleted file mode 100644 index 6f1b3d48..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/GroupChannelGrant.java +++ /dev/null @@ -1,93 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public abstract class GroupChannelGrant extends ChannelGrant -{ - public static final int[] GROUP_ADDRESS = { 104,105,106,107,108,109,110,111, - 112,113,114,115,116,117,118,119 }; - - public static final int[] SOURCE_ADDRESS = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public GroupChannelGrant( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - public String getGroupAddress() - { - return mMessage.getHex( GROUP_ADDRESS, 4 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - if( isEmergency() ) - { - sb.append( " EMERGENCY" ); - } - - sb.append( " SOURCE UNIT:" ); - sb.append( getSourceAddress() ); - - sb.append( " GROUP:" ); - sb.append( getGroupAddress() ); - - sb.append( " CHAN:" ); - sb.append( getChannelIdentifier() + "-" + getChannelNumber() ); - - sb.append( " DN:" + getDownlinkFrequency() ); - - sb.append( " UP:" + getUplinkFrequency() ); - - return sb.toString(); - } - - @Override - public String getFromID() - { - return getSourceAddress(); - } - - @Override - public Alias getFromIDAlias() - { - if( mAliasList != null ) - { - return mAliasList.getTalkgroupAlias( getFromID() ); - } - - return null; - } - - @Override - public String getToID() - { - return getGroupAddress(); - } - - @Override - public Alias getToIDAlias() - { - if( mAliasList != null ) - { - return mAliasList.getTalkgroupAlias( getGroupAddress() ); - } - - return null; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/GroupChannelGrantExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/GroupChannelGrantExplicit.java deleted file mode 100644 index bf18e3bc..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/GroupChannelGrantExplicit.java +++ /dev/null @@ -1,155 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tsbk; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public abstract class GroupChannelGrantExplicit extends ChannelGrant -{ - public static final int[] TRANSMIT_IDENTIFIER = {96, 97, 98, 99}; - public static final int[] TRANSMIT_NUMBER = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111}; - public static final int[] RECEIVE_IDENTIFIER = {112, 113, 114, 115}; - public static final int[] RECEIVE_NUMBER = {116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127}; - public static final int[] GROUP_ADDRESS = {128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, - 142, 143}; - - private IBandIdentifier mTransmitIdentifierUpdate; - private IBandIdentifier mReceiveIdentifierUpdate; - - public GroupChannelGrantExplicit(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - } - - @Override - public boolean isTDMAChannel() - { - if(mTransmitIdentifierUpdate != null) - { - return mTransmitIdentifierUpdate.isTDMA(); - } - - return false; - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - if(isEmergency()) - { - sb.append(" EMERGENCY"); - } - - sb.append(" XMIT:"); - sb.append(getTransmitChannelIdentifier() + "/" + getTransmitChannelNumber()); - - sb.append(" RCV:"); - sb.append(getReceiveChannelIdentifier() + "/" + getReceiveChannelNumber()); - - sb.append(" GRP:"); - sb.append(getGroupAddress()); - - return sb.toString(); - } - - public int getTransmitChannelIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public int getTransmitChannelNumber() - { - return mMessage.getInt(TRANSMIT_NUMBER); - } - - public String getTransmitChannel() - { - return getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber(); - } - - public int getReceiveChannelIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public int getReceiveChannelNumber() - { - return mMessage.getInt(RECEIVE_NUMBER); - } - - public String getReceiveChannel() - { - return getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber(); - } - - public String getGroupAddress() - { - return mMessage.getHex(GROUP_ADDRESS, 4); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitChannelIdentifier()) - { - mTransmitIdentifierUpdate = message; - } - - if(identifier == getReceiveChannelIdentifier()) - { - mReceiveIdentifierUpdate = message; - } - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[2]; - identifiers[0] = getTransmitChannelIdentifier(); - identifiers[1] = getReceiveChannelIdentifier(); - - return identifiers; - } - - public long getDownlinkFrequency() - { - if(mTransmitIdentifierUpdate != null) - { - return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mReceiveIdentifierUpdate != null) - { - return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/GroupMultiChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/GroupMultiChannelGrant.java deleted file mode 100644 index 4fd3d263..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/GroupMultiChannelGrant.java +++ /dev/null @@ -1,220 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tsbk; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public abstract class GroupMultiChannelGrant extends ChannelGrant implements IdentifierReceiver -{ - public static final int[] CHANNEL_IDENTIFIER_1 = {80, 81, 82, 83}; - public static final int[] CHANNEL_NUMBER_1 = {84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95}; - public static final int[] GROUP_ADDRESS_1 = {96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111}; - public static final int[] CHANNEL_IDENTIFIER_2 = {112, 113, 114, 115}; - public static final int[] CHANNEL_NUMBER_2 = {116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127}; - public static final int[] GROUP_ADDRESS_2 = {128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143}; - - private IBandIdentifier mIdentifierUpdate1; - private IBandIdentifier mIdentifierUpdate2; - - public GroupMultiChannelGrant(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" CHAN1:"); - sb.append(getChannelIdentifier1() + "/" + getChannelNumber1()); - - sb.append(" GRP1:"); - sb.append(getGroupAddress1()); - - if(hasChannelNumber2()) - { - sb.append(" CHAN2:"); - sb.append(getChannelIdentifier2() + "/" + getChannelNumber2()); - - sb.append(" GRP2:"); - sb.append(getGroupAddress2()); - } - - return sb.toString(); - } - - public int getChannelIdentifier1() - { - return mMessage.getInt(CHANNEL_IDENTIFIER_1); - } - - public int getChannelNumber1() - { - return mMessage.getInt(CHANNEL_NUMBER_1); - } - - public String getChannel1() - { - return getChannelIdentifier1() + "-" + getChannelNumber1(); - } - - public String getGroupAddress1() - { - return mMessage.getHex(GROUP_ADDRESS_1, 4); - } - - public int getChannelIdentifier2() - { - return mMessage.getInt(CHANNEL_IDENTIFIER_2); - } - - public int getChannelNumber2() - { - return mMessage.getInt(CHANNEL_NUMBER_2); - } - - public String getChannel2() - { - return getChannelIdentifier2() + "-" + getChannelNumber2(); - } - - public String getGroupAddress2() - { - return mMessage.getHex(GROUP_ADDRESS_2, 4); - } - - public boolean hasChannelNumber2() - { - return mMessage.getInt(CHANNEL_NUMBER_2) != - mMessage.getInt(CHANNEL_NUMBER_1); - } - - @Override - public String getFromID() - { - return getGroupAddress1(); - } - - @Override - public String getToID() - { - return getGroupAddress2(); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getChannelIdentifier1()) - { - mIdentifierUpdate1 = message; - } - - if(hasChannelNumber2() && identifier == getChannelIdentifier2()) - { - mIdentifierUpdate2 = message; - } - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers; - - if(hasChannelNumber2()) - { - identifiers = new int[2]; - identifiers[0] = getChannelIdentifier1(); - identifiers[1] = getChannelIdentifier2(); - } - else - { - identifiers = new int[1]; - identifiers[0] = getChannelIdentifier1(); - } - - return identifiers; - } - - public long getDownlinkFrequency1() - { - if(mIdentifierUpdate1 != null) - { - return mIdentifierUpdate1.getDownlinkFrequency(getChannelNumber1()); - } - - return 0; - } - - public boolean isTDMAChannel1() - { - if(mIdentifierUpdate1 != null) - { - return mIdentifierUpdate1.isTDMA(); - } - - return false; - } - - public long getUplinkFrequency1() - { - if(mIdentifierUpdate1 != null) - { - return mIdentifierUpdate1.getUplinkFrequency(getChannelNumber1()); - } - - return 0; - } - - public long getDownlinkFrequency2() - { - if(mIdentifierUpdate2 != null) - { - return mIdentifierUpdate2.getDownlinkFrequency(getChannelNumber2()); - } - - return 0; - } - - public boolean isTDMAChannel2() - { - if(mIdentifierUpdate2 != null) - { - return mIdentifierUpdate2.isTDMA(); - } - - return false; - } - - public long getUplinkFrequency2() - { - if(mIdentifierUpdate2 != null) - { - return mIdentifierUpdate2.getUplinkFrequency(getChannelNumber2()); - } - - return 0; - } - -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/ISPMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/ISPMessage.java new file mode 100644 index 00000000..95ffa387 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/ISPMessage.java @@ -0,0 +1,30 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.Direction; + +/** + * P25 Inbound (ISP) TSBK Message + */ +public abstract class ISPMessage extends TSBKMessage +{ + /** + * Constructs an inbound (ISP) TSBK from the binary message sequence. + * + * @param dataUnitID TSBK1/2/3 + * @param message binary sequence + * @param nac decoded from the NID + * @param timestamp for the message + */ + public ISPMessage(DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitID, message, nac, timestamp); + } + + @Override + public Direction getDirection() + { + return Direction.INBOUND; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/OSPMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/OSPMessage.java new file mode 100644 index 00000000..4c33ded5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/OSPMessage.java @@ -0,0 +1,30 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.Direction; + +/** + * P25 Outbound (OSP) TSBK Message + */ +public abstract class OSPMessage extends TSBKMessage +{ + /** + * Constructs an outbound (OSP) TSBK from the binary message sequence. + * + * @param dataUnitID TSBK1/2/3 + * @param message binary sequence + * @param nac decoded from the NID + * @param timestamp for the message + */ + public OSPMessage(DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitID, message, nac, timestamp); + } + + @Override + public Direction getDirection() + { + return Direction.OUTBOUND; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/Opcode.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/Opcode.java new file mode 100644 index 00000000..959536c1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/Opcode.java @@ -0,0 +1,296 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk; + +import io.github.dsheirer.module.decode.p25.reference.Direction; +import io.github.dsheirer.module.decode.p25.reference.Vendor; + +import java.util.EnumSet; + +public enum Opcode +{ + //Vendor: standard, Outbound Service Packet (OSP) + OSP_GROUP_VOICE_CHANNEL_GRANT(0, "GRP_VCH_GRANT", "GROUP VOICE CHANNEL GRANT"), + OSP_RESERVED_01(1, "RESERVED_01", "OSP RESERVED 1"), + OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE(2, "GRP_VCH_GRNT_UPD", "GROUP VOICE CHANNEL GRANT UPDATE"), + OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT(3, "GRP_VCH_GRNT_UPX", "GROUP VOICE CHANNEL GRANT UPDATE EXPLICIT"), + OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT(4, "UU_VCH_GRANT", "UNIT-2-UNIT VOICE CHANNEL GRANT"), + OSP_UNIT_TO_UNIT_ANSWER_REQUEST(5, "UU_ANS_REQ", "UNIT-2-UNIT ANSWER REQUEST"), + OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE(6, "UU_VCH_GRANT_UPD", "UNIT-2-UNIT VOICE CHANNEL GRANT UPDATE"), + OSP_RESERVED_07(7, "RESERVED_07", "OSP RESERVED 7"), + OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT(8, "TEL_INT_VCH_GRNT", "TELEPHONE INTERCONNECT VOICE CHANNEL GRANT"), + OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE(9, "TEL_INT_VCH_GRNU", "TELEPHONE INTERCONNECT VOICE CHANNEL GRANT UPDATE"), + OSP_TELEPHONE_INTERCONNECT_ANSWER_REQUEST(10, "TEL_INT_ANS_RQST", "TELEPHONE INTERCONNECT ANSWER REQUEST"), + OSP_RESERVED_0B(11, "RESERVED_0B", "OSP RESERVED 11"), + OSP_RESERVED_0C(12, "RESERVED_0C", "OSP RESERVED 12"), + OSP_RESERVED_0D(13, "RESERVED_0D", "OSP RESERVED 13"), + OSP_RESERVED_0E(14, "RESERVED_0E", "OSP RESERVED 14"), + OSP_RESERVED_0F(15, "RESERVED_0F", "OSP RESERVED 15"), + OSP_INDIVIDUAL_DATA_CHANNEL_GRANT(16, "IND_DCH_GRANT", "INDIVIDUAL CHANNEL GRANT-OBSOLETE"), //OBSOLETE + OSP_GROUP_DATA_CHANNEL_GRANT(17, "GRP_DCH_GRANT", "GROUP DATA CHANNEL GRANT-OBSOLETE"), //OBSOLETE + OSP_GROUP_DATA_CHANNEL_ANNOUNCEMENT(18, "GRP_DCH_ANNOUNCE", "GROUP DATA CHANNEL ANNOUNCEMENT-OBSOLETE"), //OBSOLETE + OSP_GROUP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT(19, "GRP_DCH_ANNC_EXP", "GROUP DATA CHANNEL ANNOUNCEMENT EXPLICIT-OBSOLETE"), //OBSOLETE + OSP_SNDCP_DATA_CHANNEL_GRANT(20, "SNDCP_DCH_GRANT", "SNDCP DATA CHANNEL GRANT"), + OSP_SNDCP_DATA_PAGE_REQUEST(21, "SNDCP_DCH_PAG_RQ", "SNDCP DATA CHANNEL PAGE REQUEST"), + OSP_SNDCP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT(22, "SNDCP_DCH_ANN_EX", "SNDCP DATA CHANNEL ANNOUNCEMENT EXPLICIT"), + OSP_RESERVED_17(23, "RESERVED_17", "OSP RESERVED 23"), + OSP_STATUS_UPDATE(24, "STATUS_UPDATE", "STATUS UPDATE"), + OSP_RESERVED_19(25, "RESERVED_19", "OSP RESERVED 25"), + OSP_STATUS_QUERY(26, "OSP_STATUS_QUERY", "STATUS QUERY"), + OSP_RESERVED_1B(27, "RESERVED_1B", "OSP RESERVED 27"), + OSP_MESSAGE_UPDATE(28, "MESSAGE_UPDATE", "MESSAGE UPDATE"), + OSP_RADIO_UNIT_MONITOR_COMMAND(29, "RADIO_MONITR_CMD", "RADIO MONITOR COMMAND"), + OSP_RESERVED_1E(30, "RESERVED_1E", "OSP RESERVED 30"), + OSP_CALL_ALERT(31, "CALL_ALERT", "CALL ALERT"), + OSP_ACKNOWLEDGE_RESPONSE(32, "ACK_RESPONSE_FNE", "ACKNOWLEDGE RESPONSE"), + OSP_QUEUED_RESPONSE(33, "QUEUED_RESPONSE", "QUEUED RESPONSE"), + OSP_RESERVED_22(34, "RESERVED_22", "OSP RESERVED 34"), + OSP_RESERVED_23(35, "RESERVED_23", "OSP RESERVED 35"), + OSP_EXTENDED_FUNCTION_COMMAND(36, "EXTNDED_FUNC_CMD", "EXTENDED FUNCTION COMMAND"), + OSP_RESERVED_25(37, "RESERVED_25", "OSP RESERVED 37"), + OSP_RESERVED_26(38, "RESERVED_26", "OSP RESERVED 38"), + OSP_DENY_RESPONSE(39, "DENY_RESPONSE", "DENY RESPONSE"), + OSP_GROUP_AFFILIATION_RESPONSE(40, "GRP_AFFIL_RESP", "GROUP AFFILIATION RESPONSE"), + OSP_SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT(41, "SCCB_CCH_BCST_EX", "SECONDARY CONTROL CHANNEL BROADCAST EXPLICIT"), + OSP_GROUP_AFFILIATION_QUERY(42, "GRP_AFFIL_QUERY", "GROUP AFFILIATION QUERY"), + OSP_LOCATION_REGISTRATION_RESPONSE(43, "LOCN_REG_RESPONS", "LOCATION REGISTRATION RESPONSE"), + OSP_UNIT_REGISTRATION_RESPONSE(44, "UNIT_REG_RESPONS", "UNIT REGISTRATION RESPONSE"), + OSP_UNIT_REGISTRATION_COMMAND(45, "UNIT_REG_COMMAND", "UNIT REGISTRATION COMMAND"), + OSP_AUTHENTICATION_COMMAND(46, "AUTH_COMMAND", "AUTHENTICATION COMMAND"), + OSP_UNIT_DEREGISTRATION_ACKNOWLEDGE(47, "DE_REGIST_ACK", "DE-REGISTRATION ACKNOWLEDGE"), + OSP_TDMA_SYNC_BROADCAST(48, "TDMA_SYNC_BCST", "TDMA SYNCHRONIZATION BROADCAST"), + OSP_AUTHENTICATION_DEMAND(49, "AUTH_DEMAND", "AUTHENTICATION DEMAND"), + OSP_AUTHENTICATION_FNE_RESPONSE(50, "AUTH_FNE_RESP", "AUTHENTICATION RESPONSE"), + OSP_IDENTIFIER_UPDATE_TDMA(51, "IDEN_UPDATE_TDMA", "IDENTIFIER UPDATE TDMA"), + OSP_IDENTIFIER_UPDATE_VHF_UHF_BANDS(52, "IDEN_UPDATE_VUHF", "IDENTIFIER UPDATE VHF-UHF"), + OSP_TIME_DATE_ANNOUNCEMENT(53, "TIME_DATE_ANNOUN", "TIME AND DATE ANNOUNCEMENT"), + OSP_ROAMING_ADDRESS_COMMAND(54, "ROAM_ADDR_CMD", "ROAMING ADDRESS COMMAND"), + OSP_ROAMING_ADDRESS_UPDATE(55, "ROAM_ADDR_UPDATE", "ROAMING ADDRESS UPDATE"), + OSP_SYSTEM_SERVICE_BROADCAST(56, "SYS_SVC_BCAST", "SYSTEM SERVICE BROADCAST"), + OSP_SECONDARY_CONTROL_CHANNEL_BROADCAST(57, "SEC_CCH_BROADCST", "SECONDARY CONTROL CHANNEL BROADCAST"), + OSP_RFSS_STATUS_BROADCAST(58, "RFSS_STATUS_BCST", "RFSS STATUS BROADCAST"), + OSP_NETWORK_STATUS_BROADCAST(59, "NET_STATUS_BCAST", "NETWORK STATUS BROADCAST"), + OSP_ADJACENT_STATUS_BROADCAST(60, "ADJ SITE STATUS", "ADJACENT SITE STATUS"), + OSP_IDENTIFIER_UPDATE(61, "IDEN_UPDATE", "IDENTIFIER UPDATE"), + OSP_PROTECTION_PARAMETER_BROADCAST(62, "ENCRYPT_PAR_BCST", "ENCRYPTION PARAMENTERS BROADCAST"), + OSP_PROTECTION_PARAMETER_UPDATE(63, "ENCRYPT_PAR_UPDT", "ENCRYPTION PARAMETERS UPDATE"), + OSP_UNKNOWN(-1, "OSP UNKNOWN", "OSP UNKNOWN OPCODE"), + + //Vendor: standard, Inbound Service Packet (ISP) + ISP_GROUP_VOICE_SERVICE_REQUEST(0, "GRP_V_REQ", "GROUP VOICE SERVICE REQUEST"), + ISP_RESERVED_01(1, "RESERVED_01", "ISP RESERVED 1"), + ISP_RESERVED_02(2, "RESERVED_02", "ISP RESERVED 2"), + ISP_RESERVED_03(3, "RESERVED_03", "ISP RESERVED 3"), + ISP_UNIT_TO_UNIT_VOICE_SERVICE_REQUEST(4, "UU_V_REQ", "UNIT-2-UNIT VOICE SERVICE REQUEST"), + ISP_UNIT_TO_UNIT_ANSWER_RESPONSE(5, "UU_ANS_RSP", "UNIT-2-UNIT ANSWER RESPONSE"), + ISP_RESERVED_06(6, "RESERVED_06", "ISP RESERVED 6"), + ISP_RESERVED_07(7, "RESERVED_07", "ISP RESERVED 7"), + ISP_TELEPHONE_INTERCONNECT_EXPLICIT_DIAL_REQUEST(8, "TELE_INT_DIAL_REQ", "TELEPHONE INTERCONNECT EXPLICIT DIAL REQUEST"), + ISP_TELEPHONE_INTERCONNECT_PSTN_REQUEST(9, "TELE_INT_PSTN_REQ", "TELEPHONE INTERCONNECT PSTN REQUEST"), + ISP_TELEPHONE_INTERCONNECT_ANSWER_RESPONSE(10, "TELE_INT_ANS_RSP", "TELEPHONE INTERCONNECT ANSWER RESPONSE"), + ISP_RESERVED_0B(11, "RESERVED_0B", "ISP RESERVED 11"), + ISP_RESERVED_0C(12, "RESERVED_0C", "ISP RESERVED 12"), + ISP_RESERVED_0D(13, "RESERVED_0D", "ISP RESERVED 13"), + ISP_RESERVED_0E(14, "RESERVED_0E", "ISP RESERVED 14"), + ISP_RESERVED_0F(15, "RESERVED_0F", "ISP RESERVED 15"), + ISP_INDIVIDUAL_DATA_SERVICE_REQUEST(16, "IND_DATA_REQ", "INDIVIDUAL DATA SERVICE REQUEST-OBSOLETE"), //OBSOLETE + ISP_GROUP_DATA_SERVICE_REQUEST(17, "GRP_DATA_REQ", "GROUP DATA SERVICE REQUEST-OBSOLETE"), //OBSOLETE + ISP_SNDCP_DATA_CHANNEL_REQUEST(18, "SN-DATA_CHN_REQ", "SNDCP DATA CHANNEL REQUEST"), + ISP_SNDCP_DATA_PAGE_RESPONSE(19, "SN-DATA_PAGE_RES", "SNDCP DATA PAGE REQUEST"), + ISP_SNDCP_RECONNECT_REQUEST(20, "SN-REC_REQ", "SNDCP RECONNECT REQUEST"), + ISP_RESERVED_15(21, "RESERVED_15", "ISP RESERVED 21"), + ISP_RESERVED_16(22, "RESERVED_16", "ISP RESERVED 22"), + ISP_RESERVED_17(23, "RESERVED_17", "ISP RESERVED 23"), + ISP_STATUS_UPDATE_REQUEST(24, "STS_UPDT_REQ", "STATUS UPDATE REQUEST"), + ISP_STATUS_QUERY_RESPONSE(25, "STS_Q_RSP", "STATUS QUERY RESPONSE"), + ISP_STATUS_QUERY_REQUEST(26, "STS_Q_REQ", "STATUS QUERY REQUEST"), + ISP_RESERVED_1B(27, "RESERVED_1B", "ISP RESERVED 27"), + ISP_MESSAGE_UPDATE_REQUEST(28, "MSG_UPDT_REQ", "MESSAGE UPDATE REQUEST"), + ISP_RADIO_UNIT_MONITOR_REQUEST(29, "RAD_MON_REQ", "RADIO UNIT MONITOR REQUEST"), + ISP_RESERVED_1E(30, "RESERVED_1E", "ISP RESERVED 30"), + ISP_CALL_ALERT_REQUEST(31, "CALL_ALRT_REQ", "CALL ALERT REQUEST"), + ISP_UNIT_ACKNOWLEDGE_RESPONSE(32, "ACK_RSP_U", "UNIT ACKNOWLEDGE RESPONSE"), + ISP_RESERVED_21(33, "RESERVED_21", "ISP RESERVED 33"), + ISP_RESERVED_22(34, "RESERVED_22", "ISP RESERVED 34"), + ISP_CANCEL_SERVICE_REQUEST(35, "CAN_SRV_REQ", "CANCEL SERVICE REQUEST"), + ISP_EXTENDED_FUNCTION_RESPONSE(36, "EXT_FNCT_RSP", "EXTENDED FUNCTION RESPONSE"), + ISP_RESERVED_25(37, "RESERVED_25", "ISP RESERVED 37"), + ISP_RESERVED_26(38, "RESERVED_26", "ISP RESERVED 38"), + ISP_EMERGENCY_ALARM_REQUEST(39, "EMRG_ALRM_REQ", "EMERGENCY ALARM REQUEST"), + ISP_GROUP_AFFILIATION_REQUEST(40, "GRP_AFF_REQ", "GROUP AFFILIATION REQUEST"), + ISP_GROUP_AFFILIATION_QUERY_RESPONSE(41, "GRP_AFF_Q_RSP", "GROUP AFFILIATION QUERY RESPONSE"), + ISP_RESERVED_2A(42, "RESERVED_2A", "ISP RESERVED 42"), + ISP_UNIT_DE_REGISTRATION_REQUEST(43, "U_DE_REG_REQ", "UNIT DE-REGISTRATION REQUEST"), + ISP_UNIT_REGISTRATION_REQUEST(44, "U_REG_REQ", "UNIT REGISTRATION REQUEST"), + ISP_LOCATION_REGISTRATION_REQUEST(45, "LOC_REG_REQ", "LOCATION REGISTRATION REQUEST"), + ISP_AUTHENTICATION_QUERY_OBSOLETE(46, "AUTH_Q", "AUTHENTICATION QUERY-OBSOLETE"), //OBSOLETE + ISP_AUTHENTICATION_RESPONSE_OBSOLETE(47, "AUTH_RSP", "AUTHENTICATION RESPONSE-OBSOLETE"), //OBSOLETE + ISP_PROTECTION_PARAMETER_REQUEST(48, "P_PARM_REQ", "ENCRYPTION PARAMETERS REQUEST"), + ISP_RESERVED_31(49, "RESERVED_31", "ISP RESERVED 49"), + ISP_IDENTIFIER_UPDATE_REQUEST(50, "IDEN_UP_REQ", "IDENTIFIER UPDATE REQUEST"), + ISP_RESERVED_33(51, "RESERVED_33", "ISP RESERVED 51"), + ISP_RESERVED_34(52, "RESERVED_34", "ISP RESERVED 52"), + ISP_RESERVED_35(53, "RESERVED_35", "ISP RESERVED 53"), + ISP_ROAMING_ADDRESS_REQUEST(54, "ROAM_ADDR_REQ", "ROAMING ADDRESS REQUEST"), + ISP_ROAMING_ADDRESS_RESPONSE(55, "ROAM_ADDR_RSP", "ROAMING ADDRESS RESPONSE"), + ISP_AUTHENTICATION_RESPONSE(56, "AUTH_RESP", "AUTHENTICATION RESPONSE"), + ISP_AUTHENTICATION_RESPONSE_MUTUAL(57, "AUTH_RESP_M", "AUTHENTICATION RESPONSE MUTUAL"), + ISP_AUTHENTICATION_FNE_RESULT(58, "AUTH_FNE_RST", "AUTHENTICATION RESULT"), + ISP_AUTHENTICATION_SU_DEMAND(59, "AUTH_SU_DMD", "AUTHENTICATION SU DEMAND"), + ISP_RESERVED_3C(60, "RESERVED_3C", "ISP RESERVED 60"), + ISP_RESERVED_3D(61, "RESERVED_3D", "ISP RESERVED 61"), + ISP_RESERVED_3E(62, "RESERVED_3E", "ISP RESERVED 62"), + ISP_RESERVED_3F(63, "RESERVED_3F", "ISP RESERVED 63"), + ISP_UNKNOWN(-1, "ISP UNKNOWN", "ISP UNKNOWN OPCODE"), + + //Vendor: motorola, Inbound Service Packet (ISP) + MOTOROLA_ISP_UNKNOWN(-1, "MOTOROLA ISP UNKNOWN OPCODE", "MOTOROLA ISP UNKNOWN OPCODE"), + + //Vendor: motorola, Outbound Service Packet (OSP) + MOTOROLA_OSP_PATCH_GROUP_ADD(0, "PATCH GROUP ADD", "MOTOROLA PATCH GROUP ADD"), + MOTOROLA_OSP_PATCH_GROUP_DELETE(1, "PATCH GROUP DELE", "MOTOROLA PATCH GROUP DELETE"), + MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT(2, "PTCH GRP VCHN GR", "MOTOROLA PATCH GROUP CHANNEL GRANT"), + MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT_UPDATE(3, "PTCH GRP VCH UPD", "MOTOROLA PATCH GROUP CHANNEL GRANT UPDATE"), + MOTOROLA_OSP_TRAFFIC_CHANNEL_ID(5, "CHANNEL CWID", "CHANNEL CWID"), + MOTOROLA_OSP_SYSTEM_LOADING(9, "SYSTEM LOADING", "SYSTEM LOADING"), + MOTOROLA_OSP_CONTROL_CHANNEL_ID(11, "CCH BASE STAT ID", "CONTROL CHANNEL BASE STATION ID"), + MOTOROLA_OSP_CONTROL_CHANNEL_PLANNED_SHUTDOWN(14, "CCH PLND SHUTDWN", "CONTROL CHANNEL PLANNED SHUTDOWN"), + MOTOROLA_OSP_UNKNOWN(-1, "MOTOROLA OSP UNKNOWN OPCODE", "MOTOROLA OSP UNKNOWN OPCODE"), + + //Vendor: unknown, Inbound Service Packet (ISP) + UNKNOWN_VENDOR_ISP(-1, "UNKNOWN VENDOR/OPCODE ISP", "UNKNOWN VENDOR ISP OPCODE"), + + //Vendor: unknown, Outbound Service Packet (OSP) + UNKNOWN_VENDOR_OSP(-1, "UNKNOWN VENDOR/OPCODE OSP", "UNKNOWN VENDOR OSP OPCODE"); + + private int mCode; + private String mLabel; + private String mDescription; + + public static final EnumSet STANDARD_OUTBOUND_OPCODES = EnumSet.range(OSP_GROUP_VOICE_CHANNEL_GRANT, + OSP_PROTECTION_PARAMETER_UPDATE); + public static final EnumSet STANDARD_INBOUND_OPCODES = EnumSet.range(ISP_GROUP_VOICE_SERVICE_REQUEST, + ISP_RESERVED_3F); + + Opcode(int code, String label, String description) + { + mCode = code; + mLabel = label; + mDescription = description; + } + + /** + * Formatted, fixed-length label + */ + public String getLabel() + { + return mLabel; + } + + /** + * Full description text + */ + public String getDescription() + { + return mDescription; + } + + /** + * Numeric value for the opcode + */ + public int getCode() + { + return mCode; + } + + @Override + public String toString() + { + return mLabel; + } + + @Deprecated //use the ISP/OSP version of this method instead + public static Opcode fromValue(int value) + { + if(0 <= value && value <= 63) + { + return values()[value]; + } + + return OSP_UNKNOWN; + } + + public static Opcode fromValue(int value, Direction direction, Vendor vendor) + { + switch(vendor) + { + case STANDARD: + if(direction == Direction.OUTBOUND) + { + if(0 <= value && value <= 63) + { + for(Opcode outboundOpcode : STANDARD_OUTBOUND_OPCODES) + { + if(outboundOpcode.getCode() == value) + { + return outboundOpcode; + } + } + } + + return OSP_UNKNOWN; + } + else + { + if(0 <= value && value <= 63) + { + for(Opcode inboundOpcode : STANDARD_INBOUND_OPCODES) + { + if(inboundOpcode.getCode() == value) + { + return inboundOpcode; + } + } + } + + return ISP_UNKNOWN; + } + case MOTOROLA: + if(direction == Direction.INBOUND) + { + return MOTOROLA_ISP_UNKNOWN; + } + else + { + switch(value) + { + case 0x00: + return MOTOROLA_OSP_PATCH_GROUP_ADD; + case 0x01: + return MOTOROLA_OSP_PATCH_GROUP_DELETE; + case 0x02: + return MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT; + case 0x03: + return MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT_UPDATE; + case 0x05: + return MOTOROLA_OSP_TRAFFIC_CHANNEL_ID; + case 0x09: + return MOTOROLA_OSP_SYSTEM_LOADING; + case 0x0B: + return MOTOROLA_OSP_CONTROL_CHANNEL_ID; + case 0x0E: + return MOTOROLA_OSP_CONTROL_CHANNEL_PLANNED_SHUTDOWN; + default: + return MOTOROLA_OSP_UNKNOWN; + } + } + default: + if(direction == Direction.INBOUND) + { + return UNKNOWN_VENDOR_ISP; + } + else + { + return UNKNOWN_VENDOR_OSP; + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/ServiceMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/ServiceMessage.java deleted file mode 100644 index 9e46a463..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/ServiceMessage.java +++ /dev/null @@ -1,43 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.P25Message; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public abstract class ServiceMessage extends TSBKMessage -{ - /* Service Options */ - public static final int EMERGENCY_FLAG = 80; - public static final int ENCRYPTED_CHANNEL_FLAG = 81; - public static final int DUPLEX_MODE = 82; - public static final int SESSION_MODE = 83; - - public ServiceMessage( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - public boolean isEmergency() - { - return mMessage.get( EMERGENCY_FLAG ); - } - - public boolean isEncryptedChannel() - { - return mMessage.get( ENCRYPTED_CHANNEL_FLAG ); - } - - public P25Message.DuplexMode getDuplexMode() - { - return mMessage.get( DUPLEX_MODE ) ? P25Message.DuplexMode.FULL : P25Message.DuplexMode.HALF; - } - - public P25Message.SessionMode getSessionMode() - { - return mMessage.get( SESSION_MODE ) ? - P25Message.SessionMode.CIRCUIT : P25Message.SessionMode.PACKET; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/TSBKMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/TSBKMessage.java index 6753a1e0..7f5bf752 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/TSBKMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/TSBKMessage.java @@ -1,147 +1,147 @@ package io.github.dsheirer.module.decode.p25.message.tsbk; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.edac.CRC; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.CRCP25; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.P25Utils; import io.github.dsheirer.module.decode.p25.message.P25Message; -import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.MotorolaOpcode; -import io.github.dsheirer.module.decode.p25.message.tsbk.vendor.VendorOpcode; import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; +import io.github.dsheirer.module.decode.p25.reference.Direction; import io.github.dsheirer.module.decode.p25.reference.Vendor; -public class TSBKMessage extends P25Message +import java.util.List; + +/** + * APCO 25 Trunking Signalling Block (TSBK) + */ +public abstract class TSBKMessage extends P25Message { - public static final int MESSAGE_START = 64; - public static final int LAST_BLOCK_FLAG = 64; - public static final int ENCRYPTED_FLAG = 65; - public static final int[] OPCODE = { 66,67,68,69,70,71 }; - public static final int[] VENDOR_ID = { 72,73,74,75,76,77,78,79 }; - - public static final int[] BLOCK1 = { 64,65,66,67,68,69,70,71 }; - public static final int[] BLOCK2 = { 72,73,74,75,76,77,78,79 }; - public static final int[] BLOCK3 = { 80,81,82,83,84,85,86,87 }; - public static final int[] BLOCK4 = { 88,89,90,91,92,93,94,95 }; - public static final int[] BLOCK5 = { 96,97,98,99,100,101,102,103 }; - public static final int[] BLOCK6 = { 104,105,106,107,108,109,110,111 }; - public static final int[] BLOCK7 = { 112,113,114,115,116,117,118,119 }; - public static final int[] BLOCK8 = { 120,121,122,123,124,125,126,127 }; - public static final int[] BLOCK9 = { 128,129,130,131,132,133,134,135 }; - public static final int[] BLOCK10 = { 136,137,138,139,140,141,142,143 }; - public static final int[] BLOCK11 = { 144,145,146,147,148,149,150,151 }; - public static final int[] BLOCK12 = { 152,153,154,155,156,157,158,159 }; - public static final int CRC_START = 144; + private static final int LAST_BLOCK_FLAG = 0; + private static final int ENCRYPTION_FLAG = 1; + private static final int[] OPCODE = {2, 3, 4, 5, 6, 7}; + private static final int[] VENDOR = {8, 9, 10, 11, 12, 13, 14, 15}; - public TSBKMessage( BinaryMessage message, DataUnitID duid, AliasList aliasList ) + private DataUnitID mDataUnitID; + + /** + * Constructs a TSBK from the binary message sequence. + * + * @param dataUnitID TSBK1/2/3 + * @param message binary sequence + * @param nac decoded from the NID + * @param timestamp for the message + */ + public TSBKMessage(DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timestamp) { - super( message, duid, aliasList ); + super(message, nac, timestamp); + mDataUnitID = dataUnitID; - /* Since CRC is checked before construction, mark it as passed */ - mCRC = new CRC[ 1 ]; - mCRC[ 0 ] = CRC.PASSED; + //The CRC-CCITT can correct up to 1 bit error or detect 2 or more errors. We mark the message as + //invalid if the algorithm detects more than 1 correctable error. + int errors = CRCP25.correctCCITT80(message, 0, 80); + if(errors > 1) + { + setValid(false); + } } - - public boolean isLastBlock() - { - return mMessage.get( LAST_BLOCK_FLAG ); - } - - public boolean isEncrypted() - { - return mMessage.get( ENCRYPTED_FLAG ); - } - - public Vendor getVendor() - { - return Vendor.fromValue( mMessage.getInt( VENDOR_ID ) ); - } - - public Opcode getOpcode() - { - return Opcode.fromValue( mMessage.getInt( OPCODE ) ); - } - - public MotorolaOpcode getMotorolaOpcode() - { - return MotorolaOpcode.fromValue( mMessage.getInt( OPCODE ) ); - } - - public VendorOpcode getVendorOpcode() - { - return VendorOpcode.fromValue( mMessage.getInt( OPCODE ) ); - } - - @Override - public String getMessage() + + @Override + public DataUnitID getDUID() { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " - " + getMessageHex() ); - - return sb.toString(); + return mDataUnitID; } - - protected String getMessageStub() - { - StringBuilder sb = new StringBuilder(); - Vendor vendor = getVendor(); - - sb.append( "NAC:" ); - sb.append( getNAC() ); - sb.append( " " ); - sb.append( getDUID().getLabel() ); + /** + * Indicates if this is an encrypted LCW + */ + public boolean isEncrypted() + { + return getMessage().get(ENCRYPTION_FLAG); + } - if( vendor == Vendor.STANDARD ) - { - if( isEncrypted() ) - { - sb.append( " ENCRYPTED" ); - } - else - { - sb.append( " " ); - sb.append( getOpcode().getLabel() ); - } - } - else - { - sb.append( " " ); - sb.append( vendor.getLabel() ); + /** + * Indicates if this is the last TSBK in a sequence (1-3 blocks) + */ + public boolean isLastBlock() + { + return isLastBlock(getMessage()); + } - if( isEncrypted() ) - { - sb.append( " ENCRYPTED" ); - } - else - { - sb.append( " OPCD:" ); - sb.append( mMessage.getHex( OPCODE, 2 ) ); - } - } - - return sb.toString(); - } - - public String getMessageHex() - { - StringBuilder sb = new StringBuilder(); - - sb.append( mMessage.getHex( BLOCK1, 2 ) + " " ); - sb.append( mMessage.getHex( BLOCK2, 2 ) + " " ); - sb.append( mMessage.getHex( BLOCK3, 2 ) + " " ); - sb.append( mMessage.getHex( BLOCK4, 2 ) + " " ); - sb.append( mMessage.getHex( BLOCK5, 2 ) + " " ); - sb.append( mMessage.getHex( BLOCK6, 2 ) + " " ); - sb.append( mMessage.getHex( BLOCK7, 2 ) + " " ); - sb.append( mMessage.getHex( BLOCK8, 2 ) + " " ); - sb.append( mMessage.getHex( BLOCK9, 2 ) + " " ); - sb.append( mMessage.getHex( BLOCK10, 2 ) + " " ); - sb.append( mMessage.getHex( BLOCK11, 2 ) + " " ); - sb.append( mMessage.getHex( BLOCK12, 2 ) + " " ); - - return sb.toString(); - } + /** + * Indicates if this is the last TSBK in a sequence (1-3 blocks) + */ + public static boolean isLastBlock(BinaryMessage binaryMessage) + { + return binaryMessage.get(LAST_BLOCK_FLAG); + } + + /** + * Vendor format for this TSBK. + */ + public Vendor getVendor() + { + return getVendor(getMessage()); + } + + /** + * Lookup the Vendor format for the specified LCW + */ + public static Vendor getVendor(BinaryMessage binaryMessage) + { + return Vendor.fromValue(binaryMessage.getInt(VENDOR)); + } + + /** + * Opcode for this TSBK + */ + public Opcode getOpcode() + { + return getOpcode(getMessage(), getDirection(), getVendor()); + } + + /** + * Opcode for this TSBK + */ + public static Opcode getOpcode(BinaryMessage binaryMessage, Direction direction, Vendor vendor) + { + return Opcode.fromValue(binaryMessage.getInt(OPCODE), direction, vendor); + } + + /** + * Direction - inbound (ISP) or outbound (OSP) + */ + public abstract Direction getDirection(); + + /** + * List of identifiers provided by the message + */ + public abstract List getIdentifiers(); + + /** + * Creates a string with the basic TSBK information + */ + public String getMessageStub() + { + StringBuilder sb = new StringBuilder(); + sb.append(super.getMessageStub()); + + if(isValid()) + { + sb.append(" ").append(getOpcode().getLabel()); + + P25Utils.pad(sb, 30); + + if(isEncrypted()) + { + sb.append(" ENCRYPTED"); + } + } + else + { + sb.append("**CRC-FAILED**"); + } + + return sb.toString(); + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/TSBKMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/TSBKMessageFactory.java index f5b225aa..b2e0503a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/TSBKMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/TSBKMessageFactory.java @@ -1,202 +1,305 @@ package io.github.dsheirer.module.decode.p25.message.tsbk; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.ControlChannelBaseStationIdentification; -import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.MotorolaOpcode; -import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.MotorolaTSBKMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.PatchGroupAdd; -import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.PatchGroupDelete; -import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.PatchGroupVoiceChannelGrant; -import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.PatchGroupVoiceChannelGrantUpdate; -import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.PlannedControlChannnelShutdown; -import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.SystemLoading; -import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.TrafficChannelBaseStationIdentification; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.AcknowledgeResponse; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.AdjacentStatusBroadcast; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.AuthenticationCommand; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.CallAlert; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.DenyResponse; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.ExtendedFunctionCommand; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.GroupAffiliationQuery; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.GroupAffiliationResponse; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.IdentifierUpdateNonVUHF; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.IdentifierUpdateTDMA; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.IdentifierUpdateVUHF; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.LocationRegistrationResponse; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.MessageUpdate; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.NetworkStatusBroadcast; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.ProtectionParameterUpdate; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.QueuedResponse; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.RFSSStatusBroadcast; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.RadioUnitMonitorCommand; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.RoamingAddressCommand; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SecondaryControlChannelBroadcast; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SecondaryControlChannelBroadcastExplicit; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.StatusQuery; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.StatusUpdate; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SyncBroadcast; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.SystemServiceBroadcast; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.TimeAndDateAnnouncement; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.UnitDeregistrationAcknowledge; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.UnitRegistrationCommand; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.control.UnitRegistrationResponse; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.data.GroupDataChannelAnnouncement; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.data.GroupDataChannelAnnouncementExplicit; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.data.GroupDataChannelGrant; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.data.IndividualDataChannelGrant; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.data.SNDCPDataChannelAnnouncementExplicit; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.data.SNDCPDataChannelGrant; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.data.SNDCPDataPageRequest; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice.GroupVoiceChannelGrant; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice.GroupVoiceChannelGrantUpdate; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice.GroupVoiceChannelGrantUpdateExplicit; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice.TelephoneInterconnectAnswerRequest; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice.TelephoneInterconnectVoiceChannelGrant; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice.UnitToUnitAnswerRequest; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice.UnitToUnitVoiceChannelGrant; -import io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice.UnitToUnitVoiceChannelGrantUpdate; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.trellis.ViterbiDecoder_1_2_P25; +import io.github.dsheirer.module.decode.p25.P25Interleave; +import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.isp.UnknownMotorolaISPMessage; +import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp.ChannelCWID; +import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp.ChannelLoading; +import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp.PatchGroupAdd; +import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp.PatchGroupDelete; +import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp.PatchGroupVoiceChannelGrant; +import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp.PatchGroupVoiceChannelGrantUpdate; +import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp.PlannedChannelShutdown; +import io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp.UnknownMotorolaOSPMessage; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.AuthenticationQuery; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.CallAlertRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.CancelServiceRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.EmergencyAlarmRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.ExtendedFunctionResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.FrequencyBandUpdateRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.GroupAffiliationQueryResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.GroupAffiliationRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.GroupVoiceServiceRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.IndividualDataServiceRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.LocationRegistrationRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.MessageUpdateRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.ProtectionParameterRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.RadioUnitMonitorRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.RoamingAddressRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.RoamingAddressResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.SNDCPDataChannelRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.SNDCPDataPageResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.SNDCPReconnectRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.StatusQueryRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.StatusQueryResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.StatusUpdateRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.TelephoneInterconnectAnswerResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.TelephoneInterconnectPstnRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.UnitAcknowledgeResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.UnitDeRegistrationRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.UnitRegistrationRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.UnitToUnitVoiceServiceAnswerResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.UnitToUnitVoiceServiceRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp.UnknownISPMessage; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.AcknowledgeResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.AdjacentStatusBroadcast; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.AuthenticationCommand; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.CallAlert; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.DenyResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.ExtendedFunctionCommand; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.FrequencyBandUpdate; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.FrequencyBandUpdateTDMA; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.FrequencyBandUpdateVUHF; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.GroupAffiliationQuery; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.GroupAffiliationResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.GroupDataChannelAnnouncement; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.GroupDataChannelAnnouncementExplicit; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.GroupDataChannelGrant; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.GroupVoiceChannelGrant; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.GroupVoiceChannelGrantUpdate; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.GroupVoiceChannelGrantUpdateExplicit; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.IndividualDataChannelGrant; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.LocationRegistrationResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.MessageUpdate; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.NetworkStatusBroadcast; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.ProtectionParameterUpdate; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.QueuedResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.RFSSStatusBroadcast; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.RadioUnitMonitorCommand; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.RoamingAddressCommand; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.SNDCPDataChannelAnnouncementExplicit; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.SNDCPDataChannelGrant; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.SNDCPDataPageRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.SecondaryControlChannelBroadcast; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.StatusQuery; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.StatusUpdate; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.SyncBroadcast; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.SystemServiceBroadcast; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.TelephoneInterconnectAnswerRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.TelephoneInterconnectVoiceChannelGrant; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.TelephoneInterconnectVoiceChannelGrantUpdate; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.UnitDeRegistrationAcknowledge; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.UnitRegistrationCommand; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.UnitRegistrationResponse; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.UnitToUnitAnswerRequest; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.UnitToUnitVoiceChannelGrant; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.UnitToUnitVoiceChannelGrantUpdate; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.UnknownOSPMessage; +import io.github.dsheirer.module.decode.p25.message.tsbk.unknown.isp.UnknownVendorISPMessage; +import io.github.dsheirer.module.decode.p25.message.tsbk.unknown.osp.UnknownVendorOSPMessage; import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; +import io.github.dsheirer.module.decode.p25.reference.Direction; import io.github.dsheirer.module.decode.p25.reference.Vendor; +/** + * Factory for creating Trunking Signalling Block (TSBK) message parser classes + */ public class TSBKMessageFactory { - public static TSBKMessage getMessage( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - Vendor vendor = Vendor.fromValue( - message.getInt( TSBKMessage.VENDOR_ID ) ); - - switch( vendor ) - { - case STANDARD: - Opcode opcode = - Opcode.fromValue( message.getInt( TSBKMessage.OPCODE ) ); + private static final ViterbiDecoder_1_2_P25 VITERBI_HALF_RATE_DECODER = new ViterbiDecoder_1_2_P25(); - switch( opcode ) - { - case ACKNOWLEDGE_RESPONSE: - return new AcknowledgeResponse( message, duid, aliasList ); - case ADJACENT_STATUS_BROADCAST: - return new AdjacentStatusBroadcast( message, duid, aliasList ); - case AUTHENTICATION_COMMAND: - return new AuthenticationCommand( message, duid, aliasList ); - case CALL_ALERT: - return new CallAlert( message, duid, aliasList ); - case DENY_RESPONSE: - return new DenyResponse( message, duid, aliasList ); - case EXTENDED_FUNCTION_COMMAND: - return new ExtendedFunctionCommand( message, duid, aliasList ); - case GROUP_AFFILIATION_QUERY: - return new GroupAffiliationQuery( message, duid, aliasList ); - case GROUP_AFFILIATION_RESPONSE: - return new GroupAffiliationResponse( message, duid, aliasList ); - case GROUP_DATA_CHANNEL_ANNOUNCEMENT: - return new GroupDataChannelAnnouncement( message, duid, aliasList ); - case GROUP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT: - return new GroupDataChannelAnnouncementExplicit( message, duid, aliasList ); - case GROUP_DATA_CHANNEL_GRANT: - return new GroupDataChannelGrant( message, duid, aliasList ); - case GROUP_VOICE_CHANNEL_GRANT: - return new GroupVoiceChannelGrant( message, duid, aliasList ); - case GROUP_VOICE_CHANNEL_GRANT_UPDATE: - return new GroupVoiceChannelGrantUpdate( message, duid, aliasList ); - case GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: - return new GroupVoiceChannelGrantUpdateExplicit( message, duid, aliasList ); - case IDENTIFIER_UPDATE_NON_VUHF: - return new IdentifierUpdateNonVUHF( message, duid, aliasList ); - case IDENTIFIER_UPDATE_TDMA: - return new IdentifierUpdateTDMA( message, duid, aliasList ); - case IDENTIFIER_UPDATE_VHF_UHF_BANDS: - return new IdentifierUpdateVUHF( message, duid, aliasList ); - case INDIVIDUAL_DATA_CHANNEL_GRANT: - return new IndividualDataChannelGrant( message, duid, aliasList ); - case LOCATION_REGISTRATION_RESPONSE: - return new LocationRegistrationResponse( message, duid, aliasList ); - case MESSAGE_UPDATE: - return new MessageUpdate( message, duid, aliasList ); - case NETWORK_STATUS_BROADCAST: - return new NetworkStatusBroadcast( message, duid, aliasList ); - case QUEUED_RESPONSE: - return new QueuedResponse( message, duid, aliasList ); - case PROTECTION_PARAMETER_UPDATE: - return new ProtectionParameterUpdate( message, duid, aliasList ); - case RADIO_UNIT_MONITOR_COMMAND: - return new RadioUnitMonitorCommand( message, duid, aliasList ); - case RFSS_STATUS_BROADCAST: - return new RFSSStatusBroadcast( message, duid, aliasList ); - case ROAMING_ADDRESS_COMMAND: - return new RoamingAddressCommand( message, duid, aliasList ); - case SECONDARY_CONTROL_CHANNEL_BROADCAST: - return new SecondaryControlChannelBroadcast( message, duid, aliasList ); - case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: - return new SecondaryControlChannelBroadcastExplicit( message, duid, aliasList ); - case SNDCP_DATA_CHANNEL_GRANT: - return new SNDCPDataChannelGrant( message, duid, aliasList ); - case SNDCP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT: - return new SNDCPDataChannelAnnouncementExplicit( message, duid, aliasList ); - case SNDCP_DATA_PAGE_REQUEST: - return new SNDCPDataPageRequest( message, duid, aliasList ); - case STATUS_QUERY: - return new StatusQuery( message, duid, aliasList ); - case STATUS_UPDATE: - return new StatusUpdate( message, duid, aliasList ); - case TDMA_SYNC_BROADCAST: - return new SyncBroadcast( message, duid, aliasList ); - case SYSTEM_SERVICE_BROADCAST: - return new SystemServiceBroadcast( message, duid, aliasList ); - case TELEPHONE_INTERCONNECT_ANSWER_REQUEST: - return new TelephoneInterconnectAnswerRequest( message, duid, aliasList ); - case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: - return new TelephoneInterconnectVoiceChannelGrant( message, duid, aliasList ); - case TIME_DATE_ANNOUNCEMENT: - return new TimeAndDateAnnouncement( message, duid, aliasList ); - case UNIT_DEREGISTRATION_ACKNOWLEDGE: - return new UnitDeregistrationAcknowledge( message, duid, aliasList ); - case UNIT_REGISTRATION_COMMAND: - return new UnitRegistrationCommand( message, duid, aliasList ); - case UNIT_REGISTRATION_RESPONSE: - return new UnitRegistrationResponse( message, duid, aliasList ); - case UNIT_TO_UNIT_ANSWER_REQUEST: - return new UnitToUnitAnswerRequest( message, duid, aliasList ); - case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: - return new UnitToUnitVoiceChannelGrant( message, duid, aliasList ); - case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: - return new UnitToUnitVoiceChannelGrantUpdate( message, duid, aliasList ); - default: - return new TSBKMessage( message, duid, aliasList ); - } - - case MOTOROLA: - MotorolaOpcode motorolaOpcode = MotorolaOpcode. - fromValue( message.getInt( TSBKMessage.OPCODE ) ); - - switch( motorolaOpcode ) - { - case CCH_PLANNED_SHUTDOWN: - return new PlannedControlChannnelShutdown( message, duid, aliasList ); - case CONTROL_CHANNEL_ID: - return new ControlChannelBaseStationIdentification( message, duid, aliasList ); - case PATCH_GROUP_ADD: - return new PatchGroupAdd( message, duid, aliasList ); - case PATCH_GROUP_DELETE: - return new PatchGroupDelete( message, duid, aliasList ); - case PATCH_GROUP_CHANNEL_GRANT: - return new PatchGroupVoiceChannelGrant( message, duid, aliasList ); - case PATCH_GROUP_CHANNEL_GRANT_UPDATE: - return new PatchGroupVoiceChannelGrantUpdate( message, duid, aliasList ); - case SYSTEM_LOAD: - return new SystemLoading( message, duid, aliasList ); - case TRAFFIC_CHANNEL_ID: - return new TrafficChannelBaseStationIdentification( message, duid, aliasList ); - default: - } - - return new MotorolaTSBKMessage( message, duid, aliasList ); - default: - return new TSBKMessage( message, duid, aliasList ); - } - } -} \ No newline at end of file + public static TSBKMessage create(Direction direction, DataUnitID dataUnitID, + CorrectedBinaryMessage correctedBinaryMessage, int nac, long timestamp) + { + //Get deinterleaved header chunk + CorrectedBinaryMessage deinterleaved = P25Interleave.deinterleaveChunk(P25Interleave.DATA_DEINTERLEAVE, correctedBinaryMessage); + + //Decode 1/2 rate trellis encoded PDU header + CorrectedBinaryMessage message = VITERBI_HALF_RATE_DECODER.decode(deinterleaved); + + if(message == null) + { + return null; + } + + Vendor vendor = TSBKMessage.getVendor(message); + Opcode opcode = TSBKMessage.getOpcode(message, direction, vendor); + + switch(opcode) + { + case ISP_AUTHENTICATION_QUERY_OBSOLETE: + return new AuthenticationQuery(dataUnitID, message, nac, timestamp); + case ISP_CALL_ALERT_REQUEST: + return new CallAlertRequest(dataUnitID, message, nac, timestamp); + case ISP_CANCEL_SERVICE_REQUEST: + return new CancelServiceRequest(dataUnitID, message, nac, timestamp); + case ISP_EMERGENCY_ALARM_REQUEST: + return new EmergencyAlarmRequest(dataUnitID, message, nac, timestamp); + case ISP_EXTENDED_FUNCTION_RESPONSE: + return new ExtendedFunctionResponse(dataUnitID, message, nac, timestamp); + case ISP_GROUP_AFFILIATION_QUERY_RESPONSE: + return new GroupAffiliationQueryResponse(dataUnitID, message, nac, timestamp); + case ISP_GROUP_AFFILIATION_REQUEST: + return new GroupAffiliationRequest(dataUnitID, message, nac, timestamp); + case ISP_GROUP_VOICE_SERVICE_REQUEST: + return new GroupVoiceServiceRequest(dataUnitID, message, nac, timestamp); + case ISP_IDENTIFIER_UPDATE_REQUEST: + return new FrequencyBandUpdateRequest(dataUnitID, message, nac, timestamp); + case ISP_INDIVIDUAL_DATA_SERVICE_REQUEST: + return new IndividualDataServiceRequest(dataUnitID, message, nac, timestamp); + case ISP_LOCATION_REGISTRATION_REQUEST: + return new LocationRegistrationRequest(dataUnitID, message, nac, timestamp); + case ISP_MESSAGE_UPDATE_REQUEST: + return new MessageUpdateRequest(dataUnitID, message, nac, timestamp); + case ISP_PROTECTION_PARAMETER_REQUEST: + return new ProtectionParameterRequest(dataUnitID, message, nac, timestamp); + case ISP_RADIO_UNIT_MONITOR_REQUEST: + return new RadioUnitMonitorRequest(dataUnitID, message, nac, timestamp); + case ISP_ROAMING_ADDRESS_REQUEST: + return new RoamingAddressRequest(dataUnitID, message, nac, timestamp); + case ISP_ROAMING_ADDRESS_RESPONSE: + return new RoamingAddressResponse(dataUnitID, message, nac, timestamp); + case ISP_SNDCP_DATA_CHANNEL_REQUEST: + return new SNDCPDataChannelRequest(dataUnitID, message, nac, timestamp); + case ISP_SNDCP_DATA_PAGE_RESPONSE: + return new SNDCPDataPageResponse(dataUnitID, message, nac, timestamp); + case ISP_SNDCP_RECONNECT_REQUEST: + return new SNDCPReconnectRequest(dataUnitID, message, nac, timestamp); + case ISP_STATUS_QUERY_REQUEST: + return new StatusQueryRequest(dataUnitID, message, nac, timestamp); + case ISP_STATUS_QUERY_RESPONSE: + return new StatusQueryResponse(dataUnitID, message, nac, timestamp); + case ISP_STATUS_UPDATE_REQUEST: + return new StatusUpdateRequest(dataUnitID, message, nac, timestamp); + case ISP_TELEPHONE_INTERCONNECT_ANSWER_RESPONSE: + return new TelephoneInterconnectAnswerResponse(dataUnitID, message, nac, timestamp); + case ISP_TELEPHONE_INTERCONNECT_PSTN_REQUEST: + return new TelephoneInterconnectPstnRequest(dataUnitID, message, nac, timestamp); + case ISP_UNIT_ACKNOWLEDGE_RESPONSE: + return new UnitAcknowledgeResponse(dataUnitID, message, nac, timestamp); + case ISP_UNIT_REGISTRATION_REQUEST: + return new UnitRegistrationRequest(dataUnitID, message, nac, timestamp); + case ISP_UNIT_DE_REGISTRATION_REQUEST: + return new UnitDeRegistrationRequest(dataUnitID, message, nac, timestamp); + case ISP_UNIT_TO_UNIT_ANSWER_RESPONSE: + return new UnitToUnitVoiceServiceAnswerResponse(dataUnitID, message, nac, timestamp); + case ISP_UNIT_TO_UNIT_VOICE_SERVICE_REQUEST: + return new UnitToUnitVoiceServiceRequest(dataUnitID, message, nac, timestamp); + case OSP_ACKNOWLEDGE_RESPONSE: + return new AcknowledgeResponse(dataUnitID, message, nac, timestamp); + case OSP_ADJACENT_STATUS_BROADCAST: + return new AdjacentStatusBroadcast(dataUnitID, message, nac, timestamp); + case OSP_AUTHENTICATION_COMMAND: + return new AuthenticationCommand(dataUnitID, message, nac, timestamp); + case OSP_CALL_ALERT: + return new CallAlert(dataUnitID, message, nac, timestamp); + case OSP_DENY_RESPONSE: + return new DenyResponse(dataUnitID, message, nac, timestamp); + case OSP_EXTENDED_FUNCTION_COMMAND: + return new ExtendedFunctionCommand(dataUnitID, message, nac, timestamp); + case OSP_GROUP_AFFILIATION_QUERY: + return new GroupAffiliationQuery(dataUnitID, message, nac, timestamp); + case OSP_GROUP_AFFILIATION_RESPONSE: + return new GroupAffiliationResponse(dataUnitID, message, nac, timestamp); + case OSP_GROUP_DATA_CHANNEL_ANNOUNCEMENT: + return new GroupDataChannelAnnouncement(dataUnitID, message, nac, timestamp); + case OSP_GROUP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT: + return new GroupDataChannelAnnouncementExplicit(dataUnitID, message, nac, timestamp); + case OSP_GROUP_DATA_CHANNEL_GRANT: + return new GroupDataChannelGrant(dataUnitID, message, nac, timestamp); + case OSP_GROUP_VOICE_CHANNEL_GRANT: + return new GroupVoiceChannelGrant(dataUnitID, message, nac, timestamp); + case OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE: + return new GroupVoiceChannelGrantUpdate(dataUnitID, message, nac, timestamp); + case OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: + return new GroupVoiceChannelGrantUpdateExplicit(dataUnitID, message, nac, timestamp); + case OSP_IDENTIFIER_UPDATE: + return new FrequencyBandUpdate(dataUnitID, message, nac, timestamp); + case OSP_IDENTIFIER_UPDATE_TDMA: + return new FrequencyBandUpdateTDMA(dataUnitID, message, nac, timestamp); + case OSP_IDENTIFIER_UPDATE_VHF_UHF_BANDS: + return new FrequencyBandUpdateVUHF(dataUnitID, message, nac, timestamp); + case OSP_INDIVIDUAL_DATA_CHANNEL_GRANT: + return new IndividualDataChannelGrant(dataUnitID, message, nac, timestamp); + case OSP_LOCATION_REGISTRATION_RESPONSE: + return new LocationRegistrationResponse(dataUnitID, message, nac, timestamp); + case OSP_MESSAGE_UPDATE: + return new MessageUpdate(dataUnitID, message, nac, timestamp); + case OSP_NETWORK_STATUS_BROADCAST: + return new NetworkStatusBroadcast(dataUnitID, message, nac, timestamp); + case OSP_PROTECTION_PARAMETER_UPDATE: + return new ProtectionParameterUpdate(dataUnitID, message, nac, timestamp); + case OSP_RADIO_UNIT_MONITOR_COMMAND: + return new RadioUnitMonitorCommand(dataUnitID, message, nac, timestamp); + case OSP_QUEUED_RESPONSE: + return new QueuedResponse(dataUnitID, message, nac, timestamp); + case OSP_ROAMING_ADDRESS_COMMAND: + return new RoamingAddressCommand(dataUnitID, message, nac, timestamp); + case OSP_RFSS_STATUS_BROADCAST: + return new RFSSStatusBroadcast(dataUnitID, message, nac, timestamp); + case OSP_SECONDARY_CONTROL_CHANNEL_BROADCAST: + return new SecondaryControlChannelBroadcast(dataUnitID, message, nac, timestamp); + case OSP_SNDCP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT: + return new SNDCPDataChannelAnnouncementExplicit(dataUnitID, message, nac, timestamp); + case OSP_SNDCP_DATA_CHANNEL_GRANT: + return new SNDCPDataChannelGrant(dataUnitID, message, nac, timestamp); + case OSP_SNDCP_DATA_PAGE_REQUEST: + return new SNDCPDataPageRequest(dataUnitID, message, nac, timestamp); + case OSP_STATUS_QUERY: + return new StatusQuery(dataUnitID, message, nac, timestamp); + case OSP_STATUS_UPDATE: + return new StatusUpdate(dataUnitID, message, nac, timestamp); + case OSP_TDMA_SYNC_BROADCAST: + return new SyncBroadcast(dataUnitID, message, nac, timestamp); + case OSP_SYSTEM_SERVICE_BROADCAST: + return new SystemServiceBroadcast(dataUnitID, message, nac, timestamp); + case OSP_TELEPHONE_INTERCONNECT_ANSWER_REQUEST: + return new TelephoneInterconnectAnswerRequest(dataUnitID, message, nac, timestamp); + case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: + return new TelephoneInterconnectVoiceChannelGrant(dataUnitID, message, nac, timestamp); + case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: + return new TelephoneInterconnectVoiceChannelGrantUpdate(dataUnitID, message, nac, timestamp); + case OSP_UNIT_DEREGISTRATION_ACKNOWLEDGE: + return new UnitDeRegistrationAcknowledge(dataUnitID, message, nac, timestamp); + case OSP_UNIT_REGISTRATION_COMMAND: + return new UnitRegistrationCommand(dataUnitID, message, nac, timestamp); + case OSP_UNIT_REGISTRATION_RESPONSE: + return new UnitRegistrationResponse(dataUnitID, message, nac, timestamp); + case OSP_UNIT_TO_UNIT_ANSWER_REQUEST: + return new UnitToUnitAnswerRequest(dataUnitID, message, nac, timestamp); + case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: + return new UnitToUnitVoiceChannelGrant(dataUnitID, message, nac, timestamp); + case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: + return new UnitToUnitVoiceChannelGrantUpdate(dataUnitID, message, nac, timestamp); + + case MOTOROLA_ISP_UNKNOWN: + return new UnknownMotorolaISPMessage(dataUnitID, message, nac, timestamp); + + case MOTOROLA_OSP_CONTROL_CHANNEL_ID: + return new ChannelCWID(dataUnitID, message, nac, timestamp); + case MOTOROLA_OSP_CONTROL_CHANNEL_PLANNED_SHUTDOWN: + return new PlannedChannelShutdown(dataUnitID, message, nac, timestamp); + case MOTOROLA_OSP_TRAFFIC_CHANNEL_ID: + return new ChannelCWID(dataUnitID, message, nac, timestamp); + case MOTOROLA_OSP_PATCH_GROUP_ADD: + return new PatchGroupAdd(dataUnitID, message, nac, timestamp); + case MOTOROLA_OSP_PATCH_GROUP_DELETE: + return new PatchGroupDelete(dataUnitID, message, nac, timestamp); + case MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT: + return new PatchGroupVoiceChannelGrant(dataUnitID, message, nac, timestamp); + case MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT_UPDATE: + return new PatchGroupVoiceChannelGrantUpdate(dataUnitID, message, nac, timestamp); + case MOTOROLA_OSP_SYSTEM_LOADING: + return new ChannelLoading(dataUnitID, message, nac, timestamp); + case MOTOROLA_OSP_UNKNOWN: + return new UnknownMotorolaOSPMessage(dataUnitID, message, nac, timestamp); + + case UNKNOWN_VENDOR_ISP: + return new UnknownVendorISPMessage(dataUnitID, message, nac, timestamp); + + case UNKNOWN_VENDOR_OSP: + return new UnknownVendorOSPMessage(dataUnitID, message, nac, timestamp); + + default: + if(direction == Direction.INBOUND) + { + return new UnknownISPMessage(dataUnitID, message, nac, timestamp); + } + else + { + return new UnknownOSPMessage(dataUnitID, message, nac, timestamp); + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/UnitChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/UnitChannelGrant.java deleted file mode 100644 index 61208f0d..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/UnitChannelGrant.java +++ /dev/null @@ -1,90 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tsbk; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public abstract class UnitChannelGrant extends ChannelGrant -{ - public static final int[] TARGET_ADDRESS = {96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, - 111, 112, 113, 114, 115, 116, 117, 118, 119}; - public static final int[] SOURCE_ADDRESS = {120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, - 134, 135, 136, 137, 138, 139, 140, 141, 142, 143}; - - public UnitChannelGrant(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - if(isEmergency()) - { - sb.append(" EMERGENCY"); - } - - sb.append(" FROM:"); - sb.append(getSourceAddress()); - - sb.append(" TO:"); - sb.append(getTargetAddress()); - - return sb.toString(); - } - - public String getTargetAddress() - { - return mMessage.getHex(TARGET_ADDRESS, 6); - } - - public String getSourceAddress() - { - return mMessage.getHex(SOURCE_ADDRESS, 6); - } - - @Override - public String getFromID() - { - return getSourceAddress(); - } - - @Override - public Alias getFromIDAlias() - { - if(mAliasList != null) - { - return mAliasList.getTalkgroupAlias(getFromID()); - } - - return null; - } - - @Override - public String getToID() - { - return getTargetAddress(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/ControlChannelBaseStationIdentification.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/ControlChannelBaseStationIdentification.java deleted file mode 100644 index f7e38182..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/ControlChannelBaseStationIdentification.java +++ /dev/null @@ -1,145 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tsbk.motorola; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public class ControlChannelBaseStationIdentification extends MotorolaTSBKMessage implements IdentifierReceiver -{ - public static final int[] CHARACTER_1 = {80, 81, 82, 83, 84, 85}; - public static final int[] CHARACTER_2 = {86, 87, 88, 89, 90, 91}; - public static final int[] CHARACTER_3 = {92, 93, 94, 95, 96, 97}; - public static final int[] CHARACTER_4 = {98, 99, 100, 101, 102, 103}; - public static final int[] CHARACTER_5 = {104, 105, 106, 107, 108, 109}; - public static final int[] CHARACTER_6 = {110, 111, 112, 113, 114, 115}; - public static final int[] CHARACTER_7 = {116, 117, 118, 119, 120, 121}; - public static final int[] CHARACTER_8 = {122, 123, 124, 125, 126, 127}; - - public static final int[] IDENTIFIER = {128, 129, 130, 131}; - public static final int[] CHANNEL = {132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143}; - - private IBandIdentifier mIdentifierUpdate; - - public ControlChannelBaseStationIdentification(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - } - - @Override - public String getEventType() - { - return MotorolaOpcode.CONTROL_CHANNEL_ID.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" CHAN:" + getIdentifier() + "-" + getChannel()); - - sb.append(" CWID:" + getCWID()); - - sb.append(" DN:" + getDownlinkFrequency()); - - sb.append(" UP:" + getUplinkFrequency()); - - return sb.toString(); - } - - - public String getCWID() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getCharacter(CHARACTER_1)); - sb.append(getCharacter(CHARACTER_2)); - sb.append(getCharacter(CHARACTER_3)); - sb.append(getCharacter(CHARACTER_4)); - sb.append(getCharacter(CHARACTER_5)); - sb.append(getCharacter(CHARACTER_6)); - sb.append(getCharacter(CHARACTER_7)); - sb.append(getCharacter(CHARACTER_8)); - - return sb.toString(); - } - - private String getCharacter(int[] field) - { - int value = mMessage.getInt(field); - - if(value != 0) - { - return String.valueOf((char)(value + 43)); - } - - return null; - } - - public int getIdentifier() - { - return mMessage.getInt(IDENTIFIER); - } - - public int getChannel() - { - return mMessage.getInt(CHANNEL); - } - - public long getUplinkFrequency() - { - if(mIdentifierUpdate != null) - { - mIdentifierUpdate.getUplinkFrequency(getChannel()); - } - - return 0; - } - - public long getDownlinkFrequency() - { - if(mIdentifierUpdate != null) - { - mIdentifierUpdate.getDownlinkFrequency(getChannel()); - } - - return 0; - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - mIdentifierUpdate = message; - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[1]; - - identifiers[0] = getIdentifier(); - - return identifiers; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/MotorolaOpcode.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/MotorolaOpcode.java deleted file mode 100644 index 2e734ffb..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/MotorolaOpcode.java +++ /dev/null @@ -1,111 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.motorola; - -public enum MotorolaOpcode -{ - PATCH_GROUP_ADD( "ADD PATCH GROUP", "ADD PATCH SUPER GROUP", 0x00 ), - PATCH_GROUP_DELETE( "DELETE PATCH GROUP", "DELETE PATCH SUPER GROUP", 0x01 ), - PATCH_GROUP_CHANNEL_GRANT( "PATCH VOICE CHANNEL GRANT", "PATCH VOICE CHANNEL GRANT", 0x02 ), - PATCH_GROUP_CHANNEL_GRANT_UPDATE( "PATCH VOICE CHANNEL UPDATE", "PATCH VOICE CHANNEL GRANT UPDATE", 0x03 ), - OP04( "MOT OP04:UNKNOWN", "Opcode 0x04 Unknown", 0x04 ), - TRAFFIC_CHANNEL_ID( "TRAFFIC CHAN STATION ID", "TRAFFIC CHAN STATION ID", 0x05 ), - OP06( "MOT OP06:UNKNOWN ", "Opcode 0x06 Unknown", 0x06 ), - OP07( "MOT OP07:UNKNOWN ", "Opcode 0x07 Unknown", 0x07 ), - OP08( "MOT OP08:UNKNOWN ", "Opcode 0x08 Unknown", 0x08 ), - SYSTEM_LOAD( "SYSTEM LOADING", "SYSTEM LOAD", 0x09 ), - OP0A( "OP0A:UNKNOWN ", "Opcode 0x0A Unknown", 0x0A ), - CONTROL_CHANNEL_ID( "CONTROL CHAN BASE STATION ID", "CONTROL CHAN BASE STATION ID", 0x0B ), - OP0C( "MOT OP0C:UNKNOWN ", "Opcode 0x0C Unknown", 0x0C ), - OP0D( "MOT OP0D:UNKNOWN ", "Opcode 0x0D Unknown", 0x0D ), - CCH_PLANNED_SHUTDOWN( "PLANNED CONTROL CHANNEL SHUTDOWN ", "PLANNED CONTROL CHANNEL SHUTDOWN", 0x0E ), - OP0F( "MOT OP0F:UNKNOWN ", "Opcode 0x0F Unknown", 0x0F ), - OP10( "MOT OP10:UNKNOWN ", "Opcode 0x10 Unknown", 0x10 ), - OP11( "MOT OP11:UNKNOWN ", "Opcode 0x11 Unknown", 0x11 ), - OP12( "MOT OP12:UNKNOWN ", "Opcode 0x12 Unknown", 0x12 ), - OP13( "MOT OP13:UNKNOWN ", "Opcode 0x13 Unknown", 0x13 ), - OP14( "MOT OP14:UNKNOWN ", "Opcode 0x14 Unknown", 0x14 ), - OP15( "MOT OP15:UNKNOWN ", "Opcode 0x15 Unknown", 0x15 ), - OP16( "MOT OP16:UNKNOWN ", "Opcode 0x16 Unknown", 0x16 ), - OP17( "MOT OP17:UNKNOWN ", "Opcode 0x17 Unknown", 0x17 ), - OP18( "MOT OP18:UNKNOWN ", "Opcode 0x18 Unknown", 0x18 ), - OP19( "MOT OP19:UNKNOWN ", "Opcode 0x19 Unknown", 0x19 ), - OP1A( "MOT OP1A:UNKNOWN ", "Opcode 0x1A Unknown", 0x1A ), - OP1B( "MOT OP1B:UNKNOWN ", "Opcode 0x1B Unknown", 0x1B ), - OP1C( "MOT OP1C:UNKNOWN ", "Opcode 0x1C Unknown", 0x1C ), - OP1D( "MOT OP1D:UNKNOWN ", "Opcode 0x1D Unknown", 0x1D ), - OP1E( "MOT OP1E:UNKNOWN ", "Opcode 0x1E Unknown", 0x1E ), - OP1F( "MOT OP1F:UNKNOWN ", "Opcode 0x1F Unknown", 0x1F ), - OP20( "MOT OP20:UNKNOWN ", "Opcode 0x20 Unknown", 0x20 ), - OP21( "MOT OP21:UNKNOWN ", "Opcode 0x21 Unknown", 0x21 ), - OP22( "MOT OP22:UNKNOWN ", "Opcode 0x22 Unknown", 0x22 ), - OP23( "MOT OP23:UNKNOWN ", "Opcode 0x23 Unknown", 0x23 ), - OP24( "MOT OP24:UNKNOWN ", "Opcode 0x24 Unknown", 0x24 ), - OP25( "MOT OP25:UNKNOWN ", "Opcode 0x25 Unknown", 0x25 ), - OP26( "MOT OP26:UNKNOWN ", "Opcode 0x26 Unknown", 0x26 ), - OP27( "MOT OP27:UNKNOWN ", "Opcode 0x27 Unknown", 0x27 ), - OP28( "MOT OP28:UNKNOWN ", "Opcode 0x28 Unknown", 0x28 ), - OP29( "MOT OP29:UNKNOWN ", "Opcode 0x29 Unknown", 0x29 ), - OP2A( "MOT OP2A:UNKNOWN ", "Opcode 0x2A Unknown", 0x2A ), - OP2B( "MOT OP2B:UNKNOWN ", "Opcode 0x2B Unknown", 0x2B ), - OP2C( "MOT OP2C:UNKNOWN ", "Opcode 0x2C Unknown", 0x2C ), - OP2D( "MOT OP2D:UNKNOWN ", "Opcode 0x2D Unknown", 0x2D ), - OP2E( "MOT OP2E:UNKNOWN ", "Opcode 0x2E Unknown", 0x2E ), - OP2F( "MOT OP2F:UNKNOWN ", "Opcode 0x2F Unknown", 0x2F ), - OP30( "MOT OP30:UNKNOWN ", "Opcode 0x30 Unknown", 0x30 ), - OP31( "MOT OP31:UNKNOWN ", "Opcode 0x31 Unknown", 0x31 ), - OP32( "MOT OP32:UNKNOWN ", "Opcode 0x32 Unknown", 0x32 ), - OP33( "MOT OP33:UNKNOWN ", "Opcode 0x33 Unknown", 0x33 ), - OP34( "MOT OP34:UNKNOWN ", "Opcode 0x34 Unknown", 0x34 ), - OP35( "MOT OP35:UNKNOWN ", "Opcode 0x35 Unknown", 0x35 ), - OP36( "MOT OP36:UNKNOWN ", "Opcode 0x36 Unknown", 0x36 ), - OP37( "MOT OP37:UNKNOWN ", "Opcode 0x37 Unknown", 0x37 ), - OP38( "MOT OP38:UNKNOWN ", "Opcode 0x38 Unknown", 0x38 ), - OP39( "MOT OP39:UNKNOWN ", "Opcode 0x39 Unknown", 0x39 ), - OP3A( "MOT OP3A:UNKNOWN ", "Opcode 0x3A Unknown", 0x3A ), - OP3B( "MOT OP3B:UNKNOWN ", "Opcode 0x3B Unknown", 0x3B ), - OP3C( "MOT OP3C:UNKNOWN ", "Opcode 0x3C Unknown", 0x3C ), - OP3D( "MOT OP3D:UNKNOWN ", "Opcode 0x3D Unknown", 0x3D ), - OP3E( "MOT OP3E:UNKNOWN ", "Opcode 0x3E Unknown", 0x3E ), - OP3F( "MOT OP3F:UNKNOWN ", "Opcode 0x3F Unknown", 0x3F ), - UNKNOWN( "UNKNOWN OPCODE ", "Unknown", -1 ); - - private String mLabel; - private String mDescription; - private int mCode; - - private MotorolaOpcode( String label, String description, int code ) - { - mLabel = label; - mDescription = description; - mCode = code; - } - - public String getLabel() - { - return mLabel; - } - - public String toString() - { - return getLabel(); - } - - public String getDescription() - { - return mDescription; - } - - public int getCode() - { - return mCode; - } - - public static MotorolaOpcode fromValue( int value ) - { - if( 0 <= value && value <= 0x3F ) - { - return MotorolaOpcode.values()[ value ]; - } - - return UNKNOWN; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/MotorolaTSBKMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/MotorolaTSBKMessage.java deleted file mode 100644 index a85713ac..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/MotorolaTSBKMessage.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.motorola; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public class MotorolaTSBKMessage extends TSBKMessage -{ - public MotorolaTSBKMessage( BinaryMessage message, DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - protected String getMessageStub() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "NAC:" ); - sb.append( getNAC() ); /* NAC is the system id for TSBK messages */ - sb.append( " " ); - sb.append( getDUID().getLabel() ); - - sb.append( " MOTOROLA " ); - - if( isEncrypted() ) - { - sb.append( " ENCRYPTED " ); - } - - sb.append( getMotorolaOpcode().getLabel() ); - - return sb.toString(); - } - - public MotorolaOpcode getMotorolaOpcode() - { - return MotorolaOpcode.fromValue( mMessage.getInt( OPCODE ) ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/PatchGroup.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/PatchGroup.java deleted file mode 100644 index b7a859a9..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/PatchGroup.java +++ /dev/null @@ -1,92 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.motorola; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -import java.util.ArrayList; -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, - 88,89,90,91,92,93,94,95 }; - public static final int[] GROUP_ADDRESS_1 = { 96,97,98,99,100,101,102,103, - 104,105,106,107,108,109,110,111 }; - public static final int[] GROUP_ADDRESS_2 = { 112,113,114,115,116,117,118, - 119,120,121,122,123,124,125,126,127 }; - 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 ) - { - super( message, duid, aliasList ); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " " ); - sb.append( getPatchGroupAddress() ); - sb.append(" "); - sb.append(getPatchedTalkgroups()); - - return sb.toString(); - } - - public String getPatchGroupAddress() - { - 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() - { - return mMessage.getHex( GROUP_ADDRESS_1, 4 ); - } - - public String getGroupAddress2() - { - return mMessage.getHex( GROUP_ADDRESS_2, 4 ); - } - - public String getGroupAddress3() - { - return mMessage.getHex( GROUP_ADDRESS_3, 4 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/PatchGroupAdd.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/PatchGroupAdd.java deleted file mode 100644 index 10244c95..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/PatchGroupAdd.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.motorola; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public class PatchGroupAdd extends PatchGroup -{ - public PatchGroupAdd( BinaryMessage message, DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return MotorolaOpcode.PATCH_GROUP_ADD.getLabel(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/PatchGroupDelete.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/PatchGroupDelete.java deleted file mode 100644 index f3ddbfa1..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/PatchGroupDelete.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.motorola; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public class PatchGroupDelete extends PatchGroup -{ - public PatchGroupDelete( BinaryMessage message, DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return MotorolaOpcode.PATCH_GROUP_DELETE.getLabel(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/PatchGroupVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/PatchGroupVoiceChannelGrant.java deleted file mode 100644 index 5d93b82f..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/PatchGroupVoiceChannelGrant.java +++ /dev/null @@ -1,187 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tsbk.motorola; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public class PatchGroupVoiceChannelGrant extends MotorolaTSBKMessage implements IdentifierReceiver -{ - /* Service Options */ - public static final int EMERGENCY_FLAG = 80; - public static final int ENCRYPTED_CHANNEL_FLAG = 81; - public static final int DUPLEX_MODE = 82; - public static final int SESSION_MODE = 83; - - public static final int[] PRIORITY = {85, 86, 87}; - public static final int[] IDENTIFIER = {88, 89, 90, 91}; - public static final int[] CHANNEL = {92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103}; - public static final int[] PATCH_GROUP_ADDRESS = {104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, - 117, 118, 119}; - public static final int[] SOURCE_ADDRESS = {120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, - 134, 135, 136, 137, 138, 139, 140, 141, 142, 143}; - - private IBandIdentifier mIdentifierUpdate; - - public PatchGroupVoiceChannelGrant(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - } - - public boolean isTDMAChannel() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.isTDMA(); - } - - return false; - } - - @Override - public String getEventType() - { - return MotorolaOpcode.PATCH_GROUP_CHANNEL_GRANT.getLabel(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - if(isEmergency()) - { - sb.append(" EMERGENCY"); - } - - sb.append(" PATCH GROUP:"); - sb.append(getPatchGroupAddress()); - sb.append(" FROM:"); - sb.append(getSourceAddress()); - sb.append(" PRI:"); - sb.append(getPriority()); - - sb.append(" CHAN:"); - sb.append(getChannelIdentifier() + "-" + getChannelNumber()); - - sb.append(" DN:" + getDownlinkFrequency()); - - sb.append(" UP:" + getUplinkFrequency()); - - sb.append(" SESSION MODE:"); - sb.append(getSessionMode().name()); - - return sb.toString(); - } - - public String getPatchGroupAddress() - { - return mMessage.getHex(PATCH_GROUP_ADDRESS, 4); - } - - public String getSourceAddress() - { - return mMessage.getHex(SOURCE_ADDRESS, 6); - } - - public boolean isEmergency() - { - return mMessage.get(EMERGENCY_FLAG); - } - - public boolean isEncryptedChannel() - { - return mMessage.get(ENCRYPTED_CHANNEL_FLAG); - } - - public DuplexMode getDuplexMode() - { - return mMessage.get(DUPLEX_MODE) ? DuplexMode.FULL : DuplexMode.HALF; - } - - public SessionMode getSessionMode() - { - return mMessage.get(SESSION_MODE) ? - SessionMode.CIRCUIT : SessionMode.PACKET; - } - - /** - * 1 = Lowest, 4 = Default, 7 = Highest - */ - public int getPriority() - { - return mMessage.getInt(PRIORITY); - } - - public int getChannelIdentifier() - { - return mMessage.getInt(IDENTIFIER); - } - - public int getChannelNumber() - { - return mMessage.getInt(CHANNEL); - } - - public String getChannel() - { - return getChannelIdentifier() + "-" + getChannelNumber(); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - mIdentifierUpdate = message; - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[1]; - identifiers[0] = getChannelIdentifier(); - - return identifiers; - } - - public long getDownlinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getDownlinkFrequency(getChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getUplinkFrequency(getChannelNumber()); - } - - return 0; - } - -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/PatchGroupVoiceChannelGrantUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/PatchGroupVoiceChannelGrantUpdate.java deleted file mode 100644 index 98b3b3f2..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/PatchGroupVoiceChannelGrantUpdate.java +++ /dev/null @@ -1,197 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tsbk.motorola; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public class PatchGroupVoiceChannelGrantUpdate extends MotorolaTSBKMessage implements IdentifierReceiver -{ - public static final int[] IDENTIFIER_1 = {80, 81, 82, 83}; - public static final int[] CHANNEL_1 = {84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95}; - public static final int[] PATCH_GROUP_ADDRESS_1 = {96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111}; - public static final int[] IDENTIFIER_2 = {112, 113, 114, 115}; - public static final int[] CHANNEL_2 = {116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127}; - public static final int[] PATCH_GROUP_ADDRESS_2 = {128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143}; - - private IBandIdentifier mIdentifierUpdate1; - private IBandIdentifier mIdentifierUpdate2; - - public PatchGroupVoiceChannelGrantUpdate(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - } - - @Override - public String getEventType() - { - return MotorolaOpcode.PATCH_GROUP_CHANNEL_GRANT_UPDATE.getLabel(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - - sb.append(" PATCH GRP1:"); - sb.append(getPatchGroupAddress1()); - sb.append(" GRP2:"); - sb.append(getPatchGroupAddress2()); - - sb.append(" CHAN 1:"); - sb.append(getChannel1()); - sb.append(" DN:" + getDownlinkFrequency1()); - - sb.append(" CHAN 2:"); - sb.append(getChannel2()); - sb.append(" DN:" + getDownlinkFrequency2()); - - return sb.toString(); - } - - public String getPatchGroupAddress1() - { - return mMessage.getHex(PATCH_GROUP_ADDRESS_1, 4); - } - - public String getPatchGroupAddress2() - { - return mMessage.getHex(PATCH_GROUP_ADDRESS_2, 4); - } - - public int getChannelIdentifier1() - { - return mMessage.getInt(IDENTIFIER_1); - } - - public int getChannelNumber1() - { - return mMessage.getInt(CHANNEL_1); - } - - public String getChannel1() - { - return getChannelIdentifier1() + "-" + getChannelNumber1(); - } - - public int getChannelIdentifier2() - { - return mMessage.getInt(IDENTIFIER_2); - } - - public int getChannelNumber2() - { - return mMessage.getInt(CHANNEL_2); - } - - public String getChannel2() - { - return getChannelIdentifier2() + "-" + getChannelNumber2(); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getChannelIdentifier1()) - { - mIdentifierUpdate1 = message; - } - - if(identifier == getChannelIdentifier2()) - { - mIdentifierUpdate2 = message; - } - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[2]; - identifiers[0] = getChannelIdentifier1(); - identifiers[1] = getChannelIdentifier2(); - - return identifiers; - } - - public long getDownlinkFrequency1() - { - if(mIdentifierUpdate1 != null) - { - return mIdentifierUpdate1.getDownlinkFrequency(getChannelNumber1()); - } - - return 0; - } - - public boolean isTDMAChannel1() - { - if(mIdentifierUpdate1 != null) - { - return mIdentifierUpdate1.isTDMA(); - } - - return false; - } - - public long getUplinkFrequency1() - { - if(mIdentifierUpdate1 != null) - { - return mIdentifierUpdate1.getUplinkFrequency(getChannelNumber1()); - } - - return 0; - } - - public long getDownlinkFrequency2() - { - if(mIdentifierUpdate2 != null) - { - return mIdentifierUpdate2.getDownlinkFrequency(getChannelNumber2()); - } - - return 0; - } - - public boolean isTDMAChannel2() - { - if(mIdentifierUpdate2 != null) - { - return mIdentifierUpdate2.isTDMA(); - } - - return false; - } - - public long getUplinkFrequency2() - { - if(mIdentifierUpdate2 != null) - { - return mIdentifierUpdate2.getUplinkFrequency(getChannelNumber2()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/PlannedControlChannnelShutdown.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/PlannedControlChannnelShutdown.java deleted file mode 100644 index 1654165d..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/PlannedControlChannnelShutdown.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.motorola; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public class PlannedControlChannnelShutdown extends MotorolaTSBKMessage -{ - public PlannedControlChannnelShutdown( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return MotorolaOpcode.CCH_PLANNED_SHUTDOWN.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - return sb.toString(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/SystemLoading.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/SystemLoading.java deleted file mode 100644 index 7e11128c..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/SystemLoading.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.motorola; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public class SystemLoading extends MotorolaTSBKMessage -{ - public SystemLoading( BinaryMessage message, DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return MotorolaOpcode.SYSTEM_LOAD.getLabel(); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " - " + getMessageHex() ); - - return sb.toString(); - } - -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/TrafficChannelBaseStationIdentification.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/TrafficChannelBaseStationIdentification.java deleted file mode 100644 index c833cb88..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/TrafficChannelBaseStationIdentification.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.motorola; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public class TrafficChannelBaseStationIdentification extends MotorolaTSBKMessage -{ - public static final int[] CHARACTER_1 = { 80,81,82,83,84,85 }; - public static final int[] CHARACTER_2 = { 86,87,88,89,90,91 }; - public static final int[] CHARACTER_3 = { 92,93,94,95,96,97 }; - public static final int[] CHARACTER_4 = { 98,99,100,101,102,103 }; - public static final int[] CHARACTER_5 = { 104,105,106,107,108,109 }; - public static final int[] CHARACTER_6 = { 110,111,112,113,114,115 }; - public static final int[] CHARACTER_7 = { 116,117,118,119,120,121 }; - public static final int[] CHARACTER_8 = { 122,123,124,125,126,127 }; - - public TrafficChannelBaseStationIdentification( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return MotorolaOpcode.TRAFFIC_CHANNEL_ID.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " ID:" + getID() ); - - return sb.toString(); - } - - - public String getID() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getCharacter( CHARACTER_1 ) ); - sb.append( getCharacter( CHARACTER_2 ) ); - sb.append( getCharacter( CHARACTER_3 ) ); - sb.append( getCharacter( CHARACTER_4 ) ); - sb.append( getCharacter( CHARACTER_5 ) ); - sb.append( getCharacter( CHARACTER_6 ) ); - sb.append( getCharacter( CHARACTER_7 ) ); - sb.append( getCharacter( CHARACTER_8 ) ); - - return sb.toString(); - } - - private String getCharacter( int[] field ) - { - int value = mMessage.getInt( field ); - - return String.valueOf( (char)( value + 43 ) ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/isp/UnknownMotorolaISPMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/isp/UnknownMotorolaISPMessage.java new file mode 100644 index 00000000..bf30b1ab --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/isp/UnknownMotorolaISPMessage.java @@ -0,0 +1,58 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.motorola.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.Collections; +import java.util.List; + +/** + * Unknown/Unrecognized opcode message. + */ +public class UnknownMotorolaISPMessage extends OSPMessage +{ + /** + * Constructs a TSBK from the binary message sequence. + */ + public UnknownMotorolaISPMessage(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" MOTOROLA **UNRECOGNIZED ISP OPCODE**"); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/ChannelCWID.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/ChannelCWID.java new file mode 100644 index 00000000..577d9b33 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/ChannelCWID.java @@ -0,0 +1,137 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ChannelCWID extends OSPMessage implements IFrequencyBandReceiver +{ + public static final int[] CHARACTER_1 = {16, 17, 18, 19, 20, 21}; + public static final int[] CHARACTER_2 = {22, 23, 24, 25, 26, 27}; + public static final int[] CHARACTER_3 = {28, 29, 30, 31, 32, 33}; + public static final int[] CHARACTER_4 = {34, 35, 36, 37, 38, 39}; + public static final int[] CHARACTER_5 = {40, 41, 42, 43, 44, 45}; + public static final int[] CHARACTER_6 = {46, 47, 48, 49, 50, 51}; + public static final int[] CHARACTER_7 = {52, 53, 54, 55, 56, 57}; + public static final int[] CHARACTER_8 = {58, 59, 60, 61, 62, 63}; + public static final int[] FREQUENCY_BAND = {64, 65, 66, 67}; + public static final int[] CHANNEL_NUMBER = {68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + + private String mCWID; + private IChannelDescriptor mChannel; + + public ChannelCWID(DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitID, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append(getMessageStub()); + + if(hasChannel()) + { + sb.append(" CHAN:" + getChannel()); + sb.append(" CWID:" + getCWID()); + } + else + { + sb.append(" CWID NOT SPECIFIED"); + } + + return sb.toString(); + } + + public boolean hasChannel() + { + return getMessage().getInt(CHANNEL_NUMBER) != 0; + } + + + public String getCWID() + { + if(mCWID == null) + { + StringBuilder sb = new StringBuilder(); + + sb.append(getCharacter(CHARACTER_1)); + sb.append(getCharacter(CHARACTER_2)); + sb.append(getCharacter(CHARACTER_3)); + sb.append(getCharacter(CHARACTER_4)); + sb.append(getCharacter(CHARACTER_5)); + sb.append(getCharacter(CHARACTER_6)); + sb.append(getCharacter(CHARACTER_7)); + sb.append(getCharacter(CHARACTER_8)); + + mCWID = sb.toString(); + } + + return mCWID; + } + + private String getCharacter(int[] field) + { + int value = getMessage().getInt(field); + + if(value != 0) + { + return String.valueOf((char)(value + 43)); + } + + return ""; + } + + public IChannelDescriptor getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), + getMessage().getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/ChannelLoading.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/ChannelLoading.java new file mode 100644 index 00000000..3c7a0cc8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/ChannelLoading.java @@ -0,0 +1,58 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.Collections; +import java.util.List; + +/** + * Channel loading + */ +public class ChannelLoading extends OSPMessage +{ + /** + * Constructs a TSBK from the binary message sequence. + */ + public ChannelLoading(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" MOTOROLA"); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/PatchGroupAdd.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/PatchGroupAdd.java new file mode 100644 index 00000000..2970b9a9 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/PatchGroupAdd.java @@ -0,0 +1,179 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +public class PatchGroupAdd extends OSPMessage +{ + public static final int[] PATCH_GROUP_ADDRESS = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + public static final int[] GROUP_ADDRESS_1 = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + public static final int[] GROUP_ADDRESS_2 = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + public static final int[] GROUP_ADDRESS_3 = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + + private APCO25PatchGroup mPatchGroup; + private TalkgroupIdentifier mGroupAddress1; + private TalkgroupIdentifier mGroupAddress2; + private TalkgroupIdentifier mGroupAddress3; + private List mPatchedTalkgroups; + private List mIdentifiers; + + public PatchGroupAdd(DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timeslot) + { + super(dataUnitID, message, nac, timeslot); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" PATCH GROUP:").append(getPatchGroup()); + return sb.toString(); + } + + public Identifier getPatchGroup() + { + if(mPatchGroup == null) + { + PatchGroup patchGroup = new PatchGroup(APCO25ToTalkgroup.createGroup(getPatchAddress())); + patchGroup.addPatchedGroups(getPatchedTalkgroups()); + mPatchGroup = APCO25PatchGroup.create(patchGroup); + } + + return mPatchGroup; + } + + private int getPatchAddress() + { + return getMessage().getInt(PATCH_GROUP_ADDRESS); + } + + + /** + * List of de-duplicated patched talkgroups contained in this message + */ + public List getPatchedTalkgroups() + { + if(mPatchedTalkgroups == null) + { + mPatchedTalkgroups = new ArrayList<>(); + + if(hasGroupAddress1()) + { + mPatchedTalkgroups.add(getGroupAddress1()); + } + + if(hasGroupAddress2()) + { + mPatchedTalkgroups.add(getGroupAddress2()); + } + + if(hasGroupAddress3()) + { + mPatchedTalkgroups.add(getGroupAddress3()); + } + } + + return mPatchedTalkgroups; + } + + public TalkgroupIdentifier getGroupAddress1() + { + if(mGroupAddress1 == null) + { + mGroupAddress1 = APCO25ToTalkgroup.createGroup(getAddress1()); + } + + return mGroupAddress1; + } + + public boolean hasGroupAddress1() + { + return getPatchAddress() != getAddress1(); + } + + private int getAddress1() + { + return getMessage().getInt(GROUP_ADDRESS_1); + } + + public TalkgroupIdentifier getGroupAddress2() + { + if(mGroupAddress2 == null) + { + mGroupAddress2 = APCO25ToTalkgroup.createGroup(getAddress2()); + } + + return mGroupAddress2; + } + + private int getAddress2() + { + return getMessage().getInt(GROUP_ADDRESS_2); + } + + public boolean hasGroupAddress2() + { + return getPatchAddress() != getAddress2() && getAddress1() != getAddress2(); + } + + public TalkgroupIdentifier getGroupAddress3() + { + if(mGroupAddress3 == null) + { + mGroupAddress3 = APCO25ToTalkgroup.createGroup(getAddress3()); + } + + return mGroupAddress3; + } + + private int getAddress3() + { + return getMessage().getInt(GROUP_ADDRESS_3); + } + + public boolean hasGroupAddress3() + { + return getPatchAddress() != getAddress3() && getAddress1() != getAddress3() && getAddress2() != getAddress3(); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getPatchGroup()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/PatchGroupDelete.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/PatchGroupDelete.java new file mode 100644 index 00000000..91b811a7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/PatchGroupDelete.java @@ -0,0 +1,182 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +public class PatchGroupDelete extends OSPMessage +{ + public static final int[] PATCH_GROUP_ADDRESS = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + public static final int[] GROUP_ADDRESS_1 = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + public static final int[] GROUP_ADDRESS_2 = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + public static final int[] GROUP_ADDRESS_3 = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + + private APCO25PatchGroup mPatchGroup; + private TalkgroupIdentifier mGroupAddress1; + private TalkgroupIdentifier mGroupAddress2; + private TalkgroupIdentifier mGroupAddress3; + private List mPatchedTalkgroups; + private List mIdentifiers; + + public PatchGroupDelete(DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timeslot) + { + super(dataUnitID, message, nac, timeslot); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" PATCH GROUP:").append(getPatchGroup()); + return sb.toString(); + } + + public Identifier getPatchGroup() + { + if(mPatchGroup == null) + { + PatchGroup patchGroup = new PatchGroup(APCO25ToTalkgroup.createGroup(getPatchAddress())); + patchGroup.addPatchedGroups(getPatchedTalkgroups()); + mPatchGroup = APCO25PatchGroup.create(patchGroup); + } + + return mPatchGroup; + } + + private int getPatchAddress() + { + return getMessage().getInt(PATCH_GROUP_ADDRESS); + } + + + /** + * List of de-duplicated patched talkgroups contained in this message + */ + public List getPatchedTalkgroups() + { + if(mPatchedTalkgroups == null) + { + mPatchedTalkgroups = new ArrayList<>(); + + if(hasAddress1()) + { + mPatchedTalkgroups.add(getGroupAddress1()); + } + + if(hasGroupAddress2()) + { + mPatchedTalkgroups.add(getGroupAddress2()); + } + + if(hasGroupAddress3()) + { + mPatchedTalkgroups.add(getGroupAddress3()); + } + } + + return mPatchedTalkgroups; + } + + public TalkgroupIdentifier getGroupAddress1() + { + if(mGroupAddress1 == null) + { + mGroupAddress1 = APCO25ToTalkgroup.createGroup(getAddress1()); + } + + return mGroupAddress1; + } + + private int getAddress1() + { + return getMessage().getInt(GROUP_ADDRESS_1); + } + + private boolean hasAddress1() + { + return getPatchAddress() != getAddress1(); + } + + public TalkgroupIdentifier getGroupAddress2() + { + if(mGroupAddress2 == null) + { + mGroupAddress2 = APCO25ToTalkgroup.createGroup(getAddress2()); + } + + return mGroupAddress2; + } + + private int getAddress2() + { + return getMessage().getInt(GROUP_ADDRESS_2); + } + + public boolean hasGroupAddress2() + { + return getPatchAddress() != getAddress2() && + getAddress1() != getAddress2(); + } + + public TalkgroupIdentifier getGroupAddress3() + { + if(mGroupAddress3 == null) + { + mGroupAddress3 = APCO25ToTalkgroup.createGroup(getAddress3()); + } + + return mGroupAddress3; + } + + private int getAddress3() + { + return getMessage().getInt(GROUP_ADDRESS_3); + } + + public boolean hasGroupAddress3() + { + return getPatchAddress() != getAddress2() && + getAddress1() != getAddress3() && + getAddress2() != getAddress3(); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getPatchGroup()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/PatchGroupVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/PatchGroupVoiceChannelGrant.java new file mode 100644 index 00000000..d914e5ea --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/PatchGroupVoiceChannelGrant.java @@ -0,0 +1,132 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +public class PatchGroupVoiceChannelGrant extends OSPMessage implements IFrequencyBandReceiver +{ + public static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + public static final int[] FREQUENCY_BAND = {24, 25, 26, 27}; + public static final int[] CHANNEL_NUMBER = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + public static final int[] PATCH_GROUP_ADDRESS = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + public static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private VoiceServiceOptions mVoiceServiceOptions; + private APCO25Channel mChannel; + private Identifier mSourceAddress; + private PatchGroupIdentifier mPatchGroup; + private List mIdentifiers; + + public PatchGroupVoiceChannelGrant(DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timestlot) + { + super(dataUnitID, message, nac, timestlot); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append(getMessageStub()); + sb.append(" PATCH GROUP:").append(getPatchGroup()); + sb.append(" FROM:").append(getSourceAddress()); + sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + + return sb.toString(); + } + + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + public PatchGroupIdentifier getPatchGroup() + { + if(mPatchGroup == null) + { + mPatchGroup = APCO25PatchGroup.create(new PatchGroup(APCO25ToTalkgroup.createGroup(getMessage().getInt(PATCH_GROUP_ADDRESS)))); + } + + return mPatchGroup; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), getMessage().getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getPatchGroup()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/PatchGroupVoiceChannelGrantUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/PatchGroupVoiceChannelGrantUpdate.java new file mode 100644 index 00000000..631883dd --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/PatchGroupVoiceChannelGrantUpdate.java @@ -0,0 +1,153 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +public class PatchGroupVoiceChannelGrantUpdate extends OSPMessage implements IFrequencyBandReceiver +{ + public static final int[] FREQUENCY_BAND_1 = {16, 17, 18, 19}; + public static final int[] CHANNEL_NUMBER_1 = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + public static final int[] PATCH_GROUP_1 = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + public static final int[] FREQUENCY_BAND_2 = {48, 49, 50, 51}; + public static final int[] CHANNEL_NUMBER_2 = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + public static final int[] PATCH_GROUP_2 = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + + private PatchGroupIdentifier mPatchGroup1; + private PatchGroupIdentifier mPatchGroup2; + private APCO25Channel mChannel1; + private APCO25Channel mChannel2; + private List mIdentifiers; + + public PatchGroupVoiceChannelGrantUpdate(DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitID, message, nac, timestamp); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append(getMessageStub()); + + sb.append(" PATCH GRP1:"); + sb.append(getPatchGroup1()); + sb.append(" CHAN 1:"); + sb.append(getChannel1()); + + if(hasPatchGroup2()) + { + sb.append(" GRP2:"); + sb.append(getPatchGroup2()); + sb.append(" CHAN 2:"); + sb.append(getChannel2()); + } + + return sb.toString(); + } + + public boolean hasPatchGroup2() + { + return (getMessage().getInt(PATCH_GROUP_1) != getMessage().getInt(PATCH_GROUP_2)) && + getMessage().getInt(PATCH_GROUP_2) != 0; + } + + public PatchGroupIdentifier getPatchGroup1() + { + if(mPatchGroup1 == null) + { + mPatchGroup1 = APCO25PatchGroup.create(new PatchGroup(APCO25ToTalkgroup.createGroup(getMessage().getInt(PATCH_GROUP_1)))); + } + + return mPatchGroup1; + } + + public PatchGroupIdentifier getPatchGroup2() + { + if(mPatchGroup2 == null) + { + mPatchGroup2 = APCO25PatchGroup.create(new PatchGroup(APCO25ToTalkgroup.createGroup(getMessage().getInt(PATCH_GROUP_2)))); + } + + return mPatchGroup2; + } + + public APCO25Channel getChannel1() + { + if(mChannel1 == null) + { + mChannel1 = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND_1), getMessage().getInt(CHANNEL_NUMBER_1)); + } + + return mChannel1; + } + + public APCO25Channel getChannel2() + { + if(hasPatchGroup2() && mChannel2 == null) + { + mChannel2 = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND_2), getMessage().getInt(CHANNEL_NUMBER_2)); + } + + return mChannel2; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel1()); + if(hasPatchGroup2()) + { + channels.add(getChannel2()); + } + return channels; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getPatchGroup1()); + if(hasPatchGroup2()) + { + mIdentifiers.add(getPatchGroup2()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/PlannedChannelShutdown.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/PlannedChannelShutdown.java new file mode 100644 index 00000000..ee0aea2e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/PlannedChannelShutdown.java @@ -0,0 +1,58 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.Collections; +import java.util.List; + +/** + * Planned channel shutdown + */ +public class PlannedChannelShutdown extends OSPMessage +{ + /** + * Constructs a TSBK from the binary message sequence. + */ + public PlannedChannelShutdown(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" MOTOROLA"); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/UnknownMotorolaOSPMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/UnknownMotorolaOSPMessage.java new file mode 100644 index 00000000..0933eac1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/motorola/osp/UnknownMotorolaOSPMessage.java @@ -0,0 +1,58 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.motorola.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.Collections; +import java.util.List; + +/** + * Unknown/Unrecognized opcode message. + */ +public class UnknownMotorolaOSPMessage extends OSPMessage +{ + /** + * Constructs a TSBK from the binary message sequence. + */ + public UnknownMotorolaOSPMessage(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" MOTOROLA **UNRECOGNIZED OSP OPCODE**"); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/AcknowledgeResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/AcknowledgeResponse.java deleted file mode 100644 index fd9424b8..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/AcknowledgeResponse.java +++ /dev/null @@ -1,121 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class AcknowledgeResponse extends TSBKMessage -{ - public static final int ADDITIONAL_INFORMATION_FLAG = 80; - public static final int EXTENDED_ADDRESS_FLAG = 81; - public static final int[] SERVICE_TYPE = { 82,83,84,85,86,87 }; - public static final int[] WACN = { 88,89,90,91,92,93,94,95,96,97,98,99,100, - 101,102,103,104,105,106,107 }; - public static final int[] SYSTEM_ID = { 108,109,110,111,112,113,114,115,116, - 117,118,119 }; - public static final int[] SOURCE_ADDRESS = { 96,97,98,99,100,101,102,103, - 104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119 }; - public static final int[] TARGET_ADDRESS = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public AcknowledgeResponse( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.ACKNOWLEDGE_RESPONSE.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " SVC TYPE: " + getServiceType() ); - - if( hasAdditionalInformation() ) - { - if( hasExtendedAddress() ) - { - sb.append( " WACN: " + getWACN() ); - sb.append( " SYS ID: " + getSystemID() ); - } - else - { - sb.append( " SRC: " + getSourceAddress() ); - } - } - - return sb.toString(); - } - - public boolean hasAdditionalInformation() - { - return mMessage.get( ADDITIONAL_INFORMATION_FLAG ); - } - - public boolean hasExtendedAddress() - { - return mMessage.get( EXTENDED_ADDRESS_FLAG ); - } - - public String getServiceType() - { - return mMessage.getHex( SERVICE_TYPE, 2 ); - } - - public String getWACN() - { - if( hasAdditionalInformation() && !hasExtendedAddress() ) - { - return mMessage.getHex( WACN, 5 ); - } - - return null; - } - - public String getSystemID() - { - if( hasAdditionalInformation() && !hasExtendedAddress() ) - { - return mMessage.getHex( SYSTEM_ID, 3 ); - } - - return null; - } - - public String getSourceAddress() - { - if( hasAdditionalInformation() && !hasExtendedAddress() ) - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } - - return null; - } - - @Override - public String getFromID() - { - return getSourceAddress(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - @Override - public String getToID() - { - return getTargetAddress(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/AdjacentStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/AdjacentStatusBroadcast.java deleted file mode 100644 index f2adf300..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/AdjacentStatusBroadcast.java +++ /dev/null @@ -1,216 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.IAdjacentSite; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class AdjacentStatusBroadcast extends TSBKMessage implements IdentifierReceiver, IAdjacentSite -{ - public static final int[] LOCATION_REGISTRATION_AREA = {80, 81, 82, 83, 84, 85, 86, 87}; - public static final int CONVENTIONAL_CHANNEL_FLAG = 88; - public static final int SITE_FAILURE_CONDITION_FLAG = 89; - public static final int VALID_FLAG = 90; - public static final int ACTIVE_NETWORK_CONNECTION_FLAG = 91; - public static final int[] SYSTEM_ID = {92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103}; - public static final int[] RFSS_ID = {104, 105, 106, 107, 108, 109, 110, 111}; - public static final int[] SITE_ID = {112, 113, 114, 115, 116, 117, 118, 119}; - public static final int[] IDENTIFIER = {120, 121, 122, 123}; - public static final int[] CHANNEL = {124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135}; - public static final int[] SYSTEM_SERVICE_CLASS = {136, 137, 138, 139, 140, 141, 142, 143}; - - private IBandIdentifier mIdentifierUpdate; - - public AdjacentStatusBroadcast(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - } - - @Override - public String getEventType() - { - return Opcode.ADJACENT_STATUS_BROADCAST.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" LRA:" + getLRA()); - - sb.append(" SYS:" + getSystemID()); - - sb.append(" SITE:" + getRFSS() + "-" + getSiteID()); - - sb.append(" CHAN:" + getIdentifier() + "-" + getChannel()); - - sb.append(" DN:" + getDownlinkFrequency()); - - sb.append(" UP:" + getUplinkFrequency()); - - if(isConventionalChannel()) - { - sb.append(" CONVENTIONAL"); - } - - if(isSiteFailureCondition()) - { - sb.append(" FAILURE-CONDITION"); - } - - if(isValidSiteInformation()) - { - sb.append(" VALID-INFO"); - } - - if(hasActiveNetworkConnection()) - { - sb.append(" ACTIVE-NETWORK-CONN"); - } - - sb.append(getSystemServiceClass()); - - return sb.toString(); - } - - @Override - public String getDownlinkChannel() - { - return getIdentifier() + "-" + getChannel(); - } - - @Override - public String getUplinkChannel() - { - return getIdentifier() + "-" + getChannel(); - } - - public String getLRA() - { - return mMessage.getHex(LOCATION_REGISTRATION_AREA, 2); - } - - public boolean isConventionalChannel() - { - return mMessage.get(CONVENTIONAL_CHANNEL_FLAG); - } - - public boolean isSiteFailureCondition() - { - return mMessage.get(SITE_FAILURE_CONDITION_FLAG); - } - - public boolean isValidSiteInformation() - { - return mMessage.get(VALID_FLAG); - } - - public boolean hasActiveNetworkConnection() - { - return mMessage.get(ACTIVE_NETWORK_CONNECTION_FLAG); - } - - public String getUniqueID() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getSystemID()); - sb.append(":"); - sb.append(getRFSS()); - sb.append(":"); - sb.append(getSiteID()); - - return sb.toString(); - } - - public String getSystemID() - { - return mMessage.getHex(SYSTEM_ID, 3); - } - - public String getRFSS() - { - return mMessage.getHex(RFSS_ID, 2); - } - - public String getSiteID() - { - return mMessage.getHex(SITE_ID, 2); - } - - public int getChannel() - { - return mMessage.getInt(CHANNEL); - } - - public int getIdentifier() - { - return mMessage.getInt(IDENTIFIER); - } - - public String getSystemServiceClass() - { - return SystemService.toString(mMessage.getInt(SYSTEM_SERVICE_CLASS)); - } - - public long getDownlinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getDownlinkFrequency(getChannel()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getUplinkFrequency(getChannel()); - } - - return 0; - } - - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - /* we're only expecting 1 identifier, so use whatever is received */ - mIdentifierUpdate = message; - } - - public int[] getIdentifiers() - { - int[] idens = new int[1]; - - idens[0] = getIdentifier(); - - return idens; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/AuthenticationCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/AuthenticationCommand.java deleted file mode 100644 index c782be73..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/AuthenticationCommand.java +++ /dev/null @@ -1,77 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class AuthenticationCommand extends TSBKMessage -{ - public static final int[] WACN = { 88,89,90,91,92,93,94,95,96,97,98,99,100, - 101,102,103,104,105,106,107 }; - public static final int[] SYSTEM_ID = { 108,109,110,111,112,113,114,115,116, - 117,118,119 }; - public static final int[] TARGET_ID = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public AuthenticationCommand( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.AUTHENTICATION_COMMAND.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " WACN: " + getWACN() ); - sb.append( " SYS ID: " + getSystemID() ); - sb.append( " TGT ID: " + getTargetID() ); - - return sb.toString(); - } - - public String getWACN() - { - return mMessage.getHex( WACN, 5 ); - } - - public String getSystemID() - { - return mMessage.getHex( SYSTEM_ID, 3 ); - } - - public String getTargetID() - { - return mMessage.getHex( TARGET_ID, 6 ); - } - - public String getFullTargetID() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getWACN() ); - sb.append( "-" ); - sb.append( getSystemID() ); - sb.append( "-" ); - sb.append( getTargetID() ); - - return sb.toString(); - } - - @Override - public String getToID() - { - return getTargetID(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/CallAlert.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/CallAlert.java deleted file mode 100644 index 7673d368..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/CallAlert.java +++ /dev/null @@ -1,62 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class CallAlert extends TSBKMessage -{ - public static final int[] TARGET_ADDRESS = { 96,97,98,99,100,101,102,103, - 104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119 }; - public static final int[] SOURCE_ID = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public CallAlert( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.CALL_ALERT.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " SRC ID: " + getSourceID() ); - sb.append( " TGT ADDR: " + getTargetAddress() ); - - return sb.toString(); - } - - public String getSourceID() - { - return mMessage.getHex( SOURCE_ID, 6 ); - } - - @Override - public String getFromID() - { - return getSourceID(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - @Override - public String getToID() - { - return getTargetAddress(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/DenyResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/DenyResponse.java deleted file mode 100644 index e1895272..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/DenyResponse.java +++ /dev/null @@ -1,95 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.DenyReason; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class DenyResponse extends TSBKMessage -{ - public static final int ADDITIONAL_INFORMATION_FLAG = 80; - public static final int[] SERVICE_TYPE = { 82,83,84,85,86,87 }; - public static final int[] REASON_CODE = { 88,89,90,91,92,93,94,95 }; - - public static final int[] CALL_OPTIONS = { 96,97,98,99,100,101,102,103 }; - public static final int[] SOURCE_GROUP = { 104,105,106,107,108,109,110,111 }; - public static final int[] SOURCE_ADDRESS = { 96,97,98,99,100,101,102,103,104, - 105,106,107,108,109,110,111 }; - - public static final int[] TARGET_ADDRESS = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public DenyResponse( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.DENY_RESPONSE.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " TGT ADDR: " + getTargetAddress() ); - - sb.append( " REASON:" + getReason().name() ); - - return sb.toString(); - } - - public DenyReason getReason() - { - int code = mMessage.getInt( REASON_CODE ); - - return DenyReason.fromCode( code ); - } - - public boolean hasAdditionalInformation() - { - return mMessage.get( ADDITIONAL_INFORMATION_FLAG ); - } - - public Opcode getServiceType() - { - return Opcode.fromValue( mMessage.getInt( SERVICE_TYPE ) ); - } - - public String getSourceAddress() - { - switch( getServiceType() ) - { - case GROUP_DATA_CHANNEL_GRANT: - case GROUP_VOICE_CHANNEL_GRANT: - return mMessage.getHex( SOURCE_GROUP, 4 ); - case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: - case INDIVIDUAL_DATA_CHANNEL_GRANT: - case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - default: - break; - } - - return null; - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - @Override - public String getToID() - { - return getTargetAddress(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/ExtendedFunctionCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/ExtendedFunctionCommand.java deleted file mode 100644 index ebd70c75..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/ExtendedFunctionCommand.java +++ /dev/null @@ -1,87 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.ExtendedFunction; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class ExtendedFunctionCommand extends TSBKMessage -{ - public static final int[] EXTENDED_FUNCTION = { 80,81,82,83,84,85,86,87, - 88,89,90,91,92,93,94,95 }; - public static final int[] ARGUMENT = { 96,97,98,99,100,101,102,103,104,105, - 106,107,108,109,110,111,112,113,114,115,116,117,118,119 }; - public static final int[] TARGET_ADDRESS = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public ExtendedFunctionCommand( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.EXTENDED_FUNCTION_COMMAND.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " EXTENDED FUNCTION:" + getExtendedFunction().getLabel() ); - - sb.append( " ARGUMENT: " + getTargetAddress() ); - - sb.append( " TGT ADDR: " + getTargetAddress() ); - - return sb.toString(); - } - - public ExtendedFunction getExtendedFunction() - { - return ExtendedFunction.fromValue( mMessage.getInt( EXTENDED_FUNCTION ) ); - } - - public String getArgument() - { - return mMessage.getHex( ARGUMENT, 6 ); - } - - public String getSourceAddress() - { - switch( getExtendedFunction() ) - { - case RADIO_CHECK: - case RADIO_CHECK_ACK: - case RADIO_DETACH: - case RADIO_DETACH_ACK: - case RADIO_INHIBIT: - case RADIO_INHIBIT_ACK: - case RADIO_UNINHIBIT: - case RADIO_UNINHIBIT_ACK: - return getArgument(); - default: - break; - } - - return null; - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - @Override - public String getToID() - { - return getTargetAddress(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/GroupAffiliationQuery.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/GroupAffiliationQuery.java deleted file mode 100644 index 72d3e0d9..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/GroupAffiliationQuery.java +++ /dev/null @@ -1,63 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class GroupAffiliationQuery extends TSBKMessage -{ - public static final int[] TARGET_ADDRESS = { 96,97,98,99,100,101,102,103, - 104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119 }; - public static final int[] SOURCE_ADDRESS = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public GroupAffiliationQuery( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.GROUP_AFFILIATION_QUERY.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " SRC ADDR: " + getSourceAddress() ); - - sb.append( " TGT ADDR: " + getTargetAddress() ); - - return sb.toString(); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } - - @Override - public String getFromID() - { - return getSourceAddress(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - @Override - public String getToID() - { - return getTargetAddress(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/GroupAffiliationResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/GroupAffiliationResponse.java deleted file mode 100644 index 596902d2..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/GroupAffiliationResponse.java +++ /dev/null @@ -1,86 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; -import io.github.dsheirer.module.decode.p25.reference.Response; - -public class GroupAffiliationResponse extends TSBKMessage -{ - public static final int LOCAL_GLOBAL_AFFILIATION_FLAG = 80; - public static final int[] AFFILIATION_RESPONSE = { 86,87 }; - public static final int[] ANNOUNCEMENT_GROUP_ADDRESS = { 88,89,90,91, - 92,93,94,95,96,97,98,99,100,101,102,103 }; - public static final int[] GROUP_ADDRESS = { 104,105,106,107,108,109,110,111, - 112,113,114,115,116,117,118,119 }; - public static final int[] TARGET_ADDRESS = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public GroupAffiliationResponse( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.GROUP_AFFILIATION_RESPONSE.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " AFFILIATION:" + getResponse().name() ); - sb.append( " ANNOUNCE GROUP:" + getAnnouncementGroupAddress() ); - sb.append( " GRP ADDR:" + getGroupAddress() ); - sb.append( " TGT ADDR: " + getTargetAddress() ); - - return sb.toString(); - } - - public String getAffiliationScope() - { - return mMessage.get( LOCAL_GLOBAL_AFFILIATION_FLAG) ? " GLOBAL" : " LOCAL"; - } - - public Response getResponse() - { - int response = mMessage.getInt( AFFILIATION_RESPONSE ); - - return Response.fromValue( response ); - } - - public String getAnnouncementGroupAddress() - { - return mMessage.getHex( ANNOUNCEMENT_GROUP_ADDRESS, 4 ); - } - - public String getGroupAddress() - { - return mMessage.getHex( GROUP_ADDRESS, 4 ); - } - - @Override - public String getFromID() - { - return getGroupAddress(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - @Override - public String getToID() - { - return getTargetAddress(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/IdentifierUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/IdentifierUpdate.java deleted file mode 100644 index 672e19d0..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/IdentifierUpdate.java +++ /dev/null @@ -1,83 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public abstract class IdentifierUpdate extends TSBKMessage implements IBandIdentifier -{ - public static final int[] IDENTIFIER = { 80,81,82,83 }; - - public static final int[] CHANNEL_SPACING = { 102,103,104,105,106,107,108, - 109,110,111 }; - - public static final int[] BASE_FREQUENCY = { 112,113,114,115,116,117,118, - 119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136, - 137,138,139,140,141,142,143 }; - - public IdentifierUpdate( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public int getIdentifier() - { - return mMessage.getInt( IDENTIFIER ); - } - - @Override - public long getChannelSpacing() - { - return mMessage.getLong( CHANNEL_SPACING ) * 125l; - } - - @Override - public long getBaseFrequency() - { - return mMessage.getLong( BASE_FREQUENCY ) * 5l; - } - - @Override - public abstract long getTransmitOffset(); - - @Override - public long getDownlinkFrequency(int channelNumber) - { - return getBaseFrequency() + ( channelNumber * getChannelSpacing() ); - } - - @Override - public long getUplinkFrequency(int channelNumber) - { - return getDownlinkFrequency(channelNumber) + getTransmitOffset(); - } - - @Override - public boolean isTDMA() - { - return false; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateNonVUHF.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateNonVUHF.java deleted file mode 100644 index 4a5bd0ce..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateNonVUHF.java +++ /dev/null @@ -1,77 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class IdentifierUpdateNonVUHF extends IdentifierUpdate -{ - public static final int[] BANDWIDTH = { 84,85,86,87,88,89,90,91,92 }; - - public static final int TRANSMIT_OFFSET_VHF_UHF_SIGN = 93; - - public static final int[] TRANSMIT_OFFSET = { 94,95,96,97,98,99,100,101 }; - - public IdentifierUpdateNonVUHF( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.IDENTIFIER_UPDATE_NON_VUHF.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - sb.append(toString()); - - return sb.toString(); - } - - public String toString() - { - StringBuilder sb = new StringBuilder(); - - sb.append( " ID:" + getIdentifier() ); - sb.append( " BASE:" + getBaseFrequency() ); - sb.append( " BANDWIDTH:" + getBandwidth() ); - sb.append( " SPACING:" + getChannelSpacing() ); - sb.append( " OFFSET:" + getTransmitOffset() ); - - return sb.toString(); - } - - /** - * Channel bandwidth in hertz - */ - public int getBandwidth() - { - return mMessage.getInt( BANDWIDTH ) * 125; - } - - /** - * Transmit offset in hertz - */ - @Override - public long getTransmitOffset() - { - long offset = mMessage.getLong( TRANSMIT_OFFSET ) * 250000l; - - if( mMessage.get( TRANSMIT_OFFSET_VHF_UHF_SIGN ) ) - { - return offset; - } - else - { - return -offset; - } - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateTDMA.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateTDMA.java deleted file mode 100644 index 57eaf71e..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateTDMA.java +++ /dev/null @@ -1,186 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class IdentifierUpdateTDMA extends IdentifierUpdate -{ - public static final int[] CHANNEL_TYPE = { 84,85,86,87 }; - public static final int TRANSMIT_OFFSET_SIGN = 88; - public static final int[] TRANSMIT_OFFSET = { 89,90,91,92,93,94,95,96,97,98, - 99,100,101 }; - - public IdentifierUpdateTDMA( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.IDENTIFIER_UPDATE_TDMA.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - sb.append(toString()); - - return sb.toString(); - } - - public String toString() - { - StringBuilder sb = new StringBuilder(); - - sb.append( " ID:" + getIdentifier() ); - sb.append( " BASE:" + getBaseFrequency() ); - sb.append( " BANDWIDTH:" + getBandwidth() ); - sb.append( " SPACING:" + getChannelSpacing() ); - sb.append( " OFFSET:" + getTransmitOffset() ); - sb.append( " TYPE:" + getChannelType().toString() ); - - return sb.toString(); - } - - public ChannelType getChannelType() - { - return ChannelType.fromValue( mMessage.getInt( CHANNEL_TYPE) ); - } - - /** - * Channel bandwidth in hertz - */ - public int getBandwidth() - { - return getChannelType().getBandwidth(); - } - - /** - * Transmit offset in hertz - */ - @Override - public long getTransmitOffset() - { - long offset = mMessage.getLong( TRANSMIT_OFFSET ) * - getChannelSpacing(); - - if( mMessage.get( TRANSMIT_OFFSET_SIGN ) ) - { - return offset; - } - else - { - return -offset; - } - } - - @Override - public long getDownlinkFrequency(int channelNumber) - { - if(isTDMA()) - { - return getBaseFrequency() + ( (channelNumber / getChannelType().getSlotsPerCarrier()) * getChannelSpacing() ); - } - else - { - return super.getDownlinkFrequency(channelNumber); - } - } - - @Override - public boolean isTDMA() - { - return getChannelType().getAccessType() == AccessType.TDMA; - } - - public enum ChannelType - { - TYPE_0( AccessType.FDMA, 12500, 1, Vocoder.HALF_RATE ), - TYPE_1( AccessType.FDMA, 12500, 1, Vocoder.FULL_RATE ), - TYPE_2( AccessType.FDMA, 6250, 1, Vocoder.HALF_RATE ), - TYPE_3( AccessType.TDMA, 12500, 2, Vocoder.HALF_RATE ), - TYPE_4( AccessType.TDMA, 25000, 4, Vocoder.HALF_RATE ), - TYPE_5( AccessType.TDMA, 12500, 2, Vocoder.HALF_RATE ), //HD8PSK simulcast - UNKNOWN( AccessType.UNKNOWN, 0, 0, Vocoder.HALF_RATE ); - - private AccessType mAccessType; - private int mBandwidth; - private int mSlotsPerCarrier; - private Vocoder mVocoder; - - private ChannelType( AccessType accessType, - int bandwidth, - int slots, - Vocoder vocoder ) - { - mAccessType = accessType; - mBandwidth = bandwidth; - mSlotsPerCarrier = slots; - mVocoder = vocoder; - } - - public String toString() - { - StringBuilder sb = new StringBuilder(); - - if( this.equals( TYPE_5 ) ) - { - sb.append("H-D8PSK SIMULCAST " ); - } - - sb.append( mAccessType.name() ); - sb.append( " BANDWIDTH:" + mBandwidth ); - sb.append( " TIMESLOTS PER CARRIER:" + mSlotsPerCarrier ); - sb.append( " VOCODER:" + mVocoder.name() ); - - return sb.toString(); - } - - public static ChannelType fromValue( int value ) - { - if( 0 <= value && value <= 5 ) - { - return ChannelType.values()[ value ]; - } - - return ChannelType.UNKNOWN; - } - - public AccessType getAccessType() - { - return mAccessType; - } - - public int getBandwidth() - { - return mBandwidth; - } - - public int getSlotsPerCarrier() - { - return mSlotsPerCarrier; - } - - public Vocoder getVocoder() - { - return mVocoder; - } - } - - public enum AccessType - { - FDMA,TDMA,UNKNOWN; - } - - public enum Vocoder - { - HALF_RATE, FULL_RATE; - } -} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateVUHF.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateVUHF.java deleted file mode 100644 index bf8bb500..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateVUHF.java +++ /dev/null @@ -1,89 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class IdentifierUpdateVUHF extends IdentifierUpdate -{ - public static final int[] BANDWIDTH = { 84,85,86,87 }; - - public static final int TRANSMIT_OFFSET_VHF_UHF_SIGN = 88; - - public static final int[] TRANSMIT_OFFSET = { 89,90,91,92,93,94,95,96,97,98, - 99,100,101 }; - - public IdentifierUpdateVUHF( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.IDENTIFIER_UPDATE_VHF_UHF_BANDS.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - sb.append(toString()); - - return sb.toString(); - } - - public String toString() - { - StringBuilder sb = new StringBuilder(); - - sb.append( " ID:" + getIdentifier() ); - sb.append( " BASE:" + getBaseFrequency() ); - sb.append( " BANDWIDTH:" + getBandwidth() ); - sb.append( " SPACING:" + getChannelSpacing() ); - sb.append( " OFFSET:" + getTransmitOffset() ); - - return sb.toString(); - } - - /** - * Channel bandwidth in hertz - */ - public int getBandwidth() - { - int bandwidth = mMessage.getInt( BANDWIDTH ); - - switch( bandwidth ) - { - case 4: - return 6250; - case 5: - return 12500; - default: - return 0; - } - } - - /** - * Transmit offset in hertz - */ - @Override - public long getTransmitOffset() - { - long offset = mMessage.getLong( TRANSMIT_OFFSET ) * - getChannelSpacing(); - - if( mMessage.get( TRANSMIT_OFFSET_VHF_UHF_SIGN ) ) - { - return offset; - } - else - { - return -offset; - } - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/LocationRegistrationResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/LocationRegistrationResponse.java deleted file mode 100644 index 9924e5b1..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/LocationRegistrationResponse.java +++ /dev/null @@ -1,78 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; -import io.github.dsheirer.module.decode.p25.reference.Response; - -public class LocationRegistrationResponse extends TSBKMessage -{ - public static final int[] REGISTRATION_RESPONSE = { 86,87 }; - public static final int[] GROUP_ADDRESS = { 88,89,90,91,92,93,94,95,96,97, - 98,99,100,101,102,103 }; - public static final int[] RFSS_ID = { 104,105,106,107,108,109,110,111 }; - public static final int[] SITE_ID = { 112,113,114,115,116,117,118,119 }; - public static final int[] TARGET_ADDRESS = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public LocationRegistrationResponse( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.LOCATION_REGISTRATION_RESPONSE.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " REGISTRATION:" + getResponse().name() ); - sb.append( " GROUP:" + getGroupAddress() ); - sb.append( " RFSS: " + getRFSSID() ); - sb.append( " SITE: " + getSiteID() ); - sb.append( " TGT ADDR: " + getTargetAddress() ); - - return sb.toString(); - } - - public Response getResponse() - { - return Response.fromValue( mMessage.getInt( REGISTRATION_RESPONSE ) ); - } - - public String getGroupAddress() - { - return mMessage.getHex( GROUP_ADDRESS, 4 ); - } - - public String getRFSSID() - { - return mMessage.getHex( RFSS_ID, 2 ); - } - - public String getSiteID() - { - return mMessage.getHex( SITE_ID, 2 ); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - @Override - public String getToID() - { - return getTargetAddress(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/MessageUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/MessageUpdate.java deleted file mode 100644 index 497b5bd3..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/MessageUpdate.java +++ /dev/null @@ -1,70 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class MessageUpdate extends TSBKMessage -{ - public static final int[] SHORT_DATA_MESSAGE = { 80,81,82,83,84,85,86,87,88,89,90,91, - 92,93,94,95 }; - public static final int[] TARGET_ADDRESS = { 96,97,98,99,100,101,102,103, - 104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119 }; - public static final int[] SOURCE_ADDRESS = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public MessageUpdate( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.MESSAGE_UPDATE.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " MSG:" + getShortDataMessage() ); - sb.append( " SRC ADDR: " + getSourceAddress() ); - sb.append( " TGT ADDR: " + getTargetAddress() ); - - return sb.toString(); - } - - public String getShortDataMessage() - { - return mMessage.getHex( SHORT_DATA_MESSAGE, 4 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } - - @Override - public String getFromID() - { - return getSourceAddress(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - @Override - public String getToID() - { - return getTargetAddress(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/NetworkStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/NetworkStatusBroadcast.java deleted file mode 100644 index c19510c3..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/NetworkStatusBroadcast.java +++ /dev/null @@ -1,147 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; -import io.github.dsheirer.module.decode.p25.reference.P25NetworkCallsign; - -public class NetworkStatusBroadcast extends TSBKMessage implements IdentifierReceiver -{ - public static final int[] LOCATION_REGISTRATION_AREA = {80, 81, 82, 83, 84, 85, 86, 87}; - public static final int[] WACN = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107}; - public static final int[] SYSTEM_ID = {108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119}; - public static final int[] IDENTIFIER = {120, 121, 122, 123}; - public static final int[] CHANNEL = {124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135}; - public static final int[] SYSTEM_SERVICE_CLASS = {136, 137, 138, 139, 140, 141, 142, 143}; - - private IBandIdentifier mIdentifierUpdate; - - public NetworkStatusBroadcast(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - } - - @Override - public String getEventType() - { - return Opcode.NETWORK_STATUS_BROADCAST.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" LRA:" + getLocationRegistrationArea()); - - sb.append(" WACN:" + getWACN()); - - sb.append(" SYS:" + getSystemID()); - - sb.append(" CTRL CHAN:" + getIdentifier() + "-" + getChannel()); - - sb.append(" DN:" + getDownlinkFrequency()); - - sb.append(" UP:" + getUplinkFrequency()); - - sb.append(" SYS SVC CLASS:" + - SystemService.toString(getSystemServiceClass())); - - return sb.toString(); - } - - public String getLocationRegistrationArea() - { - return mMessage.getHex(LOCATION_REGISTRATION_AREA, 2); - } - - public String getWACN() - { - return mMessage.getHex(WACN, 5); - } - - public String getSystemID() - { - return mMessage.getHex(SYSTEM_ID, 3); - } - - public String getNetworkCallsign() - { - return P25NetworkCallsign.getCallsign(mMessage.getInt(WACN), - mMessage.getInt(SYSTEM_ID)); - } - - public int getIdentifier() - { - return mMessage.getInt(IDENTIFIER); - } - - public int getChannel() - { - return mMessage.getInt(CHANNEL); - } - - public int getSystemServiceClass() - { - return mMessage.getInt(SYSTEM_SERVICE_CLASS); - } - - public long getDownlinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getDownlinkFrequency(getChannel()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getUplinkFrequency(getChannel()); - } - - return 0; - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - /* we're only expecting 1 identifier, so use whatever is received */ - mIdentifierUpdate = message; - } - - public int[] getIdentifiers() - { - int[] idens = new int[1]; - - idens[0] = getIdentifier(); - - return idens; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/ProtectionParameterUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/ProtectionParameterUpdate.java deleted file mode 100644 index 9131f693..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/ProtectionParameterUpdate.java +++ /dev/null @@ -1,66 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Encryption; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class ProtectionParameterUpdate extends TSBKMessage -{ - public static final int[] ALGORITHM_ID = { 96,97,98,99,100,101,102,103 }; - public static final int[] KEY_ID = { 104,105,106,107,108,109,110,111,112, - 113,114,115,116,117,118,119 }; - public static final int[] TARGET_ADDRESS = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public ProtectionParameterUpdate( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.PROTECTION_PARAMETER_UPDATE.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " TGT ADDR: " + getTargetAddress() ); - - sb.append( " ENCRYPTION ALGORITHM:" + getAlgorithm().name() ); - - sb.append( " KEY:" + getKeyID() ); - - return sb.toString(); - } - - public Encryption getAlgorithm() - { - return Encryption.fromValue( mMessage.getInt( ALGORITHM_ID ) ); - } - - public String getKeyID() - { - return mMessage.getHex( KEY_ID, 4 ); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - @Override - public String getToID() - { - return getTargetAddress(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/QueuedResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/QueuedResponse.java deleted file mode 100644 index 932128df..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/QueuedResponse.java +++ /dev/null @@ -1,96 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; -import io.github.dsheirer.module.decode.p25.reference.QueuedResponseReason; - -public class QueuedResponse extends TSBKMessage -{ - public static final int ADDITIONAL_INFORMATION_FLAG = 80; - public static final int[] SERVICE_TYPE = { 82,83,84,85,86,87 }; - public static final int[] REASON_CODE = { 88,89,90,91,92,93,94,95 }; - - public static final int[] CALL_OPTIONS = { 96,97,98,99,100,101,102,103 }; - public static final int[] SOURCE_GROUP = { 104,105,106,107,108,109,110,111 }; - public static final int[] SOURCE_ADDRESS = { 96,97,98,99,100,101,102,103,104, - 105,106,107,108,109,110,111 }; - - public static final int[] TARGET_ADDRESS = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public QueuedResponse( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.QUEUED_RESPONSE.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " TGT ADDR: " + getTargetAddress() ); - - sb.append( " REASON:" + getReason().name() ); - - return sb.toString(); - } - - public QueuedResponseReason getReason() - { - int code = mMessage.getInt( REASON_CODE ); - - return QueuedResponseReason.fromCode( code ); - } - - public boolean hasAdditionalInformation() - { - return mMessage.get( ADDITIONAL_INFORMATION_FLAG ); - } - - public Opcode getServiceType() - { - return Opcode.fromValue( mMessage.getInt( SERVICE_TYPE ) ); - } - - public String getSourceAddress() - { - switch( getServiceType() ) - { - case GROUP_DATA_CHANNEL_GRANT: - case GROUP_VOICE_CHANNEL_GRANT: - return mMessage.getHex( SOURCE_GROUP, 4 ); - case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: - case INDIVIDUAL_DATA_CHANNEL_GRANT: - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: - return "DELAY:" + ( mMessage.getInt( SOURCE_ADDRESS ) * 5 ) + "SECS"; - default: - break; - } - - return null; - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - @Override - public String getToID() - { - return getTargetAddress(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/RFSSStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/RFSSStatusBroadcast.java deleted file mode 100644 index a2a72d8a..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/RFSSStatusBroadcast.java +++ /dev/null @@ -1,158 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class RFSSStatusBroadcast extends TSBKMessage implements IdentifierReceiver -{ - public static final int[] LOCATION_REGISTRATION_AREA = {80, 81, 82, 83, 84, 85, 86, 87}; - public static final int ACTIVE_NETWORK_CONNECTION_FLAG = 91; - public static final int[] SYSTEM_ID = {92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103}; - public static final int[] RFSS_ID = {104, 105, 106, 107, 108, 109, 110, 111}; - public static final int[] SITE_ID = {112, 113, 114, 115, 116, 117, 118, 119}; - public static final int[] IDENTIFIER = {120, 121, 122, 123}; - public static final int[] CHANNEL = {124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135}; - public static final int[] SYSTEM_SERVICE_CLASS = {136, 137, 138, 139, 140, 141, 142, 143}; - - private IBandIdentifier mIdentifierUpdate; - - public RFSSStatusBroadcast(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - } - - @Override - public String getEventType() - { - return Opcode.RFSS_STATUS_BROADCAST.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" LRA:" + getLocationRegistrationArea()); - - sb.append(" SYSID:" + getSystemID()); - - sb.append(" RFSS:" + getRFSubsystemID()); - - sb.append(" SITE:" + getSiteID()); - - sb.append(" CHAN:" + getIdentifier() + "-" + getChannel()); - - sb.append(" DN:" + getDownlinkFrequency()); - - sb.append(" UP:" + getUplinkFrequency()); - - if(hasActiveNetworkConnection()) - { - sb.append(" ACTIVE-NETWORK-CONN"); - } - - sb.append(SystemService.toString(getSystemServiceClass())); - - return sb.toString(); - } - - public String getLocationRegistrationArea() - { - return mMessage.getHex(LOCATION_REGISTRATION_AREA, 2); - } - - public boolean hasActiveNetworkConnection() - { - return mMessage.get(ACTIVE_NETWORK_CONNECTION_FLAG); - } - - public String getSystemID() - { - return mMessage.getHex(SYSTEM_ID, 3); - } - - public String getRFSubsystemID() - { - return mMessage.getHex(RFSS_ID, 2); - } - - public String getSiteID() - { - return mMessage.getHex(SITE_ID, 2); - } - - public int getIdentifier() - { - return mMessage.getInt(IDENTIFIER); - } - - public int getChannel() - { - return mMessage.getInt(CHANNEL); - } - - public int getSystemServiceClass() - { - return mMessage.getInt(SYSTEM_SERVICE_CLASS); - } - - public long getDownlinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getDownlinkFrequency(getChannel()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mIdentifierUpdate != null) - { - return mIdentifierUpdate.getUplinkFrequency(getChannel()); - } - - return 0; - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - /* we're only expecting 1 identifier, so use whatever is received */ - mIdentifierUpdate = message; - } - - public int[] getIdentifiers() - { - int[] idens = new int[1]; - - idens[0] = getIdentifier(); - - return idens; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/RadioUnitMonitorCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/RadioUnitMonitorCommand.java deleted file mode 100644 index 9a54d794..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/RadioUnitMonitorCommand.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class RadioUnitMonitorCommand extends TSBKMessage -{ - public static final int[] TX_MULTIPLIER = { 94,95 }; - public static final int[] SOURCE_ADDRESS = { 96,97,98,99,100,101,102,103, - 104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119 }; - public static final int[] TARGET_ADDRESS = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public RadioUnitMonitorCommand( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.RADIO_UNIT_MONITOR_COMMAND.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " MULT:" + getTransmitMultiplier() ); - sb.append( " SRC ADDR: " + getSourceAddress() ); - sb.append( " TGT ADDR: " + getTargetAddress() ); - - return sb.toString(); - } - - public int getTransmitMultiplier() - { - return mMessage.getInt( TX_MULTIPLIER ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } - - @Override - public String getFromID() - { - return getSourceAddress(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - @Override - public String getToID() - { - return getTargetAddress(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/RoamingAddressCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/RoamingAddressCommand.java deleted file mode 100644 index 3a9f9545..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/RoamingAddressCommand.java +++ /dev/null @@ -1,72 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; -import io.github.dsheirer.module.decode.p25.reference.StackOperation; - -public class RoamingAddressCommand extends TSBKMessage -{ - public static final int[] STACK_OPERATION = { 80,81,82,83,84,85,86,87 }; - public static final int[] WACN = { 88,89,90,91,92,93,94,95,96,97,98,99,100, - 101,102,103,104,105,106,107 }; - public static final int[] SYSTEM_ID = { 108,109,110,111,112,113,114,115,116, - 117,118,119 }; - public static final int[] TARGET_ID = { 120,121,122,123,124,125,126,127,128, - 129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public RoamingAddressCommand( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.ROAMING_ADDRESS_COMMAND.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( getStackOperation().name() ); - sb.append( " WACN:" + getWACN() ); - sb.append( " SYSTEM: " + getSystemID() ); - sb.append( " TGT ID: " + getTargetID() ); - - return sb.toString(); - } - - public StackOperation getStackOperation() - { - return StackOperation.fromValue( mMessage.getInt( STACK_OPERATION ) ); - } - - public String getWACN() - { - return mMessage.getHex( WACN, 5 ); - } - - public String getSystemID() - { - return mMessage.getHex( SYSTEM_ID, 3 ); - } - - public String getTargetID() - { - return mMessage.getHex( TARGET_ID, 6 ); - } - - @Override - public String getToID() - { - return getTargetID(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/SecondaryControlChannelBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/SecondaryControlChannelBroadcast.java deleted file mode 100644 index 15461a76..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/SecondaryControlChannelBroadcast.java +++ /dev/null @@ -1,266 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class SecondaryControlChannelBroadcast extends TSBKMessage implements IdentifierReceiver, Comparable -{ - public static final int[] RFSS_ID = {80, 81, 82, 83, 84, 85, 86, 87}; - public static final int[] SITE_ID = {88, 89, 90, 91, 92, 93, 94, 95}; - public static final int[] IDENTIFIER_1 = {96, 97, 98, 99}; - public static final int[] CHANNEL_1 = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111}; - public static final int[] SYSTEM_SERVICE_CLASS_1 = {112, 113, 114, 115, 116, 117, 118, 119}; - public static final int[] IDENTIFIER_2 = {120, 121, 122, 123}; - public static final int[] CHANNEL_2 = {124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135}; - public static final int[] SYSTEM_SERVICE_CLASS_2 = {136, 137, 138, 139, 140, 141, 142, 143}; - - private IBandIdentifier mIdentifierUpdate1; - private IBandIdentifier mIdentifierUpdate2; - - public SecondaryControlChannelBroadcast(BinaryMessage message, - DataUnitID duid, - AliasList aliasList) - { - super(message, duid, aliasList); - } - - @Override - public String getEventType() - { - return Opcode.SECONDARY_CONTROL_CHANNEL_BROADCAST.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - sb.append(" SITE:" + getRFSS() + "-" + getSiteID()); - - sb.append(" CHAN1:" + getIdentifier1() + "-" + getChannel1()); - - sb.append(" DN1:" + getDownlinkFrequency1()); - - sb.append(" UP1:" + getUplinkFrequency1()); - - sb.append(" SVC1:" + SystemService.toString(getSystemServiceClass1())); - - if(hasChannel2()) - { - sb.append(" CHAN2:" + getIdentifier2() + "-" + getChannel2()); - - sb.append(" DN2:" + getDownlinkFrequency2()); - - sb.append(" UP2:" + getUplinkFrequency2()); - - sb.append(" SVC2:" + SystemService.toString(getSystemServiceClass2())); - } - - return sb.toString(); - } - - public String getRFSS() - { - return mMessage.getHex(RFSS_ID, 2); - } - - public String getSiteID() - { - return mMessage.getHex(SITE_ID, 2); - } - - public int getIdentifier1() - { - return mMessage.getInt(IDENTIFIER_1); - } - - public int getChannel1() - { - return mMessage.getInt(CHANNEL_1); - } - - public int getSystemServiceClass1() - { - return mMessage.getInt(SYSTEM_SERVICE_CLASS_1); - } - - public int getIdentifier2() - { - return mMessage.getInt(IDENTIFIER_2); - } - - public int getChannel2() - { - return mMessage.getInt(CHANNEL_2); - } - - public int getSystemServiceClass2() - { - return mMessage.getInt(SYSTEM_SERVICE_CLASS_2); - } - - public boolean hasChannel2() - { - return getSystemServiceClass2() != 0; - } - - - public long getDownlinkFrequency1() - { - if(mIdentifierUpdate1 != null) - { - return mIdentifierUpdate1.getDownlinkFrequency(getChannel1()); - } - - return 0; - } - - public long getUplinkFrequency1() - { - if(mIdentifierUpdate1 != null) - { - return mIdentifierUpdate1.getUplinkFrequency(getChannel1()); - } - - return 0; - } - - public long getDownlinkFrequency2() - { - if(mIdentifierUpdate2 != null) - { - return mIdentifierUpdate2.getDownlinkFrequency(getChannel2()); - } - - return 0; - } - - public long getUplinkFrequency2() - { - if(mIdentifierUpdate2 != null) - { - return mIdentifierUpdate2.getUplinkFrequency(getChannel2()); - } - - return 0; - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getIdentifier1()) - { - mIdentifierUpdate1 = message; - } - - if(identifier == getIdentifier2()) - { - mIdentifierUpdate2 = message; - } - } - - public int[] getIdentifiers() - { - int[] idens; - - if(hasChannel2()) - { - idens = new int[2]; - - idens[0] = getIdentifier1(); - idens[1] = getIdentifier2(); - } - else - { - idens = new int[1]; - - idens[0] = getIdentifier1(); - } - - return idens; - } - - @Override - public int compareTo(SecondaryControlChannelBroadcast other) - { - if(other.getRFSS().contentEquals(getRFSS()) && - other.getSiteID().contentEquals(getSiteID()) && - other.getIdentifier1() == getIdentifier1() && - other.getChannel1() == getChannel1()) - { - return 0; - } - else if(other.getIdentifier1() < getIdentifier1()) - { - return -1; - } - else - { - return 1; - } - } - - @Override - public boolean equals(Object o) - { - if(this == o) - { - return true; - } - if(!(o instanceof SecondaryControlChannelBroadcast)) - { - return false; - } - - SecondaryControlChannelBroadcast that = (SecondaryControlChannelBroadcast)o; - - if(getRFSS() != that.getRFSS()) - { - return false; - } - if(getSiteID() != that.getSiteID()) - { - return false; - } - if(getIdentifier1() != that.getIdentifier1()) - { - return false; - } - - return getChannel1() == that.getChannel1(); - } - - @Override - public int hashCode() - { - int result = getRFSS() != null ? getRFSS().hashCode() : 0; - result = 31 * result + getSiteID() != null ? getSiteID().hashCode() : 0; - result = 31 * result + getIdentifier1(); - result = 31 * result + getChannel1(); - return result; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/SecondaryControlChannelBroadcastExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/SecondaryControlChannelBroadcastExplicit.java deleted file mode 100644 index a8be1880..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/SecondaryControlChannelBroadcastExplicit.java +++ /dev/null @@ -1,57 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class SecondaryControlChannelBroadcastExplicit - extends SecondaryControlChannelBroadcast -{ - private final static Logger mLog = LoggerFactory.getLogger( - SecondaryControlChannelBroadcastExplicit.class ); - - public SecondaryControlChannelBroadcastExplicit( BinaryMessage message, - DataUnitID duid, AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " SITE:" + getRFSS() + "-" + getSiteID() ); - - sb.append( " DOWNLINK:" + getIdentifier1() + "-" + getChannel1() ); - - sb.append( " " + getDownlinkFrequency1() ); - - sb.append( " SVC1:" + - SystemService.toString( getSystemServiceClass1() ) ); - - if( hasChannel2() ) - { - sb.append( " UPLINK:" + getIdentifier2() + "-" + getChannel2() ); - - sb.append( " " + getUplinkFrequency1() ); - - sb.append( " SVC2:" + - SystemService.toString( getSystemServiceClass2() ) ); - } - - return sb.toString(); - } - - -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/StatusQuery.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/StatusQuery.java deleted file mode 100644 index 8986c317..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/StatusQuery.java +++ /dev/null @@ -1,62 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class StatusQuery extends TSBKMessage -{ - public static final int[] TARGET_ADDRESS = { 96,97,98,99,100,101,102,103, - 104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119 }; - public static final int[] SOURCE_ADDRESS = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public StatusQuery( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.STATUS_QUERY.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " SRC ADDR: " + getSourceAddress() ); - sb.append( " TGT ADDR: " + getTargetAddress() ); - - return sb.toString(); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } - - @Override - public String getFromID() - { - return getSourceAddress(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - @Override - public String getToID() - { - return getTargetAddress(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/StatusUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/StatusUpdate.java deleted file mode 100644 index 370c7874..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/StatusUpdate.java +++ /dev/null @@ -1,76 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class StatusUpdate extends TSBKMessage -{ - public static final int[] USER_STATUS = { 72,73,74,75,88,89,90,91 }; - public static final int[] UNIT_STATUS = { 92,93,94,95,96,97,98,99 }; - public static final int[] TARGET_ADDRESS = { 112,113,114,115,116,117,118, - 119,120,121,122,123,136,137,138,139,140,141,142,143,144,145,146,147 }; - public static final int[] SOURCE_ADDRESS = { 160,161,162,163,164,165,166, - 167,168,169,170,171,184,185,186,187,188,189,190,190,192,193,194,195 }; - - public StatusUpdate( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.STATUS_UPDATE.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " STATUS USER:" + getUserStatus() ); - sb.append( " UNIT:" + getUnitStatus() ); - sb.append( " SRC ADDR: " + getSourceAddress() ); - sb.append( " TGT ADDR: " + getTargetAddress() ); - - return sb.toString(); - } - - public String getUserStatus() - { - return mMessage.getHex( USER_STATUS, 2 ); - } - - public String getUnitStatus() - { - return mMessage.getHex( UNIT_STATUS, 2 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } - - @Override - public String getFromID() - { - return getSourceAddress(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - @Override - public String getToID() - { - return getTargetAddress(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/SyncBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/SyncBroadcast.java deleted file mode 100644 index e7ed4a93..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/SyncBroadcast.java +++ /dev/null @@ -1,274 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.SimpleTimeZone; -import java.util.TimeZone; - -/** - * Sync Broadcast - used to broadcast FDMA-TDMA timing synchronization - * information so that a subscriber unit that is time synchronized on an FDMA - * control channel is also time synchronized with all TDMA traffic channels at - * the site. - */ -public class SyncBroadcast extends TSBKMessage -{ - public static final int[] RESERVED = { 80,81,82,83,84,85,86,87,88,89,90,91,92 }; - public static final int SYSTEM_TIME_NOT_LOCKED_TO_EXTERNAL_REFERENCE = 93; - public static final int MICRO_SLOTS_TO_MINUTE_ROLLOVER_UNLOCKED = 94; - public static final int[] LEAP_SECOND_CORRECTION = { 95,96 }; - public static final int LOCAL_TIME_OFFSET_VALID = 97; - public static final int LOCAL_TIME_OFFSET_SIGN = 98; - public static final int[] LOCAL_TIME_OFFSET_HOURS = { 99,100,101,102 }; - public static final int LOCAL_TIME_OFFSET_HALF_HOUR = 103; - - public static final int[] YEAR = { 104,105,106,107,108,109,110 }; - public static final int[] MONTH = { 111,112,113,114 }; - public static final int[] DAY = { 115,116,117,118,119 }; - public static final int[] HOURS = { 120,121,122,123,124 }; - public static final int[] MINUTES = { 125,126,127,128,129,130 }; - public static final int[] MICRO_SLOTS = { 131,132,133,134,135,136,137,138, - 139,140,141,142,143 }; - public static final int[] TSBK_CRC = { 144,145,146,147,148,149,150,151,152, - 153,154,155,156,157,158,159 }; - - private static final DateFormat TIME_FORMATTER = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss.SSS Z" ); - private static final TimeZone NO_TIME_ZONE = new SimpleTimeZone( 0, "NONE" ); - - public SyncBroadcast( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.TDMA_SYNC_BROADCAST.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " SYSTEM TIME" ); - - if( isSystemTimeNotLockedToExternalReference() ) - { - sb.append( " UNLOCKED" ); - } - - sb.append( ":" ); - - TIME_FORMATTER.setTimeZone( getTimeZone() ); - sb.append( " " + TIME_FORMATTER.format( new Date( getSystemTime() ) ) ); - - sb.append( " LEAP-SECOND CORRECTION:" + getLeapSecondCorrection() + "mS" ); - - if( isMicroslotsLockedToMinuteRollover() ) - { - sb.append( " MICROSLOT-MINUTE ROLLOVER:SLOW" ); - } - else - { - sb.append( " MICROSLOT-MINUTE ROLLOVER:UNLOCKED" ); - } - - return sb.toString(); - } - - /** - * System Time (UTC) in milliseconds since java epoch - */ - public long getSystemTime() - { - Calendar cal = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); - - cal.clear(); - - cal.set( Calendar.YEAR, getYear() ); - cal.set( Calendar.MONTH, getMonth() - 1 ); - cal.set( Calendar.DAY_OF_MONTH, getDay() ); - cal.set( Calendar.HOUR_OF_DAY, getHours() ); - cal.set( Calendar.MINUTE, getMinutes() ); - cal.set( Calendar.MILLISECOND, getMilliSeconds() ); - - return cal.getTimeInMillis(); - } - - /** - * System Time not locked to external time reference indicator - */ - public boolean isSystemTimeNotLockedToExternalReference() - { - return mMessage.get( SYSTEM_TIME_NOT_LOCKED_TO_EXTERNAL_REFERENCE ); - } - - /** - * Micro-slot-to-Minute rollover boundary lock indicator. - * - * Note: we invert the message value so that true indicates that the micro - * slots and minute rollovers are locked, whereas the ICD uses false to - * indicate a lock. - * - * @return true if the microslots rollover at each minute interval is locked - * to the system time minute rollover. - * - * @return false if the microslots and minutes rollover are not locked. This - * indicates that the microslots minute rollover is free rolling. - * - */ - public boolean isMicroslotsLockedToMinuteRollover() - { - return !mMessage.get( MICRO_SLOTS_TO_MINUTE_ROLLOVER_UNLOCKED ); - } - - /** - * Leap second correction value that should be applied to the system time. - * - * The leap-second correction field indicates the number of 2.5 millisecond - * units that must be added to the system time to account for the insertion - * of leap seconds when the system time is represented as UTC universal time. - * - * @return leap-second correction value in milliseconds 0.0-10.0 - */ - public double getLeapSecondCorrection() - { - return (double)mMessage.getInt( LEAP_SECOND_CORRECTION ) * 2.5d; - } - - - - /** - * Indicates the local time offset fields contain valid information. - */ - public boolean isValidLocalTimeOffset() - { - return !mMessage.get( LOCAL_TIME_OFFSET_VALID ); - } - - /** - * Local time zone offset from UTC universal time (zulu) when the VALID - * LOCAL TIME OFFSET flag indicates a valid offset. Otherwise, this method - * returns a static +00:00 indicating no local time offset. - */ - public TimeZone getTimeZone() - { - if( isValidLocalTimeOffset() ) - { - int offset = 0; - - offset += mMessage.getInt( LOCAL_TIME_OFFSET_HOURS ) * 3600000; - offset += mMessage.get( LOCAL_TIME_OFFSET_HALF_HOUR ) ? 1800000 : 0; - offset = mMessage.get( LOCAL_TIME_OFFSET_SIGN ) ? -offset : offset; - - return new SimpleTimeZone( offset, "LOCAL" ); - } - else - { - return NO_TIME_ZONE; - } - } - - /** - * System time - year. - * - * @return year in range 2000-2127 - */ - public int getYear() - { - return 2000 + mMessage.getInt( YEAR ); - } - - /** - * System time - month - * - * @return month 1-12 where 1=January and 12=December - */ - public int getMonth() - { - return mMessage.getInt( MONTH ); - } - - /** - * System time - day of month - * - * @return day of month in range 1-31 - */ - public int getDay() - { - return mMessage.getInt( DAY ); - } - - /** - * System time - hours - * - * @return hours in range 0 - 23 - */ - public int getHours() - { - return mMessage.getInt( HOURS ); - } - - /** - * System time - minutes - * - * @return minutes in range 0 - 59 - */ - public int getMinutes() - { - return mMessage.getInt( MINUTES ); - } - - /** - * System time - milli-seconds - * - * Note: sub-millisecond values are rounded to nearest millisecond unit to - * conform to java Date internal milli-second precision level. - * - * @return milli-seconds in range 0 - 59999; - */ - public int getMilliSeconds() - { - return (int)( (double)getMicroSlots() * 7.5 ); - } - - /** - * TDMA Micro Slots. - * - * Number of 7.5 millisecond micro slots since the last minute rollover, or - * since the last micro-slot counter rollover when the the micro-slot to - * minute rollover is unlocked. - * - * @see isMicroslotsLockedToMinuteRollover() method. - * - * This value supports mobile subscriber timing alignment with a TDMA traffic - * channel when a channel is granted from the control channels, so that when - * the mobile changes from the control channel to the traffic channel, it is - * already aligned with the traffic channel TDMA timing. - * - * Super frames occur every 360 ms (48 micro-slots) on FDMA and Ultra frames - * occur every 4 super frames on TDMA. - * - * Valid micro-slot values range 0-7999 and represent 60,000 - * milliseconds ( 8000 x 7.5 ms). - * - * @return number of 7.5 mS micro-slots since last minute or micro-slot - * rollover - */ - public int getMicroSlots() - { - return mMessage.getInt( MICRO_SLOTS ); - } -} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/SystemService.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/SystemService.java deleted file mode 100644 index 14e07314..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/SystemService.java +++ /dev/null @@ -1,87 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -public class SystemService -{ - public static boolean isCompositeControlChannel( int service ) - { - return ( service & 0x01 ) == 0x01; - } - - public static boolean isUpdateControlChannelOnly( int service ) - { - return ( service & 0x02 ) == 0x02; - } - - public static boolean isBackupControlChannelOnly( int service ) - { - return ( service & 0x04 ) == 0x04; - } - - public static boolean providesDataServiceRequests( int service ) - { - return ( service & 0x10 ) == 0x10; - } - - public static boolean providesVoiceServiceRequests( int service ) - { - return ( service & 0x20 ) == 0x20; - } - - public static boolean providesRegistrationServices( int service ) - { - return ( service & 0x40 ) == 0x40; - } - - public static boolean providesAuthenticationServices( int service ) - { - return ( service & 0x80 ) == 0x80; - } - - public static String toString( int service ) - { - StringBuilder sb = new StringBuilder(); - - sb.append( "CONTROL CHAN[" ); - - if( isCompositeControlChannel( service ) ) - { - sb.append( " COMPOSITE" ); - } - - if( isUpdateControlChannelOnly( service ) ) - { - sb.append( " UPDATE" ); - } - - if( isBackupControlChannelOnly( service ) ) - { - sb.append( " BACKUP" ); - } - - sb.append( " ] SERVICES[" ); - - if( providesAuthenticationServices( service ) ) - { - sb.append( " AUTHENTICATION" ); - } - - if( providesDataServiceRequests( service ) ) - { - sb.append( " DATA" ); - } - - if( providesRegistrationServices( service ) ) - { - sb.append( " REGISTRATION" ); - } - - if( providesVoiceServiceRequests( service ) ) - { - sb.append( " VOICE" ); - } - - sb.append( " ]" ); - - return sb.toString(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/SystemServiceBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/SystemServiceBroadcast.java deleted file mode 100644 index a722abfb..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/SystemServiceBroadcast.java +++ /dev/null @@ -1,64 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; -import io.github.dsheirer.module.decode.p25.reference.Service; - -import java.util.List; - -public class SystemServiceBroadcast extends TSBKMessage -{ - public static final int[] AVAILABLE_SERVICES = { 88,89,90,91,92,93,94,95, - 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111 }; - public static final int[] SUPPORTED_SERVICES = { 112,113,114,115,116,117, - 118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135 }; - public static final int[] REQUEST_PRIORITY_LEVEL = { 136,137,138,139,140, - 141,142,143 }; - - public SystemServiceBroadcast( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.SYSTEM_SERVICE_BROADCAST.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " SERVICES AVAILABLE " ); - - sb.append( getAvailableServices() ); - - sb.append( " SUPPORTED " ); - - sb.append( getSupportedServices() ); - - return sb.toString(); - } - - public List getAvailableServices() - { - long bitmap = mMessage.getLong( AVAILABLE_SERVICES ); - - return Service.getServices( bitmap ); - } - - public List getSupportedServices() - { - long bitmap = mMessage.getLong( SUPPORTED_SERVICES ); - - return Service.getServices( bitmap ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/TimeAndDateAnnouncement.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/TimeAndDateAnnouncement.java deleted file mode 100644 index b85ea059..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/TimeAndDateAnnouncement.java +++ /dev/null @@ -1,156 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.TimeZone; - -public class TimeAndDateAnnouncement extends TSBKMessage -{ - public static final int VALID_DATE_INDICATOR = 80; - public static final int VALID_TIME_INDICATOR = 81; - public static final int VALID_LOCAL_TIME_OFFSET_INDICATOR = 82; - - public static final int LOCAL_TIME_OFFSET_SIGN = 84; - public static final int[] LOCAL_TIME_OFFSET = { 85,86,87,88,89,90,91,92, - 93,94,95 }; - public static final int[] MONTH = { 96,97,98,99 }; - public static final int[] DAY = { 100,101,102,103 }; - public static final int[] YEAR = { 105,106,107,108,109,110,111,112,113,114, - 115,116,117 }; - public static final int[] HOURS = { 120,121,122,123,124 }; - public static final int[] MINUTES = { 125,126,127,128,129,130 }; - public static final int[] SECONDS = { 131,132,133,134,135,136 }; - - private SimpleDateFormat mTimeFormatter = - new SimpleDateFormat( "EEE, d MMM yyyy HH:mm:ss Z" ); - - public TimeAndDateAnnouncement( BinaryMessage message, DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.TIME_DATE_ANNOUNCEMENT.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - if( hasValidDate() ) - { - sb.append( " DATE-TIME:" ); - } - - sb.append( " " ); - sb.append( mTimeFormatter.format( new Date( getDateTimestamp() ) ) ); - - return sb.toString(); - } - - public long getDateTimestamp() - { - Calendar cal = new GregorianCalendar( getTimeZone() ); - - if( hasValidDate() ) - { - cal.set( Calendar.YEAR, getYear() ); - cal.set( Calendar.MONTH, getMonth() ); - cal.set( Calendar.DAY_OF_MONTH, getDay() ); - } - - if( hasValidTime() ) - { - cal.set( Calendar.HOUR, getHour() ); - cal.set( Calendar.MINUTE, getMinute() ); - cal.set( Calendar.SECOND, getSecond() ); - } - - return cal.getTimeInMillis(); - } - - public boolean hasValidDate() - { - return mMessage.get( VALID_DATE_INDICATOR ); - } - - public boolean hasValidTime() - { - return mMessage.get( VALID_TIME_INDICATOR ); - } - - public TimeZone getTimeZone() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "GMT " ); - - sb.append( mMessage.get( LOCAL_TIME_OFFSET_SIGN ) ? "-" : "+" ); - - int offsetMinutes = getLocalTimeOffset(); - - sb.append( (int)( offsetMinutes / 60 ) ); - - sb.append( " : " ); - - sb.append( offsetMinutes % 60 ); - - return TimeZone.getTimeZone( sb.toString() ); - } - - public boolean hasValidLocalTimeOffset() - { - return mMessage.get( VALID_LOCAL_TIME_OFFSET_INDICATOR ); - } - - /** - * Local Time Offset in minutes - */ - public int getLocalTimeOffset() - { - return mMessage.getInt( LOCAL_TIME_OFFSET ); - } - - public int getMonth() - { - return mMessage.getInt( MONTH ); - } - - public int getDay() - { - return mMessage.getInt( DAY ); - } - - public int getYear() - { - return mMessage.getInt( YEAR ); - } - - public int getHour() - { - return mMessage.getInt( HOURS ); - } - - public int getMinute() - { - return mMessage.getInt( MINUTES ); - } - - public int getSecond() - { - return mMessage.getInt( SECONDS ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/UnitDeregistrationAcknowledge.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/UnitDeregistrationAcknowledge.java deleted file mode 100644 index 467a5bbf..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/UnitDeregistrationAcknowledge.java +++ /dev/null @@ -1,64 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class UnitDeregistrationAcknowledge extends TSBKMessage -{ - public static final int[] WACN = { 88,89,90,91,92,93,94,95,96,97,98,99,100, - 101,102,103,104,105,106,107 }; - public static final int[] SYSTEM_ID = { 108,109,110,111,112,113,114,115, - 116,117,118,119 }; - public static final int[] SOURCE_ID = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public UnitDeregistrationAcknowledge( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.UNIT_DEREGISTRATION_ACKNOWLEDGE.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " WACN:" + getWACN() ); - sb.append( " SYSTEM:" + getSystemID() ); - sb.append( " SRC ID: " + getSourceID() ); - - return sb.toString(); - } - - public String getWACN() - { - return mMessage.getHex( WACN, 5 ); - } - - public String getSystemID() - { - return mMessage.getHex( SYSTEM_ID, 3 ); - } - - public String getSourceID() - { - return mMessage.getHex( SOURCE_ID, 6 ); - } - - @Override - public String getToID() - { - return getSourceID(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/UnitRegistrationCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/UnitRegistrationCommand.java deleted file mode 100644 index 00b15a9e..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/UnitRegistrationCommand.java +++ /dev/null @@ -1,62 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class UnitRegistrationCommand extends TSBKMessage -{ - public static final int[] TARGET_ADDRESS = { 96,97,98,99,100,101,102,103, - 104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119 }; - public static final int[] SOURCE_ADDRESS = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public UnitRegistrationCommand( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.UNIT_REGISTRATION_COMMAND.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " SRC ADDR: " + getSourceAddress() ); - sb.append( " TGT ADDR: " + getTargetAddress() ); - - return sb.toString(); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } - - @Override - public String getFromID() - { - return getSourceAddress(); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - @Override - public String getToID() - { - return getTargetAddress(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/UnitRegistrationResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/UnitRegistrationResponse.java deleted file mode 100644 index e0c3d5da..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/control/UnitRegistrationResponse.java +++ /dev/null @@ -1,71 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.control; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; -import io.github.dsheirer.module.decode.p25.reference.Response; - -public class UnitRegistrationResponse extends TSBKMessage -{ - public static final int[] REGISTRATION_RESPONSE = { 82,83 }; - public static final int[] SYSTEM_ID = { 84,85,86,87,88,89,90,91,92,93,94,95 }; - public static final int[] SOURCE_ID = { 96,97,98,99,100,101,102,103, - 104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119 }; - public static final int[] SOURCE_ADDRESS = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public UnitRegistrationResponse( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.UNIT_REGISTRATION_RESPONSE.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " REGISTRATION:" + getResponse().name() ); - sb.append( " SYSTEM:" + getSystemID() ); - sb.append( " SRC ID: " + getSourceID() ); - sb.append( " SRC ADDR: " + getSourceAddress() ); - - return sb.toString(); - } - - public Response getResponse() - { - return Response.fromValue( mMessage.getInt( REGISTRATION_RESPONSE ) ); - } - - public String getSystemID() - { - return mMessage.getHex( SYSTEM_ID, 3 ); - } - - public String getSourceID() - { - return mMessage.getHex( SOURCE_ID, 6 ); - } - - public String getSourceAddress() - { - return mMessage.getHex( SOURCE_ADDRESS, 6 ); - } - - @Override - public String getToID() - { - return getSourceAddress(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/GroupDataChannelAnnouncement.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/GroupDataChannelAnnouncement.java deleted file mode 100644 index 70d0bd65..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/GroupDataChannelAnnouncement.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.data; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.GroupMultiChannelGrant; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class GroupDataChannelAnnouncement extends GroupMultiChannelGrant -{ - public GroupDataChannelAnnouncement( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.GROUP_DATA_CHANNEL_ANNOUNCEMENT.getDescription(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/GroupDataChannelAnnouncementExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/GroupDataChannelAnnouncementExplicit.java deleted file mode 100644 index c71a9368..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/GroupDataChannelAnnouncementExplicit.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.data; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.GroupChannelGrantExplicit; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class GroupDataChannelAnnouncementExplicit extends GroupChannelGrantExplicit -{ - public GroupDataChannelAnnouncementExplicit( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.GROUP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT.getDescription(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/GroupDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/GroupDataChannelGrant.java deleted file mode 100644 index 5ffbe530..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/GroupDataChannelGrant.java +++ /dev/null @@ -1,40 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tsbk.osp.data; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.GroupChannelGrant; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class GroupDataChannelGrant extends GroupChannelGrant - -{ - public GroupDataChannelGrant(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - } - - @Override - public String getEventType() - { - return Opcode.GROUP_DATA_CHANNEL_GRANT.getDescription(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/IndividualDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/IndividualDataChannelGrant.java deleted file mode 100644 index 7f0067a3..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/IndividualDataChannelGrant.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.data; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.UnitChannelGrant; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class IndividualDataChannelGrant extends UnitChannelGrant -{ - public IndividualDataChannelGrant( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.INDIVIDUAL_DATA_CHANNEL_GRANT.getDescription(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/SNDCPData.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/SNDCPData.java deleted file mode 100644 index 870e27e0..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/SNDCPData.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.data; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.ServiceMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public abstract class SNDCPData extends ServiceMessage -{ - public static final int[] NETWORK_SERVICE_ACCESS_POINT_ID = { 84,85,86,87 }; - - public static final int[] TARGET_ADDRESS = { 96,97,98,99,100,101,102,103, - 104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119 }; - - public static final int[] SOURCE_ADDRESS = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public SNDCPData( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - public String getNSAPI() - { - return mMessage.getHex( NETWORK_SERVICE_ACCESS_POINT_ID, 1 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/SNDCPDataChannelAnnouncementExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/SNDCPDataChannelAnnouncementExplicit.java deleted file mode 100644 index a4ef3e3e..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/SNDCPDataChannelAnnouncementExplicit.java +++ /dev/null @@ -1,178 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tsbk.osp.data; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public class SNDCPDataChannelAnnouncementExplicit extends SNDCPData implements IdentifierReceiver -{ - public static final int AUTONOMOUS_ACCESS_AVAILABLE_INDICATOR = 88; - public static final int REQUESTED_ACCESS_AVAILABLE_INDICATOR = 89; - public static final int[] TRANSMIT_IDENTIFIER = {96, 97, 98, 99}; - public static final int[] TRANSMIT_NUMBER = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111}; - public static final int[] RECEIVE_IDENTIFIER = {112, 113, 114, 115}; - public static final int[] RECEIVE_NUMBER = {116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127}; - public static final int[] DATA_ACCESS_CONTROL = {128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143}; - - private IBandIdentifier mIdentifierUpdateTransmit; - private IBandIdentifier mIdentifierUpdateReceive; - - public SNDCPDataChannelAnnouncementExplicit(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - if(isEmergency()) - { - sb.append(" EMERGENCY"); - } - - if(isAutonomousAccessAvailable()) - { - sb.append(" AUTONOMOUS ACCESS"); - } - - if(isRequestedAccessAvailable()) - { - sb.append(" DOWNLINK:"); - sb.append(getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber()); - - sb.append(" " + getUplinkFrequency()); - - sb.append(" UPLINK:"); - sb.append(getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber()); - - sb.append(" " + getDownlinkFrequency()); - - sb.append(" DATA ACCESS CONTROL:"); - } - else - { - sb.append(" ACCESS DENIED TO DATA ACCESS CONTROL:"); - } - - sb.append(getDataAccessControl()); - - return sb.toString(); - } - - public boolean isAutonomousAccessAvailable() - { - return mMessage.get(AUTONOMOUS_ACCESS_AVAILABLE_INDICATOR); - } - - public boolean isRequestedAccessAvailable() - { - return mMessage.get(REQUESTED_ACCESS_AVAILABLE_INDICATOR); - } - - public int getTransmitChannelIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public int getTransmitChannelNumber() - { - return mMessage.getInt(TRANSMIT_NUMBER); - } - - public String getTransmitChannel() - { - return getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber(); - } - - public int getReceiveChannelIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public int getReceiveChannelNumber() - { - return mMessage.getInt(RECEIVE_NUMBER); - } - - public String getReceiveChannel() - { - return getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber(); - } - - public String getDataAccessControl() - { - return mMessage.getHex(DATA_ACCESS_CONTROL, 4); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitChannelIdentifier()) - { - mIdentifierUpdateTransmit = message; - } - else if(identifier == getReceiveChannelIdentifier()) - { - mIdentifierUpdateReceive = message; - } - else - { - throw new IllegalArgumentException("unexpected channel band " - + "identifier [" + identifier + "]"); - } - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[2]; - - identifiers[0] = getTransmitChannelIdentifier(); - identifiers[1] = getReceiveChannelIdentifier(); - - return identifiers; - } - - public long getDownlinkFrequency() - { - if(mIdentifierUpdateTransmit != null) - { - return mIdentifierUpdateTransmit.getDownlinkFrequency(getTransmitChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mIdentifierUpdateReceive != null) - { - mIdentifierUpdateReceive.getUplinkFrequency(getReceiveChannelNumber()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/SNDCPDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/SNDCPDataChannelGrant.java deleted file mode 100644 index ccac4d50..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/SNDCPDataChannelGrant.java +++ /dev/null @@ -1,164 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.p25.message.tsbk.osp.data; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.IBandIdentifier; -import io.github.dsheirer.module.decode.p25.message.IdentifierReceiver; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public class SNDCPDataChannelGrant extends SNDCPData implements IdentifierReceiver -{ - public static final int[] TRANSMIT_IDENTIFIER = {88, 89, 90, 91}; - public static final int[] TRANSMIT_NUMBER = {92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103}; - public static final int[] RECEIVE_IDENTIFIER = {104, 105, 106, 107}; - public static final int[] RECEIVE_NUMBER = {108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119}; - public static final int[] TARGET_ADDRESS = {120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, - 134, 135, 136, 137, 138, 139, 140, 141, 142, 143}; - - private IBandIdentifier mIdentifierUpdateTransmit; - private IBandIdentifier mIdentifierUpdateReceive; - - public SNDCPDataChannelGrant(BinaryMessage message, DataUnitID duid, AliasList aliasList) - { - super(message, duid, aliasList); - } - - public boolean isTDMAChannel() - { - if(mIdentifierUpdateTransmit != null) - { - return mIdentifierUpdateTransmit.isTDMA(); - } - - return false; - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append(getMessageStub()); - - if(isEmergency()) - { - sb.append(" EMERGENCY"); - } - - sb.append(" UPLINK:"); - sb.append(getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber()); - - sb.append(" " + getUplinkFrequency()); - - sb.append(" DOWNLINK:"); - sb.append(getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber()); - - sb.append(" " + getDownlinkFrequency()); - - sb.append(" TGT:"); - sb.append(getTargetAddress()); - - return sb.toString(); - } - - public int getTransmitChannelIdentifier() - { - return mMessage.getInt(TRANSMIT_IDENTIFIER); - } - - public int getTransmitChannelNumber() - { - return mMessage.getInt(TRANSMIT_NUMBER); - } - - public String getTransmitChannel() - { - return getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber(); - } - - public int getReceiveChannelIdentifier() - { - return mMessage.getInt(RECEIVE_IDENTIFIER); - } - - public int getReceiveChannelNumber() - { - return mMessage.getInt(RECEIVE_NUMBER); - } - - public String getReceiveChannel() - { - return getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber(); - } - - public String getTargetAddress() - { - return mMessage.getHex(TARGET_ADDRESS, 6); - } - - @Override - public void setIdentifierMessage(int identifier, IBandIdentifier message) - { - if(identifier == getTransmitChannelIdentifier()) - { - mIdentifierUpdateTransmit = message; - } - else if(identifier == getReceiveChannelIdentifier()) - { - mIdentifierUpdateReceive = message; - } - else - { - throw new IllegalArgumentException("unexpected channel band " - + "identifier [" + identifier + "]"); - } - } - - @Override - public int[] getIdentifiers() - { - int[] identifiers = new int[2]; - - identifiers[0] = getTransmitChannelIdentifier(); - identifiers[1] = getReceiveChannelIdentifier(); - - return identifiers; - } - - public long getDownlinkFrequency() - { - if(mIdentifierUpdateTransmit != null) - { - return mIdentifierUpdateTransmit.getDownlinkFrequency(getTransmitChannelNumber()); - } - - return 0; - } - - public long getUplinkFrequency() - { - if(mIdentifierUpdateReceive != null) - { - return mIdentifierUpdateReceive.getUplinkFrequency(getReceiveChannelNumber()); - } - - return 0; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/SNDCPDataPageRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/SNDCPDataPageRequest.java deleted file mode 100644 index 1219f873..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/data/SNDCPDataPageRequest.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.data; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; - -public class SNDCPDataPageRequest extends SNDCPData -{ - public static final int[] DATA_ACCESS_CONTROL = { 104,105,106,107,108,109, - 110,111,112,113,114,115,116,117,118,119 }; - - public static final int[] TARGET_ADDRESS = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public SNDCPDataPageRequest( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - if( isEmergency() ) - { - sb.append( " EMERGENCY" ); - } - - sb.append( " TGT:" ); - sb.append( getTargetAddress() ); - - sb.append( " DAC:" ); - sb.append( getDataAccessControl() ); - - return sb.toString(); - } - - public String getDataAccessControl() - { - return mMessage.getHex( DATA_ACCESS_CONTROL, 4 ); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/GroupVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/GroupVoiceChannelGrant.java deleted file mode 100644 index fb743fec..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/GroupVoiceChannelGrant.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.GroupChannelGrant; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class GroupVoiceChannelGrant extends GroupChannelGrant -{ - public GroupVoiceChannelGrant( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.GROUP_VOICE_CHANNEL_GRANT.getDescription(); - } - -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/GroupVoiceChannelGrantUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/GroupVoiceChannelGrantUpdate.java deleted file mode 100644 index 082c499c..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/GroupVoiceChannelGrantUpdate.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.GroupMultiChannelGrant; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class GroupVoiceChannelGrantUpdate extends GroupMultiChannelGrant -{ - public GroupVoiceChannelGrantUpdate( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.GROUP_VOICE_CHANNEL_GRANT_UPDATE.getDescription(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/GroupVoiceChannelGrantUpdateExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/GroupVoiceChannelGrantUpdateExplicit.java deleted file mode 100644 index 066f3a75..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/GroupVoiceChannelGrantUpdateExplicit.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.GroupChannelGrantExplicit; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class GroupVoiceChannelGrantUpdateExplicit extends GroupChannelGrantExplicit -{ - public GroupVoiceChannelGrantUpdateExplicit( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT.getDescription(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/TelephoneInterconnectAnswerRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/TelephoneInterconnectAnswerRequest.java deleted file mode 100644 index c0944228..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/TelephoneInterconnectAnswerRequest.java +++ /dev/null @@ -1,85 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Digit; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -import java.util.ArrayList; -import java.util.List; - -public class TelephoneInterconnectAnswerRequest extends TSBKMessage -{ - public static final int[] DIGIT_1 = { 80,81,82,83 }; - public static final int[] DIGIT_2 = { 84,85,86,87 }; - public static final int[] DIGIT_3 = { 88,89,90,91 }; - public static final int[] DIGIT_4 = { 92,93,94,95 }; - public static final int[] DIGIT_5 = { 96,97,98,99 }; - public static final int[] DIGIT_6 = { 100,101,102,103 }; - public static final int[] DIGIT_7 = { 104,105,106,107 }; - public static final int[] DIGIT_8 = { 108,109,110,111 }; - public static final int[] DIGIT_9 = { 112,113,114,115 }; - public static final int[] DIGIT_10 = { 116,117,118,119 }; - - public static final int[] TARGET_ADDRESS = { 120,121,122,123,124,125,126, - 127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 }; - - public TelephoneInterconnectAnswerRequest( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.TELEPHONE_INTERCONNECT_ANSWER_REQUEST.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - sb.append( " TO:" ); - sb.append( getTargetAddress() ); - - sb.append( " TEL:" ); - sb.append( getTelephoneNumber() ); - - return sb.toString(); - } - - public String getTelephoneNumber() - { - List digits = new ArrayList(); - - digits.add( mMessage.getInt( DIGIT_1 ) ); - digits.add( mMessage.getInt( DIGIT_2 ) ); - digits.add( mMessage.getInt( DIGIT_3 ) ); - digits.add( mMessage.getInt( DIGIT_4 ) ); - digits.add( mMessage.getInt( DIGIT_5 ) ); - digits.add( mMessage.getInt( DIGIT_6 ) ); - digits.add( mMessage.getInt( DIGIT_7 ) ); - digits.add( mMessage.getInt( DIGIT_8 ) ); - digits.add( mMessage.getInt( DIGIT_9 ) ); - digits.add( mMessage.getInt( DIGIT_10 ) ); - - return Digit.decode( digits ); - } - - public String getTargetAddress() - { - return mMessage.getHex( TARGET_ADDRESS, 6 ); - } - - @Override - public String getToID() - { - return getTargetAddress(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/TelephoneInterconnectVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/TelephoneInterconnectVoiceChannelGrant.java deleted file mode 100644 index 16725c33..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/TelephoneInterconnectVoiceChannelGrant.java +++ /dev/null @@ -1,66 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.UnitChannelGrant; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class TelephoneInterconnectVoiceChannelGrant extends UnitChannelGrant -{ - public static final int[] CALL_TIMER = { 96,97,98,99,100,101,102,103, - 104,105,106,107,108,109,110,111 }; - - public TelephoneInterconnectVoiceChannelGrant( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - if( isEmergency() ) - { - sb.append( " EMERGENCY" ); - } - - sb.append( " ADDR:" + getSourceAddress() ); - - sb.append( " CHAN:" ); - sb.append( getChannel() ); - - sb.append( " CALL TIMER:" ); - sb.append( getCallTimer() ); - - sb.append( " SECS TO:" ); - sb.append( getToID() ); - - return sb.toString(); - } - - public String getAddress() - { - return getSourceAddress(); - } - - /** - * Call timer in seconds - */ - public int getCallTimer() - { - int units = mMessage.getInt( CALL_TIMER ); - - return (int)( units / 10 ); - } - - @Override - public String getEventType() - { - return Opcode.TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT.getDescription(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/TelephoneInterconnectVoiceChannelGrantUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/TelephoneInterconnectVoiceChannelGrantUpdate.java deleted file mode 100644 index db6b95e7..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/TelephoneInterconnectVoiceChannelGrantUpdate.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class TelephoneInterconnectVoiceChannelGrantUpdate - extends TelephoneInterconnectVoiceChannelGrant -{ - - public TelephoneInterconnectVoiceChannelGrantUpdate(BinaryMessage message, - DataUnitID duid, AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE.getDescription(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/UnitToUnitAnswerRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/UnitToUnitAnswerRequest.java deleted file mode 100644 index 5240d7d7..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/UnitToUnitAnswerRequest.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.UnitChannelGrant; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class UnitToUnitAnswerRequest extends UnitChannelGrant -{ - public UnitToUnitAnswerRequest( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.UNIT_TO_UNIT_ANSWER_REQUEST.getDescription(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/UnitToUnitVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/UnitToUnitVoiceChannelGrant.java deleted file mode 100644 index 6ac7541d..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/UnitToUnitVoiceChannelGrant.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.UnitChannelGrant; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class UnitToUnitVoiceChannelGrant extends UnitChannelGrant -{ - public UnitToUnitVoiceChannelGrant( BinaryMessage message, - DataUnitID duid, - AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.UNIT_TO_UNIT_VOICE_CHANNEL_GRANT.getDescription(); - } - - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - - if( isEmergency() ) - { - sb.append( " EMERGENCY" ); - } - - sb.append( " SOURCE UNIT:" ); - sb.append( getSourceAddress() ); - - sb.append( " TARGET UNIT:" ); - sb.append( getTargetAddress() ); - - sb.append( " CHAN:" + getChannelIdentifier() + "-" + getChannelNumber() ); - sb.append( " DN:" + getDownlinkFrequency() ); - sb.append( " UP:" + getUplinkFrequency() ); - - return sb.toString(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/UnitToUnitVoiceChannelGrantUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/UnitToUnitVoiceChannelGrantUpdate.java deleted file mode 100644 index 29e23e97..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/osp/voice/UnitToUnitVoiceChannelGrantUpdate.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.osp.voice; - -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.message.tsbk.UnitChannelGrant; -import io.github.dsheirer.module.decode.p25.reference.DataUnitID; -import io.github.dsheirer.module.decode.p25.reference.Opcode; - -public class UnitToUnitVoiceChannelGrantUpdate extends UnitChannelGrant -{ - public UnitToUnitVoiceChannelGrantUpdate( BinaryMessage message, - DataUnitID duid, AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getEventType() - { - return Opcode.UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE.getDescription(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/AuthenticationQuery.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/AuthenticationQuery.java new file mode 100644 index 00000000..e633740e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/AuthenticationQuery.java @@ -0,0 +1,77 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit acknowledge response + */ +public class AuthenticationQuery extends ISPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public AuthenticationQuery(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + return sb.toString(); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/CallAlertRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/CallAlertRequest.java new file mode 100644 index 00000000..d89fb325 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/CallAlertRequest.java @@ -0,0 +1,77 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Call alert request + */ +public class CallAlertRequest extends ISPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public CallAlertRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + return sb.toString(); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/CancelServiceRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/CancelServiceRequest.java new file mode 100644 index 00000000..1542ce50 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/CancelServiceRequest.java @@ -0,0 +1,104 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.message.tsbk.Opcode; +import io.github.dsheirer.module.decode.p25.reference.CancelReason; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.Direction; + +import java.util.ArrayList; +import java.util.List; + +/** + * Cancel Service Request + */ +public class CancelServiceRequest extends ISPMessage +{ + private static final int ADDITIONAL_INFORMATION_VALID_FLAG = 16; + private static final int[] SERVICE_TYPE = {18, 19, 20, 21, 22, 23}; + private static final int[] REASON_CODE = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] ADDITIONAL_INFORMATION = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private CancelReason mCancelReason; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public CancelServiceRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" REASON:").append(getCancelReason()); + return sb.toString(); + } + + public CancelReason getCancelReason() + { + if(mCancelReason == null) + { + mCancelReason = CancelReason.fromCode(getMessage().getInt(REASON_CODE)); + } + + return mCancelReason; + } + + /** + * Service type for the cancel request + */ + public Opcode getServiceType() + { + return Opcode.fromValue(getMessage().getInt(SERVICE_TYPE), Direction.INBOUND, getVendor()); + } + + /** + * Additional details about the cancel request + */ + public String getAdditionalInformation() + { + return getMessage().getHex(ADDITIONAL_INFORMATION, 6); + } + + /** + * Indicates if the additional information field contains information + */ + public boolean hasAdditionalInformation() + { + return getMessage().get(ADDITIONAL_INFORMATION_VALID_FLAG); + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/EmergencyAlarmRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/EmergencyAlarmRequest.java new file mode 100644 index 00000000..e61a96d9 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/EmergencyAlarmRequest.java @@ -0,0 +1,77 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Emergency alarm request. + */ +public class EmergencyAlarmRequest extends ISPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39}; + private static final int[] GROUP_ADDRESS = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mGroupAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public EmergencyAlarmRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getGroupAddress()); + return sb.toString(); + } + + public Identifier getGroupAddress() + { + if(mGroupAddress == null) + { + mGroupAddress = APCO25ToTalkgroup.createGroup(getMessage().getInt(GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getGroupAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/ExtendedFunctionResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/ExtendedFunctionResponse.java new file mode 100644 index 00000000..4ec91f85 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/ExtendedFunctionResponse.java @@ -0,0 +1,88 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.ExtendedFunction; + +import java.util.ArrayList; +import java.util.List; + +/** + * Extended function response. + */ +public class ExtendedFunctionResponse extends ISPMessage +{ + private static final int[] FUNCTION = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] ARGUMENTS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private ExtendedFunction mExtendedFunction; + private String mArguments; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public ExtendedFunctionResponse(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" FUNCTION:").append(getExtendedFunction()); + sb.append(" ARGUMENTS:").append(getArguments()); + return sb.toString(); + } + + public ExtendedFunction getExtendedFunction() + { + if(mExtendedFunction == null) + { + mExtendedFunction = ExtendedFunction.fromValue(getMessage().getInt(FUNCTION)); + } + + return mExtendedFunction; + } + + public String getArguments() + { + if(mArguments == null) + { + mArguments = getMessage().getHex(ARGUMENTS, 6).toUpperCase(); + } + + return mArguments; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/FrequencyBandUpdateRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/FrequencyBandUpdateRequest.java new file mode 100644 index 00000000..4b8cc16e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/FrequencyBandUpdateRequest.java @@ -0,0 +1,83 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Identifier update request + */ +public class FrequencyBandUpdateRequest extends ISPMessage +{ + private static final int REBROADCAST_ALL_FREQUENCY_BANDS_FLAG = 16; + private static final int[] RESERVED_FLAGS = {17, 18, 19}; + private static final int[] FREQUENCY_BAND = {20, 21, 22, 23}; + private static final int[] RESERVED = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public FrequencyBandUpdateRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + if(isAllFrequencyBands()) + { + sb.append(" REBROADCAST ALL FREQUENCY BANDS"); + } + else + { + sb.append(" REBROADCAST FREQUENCY BAND:").append(getFrequencyBand()); + } + return sb.toString(); + } + + public boolean isAllFrequencyBands() + { + return getMessage().get(REBROADCAST_ALL_FREQUENCY_BANDS_FLAG); + } + + public int getFrequencyBand() + { + return getMessage().getInt(FREQUENCY_BAND); + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/GroupAffiliationQueryResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/GroupAffiliationQueryResponse.java new file mode 100644 index 00000000..f745139a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/GroupAffiliationQueryResponse.java @@ -0,0 +1,92 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25AnnouncementTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Group affiliation query response. + */ +public class GroupAffiliationQueryResponse extends ISPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] ANNOUNCEMENT_GROUP_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39}; + private static final int[] GROUP_ADDRESS = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mAnnouncementGroupAddress; + private Identifier mGroupAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public GroupAffiliationQueryResponse(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getGroupAddress()); + sb.append(" ANNOUNCEMENT GROUP:").append(getAnnouncementGroupAddress()); + return sb.toString(); + } + + public Identifier getAnnouncementGroupAddress() + { + if(mAnnouncementGroupAddress == null) + { + mAnnouncementGroupAddress = APCO25AnnouncementTalkgroup.create(getMessage().getInt(ANNOUNCEMENT_GROUP_ADDRESS)); + } + + return mAnnouncementGroupAddress; + } + + public Identifier getGroupAddress() + { + if(mGroupAddress == null) + { + mGroupAddress = APCO25ToTalkgroup.createGroup(getMessage().getInt(GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getAnnouncementGroupAddress()); + mIdentifiers.add(getGroupAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/GroupAffiliationRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/GroupAffiliationRequest.java new file mode 100644 index 00000000..1d04f2b2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/GroupAffiliationRequest.java @@ -0,0 +1,91 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Group affiliation request + */ +public class GroupAffiliationRequest extends ISPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}; + private static final int[] SYSTEM_ID = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] GROUP_ID = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mSystemId; + private Identifier mGroupAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public GroupAffiliationRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getGroupAddress()); + sb.append(" SYSTEM:").append(getSystemId()); + return sb.toString(); + } + + public Identifier getSystemId() + { + if(mSystemId == null) + { + mSystemId = APCO25System.create(getMessage().getInt(SYSTEM_ID)); + } + + return mSystemId; + } + + public Identifier getGroupAddress() + { + if(mGroupAddress == null) + { + mGroupAddress = APCO25ToTalkgroup.createGroup(getMessage().getInt(GROUP_ID)); + } + + return mGroupAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSystemId()); + mIdentifiers.add(getGroupAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/GroupDataServiceRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/GroupDataServiceRequest.java new file mode 100644 index 00000000..a8ccb21c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/GroupDataServiceRequest.java @@ -0,0 +1,93 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Group data service request. + */ +public class GroupDataServiceRequest extends ISPMessage +{ + private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] RESERVED = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] GROUP_ADDRESS = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private VoiceServiceOptions mVoiceServiceOptions; + private Identifier mGroupAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public GroupDataServiceRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getGroupAddress()); + sb.append(" ").append(getVoiceServiceOptions().toString()); + return sb.toString(); + } + + /** + * Service options for the request + */ + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + public Identifier getGroupAddress() + { + if(mGroupAddress == null) + { + mGroupAddress = APCO25ToTalkgroup.createGroup(getMessage().getInt(GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getGroupAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/GroupVoiceServiceRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/GroupVoiceServiceRequest.java new file mode 100644 index 00000000..a5f57633 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/GroupVoiceServiceRequest.java @@ -0,0 +1,93 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Group voice call service request. + */ +public class GroupVoiceServiceRequest extends ISPMessage +{ + private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] RESERVED = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] GROUP_ADDRESS = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private VoiceServiceOptions mVoiceServiceOptions; + private Identifier mGroupAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public GroupVoiceServiceRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getGroupAddress()); + sb.append(" ").append(getVoiceServiceOptions().toString()); + return sb.toString(); + } + + /** + * Service options for the request + */ + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + public Identifier getGroupAddress() + { + if(mGroupAddress == null) + { + mGroupAddress = APCO25ToTalkgroup.createGroup(getMessage().getInt(GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getGroupAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/IndividualDataServiceRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/IndividualDataServiceRequest.java new file mode 100644 index 00000000..3217d541 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/IndividualDataServiceRequest.java @@ -0,0 +1,94 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Individual data service request. + */ +public class IndividualDataServiceRequest extends ISPMessage +{ + private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] RESERVED = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private VoiceServiceOptions mVoiceServiceOptions; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public IndividualDataServiceRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" ").append(getVoiceServiceOptions().toString()); + return sb.toString(); + } + + /** + * Service options for the request + */ + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/LocationRegistrationRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/LocationRegistrationRequest.java new file mode 100644 index 00000000..3dd5821c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/LocationRegistrationRequest.java @@ -0,0 +1,115 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.Capability; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Location registration request + */ +public class LocationRegistrationRequest extends ISPMessage +{ + private static final int EMERGENCY_FLAG = 16; + private static final int[] CAPABILITY = {17, 18, 19, 20, 21, 22, 23}; + private static final int[] RESERVED = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] LRA = {32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] GROUP_ADDRESS = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Capability mCapability; + private Identifier mLocationRegistrationArea; + private Identifier mGroupAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public LocationRegistrationRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + if(isEmergency()) + { + sb.append(" EMERGENCY"); + } + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getGroupAddress()); + sb.append(" LRA:").append(getLocationRegistrationArea()); + sb.append(" CAPABILITY:").append(getCapability()); + return sb.toString(); + } + + public boolean isEmergency() + { + return getMessage().get(EMERGENCY_FLAG); + } + + public Capability getCapability() + { + if(mCapability == null) + { + mCapability = new Capability(getMessage().getInt(CAPABILITY)); + } + + return mCapability; + } + + public Identifier getLocationRegistrationArea() + { + if(mLocationRegistrationArea == null) + { + mLocationRegistrationArea = APCO25Lra.create(getMessage().getInt(LRA)); + } + + return mLocationRegistrationArea; + } + + public Identifier getGroupAddress() + { + if(mGroupAddress == null) + { + mGroupAddress = APCO25ToTalkgroup.createGroup(getMessage().getInt(GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getLocationRegistrationArea()); + mIdentifiers.add(getGroupAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/MessageUpdateRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/MessageUpdateRequest.java new file mode 100644 index 00000000..5dbc5a19 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/MessageUpdateRequest.java @@ -0,0 +1,83 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Message update request + */ +public class MessageUpdateRequest extends ISPMessage +{ + private static final int[] MESSAGE = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public MessageUpdateRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" SHORT DATA MESSAGE:").append(getShortDataMessage()); + return sb.toString(); + } + + public String getShortDataMessage() + { + return getMessage().getHex(MESSAGE, 4); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createGroup(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/ProtectionParameterRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/ProtectionParameterRequest.java new file mode 100644 index 00000000..98fdc1c0 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/ProtectionParameterRequest.java @@ -0,0 +1,91 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Protection parameter request + */ +public class ProtectionParameterRequest extends ISPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] WACN = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43}; + private static final int[] SYSTEM = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ID = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mWACN; + private Identifier mSystem; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public ProtectionParameterRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" WACN:").append(getWACN()); + sb.append(" SYSTEM:").append(getSystem()); + return sb.toString(); + } + + public Identifier getWACN() + { + if(mWACN == null) + { + mWACN = APCO25Wacn.create(getMessage().getInt(WACN)); + } + + return mWACN; + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getMessage().getInt(SYSTEM)); + } + + return mSystem; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ID)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getWACN()); + mIdentifiers.add(getSystem()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/RadioUnitMonitorRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/RadioUnitMonitorRequest.java new file mode 100644 index 00000000..797d6397 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/RadioUnitMonitorRequest.java @@ -0,0 +1,88 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Radio unit monitor request + */ +public class RadioUnitMonitorRequest extends ISPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}; + private static final int[] TX_MULTIPLIER = {30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public RadioUnitMonitorRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" TX MULTIPLIER:").append(getTxMultiplier()); + return sb.toString(); + } + + /** + * Multipler for a value stored in the radio that determines how long the target address radio will be + * keyed for monitoring. + */ + public int getTxMultiplier() + { + return getMessage().getInt(TX_MULTIPLIER); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/RoamingAddressRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/RoamingAddressRequest.java new file mode 100644 index 00000000..f7fdd530 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/RoamingAddressRequest.java @@ -0,0 +1,77 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Roaming address request + */ +public class RoamingAddressRequest extends ISPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public RoamingAddressRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + return sb.toString(); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/RoamingAddressResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/RoamingAddressResponse.java new file mode 100644 index 00000000..38707b7f --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/RoamingAddressResponse.java @@ -0,0 +1,107 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Roaming address response + */ +public class RoamingAddressResponse extends ISPMessage +{ + private static final int LAST_MESSAGE_FLAG = 16; + private static final int[] MESSAGE_SEQUENCE_NUMBER = {20, 21, 22, 23}; + private static final int[] WACN = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43}; + private static final int[] SYSTEM = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ID = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mWACN; + private Identifier mSystem; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public RoamingAddressResponse(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" WACN:").append(getWACN()); + sb.append(" SYSTEM:").append(getSystem()); + sb.append(" MESSAGE NUMBER:").append(getMessageSequenceNumber()); + if(isLastMessage()) + { + sb.append("-FINAL "); + } + return sb.toString(); + } + + public boolean isLastMessage() + { + return getMessage().get(LAST_MESSAGE_FLAG); + } + + public int getMessageSequenceNumber() + { + return getMessage().getInt(MESSAGE_SEQUENCE_NUMBER); + } + + public Identifier getWACN() + { + if(mWACN == null) + { + mWACN = APCO25Wacn.create(getMessage().getInt(WACN)); + } + + return mWACN; + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getMessage().getInt(SYSTEM)); + } + + return mSystem; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ID)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getWACN()); + mIdentifiers.add(getSystem()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/SNDCPDataChannelRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/SNDCPDataChannelRequest.java new file mode 100644 index 00000000..19f854f9 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/SNDCPDataChannelRequest.java @@ -0,0 +1,102 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * SNDCP Data Channel Requenst + */ +public class SNDCPDataChannelRequest extends ISPMessage +{ + private static final int[] DATA_SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] DATA_ACCESS_CONTROL = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] RESERVED = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private DataServiceOptions mDataServiceOptions; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public SNDCPDataChannelRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" ").append(getDataServiceOptions()); + sb.append(" DAC:").append(getDataAccessControl()); + return sb.toString(); + } + + public DataServiceOptions getDataServiceOptions() + { + if(mDataServiceOptions == null) + { + mDataServiceOptions = new DataServiceOptions(getMessage().getInt(DATA_SERVICE_OPTIONS)); + } + + return mDataServiceOptions; + } + + public int getDataAccessControl() + { + return getMessage().getInt(DATA_ACCESS_CONTROL); + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/SNDCPDataPageResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/SNDCPDataPageResponse.java new file mode 100644 index 00000000..68573e4e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/SNDCPDataPageResponse.java @@ -0,0 +1,110 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.AnswerResponse; +import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * SNDCP Data Channel Requenst + */ +public class SNDCPDataPageResponse extends ISPMessage +{ + private static final int[] DATA_SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] ANSWER_RESPONSE = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] DATA_ACCESS_CONTROL = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] RESERVED = {48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private DataServiceOptions mDataServiceOptions; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public SNDCPDataPageResponse(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" RESPONSE:").append(getAnswerResponse()); + sb.append(" ").append(getDataServiceOptions()); + sb.append(" DAC:").append(getDataAccessControl()); + return sb.toString(); + } + + public AnswerResponse getAnswerResponse() + { + return AnswerResponse.fromValue(getMessage().getInt(ANSWER_RESPONSE)); + } + + public DataServiceOptions getDataServiceOptions() + { + if(mDataServiceOptions == null) + { + mDataServiceOptions = new DataServiceOptions(getMessage().getInt(DATA_SERVICE_OPTIONS)); + } + + return mDataServiceOptions; + } + + public int getDataAccessControl() + { + return getMessage().getInt(DATA_ACCESS_CONTROL); + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/SNDCPReconnectRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/SNDCPReconnectRequest.java new file mode 100644 index 00000000..47387bf1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/SNDCPReconnectRequest.java @@ -0,0 +1,109 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * SNDCP Data Channel Requenst + */ +public class SNDCPReconnectRequest extends ISPMessage +{ + private static final int[] DATA_SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] DATA_ACCESS_CONTROL = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int DATA_TO_SEND_FLAG = 40; + private static final int[] RESERVED = {41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private DataServiceOptions mDataServiceOptions; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public SNDCPReconnectRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(hasDataToSend() ? " HAS DATA TO SEND" : " HAS NO DATA TO SEND"); + sb.append(" ").append(getDataServiceOptions()); + sb.append(" DAC:").append(getDataAccessControl()); + return sb.toString(); + } + + public boolean hasDataToSend() + { + return getMessage().get(DATA_TO_SEND_FLAG); + } + + public DataServiceOptions getDataServiceOptions() + { + if(mDataServiceOptions == null) + { + mDataServiceOptions = new DataServiceOptions(getMessage().getInt(DATA_SERVICE_OPTIONS)); + } + + return mDataServiceOptions; + } + + public int getDataAccessControl() + { + return getMessage().getInt(DATA_ACCESS_CONTROL); + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/StatusQueryRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/StatusQueryRequest.java new file mode 100644 index 00000000..35c9337d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/StatusQueryRequest.java @@ -0,0 +1,77 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Status query request + */ +public class StatusQueryRequest extends ISPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public StatusQueryRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + return sb.toString(); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/StatusQueryResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/StatusQueryResponse.java new file mode 100644 index 00000000..e93c83d4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/StatusQueryResponse.java @@ -0,0 +1,105 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.status.APCO25Status; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Status query response + */ +public class StatusQueryResponse extends ISPMessage +{ + private static final int[] UNIT_STATUS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] USER_STATUS = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mUnitStatus; + private Identifier mUserStatus; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public StatusQueryResponse(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" UNIT STATUS:").append(getUnitStatus()); + sb.append(" USER STATUS:").append(getUserStatus()); + return sb.toString(); + } + + public Identifier getUnitStatus() + { + if(mUnitStatus == null) + { + mUnitStatus = APCO25Status.createUnitStatus(getMessage().getInt(UNIT_STATUS)); + } + + return mUnitStatus; + } + + public Identifier getUserStatus() + { + if(mUserStatus == null) + { + mUserStatus = APCO25Status.createUserStatus(getMessage().getInt(USER_STATUS)); + } + + return mUserStatus; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getUnitStatus()); + mIdentifiers.add(getUserStatus()); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/StatusUpdateRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/StatusUpdateRequest.java new file mode 100644 index 00000000..76529a60 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/StatusUpdateRequest.java @@ -0,0 +1,105 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.status.APCO25Status; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Status update request + */ +public class StatusUpdateRequest extends ISPMessage +{ + private static final int[] UNIT_STATUS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] USER_STATUS = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mUnitStatus; + private Identifier mUserStatus; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public StatusUpdateRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" UNIT STATUS:").append(getUnitStatus()); + sb.append(" USER STATUS:").append(getUserStatus()); + return sb.toString(); + } + + public Identifier getUnitStatus() + { + if(mUnitStatus == null) + { + mUnitStatus = APCO25Status.createUnitStatus(getMessage().getInt(UNIT_STATUS)); + } + + return mUnitStatus; + } + + public Identifier getUserStatus() + { + if(mUserStatus == null) + { + mUserStatus = APCO25Status.createUserStatus(getMessage().getInt(USER_STATUS)); + } + + return mUserStatus; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getUnitStatus()); + mIdentifiers.add(getUserStatus()); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/TelephoneInterconnectAnswerResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/TelephoneInterconnectAnswerResponse.java new file mode 100644 index 00000000..d1f66ec8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/TelephoneInterconnectAnswerResponse.java @@ -0,0 +1,90 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.AnswerResponse; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Telephone interconnect answer response + */ +public class TelephoneInterconnectAnswerResponse extends ISPMessage +{ + private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] ANSWER_RESPONSE = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] RESERVED = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private VoiceServiceOptions mVoiceServiceOptions; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public TelephoneInterconnectAnswerResponse(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" RESPONSE:").append(getAnswerResponse()); + sb.append(" ").append(getVoiceServiceOptions().toString()); + return sb.toString(); + } + + /** + * Service options for the request + */ + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + /** + * Response value + */ + public AnswerResponse getAnswerResponse() + { + return AnswerResponse.fromValue(getMessage().getInt(ANSWER_RESPONSE)); + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/TelephoneInterconnectPstnRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/TelephoneInterconnectPstnRequest.java new file mode 100644 index 00000000..a5e91267 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/TelephoneInterconnectPstnRequest.java @@ -0,0 +1,95 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.telephone.APCO25TelephoneNumber; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Implicit Dialing Telephone Interconnect request. The telephone number (PSTN) is a predefined system value and is + * identified by the PSTN address value. + */ +public class TelephoneInterconnectPstnRequest extends ISPMessage +{ + private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] RESERVED = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47}; + private static final int[] PSTN_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private VoiceServiceOptions mVoiceServiceOptions; + private Identifier mPstnAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public TelephoneInterconnectPstnRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getPstnAddress()); + sb.append(" ").append(getVoiceServiceOptions().toString()); + return sb.toString(); + } + + /** + * Service options for the request + */ + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + public Identifier getPstnAddress() + { + if(mPstnAddress == null) + { + mPstnAddress = APCO25TelephoneNumber.createTo("PSTN ENTRY #" + getMessage().getInt(PSTN_ADDRESS)); + } + + return mPstnAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getPstnAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/UnitAcknowledgeResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/UnitAcknowledgeResponse.java new file mode 100644 index 00000000..130d34ee --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/UnitAcknowledgeResponse.java @@ -0,0 +1,90 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.message.tsbk.Opcode; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.Direction; +import io.github.dsheirer.module.decode.p25.reference.Vendor; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit acknowledge response + */ +public class UnitAcknowledgeResponse extends ISPMessage +{ + private static final int[] SERVICE_TYPE = {18, 19, 20, 21, 22, 23}; + private static final int[] RESERVED = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public UnitAcknowledgeResponse(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" ACKNOWLEDGING:").append(getAcknowledgedServiceType()); + return sb.toString(); + } + + /** + * Opcode representing the service type that is being acknowledged by the radio unit. + */ + public Opcode getAcknowledgedServiceType() + { + return Opcode.fromValue(getMessage().getInt(SERVICE_TYPE), Direction.OUTBOUND, Vendor.STANDARD); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/UnitDeRegistrationRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/UnitDeRegistrationRequest.java new file mode 100644 index 00000000..a49bdefa --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/UnitDeRegistrationRequest.java @@ -0,0 +1,91 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit registration request + */ +public class UnitDeRegistrationRequest extends ISPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] WACN = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43}; + private static final int[] SYSTEM = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ID = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mWACN; + private Identifier mSystem; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public UnitDeRegistrationRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" WACN:").append(getWACN()); + sb.append(" SYSTEM:").append(getSystem()); + return sb.toString(); + } + + public Identifier getWACN() + { + if(mWACN == null) + { + mWACN = APCO25Wacn.create(getMessage().getInt(WACN)); + } + + return mWACN; + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getMessage().getInt(SYSTEM)); + } + + return mSystem; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ID)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getWACN()); + mIdentifiers.add(getSystem()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/UnitRegistrationRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/UnitRegistrationRequest.java new file mode 100644 index 00000000..0fb085df --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/UnitRegistrationRequest.java @@ -0,0 +1,115 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.Capability; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit registration request + */ +public class UnitRegistrationRequest extends ISPMessage +{ + private static final int EMERGENCY_FLAG = 16; + private static final int[] CAPABILITY = {17, 18, 19, 20, 21, 22, 23}; + private static final int[] WACN = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43}; + private static final int[] SYSTEM = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ID = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Capability mCapability; + private Identifier mWACN; + private Identifier mSystem; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public UnitRegistrationRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + + if(isEmergency()) + { + sb.append(" EMERGENCY"); + } + sb.append(" FM:").append(getSourceAddress()); + sb.append(" WACN:").append(getWACN()); + sb.append(" SYSTEM:").append(getSystem()); + sb.append(" CAPABILITY:").append(getCapability()); + return sb.toString(); + } + + public boolean isEmergency() + { + return getMessage().get(EMERGENCY_FLAG); + } + + public Capability getCapability() + { + if(mCapability == null) + { + mCapability = new Capability(getMessage().getInt(CAPABILITY)); + } + + return mCapability; + } + + public Identifier getWACN() + { + if(mWACN == null) + { + mWACN = APCO25Wacn.create(getMessage().getInt(WACN)); + } + + return mWACN; + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getMessage().getInt(SYSTEM)); + } + + return mSystem; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ID)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getWACN()); + mIdentifiers.add(getSystem()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/UnitToUnitVoiceServiceAnswerResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/UnitToUnitVoiceServiceAnswerResponse.java new file mode 100644 index 00000000..5bdfeb16 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/UnitToUnitVoiceServiceAnswerResponse.java @@ -0,0 +1,104 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.AnswerResponse; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Response to a unit-to-unit answer request + */ +public class UnitToUnitVoiceServiceAnswerResponse extends ISPMessage +{ + private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] ANSWER_RESPONSE = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private VoiceServiceOptions mVoiceServiceOptions; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public UnitToUnitVoiceServiceAnswerResponse(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" RESPONSE:").append(getAnswerResponse()); + sb.append(" ").append(getVoiceServiceOptions().toString()); + return sb.toString(); + } + + /** + * Service options for the request + */ + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + /** + * Response value + */ + public AnswerResponse getAnswerResponse() + { + return AnswerResponse.fromValue(getMessage().getInt(ANSWER_RESPONSE)); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/UnitToUnitVoiceServiceRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/UnitToUnitVoiceServiceRequest.java new file mode 100644 index 00000000..192f1b43 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/UnitToUnitVoiceServiceRequest.java @@ -0,0 +1,94 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Voice call service request between two specified subscriber units. + */ +public class UnitToUnitVoiceServiceRequest extends ISPMessage +{ + private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] RESERVED = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ID = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private VoiceServiceOptions mVoiceServiceOptions; + private Identifier mTargetId; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public UnitToUnitVoiceServiceRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetId()); + sb.append(" ").append(getVoiceServiceOptions().toString()); + return sb.toString(); + } + + /** + * Service options for the request + */ + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + public Identifier getTargetId() + { + if(mTargetId == null) + { + mTargetId = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ID)); + } + + return mTargetId; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetId()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/UnknownISPMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/UnknownISPMessage.java new file mode 100644 index 00000000..c74c1f33 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/isp/UnknownISPMessage.java @@ -0,0 +1,38 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.Collections; +import java.util.List; + +/** + * Unknown/Unrecognized opcode message. + */ +public class UnknownISPMessage extends ISPMessage +{ + /** + * Constructs a TSBK from the binary message sequence. + */ + public UnknownISPMessage(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" **UNRECOGNIZED ISP OPCODE**"); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/AcknowledgeResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/AcknowledgeResponse.java new file mode 100644 index 00000000..b8d992c4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/AcknowledgeResponse.java @@ -0,0 +1,180 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.message.tsbk.Opcode; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.Direction; +import io.github.dsheirer.module.decode.p25.reference.Vendor; + +import java.util.ArrayList; +import java.util.List; + +/** + * Acknowledge response + */ +public class AcknowledgeResponse extends OSPMessage +{ + private static final int ADDITIONAL_INFORMATION_FLAG = 16; + private static final int EXTENDED_INFORMATION_FLAG = 17; + private static final int[] SERVICE_TYPE = {18, 19, 20, 21, 22, 23}; + private static final int[] WACN = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43}; + private static final int[] SYSTEM = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79}; + + private Identifier mWACN; + private Identifier mSystemId; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public AcknowledgeResponse(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + if(hasSourceAddress()) + { + sb.append(" FM:").append(getSourceAddress()); + } + sb.append(" TO:").append(getTargetAddress()); + sb.append(" ACKNOWLEDGING:").append(getAcknowledgedServiceType()); + if(hasWACN()) + { + sb.append(" WACN:").append(getWACN()); + } + if(hasSystem()) + { + sb.append(" SYSTEM:").append(getSystemId()); + } + return sb.toString(); + } + + private boolean hasAdditionalInformation() + { + return getMessage().get(ADDITIONAL_INFORMATION_FLAG); + } + + private boolean isExtendedSystemInformation() + { + return getMessage().get(EXTENDED_INFORMATION_FLAG); + } + + public boolean hasWACN() + { + return hasAdditionalInformation() && isExtendedSystemInformation(); + } + + public Identifier getWACN() + { + if(hasWACN()) + { + if(mWACN == null) + { + mWACN = APCO25Wacn.create(getMessage().getInt(WACN)); + } + + return mWACN; + } + + return null; + } + + public boolean hasSystem() + { + return hasAdditionalInformation() && isExtendedSystemInformation(); + } + + public Identifier getSystemId() + { + if(hasSystem()) + { + if(mSystemId == null) + { + mSystemId = APCO25System.create(getMessage().getInt(SYSTEM)); + } + + return mSystemId; + } + + return null; + } + + /** + * Opcode representing the service type that is being acknowledged by the radio unit. + */ + public Opcode getAcknowledgedServiceType() + { + return Opcode.fromValue(getMessage().getInt(SERVICE_TYPE), Direction.INBOUND, Vendor.STANDARD); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public boolean hasSourceAddress() + { + return hasAdditionalInformation() && !isExtendedSystemInformation(); + } + + public Identifier getSourceAddress() + { + if(hasSourceAddress()) + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + return null; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + if(hasSourceAddress()) + { + mIdentifiers.add(getSourceAddress()); + } + + if(hasWACN()) + { + mIdentifiers.add(getWACN()); + } + if(hasSystem()) + { + mIdentifiers.add(getSystemId()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/AdjacentStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/AdjacentStatusBroadcast.java new file mode 100644 index 00000000..6d574a04 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/AdjacentStatusBroadcast.java @@ -0,0 +1,210 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Adjacent status broadcast - neighbor sites + */ +public class AdjacentStatusBroadcast extends OSPMessage implements IFrequencyBandReceiver +{ + private static final int[] LOCATION_REGISTRATION_AREA = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int CONVENTIONAL_CHANNEL_FLAG = 24; + private static final int SITE_FAILURE_FLAG = 25; + private static final int VALID_INFORMATION_FLAG = 26; + private static final int ACTIVE_NETWORK_CONNECTION_TO_RFSS_CONTROLLER_FLAG = 27; + private static final int[] SYSTEM = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] RFSS = {40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] SITE = {48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] FREQUENCY_BAND = {56, 57, 58, 59}; + private static final int[] CHANNEL_NUMBER = {60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] SYSTEM_SERVICE_CLASS = {72, 73, 74, 75, 76, 77, 78, 79}; + + private Identifier mLocationRegistrationArea; + private Identifier mSystem; + private Identifier mSite; + private Identifier mRfss; + private IChannelDescriptor mChannel; + private VoiceServiceOptions mVoiceServiceOptions; + private List mIdentifiers; + private List mSiteFlags; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public AdjacentStatusBroadcast(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" LRA:").append(getLocationRegistrationArea()); + sb.append(" SYSTEM:").append(getSystem()); + sb.append(" RFSS:").append(getRfss()); + sb.append(" SITE:").append(getSite()); + sb.append(" FLAGS ").append(getSiteFlags()); + sb.append(" SERVICES ").append(getVoiceServiceOptions()); + return sb.toString(); + } + + public List getSiteFlags() + { + if(mSiteFlags == null) + { + mSiteFlags = new ArrayList<>(); + + if(isConventionalChannel()) + { + mSiteFlags.add("CONVENTIONAL CHANNEL"); + } + + if(isFailedConditionSite()) + { + mSiteFlags.add("FAILURE CONDITION"); + } + + if(isValidSiteInformation()) + { + mSiteFlags.add("VALID INFORMATION"); + } + + if(isActiveNetworkConnectionToRfssControllerSite()) + { + mSiteFlags.add("ACTIVE RFSS CONNECTION"); + } + } + + return mSiteFlags; + } + + /** + * Indicates if the channel is a conventional repeater channel + */ + public boolean isConventionalChannel() + { + return getMessage().get(CONVENTIONAL_CHANNEL_FLAG); + } + + /** + * Indicates if the site is in a failure condition + */ + public boolean isFailedConditionSite() + { + return getMessage().get(SITE_FAILURE_FLAG); + } + + /** + * Indicates if the site informaiton is valid + */ + public boolean isValidSiteInformation() + { + return getMessage().get(VALID_INFORMATION_FLAG); + } + + /** + * Indicates if the site has an active network connection to the RFSS controller + */ + public boolean isActiveNetworkConnectionToRfssControllerSite() + { + return getMessage().get(ACTIVE_NETWORK_CONNECTION_TO_RFSS_CONTROLLER_FLAG); + } + + public Identifier getLocationRegistrationArea() + { + if(mLocationRegistrationArea == null) + { + mLocationRegistrationArea = APCO25Lra.create(getMessage().getInt(LOCATION_REGISTRATION_AREA)); + } + + return mLocationRegistrationArea; + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getMessage().getInt(SYSTEM)); + } + + return mSystem; + } + + public Identifier getSite() + { + if(mSite == null) + { + mSite = APCO25Site.create(getMessage().getInt(SITE)); + } + + return mSite; + } + + public Identifier getRfss() + { + if(mRfss == null) + { + mRfss = APCO25Rfss.create(getMessage().getInt(RFSS)); + } + + return mRfss; + } + + public IChannelDescriptor getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), getMessage().getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SYSTEM_SERVICE_CLASS)); + } + + return mVoiceServiceOptions; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getLocationRegistrationArea()); + mIdentifiers.add(getSystem()); + mIdentifiers.add(getSite()); + mIdentifiers.add(getRfss()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/AuthenticationCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/AuthenticationCommand.java new file mode 100644 index 00000000..7a20cd41 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/AuthenticationCommand.java @@ -0,0 +1,89 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Authentication command + */ +public class AuthenticationCommand extends OSPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] WACN = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43}; + private static final int[] SYSTEM = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] TARGET_ID = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mWACN; + private Identifier mSystem; + private Identifier mTargetId; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public AuthenticationCommand(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetId()); + sb.append(" WACN:").append(getWACN()); + sb.append(" SYSTEM:").append(getSystem()); + return sb.toString(); + } + + public Identifier getWACN() + { + if(mWACN == null) + { + mWACN = APCO25Wacn.create(getMessage().getInt(WACN)); + } + + return mWACN; + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getMessage().getInt(SYSTEM)); + } + + return mSystem; + } + + public Identifier getTargetId() + { + if(mTargetId == null) + { + mTargetId = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ID)); + } + + return mTargetId; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetId()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/CallAlert.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/CallAlert.java new file mode 100644 index 00000000..88dbfc58 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/CallAlert.java @@ -0,0 +1,77 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Call alert + */ +public class CallAlert extends OSPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ID = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mTargetAddress; + private Identifier mSourceId; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public CallAlert(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceId()); + sb.append(" TO:").append(getTargetAddress()); + return sb.toString(); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceId() + { + if(mSourceId == null) + { + mSourceId = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ID)); + } + + return mSourceId; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceId()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/DenyResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/DenyResponse.java new file mode 100644 index 00000000..c7655b1b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/DenyResponse.java @@ -0,0 +1,112 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.message.tsbk.Opcode; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.DenyReason; +import io.github.dsheirer.module.decode.p25.reference.Direction; +import io.github.dsheirer.module.decode.p25.reference.Vendor; + +import java.util.ArrayList; +import java.util.List; + +/** + * Deny response + */ +public class DenyResponse extends OSPMessage +{ + private static final int ADDITIONAL_INFORMATION_FLAG = 16; + private static final int[] SERVICE_TYPE = {18, 19, 20, 21, 22, 23}; + private static final int[] REASON = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] ADDITIONAL_INFO = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private DenyReason mDenyReason; + private String mAdditionalInfo; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public DenyResponse(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" SERVICE:").append(getDeniedServiceType()); + sb.append(" REASON:").append(getDenyReason()); + + if(hasAdditionalInformation()) + { + sb.append(" INFO:").append(getAdditionalInfo()); + } + + return sb.toString(); + } + + private boolean hasAdditionalInformation() + { + return getMessage().get(ADDITIONAL_INFORMATION_FLAG); + } + + public String getAdditionalInfo() + { + if(mAdditionalInfo == null) + { + mAdditionalInfo = getMessage().getHex(ADDITIONAL_INFO, 6); + } + + return mAdditionalInfo; + } + + /** + * Opcode representing the service type that is being acknowledged by the radio unit. + */ + public Opcode getDeniedServiceType() + { + return Opcode.fromValue(getMessage().getInt(SERVICE_TYPE), Direction.INBOUND, Vendor.STANDARD); + } + + public DenyReason getDenyReason() + { + if(mDenyReason == null) + { + mDenyReason = DenyReason.fromCode(getMessage().getInt(REASON)); + } + + return mDenyReason; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/ExtendedFunctionCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/ExtendedFunctionCommand.java new file mode 100644 index 00000000..53a408a3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/ExtendedFunctionCommand.java @@ -0,0 +1,88 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.ExtendedFunction; + +import java.util.ArrayList; +import java.util.List; + +/** + * Extended function response. + */ +public class ExtendedFunctionCommand extends OSPMessage +{ + private static final int[] FUNCTION = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] ARGUMENTS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55}; + private static final int[] TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79}; + + private ExtendedFunction mExtendedFunction; + private String mArguments; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public ExtendedFunctionCommand(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" FUNCTION:").append(getExtendedFunction()); + sb.append(" ARGUMENTS:").append(getArguments()); + return sb.toString(); + } + + public ExtendedFunction getExtendedFunction() + { + if(mExtendedFunction == null) + { + mExtendedFunction = ExtendedFunction.fromValue(getMessage().getInt(FUNCTION)); + } + + return mExtendedFunction; + } + + public String getArguments() + { + if(mArguments == null) + { + mArguments = getMessage().getHex(ARGUMENTS, 6).toUpperCase(); + } + + return mArguments; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/FrequencyBandUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/FrequencyBandUpdate.java new file mode 100644 index 00000000..79944b75 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/FrequencyBandUpdate.java @@ -0,0 +1,144 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBand; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.Collections; +import java.util.List; + +/** + * Identifier Update - Frequency Band details + */ +public class FrequencyBandUpdate extends OSPMessage implements IFrequencyBand +{ + private static final int[] FREQUENCY_BAND_IDENTIFIER = {16, 17, 18, 19}; + private static final int[] BANDWIDTH = {20, 21, 22, 23, 24, 25, 26, 27, 28}; + private static final int TRANSMIT_OFFSET_SIGN = 29; + private static final int[] TRANSMIT_OFFSET = {30, 31, 32, 33, 34, 35, 36, 37}; + private static final int[] CHANNEL_SPACING = {38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] BASE_FREQUENCY = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public FrequencyBandUpdate(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" ID:").append(getIdentifier()); + sb.append(" OFFSET:").append(getTransmitOffset()); + sb.append(" SPACING:").append(getChannelSpacing()); + sb.append(" BASE:").append(getBaseFrequency()); + sb.append(" FDMA BW:").append(getBandwidth()); + return sb.toString(); + } + + @Override + public int getIdentifier() + { + return getMessage().getInt(FREQUENCY_BAND_IDENTIFIER); + } + + @Override + public long getChannelSpacing() + { + return getMessage().getInt(CHANNEL_SPACING) * 125; + } + + @Override + public long getBaseFrequency() + { + return getMessage().getLong(BASE_FREQUENCY) * 5; + } + + @Override + public int getBandwidth() + { + return getMessage().getInt(BANDWIDTH) * 125; + } + + @Override + public long getTransmitOffset() + { + long offset = getMessage().getLong(TRANSMIT_OFFSET) * 250000l; + + if(!getMessage().get(TRANSMIT_OFFSET_SIGN)) + { + offset *= -1; + } + + return offset; + } + + /** + * Indicates if the frequency band has a transmit option for the subscriber unit. + */ + public boolean hasTransmitOffset() + { + return getMessage().getInt(TRANSMIT_OFFSET) != 0x80; + } + + @Override + public long getDownlinkFrequency(int channelNumber) + { + return getBaseFrequency() + (getChannelSpacing() * channelNumber); + } + + @Override + public long getUplinkFrequency(int channelNumber) + { + if(hasTransmitOffset()) + { + return getDownlinkFrequency(channelNumber) + getTransmitOffset(); + } + + return 0; + } + + @Override + public boolean isTDMA() + { + return false; + } + + @Override + public int getTimeslotCount() + { + return 1; + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/FrequencyBandUpdateTDMA.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/FrequencyBandUpdateTDMA.java new file mode 100644 index 00000000..2b9ab429 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/FrequencyBandUpdateTDMA.java @@ -0,0 +1,157 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBand; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.ChannelType; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.Collections; +import java.util.List; + +/** + * Identifier Update - Frequency Band details - TDMA bands + */ +public class FrequencyBandUpdateTDMA extends OSPMessage implements IFrequencyBand +{ + private static final int[] FREQUENCY_BAND_IDENTIFIER = {16, 17, 18, 19}; + private static final int[] CHANNEL_TYPE = {20, 21, 22, 23}; + private static final int TRANSMIT_OFFSET_SIGN = 24; + private static final int[] TRANSMIT_OFFSET = {25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37}; + private static final int[] CHANNEL_SPACING = {38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] BASE_FREQUENCY = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + + private ChannelType mChannelType; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public FrequencyBandUpdateTDMA(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" ID:").append(getIdentifier()); + sb.append(" OFFSET:").append(getTransmitOffset()); + sb.append(" SPACING:").append(getChannelSpacing()); + sb.append(" BASE:").append(getBaseFrequency()); + sb.append(" ").append(getChannelType()); + return sb.toString(); + } + + public ChannelType getChannelType() + { + if(mChannelType == null) + { + mChannelType = ChannelType.fromValue(getMessage().getInt(CHANNEL_TYPE)); + } + + return mChannelType; + } + + @Override + public int getIdentifier() + { + return getMessage().getInt(FREQUENCY_BAND_IDENTIFIER); + } + + @Override + public long getChannelSpacing() + { + return getMessage().getInt(CHANNEL_SPACING) * 125; + } + + @Override + public long getBaseFrequency() + { + return getMessage().getLong(BASE_FREQUENCY) * 5; + } + + @Override + public int getBandwidth() + { + return getChannelType().getBandwidth(); + } + + @Override + public long getTransmitOffset() + { + long offset = getMessage().getLong(TRANSMIT_OFFSET) * 250000l; + + if(!getMessage().get(TRANSMIT_OFFSET_SIGN)) + { + offset *= -1; + } + + return offset; + } + + /** + * Indicates if the frequency band has a transmit option for the subscriber unit. + */ + public boolean hasTransmitOffset() + { + return getMessage().getInt(TRANSMIT_OFFSET) != 0x80; + } + + @Override + public long getDownlinkFrequency(int channelNumber) + { + return getBaseFrequency() + (getChannelSpacing() * channelNumber); + } + + @Override + public long getUplinkFrequency(int channelNumber) + { + if(hasTransmitOffset()) + { + return getDownlinkFrequency(channelNumber) + getTransmitOffset(); + } + + return 0; + } + + @Override + public boolean isTDMA() + { + return getChannelType().isTDMA(); + } + + @Override + public int getTimeslotCount() + { + return getChannelType().getSlotsPerCarrier(); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/FrequencyBandUpdateVUHF.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/FrequencyBandUpdateVUHF.java new file mode 100644 index 00000000..380c79f8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/FrequencyBandUpdateVUHF.java @@ -0,0 +1,155 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBand; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.Collections; +import java.util.List; + +/** + * Identifier Update - VHF/UHF Frequency Band details + */ +public class FrequencyBandUpdateVUHF extends OSPMessage implements IFrequencyBand +{ + private static final int[] FREQUENCY_BAND_IDENTIFIER = {16, 17, 18, 19}; + private static final int[] BANDWIDTH = {20, 21, 22, 23}; + private static final int TRANSMIT_OFFSET_SIGN = 24; + private static final int[] TRANSMIT_OFFSET = {25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37}; + private static final int[] CHANNEL_SPACING = {38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] BASE_FREQUENCY = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public FrequencyBandUpdateVUHF(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" ID:").append(getIdentifier()); + sb.append(" OFFSET:").append(getTransmitOffset()); + sb.append(" SPACING:").append(getChannelSpacing()); + sb.append(" BASE:").append(getBaseFrequency()); + sb.append(" FDMA BW:").append(getBandwidth()); + return sb.toString(); + } + + @Override + public int getIdentifier() + { + return getMessage().getInt(FREQUENCY_BAND_IDENTIFIER); + } + + @Override + public long getChannelSpacing() + { + return getMessage().getInt(CHANNEL_SPACING) * 125; + } + + @Override + public long getBaseFrequency() + { + return getMessage().getLong(BASE_FREQUENCY) * 5; + } + + @Override + public int getBandwidth() + { + int bandwidth = getMessage().getInt(BANDWIDTH); + + if(bandwidth == 0x4) + { + return 6250; + } + else if(bandwidth == 0x5) + { + return 12500; + } + + return 0; + } + + @Override + public long getTransmitOffset() + { + long offset = getMessage().getLong(TRANSMIT_OFFSET) * getChannelSpacing(); + + if(!getMessage().get(TRANSMIT_OFFSET_SIGN)) + { + offset *= -1; + } + + return offset; + } + + /** + * Indicates if the frequency band has a transmit option for the subscriber unit. + */ + public boolean hasTransmitOffset() + { + return getMessage().getInt(TRANSMIT_OFFSET) != 0x80; + } + + @Override + public long getDownlinkFrequency(int channelNumber) + { + return getBaseFrequency() + (getChannelSpacing() * channelNumber); + } + + @Override + public long getUplinkFrequency(int channelNumber) + { + if(hasTransmitOffset()) + { + return getDownlinkFrequency(channelNumber) + getTransmitOffset(); + } + + return 0; + } + + @Override + public boolean isTDMA() + { + return false; + } + + @Override + public int getTimeslotCount() + { + return 1; + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupAffiliationQuery.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupAffiliationQuery.java new file mode 100644 index 00000000..24e1d837 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupAffiliationQuery.java @@ -0,0 +1,77 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Group affiliation query. + */ +public class GroupAffiliationQuery extends OSPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public GroupAffiliationQuery(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + return sb.toString(); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupAffiliationResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupAffiliationResponse.java new file mode 100644 index 00000000..ca756703 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupAffiliationResponse.java @@ -0,0 +1,113 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25AnnouncementTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25AnyTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.Response; + +import java.util.ArrayList; +import java.util.List; + +/** + * Group affiliation response. + */ +public class GroupAffiliationResponse extends OSPMessage +{ + private static final int GLOBAL_LOCAL_FLAG = 16; + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] RESPONSE = {22, 23}; + private static final int[] ANNOUNCEMENT_GROUP_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39}; + private static final int[] GROUP_ADDRESS = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Response mAffiliationResponse; + private Identifier mAnnouncementGroupAddress; + private Identifier mGroupAddress; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public GroupAffiliationResponse(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" GROUP ADDRESS:").append(getGroupAddress()); + sb.append(" (").append(isGlobalAffiliation() ? "GLOBAL" : "LOCAL").append(")"); + sb.append(" AFFILIATION:").append(getAffiliationResponse()); + sb.append(" ANNOUNCEMENT GROUP:").append(getAnnouncementGroupAddress()); + return sb.toString(); + } + + public boolean isGlobalAffiliation() + { + return getMessage().get(GLOBAL_LOCAL_FLAG); + } + + public Response getAffiliationResponse() + { + if(mAffiliationResponse == null) + { + mAffiliationResponse = Response.fromValue(getMessage().getInt(RESPONSE)); + } + + return mAffiliationResponse; + } + + public Identifier getAnnouncementGroupAddress() + { + if(mAnnouncementGroupAddress == null) + { + mAnnouncementGroupAddress = APCO25AnnouncementTalkgroup.create(getMessage().getInt(ANNOUNCEMENT_GROUP_ADDRESS)); + } + + return mAnnouncementGroupAddress; + } + + public Identifier getGroupAddress() + { + if(mGroupAddress == null) + { + mGroupAddress = APCO25AnyTalkgroup.create(getMessage().getInt(GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getAnnouncementGroupAddress()); + mIdentifiers.add(getGroupAddress()); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupDataChannelAnnouncement.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupDataChannelAnnouncement.java new file mode 100644 index 00000000..19336ec3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupDataChannelAnnouncement.java @@ -0,0 +1,115 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Group voice channel grant update. + */ +public class GroupDataChannelAnnouncement extends OSPMessage implements IFrequencyBandReceiver +{ + private static final int[] FREQUENCY_BAND_A = {16, 17, 18, 19}; + private static final int[] CHANNEL_NUMBER_A = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] GROUP_ADDRESS_A = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] FREQUENCY_BAND_B = {48, 49, 50, 51}; + private static final int[] CHANNEL_NUMBER_B = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] GROUP_ADDRESS_B = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + + private IChannelDescriptor mChannelA; + private Identifier mGroupAddressA; + private IChannelDescriptor mChannelB; + private Identifier mGroupAddressB; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public GroupDataChannelAnnouncement(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" GROUP A:").append(getGroupAddressA()); + sb.append(" CHAN:").append(getChannelA()); + sb.append(" GROUP B:").append(getGroupAddressB()); + sb.append(" CHAN:").append(getChannelB()); + return sb.toString(); + } + + public IChannelDescriptor getChannelA() + { + if(mChannelA == null) + { + mChannelA = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND_A), + getMessage().getInt(CHANNEL_NUMBER_A)); + } + + return mChannelA; + } + + public Identifier getGroupAddressA() + { + if(mGroupAddressA == null) + { + mGroupAddressA = APCO25ToTalkgroup.createGroup(getMessage().getInt(GROUP_ADDRESS_A)); + } + + return mGroupAddressA; + } + + public IChannelDescriptor getChannelB() + { + if(mChannelB == null) + { + mChannelB = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND_B), + getMessage().getInt(CHANNEL_NUMBER_B)); + } + + return mChannelB; + } + + public Identifier getGroupAddressB() + { + if(mGroupAddressB == null) + { + mGroupAddressB = APCO25ToTalkgroup.createGroup(getMessage().getInt(GROUP_ADDRESS_B)); + } + + return mGroupAddressB; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getGroupAddressA()); + mIdentifiers.add(getGroupAddressB()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannelA()); + channels.add(getChannelB()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupDataChannelAnnouncementExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupDataChannelAnnouncementExplicit.java new file mode 100644 index 00000000..6e98638c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupDataChannelAnnouncementExplicit.java @@ -0,0 +1,90 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Group voice channel grant update. + */ +public class GroupDataChannelAnnouncementExplicit extends OSPMessage implements IFrequencyBandReceiver +{ + private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] RESERVED = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] DOWNLINK_FREQUENCY_BAND = {32, 33, 34, 35}; + private static final int[] DOWNLINK_CHANNEL_NUMBER = {36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] UPLINK_FREQUENCY_BAND = {48, 49, 50, 51}; + private static final int[] UPLINK_CHANNEL_NUMBER = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] GROUP_ADDRESS = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + + private IChannelDescriptor mChannel; + private Identifier mGroupAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public GroupDataChannelAnnouncementExplicit(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" GROUP:").append(getGroupAddress()); + sb.append(" CHAN:").append(getChannel()); + return sb.toString(); + } + + public IChannelDescriptor getChannel() + { + if(mChannel == null) + { + mChannel = APCO25ExplicitChannel.create(getMessage().getInt(DOWNLINK_FREQUENCY_BAND), + getMessage().getInt(DOWNLINK_CHANNEL_NUMBER), getMessage().getInt(UPLINK_FREQUENCY_BAND), + getMessage().getInt(UPLINK_CHANNEL_NUMBER)); + } + + return mChannel; + } + + public Identifier getGroupAddress() + { + if(mGroupAddress == null) + { + mGroupAddress = APCO25ToTalkgroup.createGroup(getMessage().getInt(GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getGroupAddress()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupDataChannelGrant.java new file mode 100644 index 00000000..cb3c0047 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupDataChannelGrant.java @@ -0,0 +1,137 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Group voice call channel grant. + */ +public class GroupDataChannelGrant extends OSPMessage implements IFrequencyBandReceiver +{ + private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] FREQUENCY_BAND = {24, 25, 26, 27}; + private static final int[] CHANNEL_NUMBER = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] GROUP_ADDRESS = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private DataServiceOptions mDataServiceOptions; + private APCO25Channel mChannel; + private Identifier mGroupAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public GroupDataChannelGrant(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getGroupAddress()); + sb.append(" CHAN:").append(getChannel()); + sb.append(" ").append(getDataServiceOptions().toString()); + return sb.toString(); + } + + /** + * Service options for the request + */ + public DataServiceOptions getDataServiceOptions() + { + if(mDataServiceOptions == null) + { + mDataServiceOptions = new DataServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mDataServiceOptions; + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), getMessage().getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + public Identifier getGroupAddress() + { + if(mGroupAddress == null) + { + mGroupAddress = APCO25ToTalkgroup.createGroup(getMessage().getInt(GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getGroupAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupVoiceChannelGrant.java new file mode 100644 index 00000000..094f10ac --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupVoiceChannelGrant.java @@ -0,0 +1,117 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Group voice call channel grant. + */ +public class GroupVoiceChannelGrant extends OSPMessage implements IFrequencyBandReceiver +{ + private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] FREQUENCY_BAND = {24, 25, 26, 27}; + private static final int[] CHANNEL_NUMBER = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] GROUP_ADDRESS = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private VoiceServiceOptions mVoiceServiceOptions; + private APCO25Channel mChannel; + private Identifier mGroupAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public GroupVoiceChannelGrant(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getGroupAddress()); + sb.append(" CHAN:").append(getChannel()); + sb.append(" ").append(getVoiceServiceOptions().toString()); + return sb.toString(); + } + + /** + * Service options for the request + */ + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), getMessage().getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + public Identifier getGroupAddress() + { + if(mGroupAddress == null) + { + mGroupAddress = APCO25ToTalkgroup.createGroup(getMessage().getInt(GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getGroupAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupVoiceChannelGrantUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupVoiceChannelGrantUpdate.java new file mode 100644 index 00000000..e27a3814 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupVoiceChannelGrantUpdate.java @@ -0,0 +1,127 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Group voice channel grant update. + */ +public class GroupVoiceChannelGrantUpdate extends OSPMessage implements IFrequencyBandReceiver +{ + private static final int[] FREQUENCY_BAND_A = {16, 17, 18, 19}; + private static final int[] CHANNEL_NUMBER_A = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] GROUP_ADDRESS_A = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] FREQUENCY_BAND_B = {48, 49, 50, 51}; + private static final int[] CHANNEL_NUMBER_B = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] GROUP_ADDRESS_B = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + + private APCO25Channel mChannelA; + private Identifier mGroupAddressA; + private APCO25Channel mChannelB; + private Identifier mGroupAddressB; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public GroupVoiceChannelGrantUpdate(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" GROUP A:").append(getGroupAddressA()); + sb.append(" CHAN:").append(getChannelA()); + if(hasGroupB()) + { + sb.append(" GROUP B:").append(getGroupAddressB()); + sb.append(" CHAN:").append(getChannelB()); + } + return sb.toString(); + } + + public APCO25Channel getChannelA() + { + if(mChannelA == null) + { + mChannelA = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND_A), + getMessage().getInt(CHANNEL_NUMBER_A)); + } + + return mChannelA; + } + + public Identifier getGroupAddressA() + { + if(mGroupAddressA == null) + { + mGroupAddressA = APCO25ToTalkgroup.createGroup(getMessage().getInt(GROUP_ADDRESS_A)); + } + + return mGroupAddressA; + } + + public boolean hasGroupB() + { + return (getMessage().getInt(GROUP_ADDRESS_A) != getMessage().getInt(GROUP_ADDRESS_B)) && + getMessage().getInt(GROUP_ADDRESS_B) != 0; + } + + public APCO25Channel getChannelB() + { + if(mChannelB == null) + { + mChannelB = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND_B), + getMessage().getInt(CHANNEL_NUMBER_B)); + } + + return mChannelB; + } + + public Identifier getGroupAddressB() + { + if(mGroupAddressB == null) + { + mGroupAddressB = APCO25ToTalkgroup.createGroup(getMessage().getInt(GROUP_ADDRESS_B)); + } + + return mGroupAddressB; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getGroupAddressA()); + if(hasGroupB()) + { + mIdentifiers.add(getGroupAddressB()); + } + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannelA()); + channels.add(getChannelB()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupVoiceChannelGrantUpdateExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupVoiceChannelGrantUpdateExplicit.java new file mode 100644 index 00000000..f0546f57 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/GroupVoiceChannelGrantUpdateExplicit.java @@ -0,0 +1,106 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Group voice call channel grant. + */ +public class GroupVoiceChannelGrantUpdateExplicit extends OSPMessage implements IFrequencyBandReceiver +{ + private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] RESERVED = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] DOWNLINK_FREQUENCY_BAND = {32, 33, 34, 35}; + private static final int[] DOWNLINK_CHANNEL_NUMBER = {36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] UPLINK_FREQUENCY_BAND = {48, 49, 50, 51}; + private static final int[] UPLINK_CHANNEL_NUMBER = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] GROUP_ADDRESS = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + + private VoiceServiceOptions mVoiceServiceOptions; + private APCO25ExplicitChannel mChannel; + private Identifier mGroupAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public GroupVoiceChannelGrantUpdateExplicit(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TALKGROUP:").append(getGroupAddress()); + sb.append(" CHAN:").append(getChannel()); + sb.append(" ").append(getVoiceServiceOptions().toString()); + return sb.toString(); + } + + /** + * Service options for the request + */ + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + public APCO25ExplicitChannel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25ExplicitChannel.create(getMessage().getInt(DOWNLINK_FREQUENCY_BAND), + getMessage().getInt(DOWNLINK_CHANNEL_NUMBER), getMessage().getInt(UPLINK_FREQUENCY_BAND), + getMessage().getInt(UPLINK_CHANNEL_NUMBER)); + } + + return mChannel; + } + + public Identifier getGroupAddress() + { + if(mGroupAddress == null) + { + mGroupAddress = APCO25ToTalkgroup.createGroup(getMessage().getInt(GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getGroupAddress()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/IndividualDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/IndividualDataChannelGrant.java new file mode 100644 index 00000000..8b8770dc --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/IndividualDataChannelGrant.java @@ -0,0 +1,101 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Individual data channel grant. + */ +public class IndividualDataChannelGrant extends OSPMessage implements IFrequencyBandReceiver +{ + private static final int[] FREQUENCY_BAND = {16, 17, 18, 19}; + private static final int[] CHANNEL_NUMBER = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private IChannelDescriptor mChannel; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public IndividualDataChannelGrant(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" CHAN:").append(getChannel()); + return sb.toString(); + } + + public IChannelDescriptor getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), getMessage().getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/LocationRegistrationResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/LocationRegistrationResponse.java new file mode 100644 index 00000000..7d97ff77 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/LocationRegistrationResponse.java @@ -0,0 +1,140 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25AnyTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.Response; + +import java.util.ArrayList; +import java.util.List; + +/** + * Location registration response + */ +public class LocationRegistrationResponse extends OSPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21}; + private static final int[] RESPONSE = {22, 23}; + private static final int[] GROUP_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] RFSS = {40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] SITE = {48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Response mResponse; + private Identifier mRfss; + private Identifier mSite; + private Identifier mGroupAddress; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public LocationRegistrationResponse(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" GROUP:").append(getGroupAddress()); + sb.append(" RFSS:").append(getRfss()); + sb.append(" SITE:").append(getSite()); + sb.append(" RESPONSE:").append(getResponse()); + return sb.toString(); + } + + public Response getResponse() + { + if(mResponse == null) + { + mResponse = Response.fromValue(getMessage().getInt(RESPONSE)); + } + + return mResponse; + } + + public Identifier getGroupAddress() + { + if(mGroupAddress == null) + { + mGroupAddress = APCO25AnyTalkgroup.create(getMessage().getInt(GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + public Identifier getRfss() + { + if(mRfss == null) + { + mRfss = APCO25Rfss.create(getMessage().getInt(RFSS)); + } + + return mRfss; + } + + public Identifier getSite() + { + if(mSite == null) + { + mSite = APCO25Site.create(getMessage().getInt(SITE)); + } + + return mSite; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getGroupAddress()); + mIdentifiers.add(getRfss()); + mIdentifiers.add(getSite()); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/MessageUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/MessageUpdate.java new file mode 100644 index 00000000..40939e7c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/MessageUpdate.java @@ -0,0 +1,111 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.message.APCO25ShortDataMessage; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Message update + */ +public class MessageUpdate extends OSPMessage +{ + private static final int[] MESSAGE = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mShortDataMessage; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public MessageUpdate(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" SHORT DATA MESSAGE:").append(getShortDataMessage()); + return sb.toString(); + } + + public Identifier getShortDataMessage() + { + if(mShortDataMessage == null) + { + mShortDataMessage = APCO25ShortDataMessage.create(getMessage().getInt(MESSAGE)); + } + + return mShortDataMessage; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createGroup(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + mIdentifiers.add(getShortDataMessage()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/NetworkStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/NetworkStatusBroadcast.java new file mode 100644 index 00000000..d351bccc --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/NetworkStatusBroadcast.java @@ -0,0 +1,148 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Network status broadcast + */ +public class NetworkStatusBroadcast extends OSPMessage implements IFrequencyBandReceiver +{ + private static final int[] LOCATION_REGISTRATION_AREA = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] WACN = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43}; + private static final int[] SYSTEM = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] FREQUENCY_BAND = {56, 57, 58, 59}; + private static final int[] CHANNEL_NUMBER = {60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] SYSTEM_SERVICE_CLASS = {72, 73, 74, 75, 76, 77, 78, 79}; + + private Identifier mLocationRegistrationArea; + private Identifier mWacn; + private Identifier mSystem; + private IChannelDescriptor mChannel; + private VoiceServiceOptions mVoiceServiceOptions; + private List mIdentifiers; + private List mSiteFlags; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public NetworkStatusBroadcast(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" LRA:").append(getLocationRegistrationArea()); + sb.append(" WACN:").append(getWacn()); + sb.append(" SYSTEM:").append(getSystem()); + sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + return sb.toString(); + } + + public Identifier getLocationRegistrationArea() + { + if(mLocationRegistrationArea == null) + { + mLocationRegistrationArea = APCO25Lra.create(getMessage().getInt(LOCATION_REGISTRATION_AREA)); + } + + return mLocationRegistrationArea; + } + + public Identifier getWacn() + { + if(mWacn == null) + { + mWacn = APCO25Wacn.create(getMessage().getInt(WACN)); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getMessage().getInt(SYSTEM)); + } + + return mSystem; + } + + public IChannelDescriptor getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), getMessage().getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SYSTEM_SERVICE_CLASS)); + } + + return mVoiceServiceOptions; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getLocationRegistrationArea()); + mIdentifiers.add(getWacn()); + mIdentifiers.add(getSystem()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/ProtectionParameterUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/ProtectionParameterUpdate.java new file mode 100644 index 00000000..83cf9bc3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/ProtectionParameterUpdate.java @@ -0,0 +1,99 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.encryption.APCO25EncryptionKey; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Protection parameter (e.g. encryption) update + */ +public class ProtectionParameterUpdate extends OSPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] ALGORITHM_ID = {32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] KEY_ID = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mEncryptionKey; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public ProtectionParameterUpdate(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" ENCRYPTION KEY:").append(getEncryptionKey()); + return sb.toString(); + } + + public Identifier getEncryptionKey() + { + if(mEncryptionKey == null) + { + int algorithm = getMessage().getInt(ALGORITHM_ID); + int key = getMessage().getInt(KEY_ID); + mEncryptionKey = APCO25EncryptionKey.create(algorithm, key); + } + + return mEncryptionKey; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createGroup(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getEncryptionKey()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/QueuedResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/QueuedResponse.java new file mode 100644 index 00000000..c313896d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/QueuedResponse.java @@ -0,0 +1,132 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.message.tsbk.Opcode; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.Direction; +import io.github.dsheirer.module.decode.p25.reference.QueuedResponseReason; +import io.github.dsheirer.module.decode.p25.reference.Vendor; + +import java.util.ArrayList; +import java.util.List; + +/** + * Queued response + */ +public class QueuedResponse extends OSPMessage +{ + private static final int ADDITIONAL_INFORMATION_FLAG = 16; + private static final int[] SERVICE_TYPE = {18, 19, 20, 21, 22, 23}; + private static final int[] REASON = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] ADDITIONAL_INFO = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private QueuedResponseReason mQueuedResponseReason; + private String mAdditionalInfo; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public QueuedResponse(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" SERVICE:").append(getQueuedResponseServiceType()); + sb.append(" REASON:").append(getQueuedResponseReason()); + + if(hasAdditionalInformation()) + { + sb.append(" INFO:").append(getAdditionalInfo()); + } + + return sb.toString(); + } + + private boolean hasAdditionalInformation() + { + return getMessage().get(ADDITIONAL_INFORMATION_FLAG); + } + + public String getAdditionalInfo() + { + if(mAdditionalInfo == null) + { + mAdditionalInfo = getMessage().getHex(ADDITIONAL_INFO, 6); + } + + return mAdditionalInfo; + } + + /** + * Opcode representing the service type that is being acknowledged by the radio unit. + */ + public Opcode getQueuedResponseServiceType() + { + return Opcode.fromValue(getMessage().getInt(SERVICE_TYPE), Direction.OUTBOUND, Vendor.STANDARD); + } + + public QueuedResponseReason getQueuedResponseReason() + { + if(mQueuedResponseReason == null) + { + mQueuedResponseReason = QueuedResponseReason.fromCode(getMessage().getInt(REASON)); + } + + return mQueuedResponseReason; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/RFSSStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/RFSSStatusBroadcast.java new file mode 100644 index 00000000..de3ab634 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/RFSSStatusBroadcast.java @@ -0,0 +1,175 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * RF Sub-System status broadcast + */ +public class RFSSStatusBroadcast extends OSPMessage implements IFrequencyBandReceiver +{ + private static final int[] LOCATION_REGISTRATION_AREA = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int ACTIVE_NETWORK_CONNECTION_TO_RFSS_CONTROLLER_FLAG = 27; + private static final int[] SYSTEM = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] RFSS = {40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] SITE = {48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] FREQUENCY_BAND = {56, 57, 58, 59}; + private static final int[] CHANNEL_NUMBER = {60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] SYSTEM_SERVICE_CLASS = {72, 73, 74, 75, 76, 77, 78, 79}; + + private Identifier mLocationRegistrationArea; + private Identifier mSystem; + private Identifier mSite; + private Identifier mRfss; + private IChannelDescriptor mChannel; + private VoiceServiceOptions mVoiceServiceOptions; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public RFSSStatusBroadcast(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" LRA:").append(getLocationRegistrationArea()); + sb.append(" SYSTEM:").append(getSystem()); + sb.append(" RFSS:").append(getRfss()); + sb.append(" SITE:").append(getSite()); + if(isActiveNetworkConnectionToRfssControllerSite()) + { + sb.append(" ACTIVE NETWORK CONNECTION"); + } + sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + return sb.toString(); + } + + /** + * Indicates if the site has an active network connection to the RFSS controller + */ + public boolean isActiveNetworkConnectionToRfssControllerSite() + { + return getMessage().get(ACTIVE_NETWORK_CONNECTION_TO_RFSS_CONTROLLER_FLAG); + } + + public Identifier getLocationRegistrationArea() + { + if(mLocationRegistrationArea == null) + { + mLocationRegistrationArea = APCO25Lra.create(getMessage().getInt(LOCATION_REGISTRATION_AREA)); + } + + return mLocationRegistrationArea; + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getMessage().getInt(SYSTEM)); + } + + return mSystem; + } + + public Identifier getSite() + { + if(mSite == null) + { + mSite = APCO25Site.create(getMessage().getInt(SITE)); + } + + return mSite; + } + + public Identifier getRfss() + { + if(mRfss == null) + { + mRfss = APCO25Rfss.create(getMessage().getInt(RFSS)); + } + + return mRfss; + } + + public IChannelDescriptor getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), getMessage().getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SYSTEM_SERVICE_CLASS)); + } + + return mVoiceServiceOptions; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getLocationRegistrationArea()); + mIdentifiers.add(getSystem()); + mIdentifiers.add(getSite()); + mIdentifiers.add(getRfss()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/RadioUnitMonitorCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/RadioUnitMonitorCommand.java new file mode 100644 index 00000000..3794dcc9 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/RadioUnitMonitorCommand.java @@ -0,0 +1,108 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Radio unit monitor command + */ +public class RadioUnitMonitorCommand extends OSPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}; + private static final int[] TX_MULTIPLIER = {30, 31}; + private static final int[] SOURCE_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55}; + private static final int[] TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mSourceAddress; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public RadioUnitMonitorCommand(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" TX MULTIPLIER:").append(getTxMultiplier()); + return sb.toString(); + } + + /** + * Multipler for a value stored in the radio that determines how long the target address radio will be + * keyed for monitoring. + */ + public int getTxMultiplier() + { + return getMessage().getInt(TX_MULTIPLIER); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/RoamingAddressCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/RoamingAddressCommand.java new file mode 100644 index 00000000..f187cda4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/RoamingAddressCommand.java @@ -0,0 +1,124 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.ISPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.StackOperation; + +import java.util.ArrayList; +import java.util.List; + +/** + * Roaming address command + */ +public class RoamingAddressCommand extends ISPMessage +{ + private static final int[] STACK_OPERATION = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] WACN = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43}; + private static final int[] SYSTEM = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] TARGET_ID = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private StackOperation mStackOperation; + private Identifier mWACN; + private Identifier mSystem; + private Identifier mTargetId; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public RoamingAddressCommand(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetId()); + sb.append(" STACK OPERATION:").append(getStackOperation()); + sb.append(" WACN:").append(getWACN()); + sb.append(" SYSTEM:").append(getSystem()); + return sb.toString(); + } + + public StackOperation getStackOperation() + { + if(mStackOperation == null) + { + mStackOperation = StackOperation.fromValue(getMessage().getInt(STACK_OPERATION)); + } + + return mStackOperation; + } + + public Identifier getWACN() + { + if(mWACN == null) + { + mWACN = APCO25Wacn.create(getMessage().getInt(WACN)); + } + + return mWACN; + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getMessage().getInt(SYSTEM)); + } + + return mSystem; + } + + public Identifier getTargetId() + { + if(mTargetId == null) + { + mTargetId = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ID)); + } + + return mTargetId; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getWACN()); + mIdentifiers.add(getSystem()); + mIdentifiers.add(getTargetId()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/SNDCPDataChannelAnnouncementExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/SNDCPDataChannelAnnouncementExplicit.java new file mode 100644 index 00000000..0400ad54 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/SNDCPDataChannelAnnouncementExplicit.java @@ -0,0 +1,151 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Sub-Network Dependent Convergence Protocol (SNDCP) Data Channel Announcement - Explicit + */ +public class SNDCPDataChannelAnnouncementExplicit extends OSPMessage implements IFrequencyBandReceiver +{ + private static final int[] DATA_SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int AUTONOMOUS_ACCESS_FLAG = 24; + private static final int REQUESTED_ACCESS_FLAG = 25; + private static final int[] DOWNLINK_FREQUENCY_BAND = {32, 33, 34, 35}; + private static final int[] DOWNLINK_CHANNEL_NUMBER = {36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] UPLINK_FREQUENCY_BAND = {48, 49, 50, 51}; + private static final int[] UPLINK_CHANNEL_NUMBER = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] DATA_ACCESS_CONTROL = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + + private DataServiceOptions mServiceOptions; + private IChannelDescriptor mChannel; + private List mChannels; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public SNDCPDataChannelAnnouncementExplicit(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" CHAN:").append(getChannel()); + if(isAutonomousAccess() && isRequestedAccess()) + { + sb.append(" AUTONOMOUS/REQUESTED-ACCESS"); + } + else if(isAutonomousAccess()) + { + sb.append(" AUTONOMOUS-ACCESS"); + } + else if(isRequestedAccess()) + { + sb.append(" REQUESTED-ACCESS"); + } + sb.append(" DAC:").append(getDataAccessControl()); + sb.append(" **:").append(getMessage().getInt(DATA_SERVICE_OPTIONS)); + sb.append(" SERVICE OPTIONS:").append(getServiceOptions()); + return sb.toString(); + } + + public boolean isAutonomousAccess() + { + return getMessage().get(AUTONOMOUS_ACCESS_FLAG); + } + + public boolean isRequestedAccess() + { + return getMessage().get(REQUESTED_ACCESS_FLAG); + } + + public DataServiceOptions getServiceOptions() + { + if(mServiceOptions == null) + { + mServiceOptions = new DataServiceOptions(getMessage().getInt(DATA_SERVICE_OPTIONS)); + } + + return mServiceOptions; + } + + public IChannelDescriptor getChannel() + { + if(mChannel == null) + { + mChannel = APCO25ExplicitChannel.create(getMessage().getInt(DOWNLINK_FREQUENCY_BAND), + getMessage().getInt(DOWNLINK_CHANNEL_NUMBER), getMessage().getInt(UPLINK_FREQUENCY_BAND), + getMessage().getInt(UPLINK_CHANNEL_NUMBER)); + } + + return mChannel; + } + + /** + * Indicates if the channel is an explicit channel, meaning that there are separate downlink + * and uplink channel numbers included. When false, the channel number is the same for both + * uplink and downlink. + * + * @return true if this is an explicit channel. + */ + private boolean isExplicitChannel() + { + return getMessage().getInt(UPLINK_CHANNEL_NUMBER) != 4095; + } + + public int getDataAccessControl() + { + return getMessage().getInt(DATA_ACCESS_CONTROL); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } + + @Override + public List getChannels() + { + if(mChannels == null) + { + mChannels = new ArrayList<>(); + mChannels.add(getChannel()); + } + + return mChannels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/SNDCPDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/SNDCPDataChannelGrant.java new file mode 100644 index 00000000..c24bc08b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/SNDCPDataChannelGrant.java @@ -0,0 +1,148 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Sub-Network Dependent Convergence Protocol (SNDCP) Data Channel Grant + */ +public class SNDCPDataChannelGrant extends OSPMessage implements IFrequencyBandReceiver +{ + private static final int[] DATA_SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] DOWNLINK_FREQUENCY_BAND = {24, 25, 26, 27}; + private static final int[] DOWNLINK_CHANNEL_NUMBER = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] UPLINK_FREQUENCY_BAND = {40, 41, 42, 43}; + private static final int[] UPLINK_CHANNEL_NUMBER = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + + private DataServiceOptions mServiceOptions; + private APCO25Channel mChannel; + private Identifier mTargetAddress; + private List mIdentifiers; + private List mChannels; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public SNDCPDataChannelGrant(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" CHAN:").append(getChannel()); + sb.append(" SERVICE OPTIONS:").append(getServiceOptions()); + return sb.toString(); + } + + public DataServiceOptions getServiceOptions() + { + if(mServiceOptions == null) + { + mServiceOptions = new DataServiceOptions(getMessage().getInt(DATA_SERVICE_OPTIONS)); + } + + return mServiceOptions; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + if(isExplicitChannel()) + { + mChannel = APCO25ExplicitChannel.create(getMessage().getInt(DOWNLINK_FREQUENCY_BAND), + getMessage().getInt(DOWNLINK_CHANNEL_NUMBER), getMessage().getInt(UPLINK_FREQUENCY_BAND), + getMessage().getInt(UPLINK_CHANNEL_NUMBER)); + } + else + { + mChannel = APCO25Channel.create(getMessage().getInt(DOWNLINK_FREQUENCY_BAND), + getMessage().getInt(DOWNLINK_CHANNEL_NUMBER)); + } + } + + return mChannel; + } + + /** + * Indicates if the channel is an explicit channel, meaning that there are separate downlink + * and uplink channel numbers included. When false, the channel number is the same for both + * uplink and downlink. + * + * @return true if this is an explicit channel. + */ + private boolean isExplicitChannel() + { + return getMessage().getInt(UPLINK_CHANNEL_NUMBER) != 4095; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + if(mChannels == null) + { + mChannels = new ArrayList<>(); + mChannels.add(getChannel()); + } + + return mChannels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/SNDCPDataPageRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/SNDCPDataPageRequest.java new file mode 100644 index 00000000..5544926d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/SNDCPDataPageRequest.java @@ -0,0 +1,114 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Sub-Network Dependent Convergence Protocol (SNDCP) Data Channel Grant + */ +public class SNDCPDataPageRequest extends OSPMessage +{ + private static final int[] DATA_SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] RESERVED = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private DataServiceOptions mServiceOptions; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public SNDCPDataPageRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" ").append(getServiceOptions().toString()); + return sb.toString(); + } + + /** + * Service options for the request + */ + public DataServiceOptions getServiceOptions() + { + if(mServiceOptions == null) + { + mServiceOptions = new DataServiceOptions(getMessage().getInt(DATA_SERVICE_OPTIONS)); + } + + return mServiceOptions; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/SecondaryControlChannelBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/SecondaryControlChannelBroadcast.java new file mode 100644 index 00000000..002ba6d1 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/SecondaryControlChannelBroadcast.java @@ -0,0 +1,174 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Secondary control channel broadcast + */ +public class SecondaryControlChannelBroadcast extends OSPMessage implements IFrequencyBandReceiver +{ + private static final int[] RFSS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] SITE = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] FREQUENCY_BAND_A = {32, 33, 34, 35}; + private static final int[] CHANNEL_NUMBER_A = {36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] SYSTEM_SERVICE_CLASS_A = {48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] FREQUENCY_BAND_B = {56, 57, 58, 59}; + private static final int[] CHANNEL_NUMBER_B = {60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] SYSTEM_SERVICE_CLASS_B = {72, 73, 74, 75, 76, 77, 78, 79}; + + private Identifier mRfss; + private Identifier mSite; + private IChannelDescriptor mChannelA; + private IChannelDescriptor mChannelB; + private VoiceServiceOptions mVoiceServiceOptionsA; + private VoiceServiceOptions mVoiceServiceOptionsB; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public SecondaryControlChannelBroadcast(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" RFSS:").append(getRfss()); + sb.append(" SITE:").append(getSite()); + sb.append(" CHAN A:").append(getChannelA()); + sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptionsA()); + if(hasChannelB()) + { + sb.append(" CHAN B:").append(getChannelB()); + sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptionsB()); + } + return sb.toString(); + } + + public Identifier getRfss() + { + if(mRfss == null) + { + mRfss = APCO25Rfss.create(getMessage().getInt(RFSS)); + } + + return mRfss; + } + + public Identifier getSite() + { + if(mSite == null) + { + mSite = APCO25Site.create(getMessage().getInt(SITE)); + } + + return mSite; + } + + public IChannelDescriptor getChannelA() + { + if(mChannelA == null) + { + mChannelA = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND_A), getMessage().getInt(CHANNEL_NUMBER_A)); + } + + return mChannelA; + } + + public VoiceServiceOptions getVoiceServiceOptionsA() + { + if(mVoiceServiceOptionsA == null) + { + mVoiceServiceOptionsA = new VoiceServiceOptions(getMessage().getInt(SYSTEM_SERVICE_CLASS_A)); + } + + return mVoiceServiceOptionsA; + } + + private boolean hasChannelB() + { + return getMessage().getInt(CHANNEL_NUMBER_A) != getMessage().getInt(CHANNEL_NUMBER_B) && + getMessage().getInt(SYSTEM_SERVICE_CLASS_B) != 0; + } + + public IChannelDescriptor getChannelB() + { + if(hasChannelB() && mChannelB == null) + { + mChannelB = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND_B), getMessage().getInt(CHANNEL_NUMBER_B)); + } + + return mChannelB; + } + + public VoiceServiceOptions getVoiceServiceOptionsB() + { + if(mVoiceServiceOptionsB == null) + { + mVoiceServiceOptionsB = new VoiceServiceOptions(getMessage().getInt(SYSTEM_SERVICE_CLASS_B)); + } + + return mVoiceServiceOptionsB; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSite()); + mIdentifiers.add(getRfss()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannelA()); + + if(hasChannelB()) + { + channels.add(getChannelB()); + } + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/StatusQuery.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/StatusQuery.java new file mode 100644 index 00000000..98bd9454 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/StatusQuery.java @@ -0,0 +1,97 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Status query + */ +public class StatusQuery extends OSPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public StatusQuery(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + return sb.toString(); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/StatusUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/StatusUpdate.java new file mode 100644 index 00000000..da46b9ff --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/StatusUpdate.java @@ -0,0 +1,125 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.status.APCO25Status; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Status update + */ +public class StatusUpdate extends OSPMessage +{ + private static final int[] UNIT_STATUS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] USER_STATUS = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mUnitStatus; + private Identifier mUserStatus; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public StatusUpdate(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" UNIT STATUS:").append(getUnitStatus()); + sb.append(" USER STATUS:").append(getUserStatus()); + return sb.toString(); + } + + public Identifier getUnitStatus() + { + if(mUnitStatus == null) + { + mUnitStatus = APCO25Status.createUnitStatus(getMessage().getInt(UNIT_STATUS)); + } + + return mUnitStatus; + } + + public Identifier getUserStatus() + { + if(mUserStatus == null) + { + mUserStatus = APCO25Status.createUserStatus(getMessage().getInt(USER_STATUS)); + } + + return mUserStatus; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getUnitStatus()); + mIdentifiers.add(getUserStatus()); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/SyncBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/SyncBroadcast.java new file mode 100644 index 00000000..891b6c4e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/SyncBroadcast.java @@ -0,0 +1,272 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.SimpleTimeZone; +import java.util.TimeZone; + +/** + * TDMA sync broadcast + */ +public class SyncBroadcast extends OSPMessage +{ + public static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28}; + public static final int SYSTEM_TIME_NOT_LOCKED_TO_EXTERNAL_REFERENCE_FLAG = 29; + public static final int MICRO_SLOTS_TO_MINUTE_ROLLOVER_UNLOCKED_FLAG = 30; + public static final int[] LEAP_SECOND_CORRECTION = {31, 32}; + public static final int LOCAL_TIME_OFFSET_VALID_FLAG = 33; + public static final int LOCAL_TIME_OFFSET_SIGN = 34; + public static final int[] LOCAL_TIME_OFFSET_HOURS = {35, 36, 37, 38}; + public static final int LOCAL_TIME_OFFSET_HALF_HOUR = 39; + public static final int[] YEAR = {40, 41, 42, 43, 44, 45, 46}; + public static final int[] MONTH = {47, 48, 49, 50}; + public static final int[] DAY = {51, 52, 53, 54, 55}; + public static final int[] HOURS = {56, 57, 58, 59, 60}; + public static final int[] MINUTES = {61, 62, 63, 65, 65, 66}; + public static final int[] MICRO_SLOTS = {67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + + private static final DateFormat TIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS Z"); + private static final TimeZone NO_TIME_ZONE = new SimpleTimeZone(0, "NONE"); + + /** + * Constructs a TSBK from the binary message sequence. + */ + public SyncBroadcast(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" SYSTEM TIME"); + if(isSystemTimeNotLockedToExternalReference()) + { + sb.append(" UNLOCKED"); + } + sb.append(":"); + TIME_FORMATTER.setTimeZone(getTimeZone()); + sb.append(" " + TIME_FORMATTER.format(new Date(getSystemTime()))); + sb.append(" LEAP-SECOND CORRECTION:" + getLeapSecondCorrection() + "mS"); + if(isMicroslotsLockedToMinuteRollover()) + { + sb.append(" MICROSLOT-MINUTE ROLLOVER:SLOW"); + } + else + { + sb.append(" MICROSLOT-MINUTE ROLLOVER:UNLOCKED"); + } + return sb.toString(); + } + + /** + * System Time (UTC) in milliseconds since java epoch + */ + public long getSystemTime() + { + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + cal.clear(); + cal.set(Calendar.YEAR, getYear()); + cal.set(Calendar.MONTH, getMonth() - 1); + cal.set(Calendar.DAY_OF_MONTH, getDay()); + cal.set(Calendar.HOUR_OF_DAY, getHours()); + cal.set(Calendar.MINUTE, getMinutes()); + cal.set(Calendar.MILLISECOND, getMilliSeconds()); + return cal.getTimeInMillis(); + } + + /** + * System Time not locked to external time reference indicator + */ + public boolean isSystemTimeNotLockedToExternalReference() + { + return getMessage().get(SYSTEM_TIME_NOT_LOCKED_TO_EXTERNAL_REFERENCE_FLAG); + } + + /** + * Micro-slot-to-Minute rollover boundary lock indicator. + * + * Note: we invert the message value so that true indicates that the micro + * slots and minute rollovers are locked, whereas the ICD uses false to + * indicate a lock. + * + * @return false if the microslots and minutes rollover are not locked. This + * indicates that the microslots minute rollover is free rolling. + */ + public boolean isMicroslotsLockedToMinuteRollover() + { + return !getMessage().get(MICRO_SLOTS_TO_MINUTE_ROLLOVER_UNLOCKED_FLAG); + } + + /** + * Leap second correction value that should be applied to the system time. + * + * The leap-second correction field indicates the number of 2.5 millisecond + * units that must be added to the system time to account for the insertion + * of leap seconds when the system time is represented as UTC universal time. + * + * @return leap-second correction value in milliseconds 0.0-10.0 + */ + public double getLeapSecondCorrection() + { + return (double)getMessage().getInt(LEAP_SECOND_CORRECTION) * 2.5d; + } + + + /** + * Indicates the local time offset fields contain valid information. + */ + public boolean isValidLocalTimeOffset() + { + return !getMessage().get(LOCAL_TIME_OFFSET_VALID_FLAG); + } + + /** + * Local time zone offset from UTC universal time (zulu) when the VALID + * LOCAL TIME OFFSET flag indicates a valid offset. Otherwise, this method + * returns a static +00:00 indicating no local time offset. + */ + public TimeZone getTimeZone() + { + if(isValidLocalTimeOffset()) + { + int offset = 0; + + offset += getMessage().getInt(LOCAL_TIME_OFFSET_HOURS) * 3600000; + offset += getMessage().get(LOCAL_TIME_OFFSET_HALF_HOUR) ? 1800000 : 0; + offset = getMessage().get(LOCAL_TIME_OFFSET_SIGN) ? -offset : offset; + + return new SimpleTimeZone(offset, "LOCAL"); + } + else + { + return NO_TIME_ZONE; + } + } + + /** + * System time - year. + * + * @return year in range 2000-2127 + */ + public int getYear() + { + return 2000 + getMessage().getInt(YEAR); + } + + /** + * System time - month + * + * @return month 1-12 where 1=January and 12=December + */ + public int getMonth() + { + return getMessage().getInt(MONTH); + } + + /** + * System time - day of month + * + * @return day of month in range 1-31 + */ + public int getDay() + { + return getMessage().getInt(DAY); + } + + /** + * System time - hours + * + * @return hours in range 0 - 23 + */ + public int getHours() + { + return getMessage().getInt(HOURS); + } + + /** + * System time - minutes + * + * @return minutes in range 0 - 59 + */ + public int getMinutes() + { + return getMessage().getInt(MINUTES); + } + + /** + * System time - milli-seconds + * + * Note: sub-millisecond values are rounded to nearest millisecond unit to + * conform to java Date internal milli-second precision level. + * + * @return milli-seconds in range 0 - 59999; + */ + public int getMilliSeconds() + { + return (int)((double)getMicroSlots() * 7.5); + } + + /** + * TDMA Micro Slots. + * + * Number of 7.5 millisecond micro slots since the last minute rollover, or + * since the last micro-slot counter rollover when the the micro-slot to + * minute rollover is unlocked. + * + * @return number of 7.5 mS micro-slots since last minute or micro-slot + * rollover + * @see isMicroslotsLockedToMinuteRollover() method. + * + * This value supports mobile subscriber timing alignment with a TDMA traffic + * channel when a channel is granted from the control channels, so that when + * the mobile changes from the control channel to the traffic channel, it is + * already aligned with the traffic channel TDMA timing. + * + * Super frames occur every 360 ms (48 micro-slots) on FDMA and Ultra frames + * occur every 4 super frames on TDMA. + * + * Valid micro-slot values range 0-7999 and represent 60,000 + * milliseconds ( 8000 x 7.5 ms). + */ + public int getMicroSlots() + { + return getMessage().getInt(MICRO_SLOTS); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/SystemServiceBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/SystemServiceBroadcast.java new file mode 100644 index 00000000..41976288 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/SystemServiceBroadcast.java @@ -0,0 +1,90 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.Service; + +import java.util.Collections; +import java.util.List; + +/** + * Secondary control channel broadcast + */ +public class SystemServiceBroadcast extends OSPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] AVAILABLE_SERVICES = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47}; + private static final int[] SUPPORTED_SERVICES = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71}; + private static final int[] REQUEST_PRIORITY_LEVEL = {72, 73, 74, 75, 76, 77, 78, 79}; + + private List mAvailableServices; + private List mSupportedServices; + + + /** + * Constructs a TSBK from the binary message sequence. + */ + public SystemServiceBroadcast(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" AVAILABLE SERVICES ").append(getAvailableServices()); + sb.append(" SUPPORTED SERVICES ").append(getSupportedServices()); + return sb.toString(); + } + + public List getAvailableServices() + { + if(mAvailableServices == null) + { + mAvailableServices = Service.getServices(getMessage().getInt(AVAILABLE_SERVICES)); + } + + return mAvailableServices; + } + + public List getSupportedServices() + { + if(mSupportedServices == null) + { + mSupportedServices = Service.getServices(getMessage().getInt(SUPPORTED_SERVICES)); + } + + return mSupportedServices; + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/TelephoneInterconnectAnswerRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/TelephoneInterconnectAnswerRequest.java new file mode 100644 index 00000000..2863ada7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/TelephoneInterconnectAnswerRequest.java @@ -0,0 +1,97 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.telephone.APCO25TelephoneNumber; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.Digit; + +import java.util.ArrayList; +import java.util.List; + +/** + * Telephone Interconnect Answer Request. + */ +public class TelephoneInterconnectAnswerRequest extends OSPMessage +{ + private static final int[] DIGIT_1 = {16, 17, 18, 19}; + private static final int[] DIGIT_2 = {20, 21, 22, 23}; + private static final int[] DIGIT_3 = {24, 25, 26, 27}; + private static final int[] DIGIT_4 = {28, 29, 30, 31}; + private static final int[] DIGIT_5 = {32, 33, 34, 35}; + private static final int[] DIGIT_6 = {36, 37, 38, 39}; + private static final int[] DIGIT_7 = {40, 41, 42, 43}; + private static final int[] DIGIT_8 = {44, 45, 46, 47}; + private static final int[] DIGIT_9 = {48, 49, 50, 51}; + private static final int[] DIGIT_10 = {52, 53, 54, 55}; + private static final int[] TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79}; + + private Identifier mTelephoneNumber; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public TelephoneInterconnectAnswerRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getTelephoneNumber()); + sb.append(" TO:").append(getTargetAddress()); + return sb.toString(); + } + + public Identifier getTelephoneNumber() + { + if(mTelephoneNumber == null) + { + List digits = new ArrayList<>(); + digits.add(getMessage().getInt(DIGIT_1)); + digits.add(getMessage().getInt(DIGIT_2)); + digits.add(getMessage().getInt(DIGIT_3)); + digits.add(getMessage().getInt(DIGIT_4)); + digits.add(getMessage().getInt(DIGIT_5)); + digits.add(getMessage().getInt(DIGIT_6)); + digits.add(getMessage().getInt(DIGIT_7)); + digits.add(getMessage().getInt(DIGIT_8)); + digits.add(getMessage().getInt(DIGIT_9)); + digits.add(getMessage().getInt(DIGIT_10)); + + mTelephoneNumber = APCO25TelephoneNumber.createFrom(Digit.decode(digits)); + } + + return mTelephoneNumber; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getTelephoneNumber()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/TelephoneInterconnectVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/TelephoneInterconnectVoiceChannelGrant.java new file mode 100644 index 00000000..e36e3628 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/TelephoneInterconnectVoiceChannelGrant.java @@ -0,0 +1,112 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Telephone interconnect voice call channel grant. + */ +public class TelephoneInterconnectVoiceChannelGrant extends OSPMessage implements IFrequencyBandReceiver +{ + private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] FREQUENCY_BAND = {24, 25, 26, 27}; + private static final int[] CHANNEL_NUMBER = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] CALL_TIMER = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private VoiceServiceOptions mVoiceServiceOptions; + private APCO25Channel mChannel; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public TelephoneInterconnectVoiceChannelGrant(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" RADIO:").append(getSourceAddress()); + sb.append(" TIMER:").append(getCallTimer()); + sb.append("ms CHAN:").append(getChannel()); + sb.append(" ").append(getVoiceServiceOptions().toString()); + return sb.toString(); + } + + /** + * Service options for the request + */ + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + /** + * Call duration timer in milliseconds + */ + public long getCallTimer() + { + return getMessage().getInt(CALL_TIMER) * 100l; //Convert from 100ms units to milliseconds + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), getMessage().getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/TelephoneInterconnectVoiceChannelGrantUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/TelephoneInterconnectVoiceChannelGrantUpdate.java new file mode 100644 index 00000000..611d1be0 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/TelephoneInterconnectVoiceChannelGrantUpdate.java @@ -0,0 +1,112 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25AnyTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Telephone interconnect voice call channel grant. + */ +public class TelephoneInterconnectVoiceChannelGrantUpdate extends OSPMessage implements IFrequencyBandReceiver +{ + private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] FREQUENCY_BAND = {24, 25, 26, 27}; + private static final int[] CHANNEL_NUMBER = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] CALL_TIMER = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] ANY_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private VoiceServiceOptions mVoiceServiceOptions; + private APCO25Channel mChannel; + private Identifier mAnyAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public TelephoneInterconnectVoiceChannelGrantUpdate(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" RADIO:").append(getAnyAddress()); + sb.append(" TIMER:").append(getCallTimer()); + sb.append("ms CHAN:").append(getChannel()); + sb.append(" ").append(getVoiceServiceOptions().toString()); + return sb.toString(); + } + + /** + * Service options for the request + */ + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + /** + * Call duration timer in milliseconds + */ + public long getCallTimer() + { + return getMessage().getInt(CALL_TIMER) * 100l; //Convert from 100ms units to milliseconds + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), getMessage().getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + public Identifier getAnyAddress() + { + if(mAnyAddress == null) + { + mAnyAddress = APCO25AnyTalkgroup.create(getMessage().getInt(ANY_ADDRESS)); + } + + return mAnyAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getAnyAddress()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnitDeRegistrationAcknowledge.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnitDeRegistrationAcknowledge.java new file mode 100644 index 00000000..51d5819c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnitDeRegistrationAcknowledge.java @@ -0,0 +1,111 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit de-registration acknowledge + */ +public class UnitDeRegistrationAcknowledge extends OSPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] WACN = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43}; + private static final int[] SYSTEM = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] TARGET_ID = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mWACN; + private Identifier mSystem; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public UnitDeRegistrationAcknowledge(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" WACN:").append(getWACN()); + sb.append(" SYSTEM:").append(getSystem()); + return sb.toString(); + } + + public Identifier getWACN() + { + if(mWACN == null) + { + mWACN = APCO25Wacn.create(getMessage().getInt(WACN)); + } + + return mWACN; + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getMessage().getInt(SYSTEM)); + } + + return mSystem; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ID)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getWACN()); + mIdentifiers.add(getSystem()); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnitRegistrationCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnitRegistrationCommand.java new file mode 100644 index 00000000..c1eecc35 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnitRegistrationCommand.java @@ -0,0 +1,97 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit registration command + */ +public class UnitRegistrationCommand extends OSPMessage +{ + private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public UnitRegistrationCommand(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + return sb.toString(); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnitRegistrationResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnitRegistrationResponse.java new file mode 100644 index 00000000..930d7525 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnitRegistrationResponse.java @@ -0,0 +1,126 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25IdentifierTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.Response; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit registration response + */ +public class UnitRegistrationResponse extends OSPMessage +{ + private static final int[] RESERVED = {16, 17}; + private static final int[] RESPONSE = {18, 19}; + private static final int[] SYSTEM_ID = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_UNIQUE_ID = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private Response mResponse; + private Identifier mSystem; + private Identifier mTargetUniqueId; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public UnitRegistrationResponse(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO ADDR:").append(getTargetAddress()); + sb.append(" TO ID:").append(getTargetUniqueId()); + sb.append(" SYSTEM:").append(getSystem()); + sb.append(" RESPONSE:").append(getResponse()); + return sb.toString(); + } + + public Response getResponse() + { + if(mResponse == null) + { + mResponse = Response.fromValue(getMessage().getInt(RESPONSE)); + } + + return mResponse; + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getMessage().getInt(SYSTEM_ID)); + } + + return mSystem; + } + + public Identifier getTargetUniqueId() + { + if(mTargetUniqueId == null) + { + mTargetUniqueId = APCO25IdentifierTalkgroup.createIndividual(getMessage().getInt(TARGET_UNIQUE_ID)); + } + + return mTargetUniqueId; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetUniqueId()); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSystem()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnitToUnitAnswerRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnitToUnitAnswerRequest.java new file mode 100644 index 00000000..a68e2d75 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnitToUnitAnswerRequest.java @@ -0,0 +1,94 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit to Unit answer request + */ +public class UnitToUnitAnswerRequest extends OSPMessage +{ + private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] RESERVED = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private VoiceServiceOptions mVoiceServiceOptions; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public UnitToUnitAnswerRequest(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" ").append(getVoiceServiceOptions().toString()); + return sb.toString(); + } + + /** + * Service options for the request + */ + public VoiceServiceOptions getVoiceServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnitToUnitVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnitToUnitVoiceChannelGrant.java new file mode 100644 index 00000000..fad8531c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnitToUnitVoiceChannelGrant.java @@ -0,0 +1,101 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit to unit call channel grant. + */ +public class UnitToUnitVoiceChannelGrant extends OSPMessage implements IFrequencyBandReceiver +{ + private static final int[] FREQUENCY_BAND = {16, 17, 18, 19}; + private static final int[] CHANNEL_NUMBER = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private APCO25Channel mChannel; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public UnitToUnitVoiceChannelGrant(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" CHAN:").append(getChannel()); + return sb.toString(); + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), getMessage().getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnitToUnitVoiceChannelGrantUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnitToUnitVoiceChannelGrantUpdate.java new file mode 100644 index 00000000..27a1413f --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnitToUnitVoiceChannelGrantUpdate.java @@ -0,0 +1,101 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FromTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25ToTalkgroup; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit to Unit voice channel grant update + */ +public class UnitToUnitVoiceChannelGrantUpdate extends OSPMessage implements IFrequencyBandReceiver +{ + private static final int[] FREQUENCY_BAND = {16, 17, 18, 19}; + private static final int[] CHANNEL_NUMBER = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private APCO25Channel mChannel; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public UnitToUnitVoiceChannelGrantUpdate(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" CHAN:").append(getChannel()); + return sb.toString(); + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), getMessage().getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25ToTalkgroup.createIndividual(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25FromTalkgroup.createIndividual(getMessage().getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnknownOSPMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnknownOSPMessage.java new file mode 100644 index 00000000..aa2b2595 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/standard/osp/UnknownOSPMessage.java @@ -0,0 +1,38 @@ +package io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.Collections; +import java.util.List; + +/** + * Unknown/Unrecognized opcode message. + */ +public class UnknownOSPMessage extends OSPMessage +{ + /** + * Constructs a TSBK from the binary message sequence. + */ + public UnknownOSPMessage(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" **UNRECOGNIZED OSP OPCODE**"); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/unknown/isp/UnknownVendorISPMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/unknown/isp/UnknownVendorISPMessage.java new file mode 100644 index 00000000..f983c0f5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/unknown/isp/UnknownVendorISPMessage.java @@ -0,0 +1,59 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.unknown.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.Collections; +import java.util.List; + +/** + * Unknown/Unrecognized opcode message. + */ +public class UnknownVendorISPMessage extends OSPMessage +{ + /** + * Constructs a TSBK from the binary message sequence. + */ + public UnknownVendorISPMessage(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" VENDOR:").append(getVendor()); + sb.append(" UNRECOGNIZED ISP OPCODE"); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/unknown/osp/UnknownVendorOSPMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/unknown/osp/UnknownVendorOSPMessage.java new file mode 100644 index 00000000..a4a1e740 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/unknown/osp/UnknownVendorOSPMessage.java @@ -0,0 +1,59 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.tsbk.unknown.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.DataUnitID; + +import java.util.Collections; +import java.util.List; + +/** + * Unknown/Unrecognized opcode message. + */ +public class UnknownVendorOSPMessage extends OSPMessage +{ + /** + * Constructs a TSBK from the binary message sequence. + */ + public UnknownVendorOSPMessage(DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" VENDOR:").append(getVendor()); + sb.append(" UNRECOGNIZED OSP OPCODE"); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/vendor/VendorOpcode.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/vendor/VendorOpcode.java deleted file mode 100644 index acfd0524..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/tsbk/vendor/VendorOpcode.java +++ /dev/null @@ -1,111 +0,0 @@ -package io.github.dsheirer.module.decode.p25.message.tsbk.vendor; - -public enum VendorOpcode -{ - OP00( "OPCODE 00 ", "VENDOR OPCODE 0x00", 0x00 ), - OP01( "OPCODE 01 ", "VENDOR OPCODE 0x01", 0x01 ), - OP02( "OPCODE 02 ", "VENDOR OPCODE 0x02", 0x02 ), - OP03( "OPCODE 03 ", "VENDOR OPCODE 0x03", 0x03 ), - OP04( "OPCODE 04 ", "VENDOR OPCODE 0x04", 0x04 ), - OP05( "OPCODE 05 ", "VENDOR OPCODE 0x05", 0x05 ), - OP06( "OPCODE 06 ", "VENDOR OPCODE 0x06", 0x06 ), - OP07( "OPCODE 07 ", "VENDOR OPCODE 0x07", 0x07 ), - OP08( "OPCODE 08 ", "VENDOR OPCODE 0x08", 0x08 ), - OP09( "OPCODE 09 ", "VENDOR OPCODE 0x09", 0x09 ), - OP0A( "OPCODE 0A ", "VENDOR OPCODE 0x0A", 0x0A ), - OP0B( "OPCODE 0B ", "VENDOR OPCODE 0x0B", 0x0B ), - OP0C( "OPCODE 0C ", "VENDOR OPCODE 0x0C", 0x0C ), - OP0D( "OPCODE 0D ", "VENDOR OPCODE 0x0D", 0x0D ), - OP0E( "OPCODE 0E ", "VENDOR OPCODE 0x0E", 0x0E ), - OP0F( "OPCODE 0F ", "VENDOR OPCODE 0x0F", 0x0F ), - OP10( "OPCODE 10 ", "VENDOR OPCODE 0x10", 0x10 ), - OP11( "OPCODE 11 ", "VENDOR OPCODE 0x11", 0x11 ), - OP12( "OPCODE 12 ", "VENDOR OPCODE 0x12", 0x12 ), - OP13( "OPCODE 13 ", "VENDOR OPCODE 0x13", 0x13 ), - OP14( "OPCODE 14 ", "VENDOR OPCODE 0x14", 0x14 ), - OP15( "OPCODE 15 ", "VENDOR OPCODE 0x15", 0x15 ), - OP16( "OPCODE 16 ", "VENDOR OPCODE 0x16", 0x16 ), - OP17( "OPCODE 17 ", "VENDOR OPCODE 0x17", 0x17 ), - OP18( "OPCODE 18 ", "VENDOR OPCODE 0x18", 0x18 ), - OP19( "OPCODE 19 ", "VENDOR OPCODE 0x19", 0x19 ), - OP1A( "OPCODE 1A ", "VENDOR OPCODE 0x1A", 0x1A ), - OP1B( "OPCODE 1B ", "VENDOR OPCODE 0x1B", 0x1B ), - OP1C( "OPCODE 1C ", "VENDOR OPCODE 0x1C", 0x1C ), - OP1D( "OPCODE 1D ", "VENDOR OPCODE 0x1D", 0x1D ), - OP1E( "OPCODE 1E ", "VENDOR OPCODE 0x1E", 0x1E ), - OP1F( "OPCODE 1F ", "VENDOR OPCODE 0x1F", 0x1F ), - OP20( "OPCODE 20 ", "VENDOR OPCODE 0x20", 0x20 ), - OP21( "OPCODE 21 ", "VENDOR OPCODE 0x21", 0x21 ), - OP22( "OPCODE 22 ", "VENDOR OPCODE 0x22", 0x22 ), - OP23( "OPCODE 23 ", "VENDOR OPCODE 0x23", 0x23 ), - OP24( "OPCODE 24 ", "VENDOR OPCODE 0x24", 0x24 ), - OP25( "OPCODE 25 ", "VENDOR OPCODE 0x25", 0x25 ), - OP26( "OPCODE 26 ", "VENDOR OPCODE 0x26", 0x26 ), - OP27( "OPCODE 27 ", "VENDOR OPCODE 0x27", 0x27 ), - OP28( "OPCODE 28 ", "VENDOR OPCODE 0x28", 0x28 ), - OP29( "OPCODE 29 ", "VENDOR OPCODE 0x29", 0x29 ), - OP2A( "OPCODE 2A ", "VENDOR OPCODE 0x2A", 0x2A ), - OP2B( "OPCODE 2B ", "VENDOR OPCODE 0x2B", 0x2B ), - OP2C( "OPCODE 2C ", "VENDOR OPCODE 0x2C", 0x2C ), - OP2D( "OPCODE 2D ", "VENDOR OPCODE 0x2D", 0x2D ), - OP2E( "OPCODE 2E ", "VENDOR OPCODE 0x2E", 0x2E ), - OP2F( "OPCODE 2F ", "VENDOR OPCODE 0x2F", 0x2F ), - OP30( "OPCODE 30 ", "VENDOR OPCODE 0x30", 0x30 ), - OP31( "OPCODE 31 ", "VENDOR OPCODE 0x31", 0x31 ), - OP32( "OPCODE 32 ", "VENDOR OPCODE 0x32", 0x32 ), - OP33( "OPCODE 33 ", "VENDOR OPCODE 0x33", 0x33 ), - OP34( "OPCODE 34 ", "VENDOR OPCODE 0x34", 0x34 ), - OP35( "OPCODE 35 ", "VENDOR OPCODE 0x35", 0x35 ), - OP36( "OPCODE 36 ", "VENDOR OPCODE 0x36", 0x36 ), - OP37( "OPCODE 37 ", "VENDOR OPCODE 0x37", 0x37 ), - OP38( "OPCODE 38 ", "VENDOR OPCODE 0x38", 0x38 ), - OP39( "OPCODE 39 ", "VENDOR OPCODE 0x39", 0x39 ), - OP3A( "OPCODE 3A ", "VENDOR OPCODE 0x3A", 0x3A ), - OP3B( "OPCODE 3B ", "VENDOR OPCODE 0x3B", 0x3B ), - OP3C( "OPCODE 3C ", "VENDOR OPCODE 0x3C", 0x3C ), - OP3D( "OPCODE 3D ", "VENDOR OPCODE 0x3D", 0x3D ), - OP3E( "OPCODE 3E ", "VENDOR OPCODE 0x3E", 0x3E ), - OP3F( "OPCODE 3F ", "VENDOR OPCODE 0x3F", 0x3F ), - UNKNOWN( "UNKNOWN ", "Unknown", -1 ); - - private String mLabel; - private String mDescription; - private int mCode; - - private VendorOpcode( String label, String description, int code ) - { - mLabel = label; - mDescription = description; - mCode = code; - } - - public String getLabel() - { - return mLabel; - } - - public String toString() - { - return getLabel(); - } - - public String getDescription() - { - return mDescription; - } - - public int getCode() - { - return mCode; - } - - public static VendorOpcode fromValue( int value ) - { - if( 0 <= value && value <= 0x3F ) - { - return VendorOpcode.values()[ value ]; - } - - return UNKNOWN; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/vselp/VSELP1Message.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/vselp/VSELP1Message.java index 15af9627..ba876630 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/vselp/VSELP1Message.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/vselp/VSELP1Message.java @@ -1,42 +1,41 @@ package io.github.dsheirer.module.decode.p25.message.vselp; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.message.P25Message; import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import java.util.Collections; +import java.util.List; + public class VSELP1Message extends P25Message { - /** - * Motorola VSELP audio message 1. - * - * @param message - * @param duid - * @param aliasList - */ - public VSELP1Message( BinaryMessage message, DataUnitID duid, AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - sb.append( " " ); - sb.append( mMessage.toString() ); - - return sb.toString(); - } - - public String getMessageStub() + /** + * Motorola VSELP audio message 1 - not implemented. + */ + public VSELP1Message(CorrectedBinaryMessage message, int nac, long timestamp) { - StringBuilder sb = new StringBuilder(); + super(message, nac, timestamp); + } - sb.append( "VSELP 1 VOICE FRAME" ); + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } - return sb.toString(); + @Override + public DataUnitID getDUID() + { + return DataUnitID.VSELP1; + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/message/vselp/VSELP2Message.java b/src/main/java/io/github/dsheirer/module/decode/p25/message/vselp/VSELP2Message.java index 886d652a..6dd854cb 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/message/vselp/VSELP2Message.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/message/vselp/VSELP2Message.java @@ -1,42 +1,61 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.message.vselp; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.message.P25Message; import io.github.dsheirer.module.decode.p25.reference.DataUnitID; +import java.util.Collections; +import java.util.List; + public class VSELP2Message extends P25Message { - /** - * Motorola VSELP audio message 1. - * - * @param message - * @param duid - * @param aliasList - */ - public VSELP2Message( BinaryMessage message, DataUnitID duid, AliasList aliasList ) - { - super( message, duid, aliasList ); - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - sb.append( " " ); - sb.append( mMessage.toString() ); - - return sb.toString(); - } - - public String getMessageStub() + /** + * Motorola VSELP audio message - not implemented. + */ + public VSELP2Message(CorrectedBinaryMessage message, int nac, long timestamp) { - StringBuilder sb = new StringBuilder(); + super(message, nac, timestamp); + } - sb.append( "VSELP 2 VOICE FRAME" ); + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } - return sb.toString(); + @Override + public DataUnitID getDUID() + { + return DataUnitID.VSELP2; + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/network/P25NetworkConfigurationMonitor.java b/src/main/java/io/github/dsheirer/module/decode/p25/network/P25NetworkConfigurationMonitor.java new file mode 100644 index 00000000..62325035 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/network/P25NetworkConfigurationMonitor.java @@ -0,0 +1,487 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.network; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.module.decode.p25.P25Decoder; +import io.github.dsheirer.module.decode.p25.message.IFrequencyBand; +import io.github.dsheirer.module.decode.p25.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCAdjacentSiteStatusBroadcast; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCAdjacentSiteStatusBroadcastExplicit; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCNetworkStatusBroadcast; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCNetworkStatusBroadcastExplicit; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCRFSSStatusBroadcast; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCRFSSStatusBroadcastExplicit; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCSecondaryControlChannelBroadcast; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCSecondaryControlChannelBroadcastExplicit; +import io.github.dsheirer.module.decode.p25.message.lc.standard.LCSystemServiceBroadcast; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.message.pdu.ambtc.osp.AMBTCAdjacentStatusBroadcast; +import io.github.dsheirer.module.decode.p25.message.tsbk.TSBKMessage; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.AdjacentStatusBroadcast; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.NetworkStatusBroadcast; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.RFSSStatusBroadcast; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.SNDCPDataChannelAnnouncementExplicit; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.SecondaryControlChannelBroadcast; +import io.github.dsheirer.module.decode.p25.message.tsbk.standard.osp.SystemServiceBroadcast; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +/** + * Tracks the network configuration details of a P25 network from the broadcast messages + */ +public class P25NetworkConfigurationMonitor +{ + private final static Logger mLog = LoggerFactory.getLogger(P25NetworkConfigurationMonitor.class); + + private Map mFrequencyBandMap = new HashMap<>(); + + //Network Status Messages + private NetworkStatusBroadcast mTSBKNetworkStatusBroadcast; + private LCNetworkStatusBroadcast mLCNetworkStatusBroadcast; + private LCNetworkStatusBroadcastExplicit mLCNetworkStatusBroadcastExplicit; + + //Current Site Status Messagese + private RFSSStatusBroadcast mTSBKRFSSStatusBroadcast; + private LCRFSSStatusBroadcast mLCRFSSStatusBroadcast; + private LCRFSSStatusBroadcastExplicit mLCRFSSStatusBroadcastExplicit; + + //Current Site Secondary Control Channels + private Map mSecondaryControlChannels = new TreeMap<>(); + + //Current Site Data Channel + private SNDCPDataChannelAnnouncementExplicit mSNDCPDataChannel; + + //Current Site Services + private SystemServiceBroadcast mTSBKSystemServiceBroadcast; + private LCSystemServiceBroadcast mLCSystemServiceBroadcast; + + //Neighbor Sites + private Map mAMBTCNeighborSites = new HashMap<>(); + private Map mLCNeighborSites = new HashMap<>(); + private Map mLCNeighborSitesExplicit = new HashMap<>(); + private Map mTSBKNeighborSites = new HashMap<>(); + + private P25Decoder.Modulation mModulation; + + /** + * Constructs a network configuration monitor. + * + * @param modulation type used by the decoder + */ + public P25NetworkConfigurationMonitor(P25Decoder.Modulation modulation) + { + mModulation = modulation; + } + + /** + * Processes TSBK network configuration messages + */ + public void process(TSBKMessage tsbk) + { + switch(tsbk.getOpcode()) + { + case OSP_IDENTIFIER_UPDATE: + case OSP_IDENTIFIER_UPDATE_TDMA: + case OSP_IDENTIFIER_UPDATE_VHF_UHF_BANDS: + if(tsbk instanceof IFrequencyBand) + { + IFrequencyBand frequencyBand = (IFrequencyBand)tsbk; + mFrequencyBandMap.put(frequencyBand.getIdentifier(), frequencyBand); + } + break; + case OSP_NETWORK_STATUS_BROADCAST: + if(tsbk instanceof NetworkStatusBroadcast) + { + mTSBKNetworkStatusBroadcast = (NetworkStatusBroadcast)tsbk; + } + break; + case OSP_SYSTEM_SERVICE_BROADCAST: + if(tsbk instanceof SystemServiceBroadcast) + { + mTSBKSystemServiceBroadcast = (SystemServiceBroadcast)tsbk; + } + break; + case OSP_RFSS_STATUS_BROADCAST: + if(tsbk instanceof RFSSStatusBroadcast) + { + mTSBKRFSSStatusBroadcast = (RFSSStatusBroadcast)tsbk; + } + break; + case OSP_SECONDARY_CONTROL_CHANNEL_BROADCAST: + if(tsbk instanceof SecondaryControlChannelBroadcast) + { + SecondaryControlChannelBroadcast sccb = (SecondaryControlChannelBroadcast)tsbk; + + for(IChannelDescriptor secondaryControlChannel : sccb.getChannels()) + { + mSecondaryControlChannels.put(secondaryControlChannel.toString(), secondaryControlChannel); + } + } + break; + case OSP_ADJACENT_STATUS_BROADCAST: + if(tsbk instanceof AdjacentStatusBroadcast) + { + AdjacentStatusBroadcast asb = (AdjacentStatusBroadcast)tsbk; + mTSBKNeighborSites.put((int)asb.getSite().getValue(), asb); + } + break; + case OSP_SNDCP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT: + if(tsbk instanceof SNDCPDataChannelAnnouncementExplicit) + { + mSNDCPDataChannel = (SNDCPDataChannelAnnouncementExplicit)tsbk; + } + break; + } + } + + /** + * Processes Alternate Multi-Block Trunking Control (AMBTC) messages for network configuration details + */ + public void process(AMBTCMessage ambtc) + { + switch(ambtc.getHeader().getOpcode()) + { + case OSP_ADJACENT_STATUS_BROADCAST: + if(ambtc instanceof AMBTCAdjacentStatusBroadcast) + { + AMBTCAdjacentStatusBroadcast aasb = (AMBTCAdjacentStatusBroadcast)ambtc; + mAMBTCNeighborSites.put((int)aasb.getSite().getValue(), aasb); + } + break; +//TODO: process the rest of the messages here + } + } + + /** + * Processes Link Control Word (LCW) messages with network configuration details + */ + public void process(LinkControlWord lcw) + { + if(lcw.isValid()) + { + switch(lcw.getOpcode()) + { + case ADJACENT_SITE_STATUS_BROADCAST: + if(lcw instanceof LCAdjacentSiteStatusBroadcast) + { + LCAdjacentSiteStatusBroadcast assb = (LCAdjacentSiteStatusBroadcast)lcw; + mLCNeighborSites.put((int)assb.getSite().getValue(), assb); + } + break; + case ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT: + if(lcw instanceof LCAdjacentSiteStatusBroadcastExplicit) + { + LCAdjacentSiteStatusBroadcastExplicit assbe = (LCAdjacentSiteStatusBroadcastExplicit)lcw; + mLCNeighborSitesExplicit.put((int)assbe.getSite().getValue(), assbe); + } + break; + case CHANNEL_IDENTIFIER_UPDATE: + case CHANNEL_IDENTIFIER_UPDATE_EXPLICIT: + if(lcw instanceof IFrequencyBand) + { + IFrequencyBand band = (IFrequencyBand)lcw; + mFrequencyBandMap.put(band.getIdentifier(), band); + } + break; + case NETWORK_STATUS_BROADCAST: + if(lcw instanceof LCNetworkStatusBroadcast) + { + mLCNetworkStatusBroadcast = (LCNetworkStatusBroadcast)lcw; + } + break; + case NETWORK_STATUS_BROADCAST_EXPLICIT: + if(lcw instanceof LCNetworkStatusBroadcastExplicit) + { + mLCNetworkStatusBroadcastExplicit = (LCNetworkStatusBroadcastExplicit)lcw; + } + break; + case RFSS_STATUS_BROADCAST: + if(lcw instanceof LCRFSSStatusBroadcast) + { + mLCRFSSStatusBroadcast = (LCRFSSStatusBroadcast)lcw; + } + break; + case RFSS_STATUS_BROADCAST_EXPLICIT: + if(lcw instanceof LCRFSSStatusBroadcastExplicit) + { + mLCRFSSStatusBroadcastExplicit = (LCRFSSStatusBroadcastExplicit)lcw; + } + break; + case SECONDARY_CONTROL_CHANNEL_BROADCAST: + if(lcw instanceof LCSecondaryControlChannelBroadcast) + { + LCSecondaryControlChannelBroadcast sccb = (LCSecondaryControlChannelBroadcast)lcw; + + for(IChannelDescriptor channel : sccb.getChannels()) + { + mSecondaryControlChannels.put(channel.toString(), channel); + } + } + break; + case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: + if(lcw instanceof LCSecondaryControlChannelBroadcastExplicit) + { + LCSecondaryControlChannelBroadcastExplicit sccb = (LCSecondaryControlChannelBroadcastExplicit)lcw; + for(IChannelDescriptor channel : sccb.getChannels()) + { + mSecondaryControlChannels.put(channel.toString(), channel); + } + } + break; + case SYSTEM_SERVICE_BROADCAST: + if(lcw instanceof LCSystemServiceBroadcast) + { + mLCSystemServiceBroadcast = (LCSystemServiceBroadcast)lcw; + } + break; + } + + } + + } + + public void reset() + { + + } + + public String getActivitySummary() + { + StringBuilder sb = new StringBuilder(); + + sb.append("Activity Summary - Decoder:P25 ").append(mModulation.getLabel()); + + sb.append("\n\nNetwork\n"); + if(mTSBKNetworkStatusBroadcast != null) + { + sb.append(" NAC:").append(mTSBKNetworkStatusBroadcast.getNAC()); + sb.append(" WACN:").append(mTSBKNetworkStatusBroadcast.getWacn()); + sb.append(" SYSTEM:").append(mTSBKNetworkStatusBroadcast.getSystem()); + sb.append(" LRA:").append(mTSBKNetworkStatusBroadcast.getLocationRegistrationArea()); + } + else if(mLCNetworkStatusBroadcast != null) + { + sb.append(" WACN:").append(mLCNetworkStatusBroadcast.getWACN()); + sb.append(" SYSTEM:").append(mLCNetworkStatusBroadcast.getSystem()); + } + else if(mLCNetworkStatusBroadcastExplicit != null) + { + sb.append(" WACN:").append(mLCNetworkStatusBroadcastExplicit.getWACN()); + sb.append(" SYSTEM:").append(mLCNetworkStatusBroadcastExplicit.getSystem()); + } + else + { + sb.append(" UNKNOWN"); + } + + sb.append("\n\nCurrent Site\n"); + if(mTSBKRFSSStatusBroadcast != null) + { + sb.append(" SYSTEM:").append(mTSBKRFSSStatusBroadcast.getSystem()); + sb.append(" SITE:").append(mTSBKRFSSStatusBroadcast.getSite()); + sb.append(" RF SUBSYSTEM:").append(mTSBKRFSSStatusBroadcast.getRfss()); + sb.append(" LOCATION REGISTRATION AREA:").append(mTSBKRFSSStatusBroadcast.getLocationRegistrationArea()); + sb.append(" STATUS:").append(mTSBKRFSSStatusBroadcast.isActiveNetworkConnectionToRfssControllerSite() ? + "ACTIVE RFSS NETWORK CONNECTION\n" : "\n"); + sb.append(" PRI CONTROL CHANNEL:").append(mTSBKRFSSStatusBroadcast.getChannel()); + sb.append(" DOWNLINK:").append(mTSBKRFSSStatusBroadcast.getChannel().getDownlinkFrequency()); + sb.append(" UPLINK:").append(mTSBKRFSSStatusBroadcast.getChannel().getUplinkFrequency()).append("\n"); + } + else if(mLCRFSSStatusBroadcast != null) + { + sb.append(" SYSTEM:").append(mLCRFSSStatusBroadcast.getSystem()); + sb.append(" SITE:").append(mLCRFSSStatusBroadcast.getSite()); + sb.append(" RF SUBSYSTEM:").append(mLCRFSSStatusBroadcast.getRfss()); + sb.append(" LOCATION REGISTRATION AREA:").append(mLCRFSSStatusBroadcast.getLocationRegistrationArea()).append("\n"); + sb.append(" PRI CONTROL CHANNEL:").append(mLCRFSSStatusBroadcast.getChannel()); + sb.append(" DOWNLINK:").append(mLCRFSSStatusBroadcast.getChannel().getDownlinkFrequency()); + sb.append(" UPLINK:").append(mLCRFSSStatusBroadcast.getChannel().getUplinkFrequency()).append("\n"); + } + else if(mLCRFSSStatusBroadcastExplicit != null) + { + sb.append(" SITE:").append(mLCRFSSStatusBroadcastExplicit.getSite()); + sb.append(" RF SUBSYSTEM:").append(mLCRFSSStatusBroadcastExplicit.getRfss()); + sb.append(" LOCATION REGISTRATION AREA:").append(mLCRFSSStatusBroadcastExplicit.getLocationRegistrationArea()).append("\n"); + sb.append(" PRI CONTROL CHANNEL:").append(mLCRFSSStatusBroadcastExplicit.getChannel()); + sb.append(" DOWNLINK:").append(mLCRFSSStatusBroadcastExplicit.getChannel().getDownlinkFrequency()); + sb.append(" UPLINK:").append(mLCRFSSStatusBroadcastExplicit.getChannel().getUplinkFrequency()).append("\n"); + } + else + { + sb.append(" UNKNOWN"); + } + + if(!mSecondaryControlChannels.isEmpty()) + { + List channels = new ArrayList<>(mSecondaryControlChannels.keySet()); + Collections.sort(channels); + + for(String channel : channels) + { + IChannelDescriptor secondaryControlChannel = mSecondaryControlChannels.get(channel); + + if(secondaryControlChannel != null) + { + sb.append(" SEC CONTROL CHANNEL:").append(secondaryControlChannel); + sb.append(" DOWNLINK:").append(secondaryControlChannel.getDownlinkFrequency()); + sb.append(" UPLINK:").append(secondaryControlChannel.getUplinkFrequency()).append("\n"); + } + } + } + + if(mSNDCPDataChannel != null) + { + sb.append(" CURRENT DATA CHANNEL:").append(mSNDCPDataChannel.getChannel()); + sb.append(" DOWNLINK:").append(mSNDCPDataChannel.getChannel().getDownlinkFrequency()); + sb.append(" UPLINK:").append(mSNDCPDataChannel.getChannel().getUplinkFrequency()).append("\n"); + } + + if(mTSBKSystemServiceBroadcast != null) + { + sb.append(" AVAILABLE SERVICES:").append(mTSBKSystemServiceBroadcast.getAvailableServices()); + sb.append(" SUPPORTED SERVICES:").append(mTSBKSystemServiceBroadcast.getSupportedServices()); + } + else if(mLCSystemServiceBroadcast != null) + { + sb.append(" AVAILABLE SERVICES:").append(mTSBKSystemServiceBroadcast.getAvailableServices()); + sb.append(" SUPPORTED SERVICES:").append(mTSBKSystemServiceBroadcast.getSupportedServices()); + } + + + sb.append("\nNeighbor Sites\n"); + Set sites = new TreeSet<>(); + sites.addAll(mAMBTCNeighborSites.keySet()); + sites.addAll(mLCNeighborSites.keySet()); + sites.addAll(mLCNeighborSitesExplicit.keySet()); + sites.addAll(mTSBKNeighborSites.keySet()); + + if(sites.isEmpty()) + { + sb.append(" UNKNOWN"); + } + else + { + List sitesSorted = new ArrayList<>(sites); + Collections.sort(sitesSorted); + + for(Integer site : sitesSorted) + { + if(mAMBTCNeighborSites.containsKey(site)) + { + AMBTCAdjacentStatusBroadcast ambtc = mAMBTCNeighborSites.get(site); + sb.append(" SYSTEM:").append(ambtc.getSystem()); + sb.append(" SITE:").append(ambtc.getSite()); + sb.append(" LRA:").append(ambtc.getLocationRegistrationArea()); + sb.append(" RFSS:").append(ambtc.getRfss()); + sb.append(" CHANNEL:").append(ambtc.getChannel()); + sb.append(" DOWNLINK:").append(ambtc.getChannel().getDownlinkFrequency()); + sb.append(" UPLINK:").append(ambtc.getChannel().getUplinkFrequency()).append("\n"); + } + if(mLCNeighborSites.containsKey(site)) + { + LCAdjacentSiteStatusBroadcast lc = mLCNeighborSites.get(site); + sb.append(" SYSTEM:").append(lc.getSystem()); + sb.append(" SITE:").append(lc.getSite()); + sb.append(" LRA:").append(lc.getLocationRegistrationArea()); + sb.append(" RFSS:").append(lc.getRfss()); + sb.append(" CHANNEL:").append(lc.getChannel()); + sb.append(" DOWNLINK:").append(lc.getChannel().getDownlinkFrequency()); + sb.append(" UPLINK:").append(lc.getChannel().getUplinkFrequency()).append("\n"); + + } + if(mLCNeighborSitesExplicit.containsKey(site)) + { + LCAdjacentSiteStatusBroadcastExplicit lce = mLCNeighborSitesExplicit.get(site); + sb.append(" SYSTEM:---"); + sb.append(" SITE:").append(lce.getSite()); + sb.append(" LRA:").append(lce.getLocationRegistrationArea()); + sb.append(" RFSS:").append(lce.getRfss()); + sb.append(" CHANNEL:").append(lce.getChannel()); + sb.append(" DOWNLINK:").append(lce.getChannel().getDownlinkFrequency()); + sb.append(" UPLINK:").append(lce.getChannel().getUplinkFrequency()).append("\n"); + } + if(mTSBKNeighborSites.containsKey(site)) + { + AdjacentStatusBroadcast asb = mTSBKNeighborSites.get(site); + sb.append(" SYSTEM:").append(asb.getSystem()); + sb.append(" SITE:").append(asb.getSite()); + sb.append(" LRA:").append(asb.getLocationRegistrationArea()); + sb.append(" RFSS:").append(asb.getRfss()); + sb.append(" CHANNEL:").append(asb.getChannel()); + sb.append(" DOWNLINK:").append(asb.getChannel().getDownlinkFrequency()); + sb.append(" UPLINK:").append(asb.getChannel().getUplinkFrequency()); + sb.append(" STATUS:").append(asb.getSiteFlags()).append("\n"); + } + } + } + + sb.append("\nFrequency Bands\n"); + if(mFrequencyBandMap.isEmpty()) + { + sb.append(" UNKNOWN"); + } + else + { + List ids = new ArrayList<>(mFrequencyBandMap.keySet()); + Collections.sort(ids); + { + for(Integer id : ids) + { + sb.append(" ").append(formatFrequencyBand(mFrequencyBandMap.get(id))).append("\n"); + } + } + } + + return sb.toString(); + } + + /** + * Formats a frequency band + */ + private String formatFrequencyBand(IFrequencyBand band) + { + StringBuilder sb = new StringBuilder(); + sb.append("BAND:").append(band.getIdentifier()); + sb.append(" ").append(band.isTDMA() ? "TDMA" : "FDMA"); + sb.append(" BASE:").append(band.getBaseFrequency()); + sb.append(" BANDWIDTH:").append(band.getBandwidth()); + sb.append(" SPACING:").append(band.getChannelSpacing()); + sb.append(" TRANSMIT OFFSET:").append(band.getTransmitOffset()); + + if(band.isTDMA()) + { + sb.append(" TIMESLOTS:").append(band.getTimeslotCount()); + } + + return sb.toString(); + } + +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/AccessType.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/AccessType.java new file mode 100644 index 00000000..7f26645a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/AccessType.java @@ -0,0 +1,31 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; + +/** + * Access types + */ +public enum AccessType +{ + FDMA, + TDMA, + UNKNOWN; +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/AnswerResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/AnswerResponse.java new file mode 100644 index 00000000..724cec6b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/AnswerResponse.java @@ -0,0 +1,24 @@ +package io.github.dsheirer.module.decode.p25.reference; + +public enum AnswerResponse +{ + PROCEED, + DENY, + WAIT, + UNKNOWN; + + public static AnswerResponse fromValue(int value) + { + switch(value) + { + case 0x20: + return PROCEED; + case 0x21: + return DENY; + case 0x22: + return WAIT; + default: + return UNKNOWN; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/CancelReason.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/CancelReason.java new file mode 100644 index 00000000..0966299e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/CancelReason.java @@ -0,0 +1,46 @@ +package io.github.dsheirer.module.decode.p25.reference; + +public enum CancelReason +{ + NO_REASON("NO REASON"), + TERMINATE_QUEUED_CONDITION("TERMINATE QUEUED CONDITION"), + TERMINATE_RESOURCE_ASSIGNMENT("TERMINATE RESOURCE ASSIGNMENT"), + RESERVED("RESERVED"), + USER_OR_SYSTEM_DEFINED("USER OR SYSTEM DEFINED"), + UNKNOWN("UNKNOWN"); + + private String mLabel; + + CancelReason(String label) + { + mLabel = label; + } + + @Override + public String toString() + { + return mLabel; + } + + public static CancelReason fromCode(int code) + { + if(code == 0x00) + { + return CancelReason.NO_REASON; + } + else if(code == 0x10) + { + return TERMINATE_QUEUED_CONDITION; + } + else if(code == 0x20) + { + return TERMINATE_RESOURCE_ASSIGNMENT; + } + else if(code < 0x7F) + { + return RESERVED; + } + + return USER_OR_SYSTEM_DEFINED; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/Capability.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/Capability.java new file mode 100644 index 00000000..18efe066 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/Capability.java @@ -0,0 +1,123 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; + +/** + * Capability bitmap parser for FDMA/TDMA capabilities + */ +public class Capability +{ + private static int VALID_FLAG = 0x40; + private static int TDMA_HALF_RATE_2_SLOT_FLAG = 0x20; + private static int TDMA_HALF_RATE_4_SLOT_FLAG = 0x10; + private static int FDMA_HALF_RATE_FLAG = 0x08; + private static int TDMA_8PSK_FLAG = 0x04; + + private int mCapability; + + /** + * Constructs a capability bitmap parser + * @param capability + */ + public Capability(int capability) + { + mCapability = capability; + } + + /** + * Indicates that the bitmap contains valid values. This is an all or nothing field. + */ + public boolean isValid() + { + return (mCapability & VALID_FLAG) == VALID_FLAG; + } + + /** + * Indicates the device supports 2-slot TDMA and the Half-Rate vocoder + */ + public boolean isTDMAHalfRate2Slot() + { + return isValid() && (mCapability & TDMA_HALF_RATE_2_SLOT_FLAG) == TDMA_HALF_RATE_2_SLOT_FLAG; + } + + /** + * Indicates the device supports 4-slot TDMA and the Half-Rate vocoder + */ + public boolean isTDMAHalfRate4Slot() + { + return isValid() && (mCapability & TDMA_HALF_RATE_4_SLOT_FLAG) == TDMA_HALF_RATE_4_SLOT_FLAG; + } + + /** + * Indicates the device supports the FDMA Half-Rate vocoder (in addition to the standard Full-Rate) + */ + public boolean isFDMAHalfRate() + { + return isValid() && (mCapability & FDMA_HALF_RATE_FLAG) == FDMA_HALF_RATE_FLAG; + } + + /** + * Indicates the device supports TDMA with 8PSK modulation + */ + public boolean isTDMA8PSK() + { + return isValid() && (mCapability & TDMA_8PSK_FLAG) == TDMA_8PSK_FLAG; + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append("FDMA/FULL-RATE"); + + if(isFDMAHalfRate()) + { + sb.append("/HALF-RATE"); + } + + if(isTDMAHalfRate2Slot() || isTDMAHalfRate4Slot() || isTDMA8PSK()) + { + sb.append(" TDMA"); + + if(isTDMAHalfRate2Slot() || isTDMAHalfRate4Slot()) + { + sb.append("/HALF-RATE"); + } + + if(isTDMAHalfRate2Slot()) + { + sb.append("/2-SLOT"); + } + + if(isTDMAHalfRate4Slot()) + { + sb.append("/4-SLOT"); + } + + if(isTDMA8PSK()) + { + sb.append("/8PSK"); + } + } + + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/ChannelType.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/ChannelType.java new file mode 100644 index 00000000..20f1cb37 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/ChannelType.java @@ -0,0 +1,105 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; + +public enum ChannelType +{ + TYPE_0(AccessType.FDMA, 12500, 1, Vocoder.HALF_RATE), + TYPE_1(AccessType.FDMA, 12500, 1, Vocoder.FULL_RATE), + TYPE_2(AccessType.FDMA, 6250, 1, Vocoder.HALF_RATE), + TYPE_3(AccessType.TDMA, 12500, 2, Vocoder.HALF_RATE), + TYPE_4(AccessType.TDMA, 25000, 4, Vocoder.HALF_RATE), + TYPE_5(AccessType.TDMA, 12500, 2, Vocoder.HALF_RATE), //HD8PSK simulcast + UNKNOWN(AccessType.UNKNOWN, 0, 0, Vocoder.HALF_RATE); + + private AccessType mAccessType; + private int mBandwidth; + private int mSlotsPerCarrier; + private Vocoder mVocoder; + + ChannelType(AccessType accessType, int bandwidth, int slots, Vocoder vocoder) + { + mAccessType = accessType; + mBandwidth = bandwidth; + mSlotsPerCarrier = slots; + mVocoder = vocoder; + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(isTDMA()) + { + sb.append("TDMA"); + if(this.equals(TYPE_5)) + { + sb.append(" H-D8PSK"); + } + sb.append(" BW:" + mBandwidth); + sb.append(" TIMESLOTS:" + mSlotsPerCarrier); + sb.append(" VOCODER:" + mVocoder.name()); + } + else + { + sb.append("FDMA"); + sb.append(" BW:" + mBandwidth); + sb.append(" VOCODER:" + mVocoder.name()); + } + + return sb.toString(); + } + + public boolean isTDMA() + { + return getAccessType() == AccessType.TDMA; + } + + public static ChannelType fromValue(int value) + { + if(0 <= value && value <= 5) + { + return ChannelType.values()[value]; + } + + return ChannelType.UNKNOWN; + } + + public AccessType getAccessType() + { + return mAccessType; + } + + public int getBandwidth() + { + return mBandwidth; + } + + public int getSlotsPerCarrier() + { + return mSlotsPerCarrier; + } + + public Vocoder getVocoder() + { + return mVocoder; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/DataServiceOptions.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/DataServiceOptions.java new file mode 100644 index 00000000..06d7548e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/DataServiceOptions.java @@ -0,0 +1,59 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; + +public class DataServiceOptions extends ServiceOptions +{ + private static final int NSAPI = 0x0F; + + public DataServiceOptions(int serviceOptions) + { + super(serviceOptions); + } + + /** + * Network Service Access Point ID + */ + public int getNSAPI() + { + return (mServiceOptions & NSAPI); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("NSAPI:").append(getNSAPI()); + + if(isEncrypted()) + { + sb.append(" ENCRYPTED"); + } + + if(isEmergency()) + { + sb.append(" EMERGENCY"); + } + + sb.append(" ").append(getSessionMode()); + + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/DataSubscriberUnitType.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/DataSubscriberUnitType.java new file mode 100644 index 00000000..8b30a438 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/DataSubscriberUnitType.java @@ -0,0 +1,56 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; + +/** + * Data Subscriber (ie mobile radio/terminal) Unit Type + */ +public enum DataSubscriberUnitType +{ + DATA_ONLY("DATA ONLY"), + DATA_OR_VOICE("DATA/VOICE"), + RESERVED("RESERVED"); + + private String mLabel; + + DataSubscriberUnitType(String label) + { + mLabel = label; + } + + public String getLabel() + { + return mLabel; + } + + public static DataSubscriberUnitType fromValue(int value) + { + switch(value) + { + case 0: + return DATA_ONLY; + case 1: + return DATA_OR_VOICE; + default: + return RESERVED; + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/DataUnitID.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/DataUnitID.java index a0498efb..ef63a6e3 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/DataUnitID.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/DataUnitID.java @@ -1,94 +1,139 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; /** - * The Data Unit ID (DUID) is part of the Network ID (NID) field and indicates - * the type of message. + * The Data Unit ID (DUID) is part of the Network ID (NID) field and indicates the type of message. */ public enum DataUnitID { - NID ( -1, 64, false, "NID ", "Network and Data Unit ID" ), - HDU ( 0, 712, false, "HDU ", "Header Data Unit" ), - TDU ( 3, 64, false, "TDU ", "Simple Terminator Data Unit" ), - LDU1 ( 5, 1632, true, "LDU1 ", "Logical Link Data Unit 1" ), - VSELP1( 6, 1680, false, "VSEL1", "Motorola VSELP Unit 1" ), - TSBK1( 7, 260, false, "TSBK1", "Trunking Signaling Block" ), - TSBK2( 7, 260, false, "TSBK2", "Trunking Signaling Block" ), - TSBK3( 7, 260, false, "TSBK3", "Trunking Signaling Block" ), - VSELP2( 9, 1680, false, "VSEL2", "Motorola VSELP Unit 2" ), - LDU2 ( 10, 1632, true, "LDU2 ", "Logical Link Data Unit 2" ), - PDU0 ( 12, 260, false, "PDU0 ", "Packet Header Data Unit" ), - PDU1 ( 12, 356, false, "PDU1 ", "Packet Data Unit-Unconfirmed" ), - PDU2 ( 12, 452, false, "PDU2 ", "Packet Data Unit-Unconfirmed" ), - PDU3 ( 12, 548, false, "PDU3 ", "Packet Data Unit-Unconfirmed" ), - PDUC ( 12, 356, false, "PDUC", "Packet Data Unit-Confirmed" ), - TDULC( 15, 352, false, "TDULC", "Terminator Data Unit With Link Control" ), - UNKN ( -1, 0, false, "UNKWN", "Unknown" ); - - private int mValue; - private int mMessageLength; - private boolean mParity; - private String mLabel; - private String mDescription; - - private DataUnitID( int value, int length, boolean parity, String label, - String description ) - { - mValue = value; - mMessageLength = length; - mParity = parity; - mLabel = label; - mDescription = description; - } - - public int getValue() - { - return mValue; - } - - public int getMessageLength() - { - return mMessageLength; - } - - public String getLabel() - { - return mLabel; - } - - public String getDescription() - { - return mDescription; - } - - public boolean getParity() - { - return mParity; - } - - public static DataUnitID fromValue( int value ) - { - switch( value ) - { - case 0: - return HDU; - case 3: - return TDU; - case 5: - return LDU1; - case 6: - return VSELP1; - case 7: - return TSBK1; - case 9: - return VSELP2; - case 10: - return LDU2; - case 12: - return PDU0; - case 15: - return TDULC; - default: - return UNKN; - } - } + HEADER_DATA_UNIT(0, 648 + 10, true, "HDU "), + UNKNOWN_1(1, -1, false, "UNK01"), + UNKNOWN_2(2, -1, false, "UNK02"), + TERMINATOR_DATA_UNIT(3, 28, true, "TDU "), + UNKNOWN_4(4, -1, false, "UNK04"), + LOGICAL_LINK_DATA_UNIT_1(5, 1568, true, "LDU1 "), + VSELP1(6, 64 + 1616, false, "VSEL1"), + UNKNOWN_8(8, -1, false, "UNK08"), + VSELP2(9, 1616 + 64, false, "VSEL2"), + LOGICAL_LINK_DATA_UNIT_2(10, 1568, true, "LDU2 "), + UNKNOWN_11(11, -1, false, "UNK11"), + PACKET_HEADER_DATA_UNIT(12, 196, false, "PDUH "), + PACKET_DATA_UNIT(-1, 196, false, "PDU "), + UNKNOWN_13(13, -1, false, "UNK13"), + UNKNOWN_14(14, -1, false, "UNK14"), + TERMINATOR_DATA_UNIT_LINK_CONTROL(15, 308, true, "TDULC"), + + //The following are not true data unit identifiers, rather they are used as identifiers + ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL(-1, -1, false, "AMBTC"), + IP_PACKET_DATA(-1, 0, false, "IPPKT"), + SUBNETWORK_DEPENDENT_CONVERGENCE_PROTOCOL(-1, 0, false, "SNDCP"), + TRUNKING_SIGNALING_BLOCK_1(7, 196, false, "TSBK1"), + TRUNKING_SIGNALING_BLOCK_2(7, 196, false, "TSBK2"), + TRUNKING_SIGNALING_BLOCK_3(7, 196, false, "TSBK3"), + UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL(-1, -1, false, "UMBTC"), + + UNKNOWN(-1, -1, false, "UNKN "); + + private int mValue; + private int mMessageLength; + private boolean mTrailingStatusDibit; + private String mLabel; + + DataUnitID(int value, int length, boolean trailingStatusDibit, String label) + { + mValue = value; + mMessageLength = length; + mTrailingStatusDibit = trailingStatusDibit; + mLabel = label; + } + + /** + * Data Unit ID value + */ + public int getValue() + { + return mValue; + } + + /** + * Length of the message in bits + */ + public int getMessageLength() + { + return mMessageLength; + } + + /** + * Short display label + */ + public String getLabel() + { + return mLabel; + } + + /** + * Indicates if the message has a trailing status dibit that must be processed + */ + public boolean hasTrailingStatusDibit() + { + return mTrailingStatusDibit; + } + + /** + * Lookup the Data Unit ID from an integer value + */ + public static DataUnitID fromValue(int value) + { + switch(value) + { + case 0: + return HEADER_DATA_UNIT; + case 1: + return UNKNOWN_1; + case 2: + return UNKNOWN_2; + case 3: + return TERMINATOR_DATA_UNIT; + case 4: + return UNKNOWN_4; + case 5: + return LOGICAL_LINK_DATA_UNIT_1; + case 6: + return VSELP1; + case 7: + return TRUNKING_SIGNALING_BLOCK_1; + case 8: + return UNKNOWN_8; + case 9: + return VSELP2; + case 10: + return LOGICAL_LINK_DATA_UNIT_2; + case 11: + return UNKNOWN_11; + case 12: + return PACKET_HEADER_DATA_UNIT; + case 13: + return UNKNOWN_13; + case 14: + return UNKNOWN_14; + case 15: + return TERMINATOR_DATA_UNIT_LINK_CONTROL; + default: + throw new IllegalArgumentException("Data Unit ID must be in range 0 - 15"); + } + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/Digit.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/Digit.java index 7e2e2231..0c4b14a3 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/Digit.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/Digit.java @@ -20,7 +20,7 @@ public enum Digit D13( "HOOK FLASH", "-" ), D14( "PAUSE", "-" ), D15( "ESC", "NULL" ), - DUNK( "?", "?" ); + UNKNOWN( "?", "?" ); private String mValue; private String mEscapedValue; @@ -48,7 +48,7 @@ public enum Digit return values()[ value ]; } - return DUNK; + return UNKNOWN; } public static String decode( List values ) diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/Direction.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/Direction.java new file mode 100644 index 00000000..0f93a80e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/Direction.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; + +/** + * Message direction + */ +public enum Direction +{ + /** + * Inbound: MOBILE >> REPEATER + */ + INBOUND("ISP"), + + /** + * Output: REPEATER >> MOBILE + */ + OUTBOUND("OSP"); + + private String mLabel; + + Direction(String label) + { + mLabel = label; + } + + /** + * Lookup method for direction. + * + * @param direction + * @return + */ + public static Direction fromValue(boolean direction) + { + return (direction ? OUTBOUND : INBOUND); + } + + /** + * Overrides the .name() method to provide a label + */ + @Override + public String toString() + { + return mLabel; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/Duplex.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/Duplex.java new file mode 100644 index 00000000..f530662b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/Duplex.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; + +public enum Duplex +{ + HALF, FULL +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/ExtendedFunction.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/ExtendedFunction.java index 2a868ea6..77ef2f2c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/ExtendedFunction.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/ExtendedFunction.java @@ -2,70 +2,76 @@ package io.github.dsheirer.module.decode.p25.reference; public enum ExtendedFunction { - RADIO_CHECK( 0x0000, "RADIO CHECK" ), - RADIO_DETACH( 0x007D, "RADIO DETACH" ), - RADIO_UNINHIBIT( 0x007E, "RADIO UNINHIBIT" ), - RADIO_INHIBIT( 0x007F, "RADIO INHIBIT" ), - RADIO_CHECK_ACK( 0x0080, "RADIO CHECK ACK" ), - RADIO_DETACH_ACK( 0x00FD, "RADIO DETACH ACK" ), - RADIO_UNINHIBIT_ACK( 0x00FE, "RADIO UNINHIBIT ACK" ), - RADIO_INHIBIT_ACK( 0x00FF, "RADIO INHIBIT ACK" ), - - GROUP_CONTROL_COMMAND( 0x0100, "GROUP CONTROL COMMAND" ), - UNIT_DYNAMIC_COMMAND( 0x0200, "UNIT DYNAMIC COMMAND" ), - GROUP_DYNAMIC_COMMAND( 0x0300, "GROUP DYNAMIC COMMAND" ), - - UNKNOWN( -1, "UNKNOWN" ); + RADIO_CHECK(0x0000, "RADIO CHECK"), + RADIO_DETACH(0x007D, "RADIO DETACH"), + RADIO_UNINHIBIT(0x007E, "RADIO UNINHIBIT"), + RADIO_INHIBIT(0x007F, "RADIO INHIBIT"), + RADIO_CHECK_ACK(0x0080, "RADIO CHECK ACK"), + RADIO_DETACH_ACK(0x00FD, "RADIO DETACH ACK"), + RADIO_UNINHIBIT_ACK(0x00FE, "RADIO UNINHIBIT ACK"), + RADIO_INHIBIT_ACK(0x00FF, "RADIO INHIBIT ACK"), - private int mFunction; - private String mLabel; - - private ExtendedFunction( int function, String label ) - { - mFunction = function; - mLabel = label; - } - - public String getLabel() - { - return mLabel; - } - - public static ExtendedFunction fromValue( int function ) - { - switch( function ) - { - case 0x0000: - return RADIO_CHECK; - case 0x007D: - return RADIO_DETACH; - case 0x007E: - return RADIO_UNINHIBIT; - case 0x007F: - return RADIO_INHIBIT; - case 0x0080: - return RADIO_CHECK_ACK; - case 0x00FD: - return RADIO_DETACH_ACK; - case 0x00FE: - return RADIO_UNINHIBIT_ACK; - case 0x00FF: - return RADIO_INHIBIT_ACK; - } - - if( ( function & 0x0100 ) == 0x0100 ) - { - return GROUP_CONTROL_COMMAND; - } - if( ( function & 0x0200 ) == 0x0200 ) - { - return UNIT_DYNAMIC_COMMAND; - } - if( ( function & 0x0300 ) == 0x0300 ) - { - return GROUP_DYNAMIC_COMMAND; - } + GROUP_CONTROL_COMMAND(0x0100, "GROUP CONTROL COMMAND"), + UNIT_DYNAMIC_COMMAND(0x0200, "UNIT DYNAMIC COMMAND"), + GROUP_DYNAMIC_COMMAND(0x0300, "GROUP DYNAMIC COMMAND"), - return UNKNOWN; - } + UNKNOWN(-1, "UNKNOWN"); + + private int mFunction; + private String mLabel; + + ExtendedFunction(int function, String label) + { + mFunction = function; + mLabel = label; + } + + public String getLabel() + { + return mLabel; + } + + @Override + public String toString() + { + return mLabel; + } + + public static ExtendedFunction fromValue(int function) + { + switch(function) + { + case 0x0000: + return RADIO_CHECK; + case 0x007D: + return RADIO_DETACH; + case 0x007E: + return RADIO_UNINHIBIT; + case 0x007F: + return RADIO_INHIBIT; + case 0x0080: + return RADIO_CHECK_ACK; + case 0x00FD: + return RADIO_DETACH_ACK; + case 0x00FE: + return RADIO_UNINHIBIT_ACK; + case 0x00FF: + return RADIO_INHIBIT_ACK; + } + + if((function & 0x0100) == 0x0100) + { + return GROUP_CONTROL_COMMAND; + } + if((function & 0x0200) == 0x0200) + { + return UNIT_DYNAMIC_COMMAND; + } + if((function & 0x0300) == 0x0300) + { + return GROUP_DYNAMIC_COMMAND; + } + + return UNKNOWN; + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/IPHeaderCompression.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/IPHeaderCompression.java index 67dbe7e2..6de90d39 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/IPHeaderCompression.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/IPHeaderCompression.java @@ -1,3 +1,23 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; public enum IPHeaderCompression @@ -10,7 +30,7 @@ public enum IPHeaderCompression private int mValue; private String mLabel; - private IPHeaderCompression( int value, String label ) + IPHeaderCompression( int value, String label ) { mValue = value; mLabel = label; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/LinkControlOpcode.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/LinkControlOpcode.java deleted file mode 100644 index 18e14ecd..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/LinkControlOpcode.java +++ /dev/null @@ -1,106 +0,0 @@ -package io.github.dsheirer.module.decode.p25.reference; - -public enum LinkControlOpcode -{ - GROUP_VOICE_CHANNEL_USER( "GRP_VCH_USER ", "Group Voice Channel User", 0 ), - RESERVED_01( "RESERVED_01 ", "Reserved", 1 ), - GROUP_VOICE_CHANNEL_UPDATE( "GRP_VCH_UPDATE ", "Group Voice Channel Grant Update", 2 ), - UNIT_TO_UNIT_VOICE_CHANNEL_USER( "UU_VCH_USER ", "Unit-to-Unit Voice Channel User", 3 ), - GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT( "GRP_VCH_UPD_EXPL", "Group Voice Channel Update Explicit", 4 ), - UNIT_TO_UNIT_ANSWER_REQUEST( "UU_ANS_REQ ", "Unit-to-Unit Answer Request", 5 ), - TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER( "TEL_INT_VCH_USER", "Telephone Interconnect Voice Channel User", 6 ), - TELEPHONE_INTERCONNECT_ANSWER_REQUEST( "TEL_INT_ANS_RQST", "Telephone Interconnect Answer Request", 7 ), - RESERVED_08( "RESERVED_08 ", "Reserved", 8 ), - RESERVED_09( "RESERVED_09 ", "Reserved", 9 ), - RESERVED_0A( "RESERVED_0A ", "Reserved", 10 ), - RESERVED_0B( "RESERVED_0B ", "Reserved", 11 ), - RESERVED_0C( "RESERVED_0C ", "Reserved", 12 ), - RESERVED_0D( "RESERVED_0D ", "Reserved", 13 ), - RESERVED_0E( "RESERVED_0E ", "Reserved", 14 ), - CALL_TERMINATION_OR_CANCELLATION( "CALL TERMINATION", "Call Termination Cancellation", 15 ), - GROUP_AFFILIATION_QUERY( "GRP_AFFIL_QUERY ", "Group Affiliation Query", 16 ), - UNIT_REGISTRATION_COMMAND( "UNIT_REG_COMMAND", "Unit Registration Command", 17 ), - UNIT_AUTHENTICATION_COMMAND( "UNIT_AUTHEN_CMD ", "Unit Authentication Command", 18 ), - STATUS_QUERY( "STATUS QUERY ", "Status Query", 19 ), - STATUS_UPDATE( "STATUS_UPDATE ", "Status Update", 20 ), - MESSAGE_UPDATE( "MESSAGE UPDATE ", "Message Update", 21 ), - CALL_ALERT( "CALL ALERT", "Call Alert", 22 ), - EXTENDED_FUNCTION_COMMAND( "EXT FUNC COMMAND", "Extended Function Command", 23 ), - CHANNEL_IDENTIFIER_UPDATE( "CHAN IDEN UPDATE", "Channel Identifier Update", 24 ), - CHANNEL_IDENTIFIER_UPDATE_EXPLICIT( "CHAN IDEN UPD EX", "Channel Identifier Update Explicit", 25 ), - RESERVED_1A( "RESERVED_1A ", "Reserved", 26 ), - RESERVED_1B( "RESERVED_1B ", "Reserved", 27 ), - RESERVED_1C( "RESERVED_1C ", "Reserved", 28 ), - RESERVED_1D( "RESERVED_1D ", "Reserved", 29 ), - RESERVED_1E( "RESERVED_1E ", "Reserved", 30 ), - RESERVED_1F( "RESERVED_1F ", "Reserved", 31 ), - SYSTEM_SERVICE_BROADCAST( "SYS_SVC_BCAST ", "System Service Broadcast", 32 ), - SECONDARY_CONTROL_CHANNEL_BROADCAST( "SEC_CCH_BROADCST", "Secondary Control Channel Broadcast", 33 ), - ADJACENT_SITE_STATUS_BROADCAST( "ADJ SITE STATUS ", "Adjacent Site Status Broadcast", 34 ), - RFSS_STATUS_BROADCAST( "RFSS_STATUS_BCST", "RFSS Status Broadcast", 35 ), - NETWORK_STATUS_BROADCAST( "NET_STATUS_BCAST", "Network Status Broadcast", 36 ), - PROTECTION_PARAMETER_BROADCAST( "ENCRYPT_PAR_BCST", "Protection Parameter Broadcast", 37 ), - SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT( "SCCB_CCH_BCST_EX", "Secondary Control Channel Broadcast-Explicit", 38 ), - ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT( "ADJ SITE STAT EX", "Adjacent Site Status Broadcast Explicit", 39 ), - RFSS_STATUS_BROADCAST_EXPLICIT( "RFSS STAT BCST E", "RFSS Status Broadcast Explicit", 40 ), - NETWORK_STATUS_BROADCAST_EXPLICIT( "NET STAT BCAST E", "Network Status Broadcast", 41 ), - RESERVED_2A( "RESERVED_2A ", "Reserved", 42 ), - RESERVED_2B( "RESERVED_2B ", "Reserved", 43 ), - RESERVED_2C( "RESERVED_2C ", "Reserved", 44 ), - RESERVED_2D( "RESERVED_2D ", "Reserved", 45 ), - RESERVED_2E( "RESERVED_2E ", "Reserved", 46 ), - RESERVED_2F( "RESERVED_2F ", "Reserved", 47 ), - RESERVED_30( "RESERVED_30 ", "Reserved", 48 ), - RESERVED_31( "RESERVED_31 ", "Reserved", 49 ), - RESERVED_32( "RESERVED_32 ", "Reserved", 50 ), - RESERVED_33( "RESERVED_33 ", "Reserved", 51 ), - RESERVED_34( "RESERVED_34 ", "Reserved", 52 ), - RESERVED_35( "RESERVED_35 ", "Reserved", 53 ), - RESERVED_36( "RESERVED_36 ", "Reserved", 54 ), - RESERVED_37( "RESERVED_37 ", "Reserved", 55 ), - RESERVED_38( "RESERVED_38 ", "Reserved", 56 ), - RESERVED_39( "RESERVED_39 ", "Reserved", 57 ), - RESERVED_3A( "RESERVED_3A ", "Reserved", 58 ), - RESERVED_3B( "RESERVED_3B ", "Reserved", 59 ), - RESERVED_3C( "RESERVED_3C ", "Reserved", 60 ), - RESERVED_3D( "RESERVED_3D ", "Reserved", 61 ), - RESERVED_3E( "RESERVED_3E ", "Reserved", 62 ), - RESERVED_3F( "RESERVED_3F ", "Reserved", 63 ), - UNKNOWN( "UNKNOWN ", "Unknown", -1 ); - - private String mLabel; - private String mDescription; - private int mCode; - - private LinkControlOpcode( String label, String description, int code ) - { - mLabel = label; - mDescription = description; - mCode = code; - } - - public String getLabel() - { - return mLabel; - } - - public String getDescription() - { - return mDescription; - } - - public int getCode() - { - return mCode; - } - - public static LinkControlOpcode fromValue( int value ) - { - if( 0 <= value && value <= 63 ) - { - return values()[ value ]; - } - - return UNKNOWN; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/MaximumTransmitUnit.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/MaximumTransmitUnit.java new file mode 100644 index 00000000..9864e195 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/MaximumTransmitUnit.java @@ -0,0 +1,59 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; + +public enum MaximumTransmitUnit +{ + RESERVED(0), + BYTES_296(296), + BYTES_510(510), + BYTES_1020(1020), + BYTES_1500(1500); + + private int mByteCount; + + MaximumTransmitUnit(int byteCount) + { + mByteCount = byteCount; + } + + public int getByteCount() + { + return mByteCount; + } + + public static MaximumTransmitUnit fromValue(int value) + { + switch(value) + { + case 1: + return BYTES_296; + case 2: + return BYTES_510; + case 3: + return BYTES_1020; + case 4: + return BYTES_1500; + default: + return RESERVED; + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/NetworkAddressType.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/NetworkAddressType.java new file mode 100644 index 00000000..c044b219 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/NetworkAddressType.java @@ -0,0 +1,74 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; + +/** + * SNDCP Network Address Type + */ +public enum NetworkAddressType +{ + IPV4_STATIC_ADDRESS("IPV4 STATIC ADDRESS"), + IPV4_DYNAMIC_ADDRESS("IPV4 DYNAMIC ADDRESS"), + RESERVED("RESERVED"), + NO_ADDRESS("NO ADDRESS"), + UNKNOWN("UNKNOWN"); + + private String mLabel; + + NetworkAddressType(String label) + { + mLabel = label; + } + + @Override + public String toString() + { + return mLabel; + } + + public static NetworkAddressType fromValue(int value) + { + switch(value) + { + case 0: + return IPV4_STATIC_ADDRESS; + case 1: + return IPV4_DYNAMIC_ADDRESS; + case 2: + case 3: + case 4: + case 5: + case 6: + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + return RESERVED; + case 15: + return NO_ADDRESS; + default: + return UNKNOWN; + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/Opcode.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/Opcode.java deleted file mode 100644 index 14b31c3b..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/Opcode.java +++ /dev/null @@ -1,106 +0,0 @@ -package io.github.dsheirer.module.decode.p25.reference; - -public enum Opcode -{ - GROUP_VOICE_CHANNEL_GRANT( "GRP_VCH_GRANT ", "Group Voice Channel Grant", 0 ), - RESERVED_01( "RESERVED_01 ", "Reserved", 1 ), - GROUP_VOICE_CHANNEL_GRANT_UPDATE( "GRP_VCH_GRNT_UPD", "Group Voice Channel Grant Update", 2 ), - GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT( "GRP_VCH_GRNT_UPX", "Group Voice Channel Grant Update - Explicit", 3 ), - UNIT_TO_UNIT_VOICE_CHANNEL_GRANT( "UU_VCH_GRANT ", "Unit-to-Unit Voice Channel Grant", 4 ), - UNIT_TO_UNIT_ANSWER_REQUEST( "UU_ANS_REQ ", "Unit-to-Unit Answer Request", 5 ), - UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE( "UU_VCH_GRANT_UPD", "Unit-to-Unit Voice Channel Grant Update", 6 ), - RESERVED_07( "RESERVED_07 ", "Reserved", 7 ), - TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT( "TEL_INT_VCH_GRNT", "Telephone Interconnect Voice Channel Grant", 8 ), - TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE( "TEL_INT_VCH_GRNU","Telephone Interconnect Voice Channel Grant Update", 9 ), - TELEPHONE_INTERCONNECT_ANSWER_REQUEST( "TEL_INT_ANS_RQST", "Telephone Interconnect Answer Request", 10 ), - RESERVED_0B( "RESERVED_0B ", "Reserved", 11 ), - RESERVED_0C( "RESERVED_0C ", "Reserved", 12 ), - RESERVED_0D( "RESERVED_0D ", "Reserved", 13 ), - RESERVED_0E( "RESERVED_0E ", "Reserved", 14 ), - RESERVED_0F( "RESERVED_0F ", "Reserved", 15 ), - INDIVIDUAL_DATA_CHANNEL_GRANT( "IND_DCH_GRANT ", "Individual Data Channel Grant", 16 ), - GROUP_DATA_CHANNEL_GRANT( "GRP_DCH_GRANT ", "Group Data Channel Grant", 17 ), - GROUP_DATA_CHANNEL_ANNOUNCEMENT( "GRP_DCH_ANNOUNCE", "Group Data Channel Announcement", 18 ), - GROUP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT( "GRP_DCH_ANNC_EXP", "Group Data Channel Announcement-Explicit", 19 ), - SNDCP_DATA_CHANNEL_GRANT( "SNDCP_DCH_GRANT ", "SNDCP Data Channel Grant", 20 ), - SNDCP_DATA_PAGE_REQUEST( "SNDCP_DCH_PAG_RQ", "SNDCP Data Page Request", 21 ), - SNDCP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT( "SNDCP_DCH_ANN_EX", "SNDCP Data Channel Announcement Explicit", 22 ), - RESERVED_17( "RESERVED_17 ", "Reserved", 23 ), - STATUS_UPDATE( "STATUS_UPDATE ", "Status Update", 24 ), - RESERVED_19( "RESERVED_19 ", "Reserved", 25 ), - STATUS_QUERY( "STATUS_QUERY ", "Status Query", 26 ), - RESERVED_1B( "RESERVED_1B ", "Reserved", 27 ), - MESSAGE_UPDATE( "MESSAGE_UPDATE ", "Message Update", 28 ), - RADIO_UNIT_MONITOR_COMMAND( "RADIO_MONITR_CMD", "Radio Unit Monitor Command", 29 ), - RESERVED_1E( "RESERVED_1E ", "Reserved", 30 ), - CALL_ALERT( "CALL_ALERT ", "Call Alert", 31 ), - ACKNOWLEDGE_RESPONSE( "ACK_RESPONSE_FNE", "Acknowledge Response - FNE", 32 ), - QUEUED_RESPONSE( "QUEUED_RESPONSE ", "Queued Response", 33 ), - RESERVED_22( "RESERVED_22 ", "Reserved", 34 ), - RESERVED_23( "RESERVED_23 ", "Reserved", 35 ), - EXTENDED_FUNCTION_COMMAND( "EXTNDED_FUNC_CMD", "Extended Function Command", 36 ), - RESERVED_25( "RESERVED_25 ", "Reserved", 37 ), - RESERVED_26( "RESERVED_26 ", "Reserved", 38 ), - DENY_RESPONSE( "DENY_RESPONSE ", "Deny Response", 39 ), - GROUP_AFFILIATION_RESPONSE( "GRP_AFFIL_RESP ", "Group Affiliation Response", 40 ), - SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT( "SCCB_CCH_BCST_EX", "Secondary Control Channel Broadcast-Explicit", 41 ), - GROUP_AFFILIATION_QUERY( "GRP_AFFIL_QUERY ", "Group Affiliation Query", 42 ), - LOCATION_REGISTRATION_RESPONSE( "LOCN_REG_RESPONS", "Location Registration Response", 43 ), - UNIT_REGISTRATION_RESPONSE( "UNIT_REG_RESPONS", "Unit Registration Response", 44 ), - UNIT_REGISTRATION_COMMAND( "UNIT_REG_COMMAND", "Unit Registration Command", 45 ), - AUTHENTICATION_COMMAND( "AUTH_COMMAND ", "Authentication Command", 46 ), - UNIT_DEREGISTRATION_ACKNOWLEDGE( "DE_REGIST_ACK ", "De-Registration Acknowledge", 47 ), - TDMA_SYNC_BROADCAST( "TDMA_SYNC_BCST ", "FDMA-TDMA Sync Broadcast", 48 ), - RESERVED_31( "RESERVED_31 ", "Reserved", 49 ), - RESERVED_32( "RESERVED_32 ", "Reserved", 50 ), - IDENTIFIER_UPDATE_TDMA( "IDEN_UPDATE_TDMA", "Identifier Update for TDMA", 51 ), - IDENTIFIER_UPDATE_VHF_UHF_BANDS( "IDEN_UPDATE_VUHF", "Identifier Update for VHF/UHF Bands", 52 ), - TIME_DATE_ANNOUNCEMENT( "TIME_DATE_ANNOUN", "Time and Date Announcement", 53 ), - ROAMING_ADDRESS_COMMAND( "ROAM_ADDR_CMD ", "Roaming Address Command", 54 ), - ROAMING_ADDRESS_UPDATE( "ROAM_ADDR_UPDATE", "Roaming Address Update", 55 ), - SYSTEM_SERVICE_BROADCAST( "SYS_SVC_BCAST ", "System Service Broadcast", 56 ), - SECONDARY_CONTROL_CHANNEL_BROADCAST( "SEC_CCH_BROADCST", "Secondary Control Channel Broadcast", 57 ), - RFSS_STATUS_BROADCAST( "RFSS_STATUS_BCST", "RFSS Status Broadcast", 58 ), - NETWORK_STATUS_BROADCAST( "NET_STATUS_BCAST", "Network Status Broadcast", 59 ), - ADJACENT_STATUS_BROADCAST( "ADJ SITE STATUS ", "Adjacent Site Status Broadcast", 60 ), - IDENTIFIER_UPDATE_NON_VUHF( "IDEN_UPDATE ", "Identifier Update", 61 ), - PROTECTION_PARAMETER_BROADCAST( "ENCRYPT_PAR_BCST", "Protection Parameter Broadcast", 62 ), - PROTECTION_PARAMETER_UPDATE( "ENCRYPT_PAR_UPDT", "Protection Parameter Update", 63 ), - UNKNOWN( "UNKNOWN ", "Unknown", -1 ); - - private String mLabel; - private String mDescription; - private int mCode; - - private Opcode( String label, String description, int code ) - { - mLabel = label; - mDescription = description; - mCode = code; - } - - public String getLabel() - { - return mLabel; - } - - public String getDescription() - { - return mDescription; - } - - public int getCode() - { - return mCode; - } - - public static Opcode fromValue( int value ) - { - if( 0 <= value && value <= 63 ) - { - return values()[ value ]; - } - - return UNKNOWN; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/PDUFormat.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/PDUFormat.java index f10155b7..b27a2d98 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/PDUFormat.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/PDUFormat.java @@ -2,66 +2,71 @@ package io.github.dsheirer.module.decode.p25.reference; public enum PDUFormat { - F00( "F00", 0 ), - F01( "F01", 1 ), - F02( "F02", 2 ), - F03( "F03", 3 ), - F04( "F04", 4 ), - F05( "F05", 5 ), - F06( "F06", 6 ), - F07( "F07", 7 ), - F08( "F08", 8 ), - F09( "F09", 9 ), - F10( "F10", 10 ), - F11( "F11", 11 ), - F12( "F12", 12 ), - F13( "F13", 13 ), - F14( "F14", 14 ), - F15( "F15", 15 ), - F16( "F16", 16 ), - F17( "F17", 17 ), - F18( "F18", 18 ), - F19( "F19", 19 ), - F20( "F20", 20 ), - UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL( "UNCONFIRMED MBTC", 21 ), - PACKET_DATA( "PACKET DATA", 22 ), - ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL( "ALTERNATE MBTC", 23 ), - F24( "F24", 24 ), - F25( "F25", 25 ), - F26( "F26", 26 ), - F27( "F27", 27 ), - F28( "F28", 28 ), - F29( "F29", 29 ), - F30( "F30", 30 ), - F31( "F31", 31 ), - UNKNOWN( "UNKN", -1 ); - - private String mLabel; - private int mValue; - - private PDUFormat( String label, int value ) - { - mLabel = label; - mValue = value; - } - - public String getLabel() - { - return mLabel; - } - - public int getValue() - { - return mValue; - } - - public static PDUFormat fromValue( int value ) - { - if( 0 <= value && value <= 31 ) - { - return values()[ value ]; - } - - return UNKNOWN; - } + F00("F00", 0), + F01("F01", 1), + F02("F02", 2), + RESPONSE_PACKET_HEADER_FORMAT("RESPONSE", 3), + F04("F04", 4), + F05("F05", 5), + F06("F06", 6), + F07("F07", 7), + F08("F08", 8), + F09("F09", 9), + F10("F10", 10), + F11("F11", 11), + F12("F12", 12), + F13("F13", 13), + F14("F14", 14), + F15("F15", 15), + F16("F16", 16), + F17("F17", 17), + F18("F18", 18), + F19("F19", 19), + F20("F20", 20), + UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL("UNCONFIRMED MBTC", 21), + PACKET_DATA("PACKET DATA", 22), + ALTERNATE_MULTI_BLOCK_TRUNKING_CONTROL("ALTERNATE MBTC", 23), + F24("F24", 24), + F25("F25", 25), + F26("F26", 26), + F27("F27", 27), + F28("F28", 28), + F29("F29", 29), + F30("F30", 30), + F31("F31", 31), + UNKNOWN("UNKN", -1); + + private String mLabel; + private int mValue; + + PDUFormat(String label, int value) + { + mLabel = label; + mValue = value; + } + + public String getLabel() + { + return mLabel; + } + + public int getValue() + { + return mValue; + } + + public static PDUFormat fromValue(int value) + { + if(0 <= value && value <= 31) + { + return values()[value]; + } + + return UNKNOWN; + } + + public String toString() + { + return mLabel; + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/PDUPriorityMaximumType.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/PDUPriorityMaximumType.java new file mode 100644 index 00000000..4cb4df12 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/PDUPriorityMaximumType.java @@ -0,0 +1,65 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; + +public enum PDUPriorityMaximumType +{ + PRI_0("LOW-0"), + PRI_1("MED-1"), + PRI_2("MED-2"), + PRI_3("MED-3"), + PRI_4("MED-4"), + PRI_5("MED-5"), + PRI_6("MED-6"), + PRI_7("MED-7"), + PRI_8("MED-8"), + PRI_9("MED-9"), + PRI_10("MED-10"), + PRI_11("MED-11"), + PRI_12("MED-12"), + PRI_13("MED-13"), + PRI_14("HIGH-14"), + PRI_15("EMERGENCY-15"), + UNKNOWN("UNKNOWN"); + + private String mLabel; + + PDUPriorityMaximumType(String label) + { + mLabel = label; + } + + @Override + public String toString() + { + return mLabel; + } + + public static PDUPriorityMaximumType fromValue(int value) + { + if(0 <= value && value <= 15) + { + return PDUPriorityMaximumType.values()[value]; + } + + return UNKNOWN; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/PDUType.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/PDUType.java index 2066dc25..993a5a4b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/PDUType.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/PDUType.java @@ -2,55 +2,83 @@ package io.github.dsheirer.module.decode.p25.reference; public enum PDUType { - /* Outbound */ - SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT( "SNDCP-ACTIVATE TDS CONTEXT ACCEPT", 0 ), - SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT( "SNDCP-DEACTIVATE TDS CONTEXT ACCEPT", 1 ), - SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST( "SNDCP-DEACTIVATE TDS CONTEXT REQUEST", 2 ), - SNDCP_ACTIVATE_TDS_CONTEXT_REJECT( "SNDCP-ACTIVATE TDS CONTEXT REJECT", 3 ), - SNDCP_RF_UNCONFIRMED_DATA( "SNDCP-RF UNCONFIRMED DATA", 4 ), - SNDCP_RF_CONFIRMED_DATA( "SNDCP-RF CONFIRMED DATA", 5 ), - PDU_TYPE_6( "PDU TYPE 6 UNKNOWN", 6 ), - PDU_TYPE_7( "PDU TYPE 7 UNKNOWN", 7 ), - PDU_TYPE_8( "PDU TYPE 8 UNKNOWN", 8 ), - PDU_TYPE_9( "PDU TYPE 9 UNKNOWN", 9 ), - PDU_TYPE_10( "PDU TYPE 10 UNKNOWN", 10 ), - PDU_TYPE_11( "PDU TYPE 11 UNKNOWN", 11 ), - - /* Inbound */ - SNDCP_ACTIVATE_TDS_CONTEXT_REQUEST( "SNDCP-ACTIVATE TDS CONTEXT REQUEST", 0 ), + /* Outbound */ + OUTBOUND_SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT("ACTIVATE TDS CONTEXT ACCEPT", 0), + OUTBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT("DEACTIVATE TDS CONTEXT ACCEPT", 1), + OUTBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST("DEACTIVATE TDS CONTEXT REQUEST", 2), + OUTBOUND_SNDCP_ACTIVATE_TDS_CONTEXT_REJECT("ACTIVATE TDS CONTEXT REJECT", 3), + OUTBOUND_SNDCP_RF_UNCONFIRMED_DATA("RF UNCONFIRMED DATA", 4), + OUTBOUND_SNDCP_RF_CONFIRMED_DATA("RF CONFIRMED DATA", 5), + OUTBOUND_UNKNOWN("OUTBOUND UNKNOWN PDU TYPE", -1), - UNKNOWN( "UNKNOWN", -1 ); - - private String mLabel; - private int mValue; - - private PDUType( String label, int value ) - { - mLabel = label; - mValue = value; - } - - public String getLabel() - { - return mLabel; - } - - public int getValue() - { - return mValue; - } - - public static PDUType fromValue( int value, boolean outbound ) - { - if( outbound && 0 <= value && value <= 11 ) - { - return values()[ value ]; - } - else if( value == 0 && !outbound ) - { - return SNDCP_ACTIVATE_TDS_CONTEXT_REQUEST; - } - - return UNKNOWN; - } + INBOUND_SNDCP_ACTIVATE_TDS_CONTEXT_REQUEST("ACTIVATE TDS CONTEXT REQUEST", 0), + INBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT("DEACTIVATE TDS CONTEXT ACCEPT", 1), + INBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST("DEACTIVATE TDS CONTEXT REQUEST", 2), + INBOUND_SNDCP_RF_CONFIRMED_DATA("RF CONFIRMED DATA", 5), + INBOUND_UNKNOWN("INBOUND UNKNOWN PDU TYPE", -1); + + private String mLabel; + private int mValue; + + PDUType(String label, int value) + { + mLabel = label; + mValue = value; + } + + @Override + public String toString() + { + return mLabel; + } + + public String getLabel() + { + return mLabel; + } + + public int getValue() + { + return mValue; + } + + public static PDUType fromValue(int value, boolean outbound) + { + if(outbound) + { + switch(value) + { + case 0: + return OUTBOUND_SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT; + case 1: + return OUTBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT; + case 2: + return OUTBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST; + case 3: + return OUTBOUND_SNDCP_ACTIVATE_TDS_CONTEXT_REJECT; + case 4: + return OUTBOUND_SNDCP_RF_UNCONFIRMED_DATA; + case 5: + return OUTBOUND_SNDCP_RF_CONFIRMED_DATA; + default: + return OUTBOUND_UNKNOWN; + } + } + else + { + switch(value) + { + case 0: + return INBOUND_SNDCP_ACTIVATE_TDS_CONTEXT_REQUEST; + case 1: + return INBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT; + case 2: + return INBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST; + case 5: + return INBOUND_SNDCP_RF_CONFIRMED_DATA; + default: + return INBOUND_UNKNOWN; + } + } + } } \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/PacketResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/PacketResponse.java new file mode 100644 index 00000000..61ebf74a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/PacketResponse.java @@ -0,0 +1,51 @@ +package io.github.dsheirer.module.decode.p25.reference; + +/** + * Response Packet responses + */ +public enum PacketResponse +{ + ALL_BLOCKS_SUCCESSFULLY_RECEIVED("ALL BLOCKS RECEIVED", 0x08), + ILLEGAL_FORMAT("ILLEGAL FORMAT", 0x40), + PACKET_CRC_FAIL("PACKET CRC-32 FAILED", 0x48), + MEMORY_FULL("MEMORY FULL", 0x50), + FSN_OUT_OF_SEQUENCE("FSN OUT OF SEQUENCE", 0x58), + UNDELIVERABLE("UNDELIVERABLE", 0x60), + MSN_OUT_OF_SEQUENCE("MSG OUT OF SEQUENCE", 0x68), + UNAUTHORIZED_USER("UNAUTHORIZED USER", 0x70), + SELECTIVE_RETRY("SELECTIVE RETRY", 0x80), + UNKNOWN("UNKNOWN", 0xFF); + + private String mLabel; + private int mMask; + + PacketResponse(String label, int mask) + { + mLabel = label; + mMask = mask; + } + + public static PacketResponse fromValue(int value) + { + for(PacketResponse packetResponse : values()) + { + if((value & packetResponse.getMask()) == packetResponse.getMask()) + { + return packetResponse; + } + } + + return UNKNOWN; + } + + private int getMask() + { + return mMask; + } + + @Override + public String toString() + { + return mLabel; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/ReadyTimer.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/ReadyTimer.java new file mode 100644 index 00000000..6945907f --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/ReadyTimer.java @@ -0,0 +1,64 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; + +public enum ReadyTimer +{ + SECONDS_RESERVED(0), + SECONDS_1(1), + SECONDS_2(2), + SECONDS_4(4), + SECONDS_6(6), + SECONDS_8(8), + SECONDS_10(10), + SECONDS_15(15), + SECONDS_20(20), + SECONDS_25(25), + SECONDS_30(30), + SECONDS_60(60), + SECONDS_120(120), + SECONDS_180(180), + SECONDS_300(300), + SECONDS_ALWAYS(86400), //Infinity + UNKNOWN(0); + + private int mSeconds; + + ReadyTimer(int seconds) + { + mSeconds = seconds; + } + + public int getSeconds() + { + return mSeconds; + } + + public static ReadyTimer fromValue(int value) + { + if(0 <= value && value <= 15) + { + return ReadyTimer.values()[value]; + } + + return UNKNOWN; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/SNDCPActivationRejectReason.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/RejectReason.java similarity index 85% rename from src/main/java/io/github/dsheirer/module/decode/p25/reference/SNDCPActivationRejectReason.java rename to src/main/java/io/github/dsheirer/module/decode/p25/reference/RejectReason.java index 722da47f..a464974a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/SNDCPActivationRejectReason.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/RejectReason.java @@ -1,6 +1,6 @@ package io.github.dsheirer.module.decode.p25.reference; -public enum SNDCPActivationRejectReason +public enum RejectReason { R0( 0, "ANY REASON" ), R1( 1, "MRC NOT PROVISIONED FOR TDS" ), @@ -20,7 +20,7 @@ public enum SNDCPActivationRejectReason private int mValue; public String mLabel; - private SNDCPActivationRejectReason( int value, String label ) + private RejectReason(int value, String label ) { mValue = value; mLabel = label; @@ -41,7 +41,7 @@ public enum SNDCPActivationRejectReason return getLabel(); } - public static SNDCPActivationRejectReason fromValue( int value ) + public static RejectReason fromValue(int value ) { if( 0 <= value && value <= 12 ) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/ServiceAccessPoint.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/ServiceAccessPoint.java index e133aeb3..9c6e33d9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/ServiceAccessPoint.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/ServiceAccessPoint.java @@ -1,99 +1,119 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; public enum ServiceAccessPoint { - UNENCRYPTED_USER_DATA( "USER DATA", 0 ), - ENCRYPTED_USER_DATA( "ENCRYPTED USER DATA", 1 ), - CIRCUIT_DATA( "CIRCUIT DATA", 2 ), - CIRCUIT_DATA_CONTROL( "CIRCUIT DATA CONTROL", 3 ), - PACKET_DATA( "PACKET DATA", 4 ), - ADDRESS_RESOLUTION_PROTOCOL( "ADDRESS RESOLUTION PROTOCOL", 5 ), - SNDCP_PACKET_DATA_CONTROL( "SNDCP PACKET DATA CONTROL", 6 ), - SAP_7( "7", 7 ), - SAP_8( "8", 8 ), - SAP_9( "9", 9 ), - SAP_10( "10", 10 ), - SAP_11( "11", 11 ), - SAP_12( "12", 12 ), - SAP_13( "13", 13 ), - SAP_14( "14", 14 ), - SAP_15( "15", 15 ), - SAP_16( "16", 16 ), - SAP_17( "17", 17 ), - SAP_18( "18", 18 ), - SAP_19( "19", 19 ), - SAP_20( "20", 20 ), - SAP_21( "21", 21 ), - SAP_22( "22", 22 ), - SAP_23( "23", 23 ), - SAP_24( "24", 24 ), - SAP_25( "25", 25 ), - SAP_26( "26", 26 ), - SAP_27( "27", 27 ), - SAP_28( "28", 28 ), - SAP_29( "29", 29 ), - SAP_30( "30", 30 ), - EXTENDED_ADDRESS( "EXTENDED ADDRESS", 31 ), - REGISTRATION_AND_AUTHORIZATION( "REGISTRATION AND AUTHORIZATION", 32 ), - CHANNEL_REASSIGNMENT( "CHANNEL REASSIGNMENT", 33 ), - SYSTEM_CONFIGURATION( "SYSTEM CONFIGURATION", 34 ), - MR_LOOPBACK( "MOBILE RADIO LOOPBACK", 35 ), - MR_STATISTICS( "MOBILE RADIO STATISTICS", 36 ), - MR_OUT_OF_SERVICE( "MOBILE RADIO OUT OF SERVICE", 37 ), - MR_PAGING( "MOBILE RADIO PAGING", 38 ), - MR_CONFIGURATION( "MOBILE RADIO CONFIGURATION", 39 ), - UNENCRYPTED_KEY_MANAGEMENT_MESSAGE( "UNENCRYPTED KEY MANAGEMENT", 40 ), - ENCRYPTED_KEY_MANAGEMENT_MESSAGE( "ENCRYPTED KEY MANAGEMENT", 41 ), - SAP_42( "42", 42 ), - SAP_43( "43", 43 ), - SAP_44( "44", 44 ), - SAP_45( "45", 45 ), - SAP_46( "46", 46 ), - SAP_47( "47", 47 ), - SAP_48( "48", 48 ), - SAP_49( "49", 49 ), - SAP_50( "50", 50 ), - SAP_51( "51", 51 ), - SAP_52( "52", 52 ), - SAP_53( "53", 53 ), - SAP_54( "54", 54 ), - SAP_55( "55", 55 ), - SAP_56( "56", 56 ), - SAP_57( "57", 57 ), - SAP_58( "58", 58 ), - SAP_59( "59", 59 ), - SAP_60( "60", 60 ), - UNENCRYPTED_TRUNKING_CONTROL( "TRUNKING CONTROL", 61 ), - SAP_62( "62", 62 ), - ENCRYPTED_TRUNKING_CONTROL( "ENCRYPTED TRUNKING CONTROL", 63 ), - UKNOWN( "UNKN", -1 ); + UNENCRYPTED_USER_DATA("USER DATA", 0), + ENCRYPTED_USER_DATA("ENCRYPTED USER DATA", 1), + CIRCUIT_DATA("CIRCUIT DATA", 2), + CIRCUIT_DATA_CONTROL("CIRCUIT DATA CONTROL", 3), + PACKET_DATA("PACKET DATA", 4), + ADDRESS_RESOLUTION_PROTOCOL("ADDRESS RESOLUTION PROTOCOL", 5), + SNDCP_PACKET_DATA_CONTROL("SNDCP PACKET DATA CONTROL", 6), + SAP_7("7", 7), + SAP_8("8", 8), + SAP_9("9", 9), + SAP_10("10", 10), + SAP_11("11", 11), + SAP_12("12", 12), + SAP_13("13", 13), + SAP_14("14", 14), + SAP_15("15", 15), + SAP_16("16", 16), + SAP_17("17", 17), + SAP_18("18", 18), + SAP_19("19", 19), + SAP_20("20", 20), + SAP_21("21", 21), + SAP_22("22", 22), + SAP_23("23", 23), + SAP_24("24", 24), + SAP_25("25", 25), + SAP_26("26", 26), + SAP_27("27", 27), + SAP_28("28", 28), + SAP_29("29", 29), + SAP_30("30", 30), + EXTENDED_ADDRESS("EXTENDED ADDRESS", 31), + REGISTRATION_AND_AUTHORIZATION("REGISTRATION AND AUTHORIZATION", 32), + CHANNEL_REASSIGNMENT("CHANNEL REASSIGNMENT", 33), + SYSTEM_CONFIGURATION("SYSTEM CONFIGURATION", 34), + MOBILE_RADIO_LOOPBACK("MOBILE RADIO LOOPBACK", 35), + MOBILE_RADIO_STATISTICS("MOBILE RADIO STATISTICS", 36), + MOBILE_RADIO_OUT_OF_SERVICE("MOBILE RADIO OUT OF SERVICE", 37), + MOBILE_RADIO_PAGING("MOBILE RADIO PAGING", 38), + MOBILE_RADIO_CONFIGURATION("MOBILE RADIO CONFIGURATION", 39), + UNENCRYPTED_KEY_MANAGEMENT_MESSAGE("UNENCRYPTED KEY MANAGEMENT", 40), + ENCRYPTED_KEY_MANAGEMENT_MESSAGE("ENCRYPTED KEY MANAGEMENT", 41), + SAP_42("42", 42), + SAP_43("43", 43), + SAP_44("44", 44), + SAP_45("45", 45), + SAP_46("46", 46), + SAP_47("47", 47), + SAP_48("48", 48), + SAP_49("49", 49), + SAP_50("50", 50), + SAP_51("51", 51), + SAP_52("52", 52), + SAP_53("53", 53), + SAP_54("54", 54), + SAP_55("55", 55), + SAP_56("56", 56), + SAP_57("57", 57), + SAP_58("58", 58), + SAP_59("59", 59), + SAP_60("60", 60), + UNENCRYPTED_TRUNKING_CONTROL("TRUNKING CONTROL", 61), + SAP_62("62", 62), + ENCRYPTED_TRUNKING_CONTROL("ENCRYPTED TRUNKING CONTROL", 63), + UNKNOWN("UNKN", -1); - private String mLabel; - private int mValue; - - private ServiceAccessPoint( String label, int value ) - { - mLabel = label; - mValue = value; - } - - public String getLabel() - { - return mLabel; - } - - public int getValue() - { - return mValue; - } - - public static ServiceAccessPoint fromValue( int value ) - { - if( 0 <= value && value <= 63 ) - { - return values()[ value ]; - } - - return UKNOWN; - } + private String mLabel; + private int mValue; + + private ServiceAccessPoint(String label, int value) + { + mLabel = label; + mValue = value; + } + + public String getLabel() + { + return mLabel; + } + + public int getValue() + { + return mValue; + } + + public static ServiceAccessPoint fromValue(int value) + { + if(0 <= value && value <= 63) + { + return values()[value]; + } + + return UNKNOWN; + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/ServiceOptions.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/ServiceOptions.java new file mode 100644 index 00000000..4e1ee1d4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/ServiceOptions.java @@ -0,0 +1,99 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; + +/** + * Channel allocation service options + */ +public class ServiceOptions +{ + private static final int EMERGENCY_FLAG = 0x80; + private static final int ENCRYPTION_FLAG = 0x40; + private static final int DUPLEX = 0x20; + private static final int SESSION_MODE = 0x10; + + protected int mServiceOptions; + + public ServiceOptions(int serviceOptions) + { + mServiceOptions = serviceOptions; + } + + /** + * Indicates if the channel is specially processed as an emergency service. + */ + public boolean isEmergency() + { + return isSet(EMERGENCY_FLAG); + } + + /** + * Indicates if the channel is encrypted for the session. + */ + public boolean isEncrypted() + { + return isSet(ENCRYPTION_FLAG); + } + + /** + * Duplex mode for the session. Half duplex indicates that only one entity can communicate + * at a time. Full duplex indicates that both entities can communicate simultaneously. + */ + public Duplex getDuplex() + { + return isSet(DUPLEX) ? Duplex.FULL : Duplex.HALF; + } + + /** + * Network mode for the session, packet (data) or circuit (voice). + */ + public SessionMode getSessionMode() + { + return isSet(SESSION_MODE) ? SessionMode.PACKET : SessionMode.CIRCUIT; + } + + /** + * Indicates if this session mode is for packet + */ + public boolean isPacket() + { + return getSessionMode() == SessionMode.PACKET; + } + + /** + * Indicates if this session mode is for voice + */ + public boolean isVoice() + { + return getSessionMode() == SessionMode.CIRCUIT; + } + + /** + * Indicates if the bit(s) referenced by the mask is set in the service options value. + * + * @param mask to check for set bits + * @return true if the masked bit value(s) are all set. + */ + private boolean isSet(int mask) + { + return (mServiceOptions & mask) == mask; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/SessionMode.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/SessionMode.java new file mode 100644 index 00000000..a7ad1765 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/SessionMode.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; + +public enum SessionMode +{ + PACKET, CIRCUIT +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/StandbyTimer.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/StandbyTimer.java new file mode 100644 index 00000000..d20a85f6 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/StandbyTimer.java @@ -0,0 +1,64 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; + +public enum StandbyTimer +{ + SECONDS_RESERVED(0), + SECONDS_10(10), + SECONDS_30(30), + MINUTES_1(60), + MINUTES_5(600), + MINUTES_10(1200), + MINUTES_30(1800), + HOURS_1(3600), + HOURS_2(7200), + HOURS_4(14400), + HOURS_8(28800), + HOURS_12(43200), + HOURS_24(86400), + HOURS_48(172800), + HOURS_72(259200), + ALWAYS(1000000), //Infinity + UNKNOWN(0); + + private int mSeconds; + + StandbyTimer(int seconds) + { + mSeconds = seconds; + } + + public int getSeconds() + { + return mSeconds; + } + + public static StandbyTimer fromValue(int value) + { + if(0 <= value && value <= 15) + { + return StandbyTimer.values()[value]; + } + + return UNKNOWN; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/Status.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/Status.java index 0e4bc163..5f3b6a8b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/Status.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/Status.java @@ -2,20 +2,25 @@ package io.github.dsheirer.module.decode.p25.reference; public enum Status { - TALKAROUND, - BUSY, - REPEATER, - IDLE, - UNKNOWN; - - - public Status fromValue( int value ) - { - if( 0 <= value && value <= 3 ) - { - return values()[ value ]; - } - - return UNKNOWN; - } + //Used by subscriber radios for repeated or direct connect communications + MOBILE_RADIO, + + //Outbound repeater, inbound channel is busy + REPEATER_BUSY, + + //Subscriber or repeater, unknown status + UNKNOWN, + + //Outbound repeater, inbound channel is idle + REPEATER_IDLE; + + public Status fromValue(int value) + { + if(0 <= value && value <= 3) + { + return values()[value]; + } + + return UNKNOWN; + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/UDPHeaderCompression.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/UDPHeaderCompression.java new file mode 100644 index 00000000..7ed14e24 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/UDPHeaderCompression.java @@ -0,0 +1,56 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; + +public enum UDPHeaderCompression +{ + NONE( 0, "NONE" ), + UNKNOWN( -1, "UNKNOWN" ); + + private int mValue; + private String mLabel; + + UDPHeaderCompression(int value, String label ) + { + mValue = value; + mLabel = label; + } + + public int getValue() + { + return mValue; + } + + public String getLabel() + { + return mLabel; + } + + public static UDPHeaderCompression fromValue(int value ) + { + if(value == 0) + { + return NONE; + } + + return UNKNOWN; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/Vocoder.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/Vocoder.java new file mode 100644 index 00000000..01778346 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/Vocoder.java @@ -0,0 +1,30 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; + +/** + * Vocoder data rates + */ +public enum Vocoder +{ + FULL_RATE, + HALF_RATE; +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/VoiceServiceOptions.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/VoiceServiceOptions.java new file mode 100644 index 00000000..6761dbce --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/VoiceServiceOptions.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25.reference; + +public class VoiceServiceOptions extends ServiceOptions +{ + private static final int PRIORITY = 0x07; + + public VoiceServiceOptions(int serviceOptions) + { + super(serviceOptions); + } + + /** + * Priority, 1 - 7, assigned to the channel for the session. + * + * 0 = Reserved + * 1 = Lowest priority + * ... + * 4 = Default + * ... + * 7 = Highest priority + * + * @return priority for the session + */ + public int getPriority() + { + return (mServiceOptions & PRIORITY); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("PRI").append(getPriority()); + + if(isEncrypted()) + { + sb.append(" ENCRYPTED"); + } + + if(isEmergency()) + { + sb.append(" EMERGENCY"); + } + + sb.append(" ").append(getSessionMode()); + + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/passport/PassportCallEvent.java b/src/main/java/io/github/dsheirer/module/decode/passport/PassportCallEvent.java deleted file mode 100644 index 40fc6b12..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/passport/PassportCallEvent.java +++ /dev/null @@ -1,164 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.decode.passport; - -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.module.decode.event.CallEvent; - -public class PassportCallEvent extends CallEvent -{ - private String mChannel; - private long mFrequency; - - public PassportCallEvent( CallEventType callEventType, - AliasList aliasList, - String fromID, - String toID, - String channel, - long frequency, - String details ) - { - super( DecoderType.PASSPORT, callEventType, - aliasList, fromID, toID, details ); - - mChannel = channel; - mFrequency = frequency; - } - - private Alias getAlias(String talkgroup ) - { - if( hasAliasList() ) - { - return getAliasList().getTalkgroupAlias( talkgroup ); - } - - return null; - } - - private Alias getMobileIDAlias( String mobileID ) - { - if( hasAliasList() ) - { - return getAliasList().getMobileIDNumberAlias( mobileID ); - } - - return null; - } - - @Override - public Alias getFromIDAlias() - { - return getMobileIDAlias( getFromID() ); - } - - @Override - public Alias getToIDAlias() - { - return getAlias( getToID() ); - } - - @Override - public String getChannel() - { - return mChannel; - } - - @Override - public long getFrequency() - { - return mFrequency; - } - - public static class Builder - { - /* Required parameters */ - private CallEventType mCallEventType; - - /* Optional parameters */ - private AliasList mAliasList; - private String mFromID; - private String mToID; - private String mDetails; - private String mChannel; - private long mFrequency; - - public Builder( CallEventType callEventType ) - { - mCallEventType = callEventType; - } - - public Builder aliasList( AliasList aliasList ) - { - mAliasList = aliasList; - return this; - } - - public Builder from( String val ) - { - mFromID = val; - return this; - } - - public Builder channel( String channel ) - { - mChannel = channel; - return this; - } - - public Builder details( String details ) - { - mDetails = details; - return this; - } - - public Builder frequency( long frequency ) - { - mFrequency = frequency; - return this; - } - - public Builder to( String toID ) - { - mToID = toID; - return this; - } - - public PassportCallEvent build() - { - return new PassportCallEvent( this ); - } - - } - - /** - * Private constructor for the builder - */ - private PassportCallEvent( Builder builder ) - { - this( builder.mCallEventType, - builder.mAliasList, - builder.mFromID, - builder.mToID, - builder.mChannel, - builder.mFrequency, - builder.mDetails ); - } - -} diff --git a/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoder.java b/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoder.java index ab94e76a..d9336b5a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoder.java @@ -1,21 +1,24 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.module.decode.passport; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.bits.MessageFramer; import io.github.dsheirer.bits.SyncPattern; import io.github.dsheirer.dsp.fsk.LTRDecoder; @@ -38,7 +41,7 @@ public class PassportDecoder extends Decoder implements IReusableBufferListener, * Passport Decoder. Decodes unfiltered (e.g. demodulated but with no DC or * audio filtering) samples and produces Passport messages. */ - public PassportDecoder(DecodeConfiguration config, AliasList aliasList) + public PassportDecoder(DecodeConfiguration config) { mLTRDecoder = new LTRDecoder(PASSPORT_MESSAGE_LENGTH); @@ -46,7 +49,7 @@ public class PassportDecoder extends Decoder implements IReusableBufferListener, mLTRDecoder.setMessageFramer(mPassportMessageFramer); mPassportMessageFramer.setSyncDetectListener(mLTRDecoder); - mPassportMessageProcessor = new PassportMessageProcessor(aliasList); + mPassportMessageProcessor = new PassportMessageProcessor(); mPassportMessageFramer.addMessageListener(mPassportMessageProcessor); mPassportMessageProcessor.setMessageListener(getMessageListener()); } diff --git a/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoderState.java b/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoderState.java index 805278ec..7a47bad8 100644 --- a/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoderState.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,23 +15,25 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.module.decode.passport; -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.alias.id.AliasIDType; -import io.github.dsheirer.channel.metadata.AliasedStringAttributeMonitor; -import io.github.dsheirer.channel.metadata.Attribute; -import io.github.dsheirer.channel.metadata.AttributeChangeRequest; import io.github.dsheirer.channel.state.DecoderState; import io.github.dsheirer.channel.state.DecoderStateEvent; import io.github.dsheirer.channel.state.DecoderStateEvent.Event; import io.github.dsheirer.channel.state.State; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.message.MessageType; import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.module.decode.event.CallEvent; +import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.passport.identifier.PassportTalkgroup; +import io.github.dsheirer.protocol.Protocol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,34 +42,30 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.TreeSet; public class PassportDecoderState extends DecoderState { private final static Logger mLog = LoggerFactory.getLogger(PassportDecoderState.class); - private HashSet mTalkgroupsFirstHeard = new HashSet(); - private TreeSet mTalkgroups = new TreeSet(); - private TreeSet mMobileIDs = new TreeSet(); - private HashMap mSiteLCNs = new HashMap(); - private HashMap mNeighborLCNs = new HashMap(); - - private AliasedStringAttributeMonitor mFromMobileIDAttribute; - private AliasedStringAttributeMonitor mToTalkgroupAttribute; + private Set mTalkgroupsFirstHeard = new HashSet<>(); + private Set mTalkgroups = new TreeSet<>(); + private Set mMobileIDs = new TreeSet<>(); + private Map mSiteLCNs = new HashMap<>(); + private Map mNeighborLCNs = new HashMap<>(); + private Map mDetectedCalls = new HashMap<>(); + private DecodeEvent mCurrentDecodeEvent; private int mChannelNumber; private int mSiteNumber; private PassportBand mSiteBand; - private HashMap mActiveCalls = new HashMap(); private long mFrequency; - public PassportDecoderState(AliasList aliasList) + public PassportDecoderState() { - super(aliasList); - - mFromMobileIDAttribute = new AliasedStringAttributeMonitor(Attribute.PRIMARY_ADDRESS_FROM, - getAttributeChangeRequestListener(), getAliasList(), AliasIDType.MIN); - mToTalkgroupAttribute = new AliasedStringAttributeMonitor(Attribute.PRIMARY_ADDRESS_TO, - getAttributeChangeRequestListener(), getAliasList(), AliasIDType.TALKGROUP); } @Override @@ -75,7 +74,7 @@ public class PassportDecoderState extends DecoderState return DecoderType.PASSPORT; } - private void logTalkgroup(String talkgroup) + private void addTalkgroup(PassportTalkgroup talkgroup) { if(mTalkgroupsFirstHeard.contains(talkgroup)) { @@ -87,265 +86,149 @@ public class PassportDecoderState extends DecoderState } } - private PassportCallEvent getCurrentCallEvent() + /** + * Compares the talkgroups for equality + */ + private boolean isSameTalkgroup(Identifier id1, Identifier id2) { - return (PassportCallEvent) mCurrentCallEvent; + return Objects.equals(id1, id2); } /** - * Indicates if the talkgroup is different than the talkgroup specified in - * the current call event + * Retrieves the first identifier with a TO role. + * + * @param collection containing a TO identifier + * @return TO identifier or null */ - private boolean isDifferentTalkgroup(String talkgroup) + private Identifier getToIdentifier(IdentifierCollection collection) { - return talkgroup != null && - mCurrentCallEvent != null && - mCurrentCallEvent.getToID() != null && - !mCurrentCallEvent.getToID().contentEquals(talkgroup); + List identifiers = collection.getIdentifiers(Role.TO); + + if(identifiers.size() >= 1) + { + return identifiers.get(0); + } + + return null; } + @Override - public void receive(Message message) + public void receive(IMessage message) { - if(message instanceof PassportMessage) + if(message instanceof PassportMessage && message.isValid()) { - PassportMessage passport = (PassportMessage) message; + PassportMessage passport = (PassportMessage)message; if(passport.isValid()) { switch(passport.getMessageType()) { case CA_STRT: - mSiteLCNs.put(passport.getLCN(), - passport.getLCNFrequencyFormatted()); + case DA_STRT: + mSiteLCNs.put(passport.getLCN(), passport.getLCNFrequency()); - String talkgroup = - String.valueOf(passport.getTalkgroupID()); - - logTalkgroup(talkgroup); + PassportTalkgroup to = passport.getToIdentifier(); + addTalkgroup(to); if(mChannelNumber == 0) { setChannelNumber(passport.getLCN()); } - /* Call on this channel */ + /* Call on this channel */ if(passport.getLCN() == mChannelNumber) { - mToTalkgroupAttribute.process(talkgroup); + getIdentifierCollection().update(to); - PassportCallEvent current = getCurrentCallEvent(); - - /* If we're already in a call event, add the message - * to the current call event ... if false, then we - * have a different call ... cleanup the old one. */ - if(current != null && isDifferentTalkgroup(talkgroup)) + if(mCurrentDecodeEvent != null) { - mCurrentCallEvent.end(); - mCurrentCallEvent = null; + if(isSameTalkgroup(getToIdentifier(mCurrentDecodeEvent.getIdentifierCollection()), to)) + { + mCurrentDecodeEvent.update(passport.getTimestamp()); + broadcast(mCurrentDecodeEvent); + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL)); + return; + } + else + { + mCurrentDecodeEvent.end(message.getTimestamp()); + broadcast(mCurrentDecodeEvent); + mCurrentDecodeEvent = null; + } } - if(mCurrentCallEvent == null) - { - mCurrentCallEvent = new PassportCallEvent - .Builder(CallEvent.CallEventType.CALL) - .aliasList(getAliasList()) - .channel(String.valueOf(mChannelNumber)) - .frequency(passport.getLCNFrequency()) - .to(String.valueOf(passport.getTalkgroupID())) - .build(); - - broadcast(mCurrentCallEvent); - - broadcast(new DecoderStateEvent(this, - Event.START, State.CALL)); - } - else - { - broadcast(new DecoderStateEvent(this, - Event.CONTINUATION, State.CALL)); - } + mCurrentDecodeEvent = DecodeEvent.builder(passport.getTimestamp()) + .eventDescription(passport.getMessageType() == MessageType.CA_STRT ? "Call" : "Data") + .identifiers(getIdentifierCollection().copyOf()) + .details(passport.toString()) + .build(); + broadcast(mCurrentDecodeEvent); + broadcast(new DecoderStateEvent(this, Event.START, State.CALL)); } else { //Call Detection int lcn = passport.getLCN(); - String tg = String.valueOf(passport.getTalkgroupID()); - if(!mActiveCalls.containsKey(lcn) || - !mActiveCalls.get(lcn).contentEquals(tg)) + DecodeEvent callDetect = mDetectedCalls.get(lcn); + + if(callDetect == null || + !isSameTalkgroup(to, getToIdentifier(callDetect.getIdentifierCollection())) || + callDetect.getTimeStart() < (passport.getTimestamp() - 45000)) { - mActiveCalls.put(lcn, tg); - - broadcast(new PassportCallEvent - .Builder(CallEvent.CallEventType.CALL_DETECT) - .aliasList(getAliasList()) - .channel(String.valueOf(lcn)) - .details("Site: " + passport.getSite()) - .frequency(passport.getLCNFrequency()) - .to(tg) - .build()); + callDetect = DecodeEvent.builder(passport.getTimestamp()) + .eventDescription("Call Detect") + .identifiers(new IdentifierCollection(passport.getIdentifiers())) +// .channel(...) + .build(); + mDetectedCalls.put(lcn, callDetect); } + else + { + callDetect.update(passport.getTimestamp()); + } + + broadcast(callDetect); } break; case CA_ENDD: - String endTalkgroup = - String.valueOf(passport.getTalkgroupID()); - - logTalkgroup(endTalkgroup); - mToTalkgroupAttribute.process(endTalkgroup); - - broadcast(new DecoderStateEvent(this, - Event.END, State.CALL)); - - if(mCurrentCallEvent != null) - { - mCurrentCallEvent.end(); - broadcast(mCurrentCallEvent); - mCurrentCallEvent = null; - - broadcast(new DecoderStateEvent(this, Event.RESET, State.FADE)); - } - break; - case DA_STRT: - mSiteLCNs.put(passport.getLCN(), - passport.getLCNFrequencyFormatted()); - - String dataTalkgroup = - String.valueOf(passport.getTalkgroupID()); - - logTalkgroup(dataTalkgroup); - - if(mChannelNumber == 0) - { - setChannelNumber(passport.getLCN()); - } - - /* Data call on this channel */ - if(passport.getLCN() == mChannelNumber) - { - mToTalkgroupAttribute.process(dataTalkgroup); - - PassportCallEvent current = getCurrentCallEvent(); - - /* If we're already in a call event, add the message - * to the current call event ... if false, then we - * have a different call ... cleanup the old one. */ - if(current != null && isDifferentTalkgroup(dataTalkgroup)) - { - mCurrentCallEvent.end(); - mCurrentCallEvent = null; - } - - if(mCurrentCallEvent == null) - { - mCurrentCallEvent = new PassportCallEvent - .Builder(CallEvent.CallEventType.DATA_CALL) - .aliasList(getAliasList()) - .channel(String.valueOf(mChannelNumber)) - .frequency(passport.getLCNFrequency()) - .to(String.valueOf(passport.getTalkgroupID())) - .build(); - - broadcast(mCurrentCallEvent); - - broadcast(new DecoderStateEvent(this, - Event.START, State.DATA)); - } - else - { - broadcast(new DecoderStateEvent(this, - Event.CONTINUATION, State.DATA)); - } - } - else - { - //Call Detection - int lcn = passport.getLCN(); - String tg = String.valueOf(passport.getTalkgroupID()); - - if(!mActiveCalls.containsKey(lcn) || - !mActiveCalls.get(lcn).contentEquals(tg)) - { - mActiveCalls.put(lcn, tg); - - broadcast(new PassportCallEvent - .Builder(CallEvent.CallEventType.DATA_CALL) - .aliasList(getAliasList()) - .channel(String.valueOf(lcn)) - .details("Site: " + passport.getSite()) - .frequency(passport.getLCNFrequency()) - .to(tg) - .build()); - } - } - break; case DA_ENDD: - String dataEndTalkgroup = - String.valueOf(passport.getTalkgroupID()); + PassportTalkgroup endTalkgroup = passport.getToIdentifier(); + addTalkgroup(endTalkgroup); - logTalkgroup(dataEndTalkgroup); - mToTalkgroupAttribute.process(dataEndTalkgroup); - - broadcast(new DecoderStateEvent(this, - Event.END, State.DATA)); - - if(mCurrentCallEvent != null) + if(mCurrentDecodeEvent != null && isSameTalkgroup(endTalkgroup, getToIdentifier(mCurrentDecodeEvent.getIdentifierCollection()))) { - mCurrentCallEvent.end(); - broadcast(mCurrentCallEvent); - mCurrentCallEvent = null; - - broadcast(new DecoderStateEvent(this, Event.RESET, State.FADE)); + mCurrentDecodeEvent.end(passport.getTimestamp()); + mCurrentDecodeEvent = null; } + + broadcast(new DecoderStateEvent(this, Event.END, State.CALL)); break; case ID_RDIO: - String min = passport.getMobileID(); + PassportTalkgroup mobileId = passport.getFromIdentifier(); + mMobileIDs.add(mobileId); + getIdentifierCollection().update(mobileId); - if(min != null) + if(mCurrentDecodeEvent != null) { - mMobileIDs.add(min); - } - - mFromMobileIDAttribute.process(min); - - final CallEvent current = getCurrentCallEvent(); - - if(current != null) - { - current.setFromID(min); - broadcast(current); - + mCurrentDecodeEvent.setIdentifierCollection(getIdentifierCollection().copyOf()); + mCurrentDecodeEvent.update(passport.getTimestamp()); + broadcast(mCurrentDecodeEvent); } break; case RA_REGI: - if(mCurrentCallEvent == null || - mCurrentCallEvent.getCallEventType() != CallEvent.CallEventType.REGISTER) - { - mCurrentCallEvent = new PassportCallEvent - .Builder(CallEvent.CallEventType.REGISTER) - .aliasList(getAliasList()) - .channel(String.valueOf(passport.getLCN())) - .frequency(passport.getLCNFrequency()) - .to(passport.getToID()) - .build(); + broadcast(DecodeEvent.builder(passport.getTimestamp()) + .eventDescription("Register") + .identifiers(new IdentifierCollection(passport.getIdentifiers())) + .build()); - broadcast(mCurrentCallEvent); - - broadcast(new DecoderStateEvent(this, - Event.START, State.DATA)); - } - else - { - broadcast(new DecoderStateEvent(this, - Event.CONTINUATION, State.DATA)); - } + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA)); break; case SY_IDLE: if(passport.getFree() != 0) { - mNeighborLCNs.put(passport.getFree(), - passport.getFreeFrequencyFormatted()); + mNeighborLCNs.put(passport.getFree(), passport.getFreeFrequency()); } setSiteNumber(passport.getSite()); @@ -360,26 +243,6 @@ public class PassportDecoderState extends DecoderState } } - public static String formatTalkgroup(String talkgroup) - { - StringBuilder sb = new StringBuilder(); - - if(talkgroup.length() == 6) - { - sb.append(talkgroup.substring(0, 1)); - sb.append("-"); - sb.append(talkgroup.substring(1, 3)); - sb.append("-"); - sb.append(talkgroup.substring(3, 6)); - - return sb.toString(); - } - else - { - return talkgroup; - } - } - @Override public String getActivitySummary() { @@ -395,7 +258,7 @@ public class PassportDecoderState extends DecoderState } else { - ArrayList channels = new ArrayList<>(mSiteLCNs.keySet()); + List channels = new ArrayList<>(mSiteLCNs.keySet()); Collections.sort(channels); for(Integer channel : channels) @@ -414,7 +277,7 @@ public class PassportDecoderState extends DecoderState } else { - ArrayList channels = new ArrayList<>(mNeighborLCNs.keySet()); + List channels = new ArrayList<>(mNeighborLCNs.keySet()); Collections.sort(channels); for(Integer channel : channels) @@ -433,27 +296,11 @@ public class PassportDecoderState extends DecoderState } else { - Iterator it = mTalkgroups.iterator(); + Iterator it = mTalkgroups.iterator(); while(it.hasNext()) { - String tgid = it.next(); - - sb.append(" "); - sb.append(tgid); - sb.append(" "); - - if(hasAliasList()) - { - Alias alias = getAliasList().getTalkgroupAlias(tgid); - - if(alias != null) - { - sb.append(alias.getName()); - } - } - - sb.append("\n"); + sb.append(" ").append(it.next()).append("\n"); } } @@ -465,27 +312,11 @@ public class PassportDecoderState extends DecoderState } else { - Iterator it = mMobileIDs.iterator(); + Iterator it = mMobileIDs.iterator(); while(it.hasNext()) { - String min = it.next(); - - sb.append(" "); - sb.append(min); - sb.append(" "); - - if(hasAliasList()) - { - Alias alias = getAliasList().getMobileIDNumberAlias(min); - - if(alias != null) - { - sb.append(alias.getName()); - } - } - - sb.append("\n"); + sb.append(" ").append(it.next()).append("\n"); } } @@ -494,6 +325,7 @@ public class PassportDecoderState extends DecoderState public void reset() { + getIdentifierCollection().remove(IdentifierClass.USER); mTalkgroupsFirstHeard.clear(); mTalkgroups.clear(); mMobileIDs.clear(); @@ -521,10 +353,15 @@ public class PassportDecoderState extends DecoderState } - private void resetState() + protected void resetState() { - mToTalkgroupAttribute.reset(); - mFromMobileIDAttribute.reset(); + super.resetState(); + + if(mCurrentDecodeEvent != null) + { + mCurrentDecodeEvent.end(System.currentTimeMillis()); + mCurrentDecodeEvent = null; + } } public int getChannelNumber() @@ -537,7 +374,8 @@ public class PassportDecoderState extends DecoderState if(mChannelNumber != channel) { mChannelNumber = channel; - broadcast(new AttributeChangeRequest(Attribute.CHANNEL_FREQUENCY_LABEL, "CHAN:" + mChannelNumber)); + getIdentifierCollection().update(DecoderLogicalChannelNameIdentifier.create(String.valueOf(mChannelNumber), + Protocol.PASSPORT)); } } @@ -551,8 +389,6 @@ public class PassportDecoderState extends DecoderState if(mSiteNumber != site) { mSiteNumber = site; - Alias alias = hasAliasList() ? getAliasList().getSiteID(String.valueOf(getSiteNumber())) : null; - broadcast(new AttributeChangeRequest(Attribute.NETWORK_ID_1, "SITE:" + mSiteNumber, alias)); } } @@ -566,7 +402,6 @@ public class PassportDecoderState extends DecoderState if(mSiteBand == null) { mSiteBand = band; - broadcast(new AttributeChangeRequest(Attribute.NETWORK_ID_2, "BAND:" + band.getDescription())); } } @@ -580,7 +415,6 @@ public class PassportDecoderState extends DecoderState break; case SOURCE_FREQUENCY: mFrequency = event.getFrequency(); - broadcast(new AttributeChangeRequest(Attribute.CHANNEL_FREQUENCY, mFrequency)); break; default: break; diff --git a/src/main/java/io/github/dsheirer/module/decode/passport/PassportMessage.java b/src/main/java/io/github/dsheirer/module/decode/passport/PassportMessage.java index cfaf1331..f67c6520 100644 --- a/src/main/java/io/github/dsheirer/module/decode/passport/PassportMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/passport/PassportMessage.java @@ -1,123 +1,152 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.passport; -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.edac.CRC; import io.github.dsheirer.edac.CRCPassport; -import io.github.dsheirer.map.Plottable; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.message.Message; import io.github.dsheirer.message.MessageType; -import io.github.dsheirer.module.decode.DecoderType; +import io.github.dsheirer.module.decode.passport.identifier.PassportTalkgroup; +import io.github.dsheirer.protocol.Protocol; -import java.text.DecimalFormat; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; public class PassportMessage extends Message { - private static final String sUNKNOWN = "**UNKNOWN**"; - private SimpleDateFormat mSDF = new SimpleDateFormat( "yyyyMMdd HHmmss" ); - private DecimalFormat mDecimalFormatter = new DecimalFormat("0.00000"); - - @SuppressWarnings( "unused" ) - private static final int[] sSYNC = { 8,7,6,5,4,3,2,1,0 }; - private static final int[] sDCC = { 10,9 }; //Digital Color Code - private static final int[] sLCN = { 21,20,19,18,17,16,15,14,13,12,11 }; - private static final int[] sSITE = { 28,27,26,25,24,23,22 }; - private static final int[] sGROUP = { 44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29 }; - private static final int[] sRADIO_ID = { 44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22 }; -// private static final int[] sNEIGHBOR_OFFSET = { 32,31,30 }; - private static final int[] sNEIGHBOR_BAND = { 36,35,34,33 }; - private static final int[] sNEIGHBOR_REGISTRATION = { 29 }; - private static final int[] sSITE_OFFSET = { 40,39,38 }; - private static final int[] sSITE_BAND = { 44,43,42,41 }; - private static final int[] sSITE_REGISTRATION = { 37 }; - private static final int[] sTYPE = { 48,47,46,45 }; - private static final int[] sFREE = { 59,58,57,56,55,54,53,52,51,50,49 }; - private static final int[] sCHECKSUM = { 67,66,65,64,63,62,61,60 }; - private BinaryMessage mMessage; + private static final int[] SYNC = {0, 1, 2, 3, 4, 5, 6, 7, 8}; + private static final int[] DIGITAL_COLOR_CODE = {9, 10}; + private static final int[] CHANNEL_NUMBER = {11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21}; + private static final int[] SITE = {22, 23, 24, 25, 26, 27, 28}; + private static final int[] GROUP = {29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44}; + private static final int[] RADIO_ID = {22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44}; + private static final int[] NEIGHBOR_BAND = {33, 34, 35, 36}; + private static final int[] SITE_BAND = {41, 42, 43, 44}; + private static final int[] TYPE = {45, 46, 47, 48}; + private static final int[] FREE = {49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59}; + private static final int[] CHECKSUM = {60, 61, 62, 63, 64, 65, 66, 67}; + + private CorrectedBinaryMessage mMessage; private CRC mCRC; - private AliasList mAliasList; - private PassportMessage mIdleMessage; - - public PassportMessage( BinaryMessage message, - PassportMessage idleMessage, - AliasList list ) + private PassportMessage mIdleMessage; + private PassportTalkgroup mFromIdentifier; + private PassportTalkgroup mToIdentifier; + private List mIdentifiers; + + public PassportMessage(CorrectedBinaryMessage message, PassportMessage idleMessage) { - mMessage = CRCPassport.correct( message ); + mMessage = CRCPassport.correct(message); mIdleMessage = idleMessage; - mCRC = CRCPassport.check( mMessage ); - mAliasList = list; + mCRC = CRCPassport.check(mMessage); } - - public PassportMessage( BinaryMessage message, AliasList list ) + + public PassportMessage(CorrectedBinaryMessage message) { - this( message, null, list ); + this(message, null); } - - public BinaryMessage getBitSetBuffer() + + public CorrectedBinaryMessage getMessage() { - return mMessage; + return mMessage; } - + + public PassportTalkgroup getToIdentifier() + { + if(mToIdentifier == null) + { + mToIdentifier = PassportTalkgroup.createTo(getMessage().getInt(GROUP)); + } + + return mToIdentifier; + } + + public PassportTalkgroup getFromIdentifier() + { + if(mFromIdentifier == null) + { + mFromIdentifier = PassportTalkgroup.createFrom(getMessage().getInt(RADIO_ID)); + } + + return mFromIdentifier; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + + if(hasFromIdentifier()) + { + mIdentifiers.add(getFromIdentifier()); + } + + if(hasToIdentifier()) + { + mIdentifiers.add(getToIdentifier()); + } + } + + return mIdentifiers; + } + public boolean isValid() { - return mCRC != CRC.FAILED_CRC && - mCRC != CRC.FAILED_PARITY && - mCRC != CRC.UNKNOWN; + return mCRC.passes(); } - + public CRC getCRC() { - return mCRC; + return mCRC; } public MessageType getMessageType() { MessageType retVal = MessageType.UN_KNWN; - + int type = getMessageTypeNumber(); int lcn = getLCN(); - - switch( type ) + + switch(type) { case 0: //Group Call retVal = MessageType.CA_STRT; - break; + break; case 1: - if( getFree() == 2042 ) - { - retVal = MessageType.ID_TGAS; - } - else if( lcn < 1792 ) + if(getFree() == 2042) { - retVal = MessageType.CA_STRT; + retVal = MessageType.ID_TGAS; } - else if( lcn == 1792 || lcn == 1793 ) + else if(lcn < 1792) + { + retVal = MessageType.CA_STRT; + } + else if(lcn == 1792 || lcn == 1793) { retVal = MessageType.SY_IDLE; } - else if( lcn == 2047 ) + else if(lcn == 2047) { retVal = MessageType.CA_ENDD; } @@ -126,513 +155,226 @@ public class PassportMessage extends Message retVal = MessageType.CA_STRT; break; case 5: - retVal = MessageType.CA_PAGE; - break; + retVal = MessageType.CA_PAGE; + break; case 6: - retVal = MessageType.ID_RDIO; - break; + retVal = MessageType.ID_RDIO; + break; case 9: - retVal = MessageType.DA_STRT; - break; + retVal = MessageType.DA_STRT; + break; case 11: - retVal = MessageType.RA_REGI; - break; + retVal = MessageType.RA_REGI; + break; default: break; } - + return retVal; } - - - public int getDCC() + + + public int getColorCode() { - return getInt( sDCC ); + return getMessage().getInt(DIGITAL_COLOR_CODE); } - + public int getSite() { - return getInt( sSITE ); + return getMessage().getInt(SITE); } - + public int getMessageTypeNumber() { - return getInt( sTYPE ); + return getMessage().getInt(TYPE); } - - public int getTalkgroupID() - { - if( getMessageType() == MessageType.SY_IDLE ) - { - return 0; - } - else - { - return getInt( sGROUP ); - } - } - - public Alias getTalkgroupIDAlias() - { - int tg = getTalkgroupID(); - if( mAliasList != null ) - { - return mAliasList.getTalkgroupAlias( String.valueOf( tg ) ); - } - - return null; + public boolean hasToIdentifier() + { + return getMessageType() != MessageType.SY_IDLE; } - + public int getLCN() { - return getInt( sLCN ); + return getMessage().getInt(CHANNEL_NUMBER); } public long getLCNFrequency() { - return getSiteFrequency( getLCN() ); + return getSiteFrequency(getLCN()); } - - public String getLCNFrequencyFormatted() - { - return mDecimalFormatter.format( (double)getLCNFrequency() / 1000000.0d ); - } - + public PassportBand getSiteBand() { - return PassportBand.lookup( getInt( sSITE_BAND ) ); + return PassportBand.lookup(getMessage().getInt(SITE_BAND)); } public PassportBand getNeighborBand() { - return PassportBand.lookup( getInt( sNEIGHBOR_BAND ) ); + return PassportBand.lookup(getMessage().getInt(NEIGHBOR_BAND)); } public int getFree() { - return getInt( sFREE ); + return getMessage().getInt(FREE); } - + public long getFreeFrequency() { - return getSiteFrequency( getFree() ); + return getSiteFrequency(getFree()); } - - public String getFreeFrequencyFormatted() - { - return mDecimalFormatter.format( (double)getFreeFrequency() / 1000000.0d ); - } - + public long getNeighborFrequency() { - if( getMessageType() == MessageType.SY_IDLE ) - { - PassportBand band = getNeighborBand(); - - return band.getFrequency( getFree() ); - } - - return 0; + if(getMessageType() == MessageType.SY_IDLE) + { + PassportBand band = getNeighborBand(); + + return band.getFrequency(getFree()); + } + + return 0; } - + + public boolean hasFromIdentifier() + { + return getMessageType() == MessageType.ID_RDIO; + } + /** - * Returns the radio id in hex format, or null if not the correct message - * type - * - * @return - radio id or null + * Appends spaces to the end of the stringbuilder to make it length long */ - public String getMobileID() + private void pad(StringBuilder sb, int length) { - if( getMessageType() == MessageType.ID_RDIO ) - { - int radioId = getInt( sRADIO_ID ); - - return String.format("%06X", radioId & 0xFFFFFF ); - } - else - { - return null; - } - } - - public Alias getMobileIDAlias() - { - String min = getMobileID(); - - if( mAliasList != null && min != null ) - { - return mAliasList.getMobileIDNumberAlias( min ); - } - - return null; - } - - /** - * Appends spaces to the end of the stringbuilder to make it length long - */ - private void pad( StringBuilder sb, int length ) - { - while( sb.length() < length ) - { - sb.append( " " ); - } - } - - @Override - public String getBinaryMessage() - { - return mMessage.toString(); + while(sb.length() < length) + { + sb.append(" "); + } } - private int getInt( int[] bits ) - { - int retVal = 0; - - for( int x = 0; x < bits.length; x++ ) - { - if( mMessage.get( bits[ x ] ) ) - { - retVal += 1< 0 && free < 896 ) - { - sb.append( "/" ); - sb.append( getFreeFrequencyFormatted() ); - } - sb.append( " TYP:" ); - sb.append( format( getMessageTypeNumber(), 2 ) ); - sb.append( " TG:" ); - sb.append( format( getTalkgroupID(), 5 ) ); - break; - } - - return sb.toString(); - } - - @Override + @Override public String toString() { - StringBuilder sb = new StringBuilder(); - - sb.append( mSDF.format( mTimeReceived ) ); - sb.append( " " ); - sb.append( getMessage() ); - pad( sb, 100 ); - sb.append( mCRC.toString() ); - pad( sb, 110 ); - sb.append( " [" ); - sb.append( mMessage ); - sb.append( "]" ); + StringBuilder sb = new StringBuilder(); + sb.append("DCC:").append(getColorCode()); - return sb.toString(); + switch(getMessageType()) + { + case SY_IDLE: + sb.append(" IDLE SITE:").append(format(getSite(), 3)); + sb.append(" NEIGHBOR:").append(format(getFree(), 3)).append("/").append(getFreeFrequency()); + break; + case CA_PAGE: + sb.append(" PAGING TG:").append(getToIdentifier()); + sb.append(" SITE:").append(format(getSite(), 3)); + sb.append(" CHAN:").append(format(getLCN(), 4)).append("/").append(getLCNFrequency()); + sb.append(" FREE:").append(format(getFree(), 3)).append("/").append(getFreeFrequency()); + break; + case CA_STRT: + sb.append(" CALL TG:").append(getToIdentifier()); + sb.append(" SITE:").append(format(getSite(), 3)); + sb.append(" CHAN:").append(format(getLCN(), 4)).append("/").append(getLCNFrequency()); + sb.append(" FREE:").append(format(getFree(), 3)).append("/").append(getFreeFrequency()); + break; + case DA_STRT: + sb.append(" ** DATA TG:").append(getToIdentifier()); + sb.append(" SITE:").append(format(getSite(), 3)); + sb.append(" CHAN:").append(format(getLCN(), 4)).append("/").append(getLCNFrequency()); + sb.append(" FREE:").append(format(getFree(), 3)).append("/").append(getFreeFrequency()); + break; + case CA_ENDD: + sb.append(" END TG:").append(getToIdentifier()); + sb.append(" SITE:").append(format(getSite(), 3)); + sb.append(" CHAN:").append(format(getLCN(), 4)).append("/").append(getLCNFrequency()); + sb.append(" FREE:").append(format(getFree(), 3)).append("/").append(getFreeFrequency()); + break; + case ID_RDIO: + sb.append(" MOBILE ID MIN:").append(getFromIdentifier()); + sb.append(" FREE:").append(format(getFree(), 3)).append("/").append(getFreeFrequency()); + break; + case ID_TGAS: + sb.append(" ASSIGN TALKGROUP:").append(getToIdentifier()); + sb.append(" SITE:").append(format(getSite(), 3)); + sb.append(" CHAN:").append(format(getLCN(), 4)).append("/").append(getLCNFrequency()); + break; + case RA_REGI: + sb.append(" RADIO REGISTER TG: ").append(getToIdentifier()); + break; + default: + sb.append(" UNKNOWN SITE:").append(format(getSite(), 3)); + sb.append(" CHAN:").append(format(getLCN(), 4)).append("/").append(getLCNFrequency()); + sb.append(" FREE:"); + int free = getFree(); + sb.append(format(free, 3)); + if(free > 0 && free < 896) + { + sb.append("/"); + sb.append(getFreeFrequency()); + } + sb.append(" TYP:").append(format(getMessageTypeNumber(), 2)); + sb.append(" TG:").append(getToIdentifier()); + break; + } + + return sb.toString(); } - @Override - public String getErrorStatus() + public boolean matches(PassportMessage otherMessage) { - return mCRC.getDisplayText(); + return this.getMessage().equals(otherMessage.getMessage()); } - - public boolean matches( PassportMessage otherMessage ) - { - return this.getBitSetBuffer().equals( otherMessage.getBitSetBuffer() ); - } - - @Override - public Plottable getPlottable() - { - // TODO Auto-generated method stub - return null; - } - - /** - * Provides a listing of aliases contained in the message. - */ - public List getAliases() - { - List aliases = new ArrayList(); - - Alias talkgroup = getTalkgroupIDAlias(); - - if( talkgroup != null ) - { - aliases.add( talkgroup ); - } - - Alias mid = getMobileIDAlias(); - - if( mid != null ) - { - aliases.add( mid ); - } - - return aliases; - } } diff --git a/src/main/java/io/github/dsheirer/module/decode/passport/PassportMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/passport/PassportMessageFilter.java index ee98e88a..f58b086f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/passport/PassportMessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/passport/PassportMessageFilter.java @@ -2,67 +2,58 @@ package io.github.dsheirer.module.decode.passport; import io.github.dsheirer.filter.Filter; import io.github.dsheirer.filter.FilterElement; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.MessageType; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; -public class PassportMessageFilter extends Filter +public class PassportMessageFilter extends Filter { - private HashMap> mElements = - new HashMap>(); - - public PassportMessageFilter() - { - super( "Passport Message Filter" ); + private Map> mElements = new HashMap>(); - mElements.put( MessageType.CA_STRT, - new FilterElement( MessageType.CA_STRT ) ); - mElements.put( MessageType.CA_ENDD, - new FilterElement( MessageType.CA_ENDD ) ); - mElements.put( MessageType.SY_IDLE, - new FilterElement( MessageType.SY_IDLE ) ); - mElements.put( MessageType.ID_TGAS, - new FilterElement( MessageType.ID_TGAS ) ); - mElements.put( MessageType.ID_ESNH, - new FilterElement( MessageType.ID_ESNH ) ); - mElements.put( MessageType.CA_PAGE, - new FilterElement( MessageType.CA_PAGE ) ); - mElements.put( MessageType.ID_RDIO, - new FilterElement( MessageType.ID_RDIO ) ); - mElements.put( MessageType.DA_STRT, - new FilterElement( MessageType.DA_STRT ) ); - mElements.put( MessageType.RA_REGI, - new FilterElement( MessageType.RA_REGI ) ); - } - - @Override - public boolean passes( Message message ) + public PassportMessageFilter() { - if( mEnabled && canProcess( message ) ) - { - PassportMessage passport = (PassportMessage)message; - - if( mElements.containsKey( passport.getMessageType() ) ) - { - return mElements.get( passport.getMessageType() ).isEnabled(); - } - } - - return false; + super("Passport Message Filter"); + + mElements.put(MessageType.CA_STRT, new FilterElement(MessageType.CA_STRT)); + mElements.put(MessageType.CA_ENDD, new FilterElement(MessageType.CA_ENDD)); + mElements.put(MessageType.SY_IDLE, new FilterElement(MessageType.SY_IDLE)); + mElements.put(MessageType.ID_TGAS, new FilterElement(MessageType.ID_TGAS)); + mElements.put(MessageType.ID_ESNH, new FilterElement(MessageType.ID_ESNH)); + mElements.put(MessageType.CA_PAGE, new FilterElement(MessageType.CA_PAGE)); + mElements.put(MessageType.ID_RDIO, new FilterElement(MessageType.ID_RDIO)); + mElements.put(MessageType.DA_STRT, new FilterElement(MessageType.DA_STRT)); + mElements.put(MessageType.RA_REGI, new FilterElement(MessageType.RA_REGI)); } - @Override - public boolean canProcess( Message message ) + @Override + public boolean passes(IMessage message) { - return message instanceof PassportMessage; + if(mEnabled && canProcess(message)) + { + PassportMessage passport = (PassportMessage) message; + + if(mElements.containsKey(passport.getMessageType())) + { + return mElements.get(passport.getMessageType()).isEnabled(); + } + } + + return false; } - @Override + @Override + public boolean canProcess(IMessage message) + { + return message instanceof PassportMessage; + } + + @Override public List> getFilterElements() { - return new ArrayList>( mElements.values() ); + return new ArrayList>(mElements.values()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/passport/PassportMessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/passport/PassportMessageProcessor.java index ddce69b7..23820cd3 100644 --- a/src/main/java/io/github/dsheirer/module/decode/passport/PassportMessageProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/passport/PassportMessageProcessor.java @@ -1,111 +1,114 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.passport; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.MessageType; import io.github.dsheirer.sample.Listener; import java.util.HashMap; +import java.util.Map; -public class PassportMessageProcessor implements Listener +public class PassportMessageProcessor implements Listener { - private Listener mMessageListener; - private IdleMessageFinder mIdleFinder = new IdleMessageFinder(); - private AliasList mAliasList; - private PassportMessage mIdleMessage; - - public PassportMessageProcessor( AliasList list ) - { - mAliasList = list; - } - - @Override - public void receive( BinaryMessage buffer ) - { - if( mMessageListener != null ) - { - PassportMessage message; - - if( mIdleMessage != null ) - { - message = new PassportMessage( buffer, mIdleMessage, mAliasList ); - } - else - { - message = new PassportMessage( buffer, mAliasList ); - mIdleFinder.receive( message ); - } + private Listener mMessageListener; + private IdleMessageFinder mIdleFinder = new IdleMessageFinder(); + private PassportMessage mIdleMessage; - mMessageListener.receive( message ); - } - } - - public void setMessageListener( Listener listener ) + public PassportMessageProcessor() { - mMessageListener = listener; + } + + @Override + public void receive(CorrectedBinaryMessage buffer) + { + if(mMessageListener != null) + { + PassportMessage message; + + if(mIdleMessage != null) + { + message = new PassportMessage(buffer, mIdleMessage); + } + else + { + message = new PassportMessage(buffer); + mIdleFinder.receive(message); + } + + if(message.isValid()) + { + mMessageListener.receive(message); + } + + } + } + + public void setMessageListener(Listener listener) + { + mMessageListener = listener; } public void removeMessageListener() { - mMessageListener = null; + mMessageListener = null; + } + + public class IdleMessageFinder + { + public Map mMessageCounts = new HashMap(); + + public boolean mIdleMessageFound = false; + + public IdleMessageFinder() + { + } + + public void receive(PassportMessage message) + { + if(!mIdleMessageFound && + message.isValid() && + message.getMessageType() == MessageType.SY_IDLE) + { + if(mMessageCounts.containsKey(message.toString())) + { + int count = mMessageCounts.get(message.toString()); + + if(count >= 3) + { + mIdleMessageFound = true; + mIdleMessage = message; + mMessageCounts = null; + } + else + { + count++; + mMessageCounts.put(message.toString(), count); + } + } + else + { + mMessageCounts.put(message.toString(), 1); + } + } + } } - - public class IdleMessageFinder - { - public HashMap mMessageCounts = - new HashMap(); - - public boolean mIdleMessageFound = false; - - public IdleMessageFinder() - { - } - - public void receive( PassportMessage message ) - { - if( !mIdleMessageFound && - message.isValid() && - message.getMessageType() == MessageType.SY_IDLE ) - { - if( mMessageCounts.containsKey( message.getBinaryMessage() ) ) - { - int count = mMessageCounts.get( message.getBinaryMessage() ); - - if( count >= 3 ) - { - mIdleMessageFound = true; - mIdleMessage = message; - mMessageCounts = null; - } - else - { - count++; - mMessageCounts.put( message.getBinaryMessage(), count ); - } - } - else - { - mMessageCounts.put( message.getBinaryMessage(), 1 ); - } - } - } - } } diff --git a/src/main/java/io/github/dsheirer/module/decode/passport/identifier/PassportTalkgroup.java b/src/main/java/io/github/dsheirer/module/decode/passport/identifier/PassportTalkgroup.java new file mode 100644 index 00000000..e5a0324c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/passport/identifier/PassportTalkgroup.java @@ -0,0 +1,67 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.passport.identifier; + +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.protocol.Protocol; + +/** + * Passport Talkgroup + */ +public class PassportTalkgroup extends TalkgroupIdentifier implements Comparable +{ + /** + * Constructs a Passport talkgroup with a TO role. + */ + public PassportTalkgroup(Integer value, Role role, boolean isGroup) + { + super(value, role, isGroup); + } + + @Override + public Protocol getProtocol() + { + return Protocol.PASSPORT; + } + + /** + * Creates a Passport talkgroup with the value as the TO role + */ + public static PassportTalkgroup createTo(int value) + { + return new PassportTalkgroup(value, Role.TO, true); + } + + /** + * Creates a Passport talkgroup (ie mobile id) with the value as the TO role + */ + public static PassportTalkgroup createFrom(int value) + { + return new PassportTalkgroup(value, Role.FROM, false); + } + + @Override + public int compareTo(PassportTalkgroup o) + { + return Integer.compare(getValue(), o.getValue()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200ANIMessage.java b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200ANIMessage.java index 9bcc471d..24ecf553 100644 --- a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200ANIMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200ANIMessage.java @@ -1,28 +1,30 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.tait; -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.edac.CRC; -import io.github.dsheirer.map.Plottable; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.message.Message; +import io.github.dsheirer.module.decode.tait.identifier.TaitIdentifier; +import io.github.dsheirer.protocol.Protocol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,241 +34,133 @@ import java.util.List; public class Tait1200ANIMessage extends Message { - private final static Logger mLog = LoggerFactory.getLogger( Tait1200ANIMessage.class ); + private final static Logger mLog = LoggerFactory.getLogger(Tait1200ANIMessage.class); - public static int[] REVS_1 = { 0,1,2,3 }; - public static int[] SYNC = { 4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 }; - public static int[] SIZE = { 20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35 }; - public static int[] FROM_DIGIT_1 = { 36,37,38,39,40,41,42,43 }; - public static int[] FROM_DIGIT_2 = { 44,45,46,47,48,49,50,51 }; - public static int[] FROM_DIGIT_3 = { 52,53,54,55,56,57,58,59 }; - public static int[] FROM_DIGIT_4 = { 60,61,62,63,64,65,66,67 }; - public static int[] FROM_DIGIT_5 = { 68,69,70,71,72,73,74,75 }; - public static int[] FROM_DIGIT_6 = { 76,77,78,79,80,81,82,83 }; - public static int[] FROM_DIGIT_7 = { 84,85,86,87,88,89,90,91 }; - public static int[] FROM_DIGIT_8 = { 92,93,94,95,96,97,98,99 }; - public static int[] CHECKSUM_1 = { 100,101,102,103,104,105,106,107,108,109, - 110,111,112,113,114,115 }; + public static int[] REVS_1 = {0, 1, 2, 3}; + public static int[] SYNC = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; + public static int[] SIZE = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35}; + public static int[] FROM_DIGIT_1 = {36, 37, 38, 39, 40, 41, 42, 43}; + public static int[] FROM_DIGIT_2 = {44, 45, 46, 47, 48, 49, 50, 51}; + public static int[] FROM_DIGIT_3 = {52, 53, 54, 55, 56, 57, 58, 59}; + public static int[] FROM_DIGIT_4 = {60, 61, 62, 63, 64, 65, 66, 67}; + public static int[] FROM_DIGIT_5 = {68, 69, 70, 71, 72, 73, 74, 75}; + public static int[] FROM_DIGIT_6 = {76, 77, 78, 79, 80, 81, 82, 83}; + public static int[] FROM_DIGIT_7 = {84, 85, 86, 87, 88, 89, 90, 91}; + public static int[] FROM_DIGIT_8 = {92, 93, 94, 95, 96, 97, 98, 99}; + public static int[] CHECKSUM_1 = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115}; - public static int[] REVS_2 = { 116,117,118,119,120,121,122,123,124,125,126,127, - 128,129,130,131 }; - public static int[] SIZE_2 = { 188,189,190,191,192,193,194,195,196,197,198, - 199,200,201,202,203 }; - public static int[] TO_DIGIT_1 = { 204,205,206,207,208,209,210,211 }; - public static int[] TO_DIGIT_2 = { 212,213,214,215,216,217,218,219 }; - public static int[] TO_DIGIT_3 = { 220,221,222,223,224,225,226,227 }; - public static int[] TO_DIGIT_4 = { 228,229,230,231,232,233,234,235 }; - public static int[] TO_DIGIT_5 = { 236,237,238,239,240,241,242,243 }; - public static int[] TO_DIGIT_6 = { 244,245,246,247,248,249,250,251 }; - public static int[] TO_DIGIT_7 = { 252,253,254,255,256,257,258,259 }; - public static int[] TO_DIGIT_8 = { 260,261,262,263,264,265,266,267 }; - public static int[] UNKNOWN_1 = { 268,269,270,271,272,273,274,275 }; - public static int[] CHECKSUM_2 = { 276,277,278,279,280,281,282,283,284,285, - 286,287,288,289,290,291 }; + public static int[] REVS_2 = {116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, + 128, 129, 130, 131}; + public static int[] SIZE_2 = {188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, + 199, 200, 201, 202, 203}; + public static int[] TO_DIGIT_1 = {204, 205, 206, 207, 208, 209, 210, 211}; + public static int[] TO_DIGIT_2 = {212, 213, 214, 215, 216, 217, 218, 219}; + public static int[] TO_DIGIT_3 = {220, 221, 222, 223, 224, 225, 226, 227}; + public static int[] TO_DIGIT_4 = {228, 229, 230, 231, 232, 233, 234, 235}; + public static int[] TO_DIGIT_5 = {236, 237, 238, 239, 240, 241, 242, 243}; + public static int[] TO_DIGIT_6 = {244, 245, 246, 247, 248, 249, 250, 251}; + public static int[] TO_DIGIT_7 = {252, 253, 254, 255, 256, 257, 258, 259}; + public static int[] TO_DIGIT_8 = {260, 261, 262, 263, 264, 265, 266, 267}; + public static int[] UNKNOWN_1 = {268, 269, 270, 271, 272, 273, 274, 275}; + public static int[] CHECKSUM_2 = {276, 277, 278, 279, 280, 281, 282, 283, 284, 285, + 286, 287, 288, 289, 290, 291}; - private static SimpleDateFormat mSDF = new SimpleDateFormat( "yyyyMMdd HHmmss" ); + private static SimpleDateFormat mSDF = new SimpleDateFormat("yyyyMMdd HHmmss"); - private BinaryMessage mMessage; - private AliasList mAliasList; + private CorrectedBinaryMessage mMessage; private CRC mCRC; - - public Tait1200ANIMessage( BinaryMessage message, AliasList list ) + private TaitIdentifier mFromIdentifier; + private TaitIdentifier mToIdentifier; + private List mIdentifiers; + + public Tait1200ANIMessage(CorrectedBinaryMessage message) { - mMessage = message; - mAliasList = list; - -// checkCRC(); -// -// switch( mCRC ) -// { -// case CORRECTED: -// mLog.debug( "CORR:" + message.toString() ); -// break; -// case FAILED_CRC: -// mLog.debug( "FAIL:" + message.toString() ); -// break; -// case PASSED: -// mLog.debug( "PASS:" + message.toString() ); -// break; -// } + mMessage = message; } - - @Override - public String getFromID() + + public TaitIdentifier getFromIdentifier() { - StringBuilder sb = new StringBuilder(); - - sb.append( getCharacter( FROM_DIGIT_1 ) ); - sb.append( getCharacter( FROM_DIGIT_2 ) ); - sb.append( getCharacter( FROM_DIGIT_3 ) ); - sb.append( getCharacter( FROM_DIGIT_4 ) ); - sb.append( getCharacter( FROM_DIGIT_5 ) ); - sb.append( getCharacter( FROM_DIGIT_6 ) ); - sb.append( getCharacter( FROM_DIGIT_7 ) ); - sb.append( getCharacter( FROM_DIGIT_8 ) ); - - return sb.toString(); + if(mFromIdentifier == null) + { + StringBuilder sb = new StringBuilder(); + + sb.append(getCharacter(FROM_DIGIT_1)); + sb.append(getCharacter(FROM_DIGIT_2)); + sb.append(getCharacter(FROM_DIGIT_3)); + sb.append(getCharacter(FROM_DIGIT_4)); + sb.append(getCharacter(FROM_DIGIT_5)); + sb.append(getCharacter(FROM_DIGIT_6)); + sb.append(getCharacter(FROM_DIGIT_7)); + sb.append(getCharacter(FROM_DIGIT_8)); + + mFromIdentifier = TaitIdentifier.createFrom(sb.toString().trim()); + } + + return mFromIdentifier; } - - @Override - public Alias getFromIDAlias() + + public TaitIdentifier getToIdentifier() { - if( mAliasList != null ) - { - return mAliasList.getTalkgroupAlias( getFromID() ); - } - - return null; + if(mToIdentifier == null) + { + StringBuilder sb = new StringBuilder(); + + sb.append(getCharacter(TO_DIGIT_1)); + sb.append(getCharacter(TO_DIGIT_2)); + sb.append(getCharacter(TO_DIGIT_3)); + sb.append(getCharacter(TO_DIGIT_4)); + sb.append(getCharacter(TO_DIGIT_5)); + sb.append(getCharacter(TO_DIGIT_6)); + sb.append(getCharacter(TO_DIGIT_7)); + sb.append(getCharacter(TO_DIGIT_8)); + + mToIdentifier = TaitIdentifier.createTo(sb.toString().trim()); + } + + return mToIdentifier; } - - @Override - public String getToID() + + public char getCharacter(int[] bits) { - StringBuilder sb = new StringBuilder(); - - sb.append( getCharacter( TO_DIGIT_1 ) ); - sb.append( getCharacter( TO_DIGIT_2 ) ); - sb.append( getCharacter( TO_DIGIT_3 ) ); - sb.append( getCharacter( TO_DIGIT_4 ) ); - sb.append( getCharacter( TO_DIGIT_5 ) ); - sb.append( getCharacter( TO_DIGIT_6 ) ); - sb.append( getCharacter( TO_DIGIT_7 ) ); - sb.append( getCharacter( TO_DIGIT_8 ) ); - - return sb.toString(); - } - - @Override - public Alias getToIDAlias() - { - if( mAliasList != null ) - { - return mAliasList.getTalkgroupAlias( getToID() ); - } - - return null; - } - - public char getCharacter( int[] bits ) - { - int value = mMessage.getInt( bits ); - - return (char)value; + int value = mMessage.getInt(bits); + + return (char)value; } public boolean isValid() { // return mCRC == CRC.PASSED || mCRC == CRC.CORRECTED; - return true; + return true; } - + @Override public String toString() { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); - sb.append( "ANI FROM:" ); - sb.append( getFromID() ); - sb.append( " TO:" ); - sb.append( getToID() ); + sb.append("ANI FROM:").append(getFromIdentifier()); + sb.append(" TO:").append(getToIdentifier()); - return sb.toString(); + return sb.toString(); } - /** - * Pads spaces onto the end of the value to make it 'places' long - */ - public String pad( String value, int places, String padCharacter ) + @Override + public Protocol getProtocol() { - StringBuilder sb = new StringBuilder(); - - sb.append( value ); - - while( sb.length() < places ) - { - sb.append( padCharacter ); - } - - return sb.toString(); - } - - /** - * Pads an integer value with additional zeroes to make it decimalPlaces long - */ - public String format( int number, int decimalPlaces ) - { - StringBuilder sb = new StringBuilder(); - - int paddingRequired = decimalPlaces - ( String.valueOf( number ).length() ); - - for( int x = 0; x < paddingRequired; x++) - { - sb.append( "0" ); - } - - sb.append( number ); - - return sb.toString(); - } - - @Override - public String getBinaryMessage() - { - return mMessage.toString(); - } - - @Override - public String getProtocol() - { - return "Tait-1200"; + return Protocol.TAIT1200; } - @Override - public String getEventType() + @Override + public List getIdentifiers() { - return "ANI"; - } + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getFromIdentifier()); + mIdentifiers.add(getToIdentifier()); + } - @Override - public String getMessage() - { - return toString(); + return mIdentifiers; } - - @Override - public String getErrorStatus() - { - return "UNKNOWN"; -// return mCRC.getDisplayText(); - } - - @Override - public Plottable getPlottable() - { - return null; - } - - /** - * Provides a listing of aliases contained in the message. - */ - public List getAliases() - { - List aliases = new ArrayList(); - - Alias from = getFromIDAlias(); - - if( from != null ) - { - aliases.add( from ); - } - - Alias to = getToIDAlias(); - - if( to != null ) - { - aliases.add( to ); - } - - return aliases; - } } diff --git a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200ANIMessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200ANIMessageProcessor.java index d384ad28..382c26c9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200ANIMessageProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200ANIMessageProcessor.java @@ -1,65 +1,61 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.tait; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.Message; import io.github.dsheirer.sample.Listener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class Tait1200ANIMessageProcessor implements Listener +public class Tait1200ANIMessageProcessor implements Listener { - private final static Logger mLog = - LoggerFactory.getLogger( Tait1200ANIMessageProcessor.class ); + private final static Logger mLog = LoggerFactory.getLogger(Tait1200ANIMessageProcessor.class); + private Listener mMessageListener; - private Listener mMessageListener; - - private AliasList mAliasList; - - public Tait1200ANIMessageProcessor( AliasList list ) - { - mAliasList = list; - } - - public void dispose() - { - mMessageListener = null; - mAliasList = null; - } - - @Override - public void receive( BinaryMessage buffer ) + public Tait1200ANIMessageProcessor() { - if( mMessageListener != null ) - { - mMessageListener.receive( new Tait1200ANIMessage( buffer, mAliasList ) ); - } - } - - public void setMessageListener( Listener listener ) - { - mMessageListener = listener; } - public void removeMessageListener( Listener listener ) + public void dispose() { - mMessageListener = null; + mMessageListener = null; + } + + @Override + public void receive(CorrectedBinaryMessage buffer) + { + if(mMessageListener != null) + { + mMessageListener.receive(new Tait1200ANIMessage(buffer)); + } + } + + public void setMessageListener(Listener listener) + { + mMessageListener = listener; + } + + public void removeMessageListener(Listener listener) + { + mMessageListener = null; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200CallEvent.java b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200CallEvent.java index e6a53d70..5cbf898c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200CallEvent.java +++ b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200CallEvent.java @@ -1,20 +1,22 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.tait; import io.github.dsheirer.alias.Alias; @@ -24,36 +26,31 @@ import io.github.dsheirer.module.decode.event.CallEvent; public class Tait1200CallEvent extends CallEvent { - public Tait1200CallEvent( CallEventType callEventType, - AliasList aliasList, - String fromID, - String toID, - String details ) + public Tait1200CallEvent(CallEventType callEventType, + AliasList aliasList, + String fromID, + String toID, + String details) { - super( DecoderType.TAIT_1200, callEventType, - aliasList, fromID, toID, details ); + super(DecoderType.TAIT_1200, callEventType, + aliasList, fromID, toID, details); } - private Alias getAlias( String ident ) + private Alias getAlias(String ident) { - if( hasAliasList() ) - { - return getAliasList().getFleetsyncAlias( ident ); - } - return null; } - + @Override public Alias getFromIDAlias() { - return getAlias( getFromID() ); + return getAlias(getFromID()); } @Override public Alias getToIDAlias() { - return getAlias( getToID() ); + return getAlias(getToID()); } @Override @@ -79,30 +76,30 @@ public class Tait1200CallEvent extends CallEvent private String mToID; private String mDetails; - public Builder( CallEventType callEventType ) + public Builder(CallEventType callEventType) { mCallEventType = callEventType; } - - public Builder aliasList( AliasList aliasList ) + + public Builder aliasList(AliasList aliasList) { mAliasList = aliasList; return this; } - - public Builder from( String val ) + + public Builder from(String val) { mFromID = val; return this; -} + } - public Builder details( String details ) + public Builder details(String details) { mDetails = details; return this; } - - public Builder to( String toID ) + + public Builder to(String toID) { mToID = toID; return this; @@ -110,28 +107,22 @@ public class Tait1200CallEvent extends CallEvent public Tait1200CallEvent build() { - return new Tait1200CallEvent( this ); + return new Tait1200CallEvent(this); } } /** * Private constructor for the builder */ - private Tait1200CallEvent( Builder builder ) + private Tait1200CallEvent(Builder builder) { - this( builder.mCallEventType, - builder.mAliasList, - builder.mFromID, - builder.mToID, - builder.mDetails ); + this(builder.mCallEventType, builder.mAliasList, builder.mFromID, builder.mToID, builder.mDetails); } - - public static Tait1200CallEvent getLJ1200Event( Tait1200GPSMessage message ) + + public static Tait1200CallEvent getLJ1200Event(Tait1200GPSMessage message) { CallEventType type = CallEventType.COMMAND; - - return new Tait1200CallEvent.Builder( type ) - .details( message.getMessage() ) - .build(); + + return new Tait1200CallEvent.Builder(type).details(message.toString()).build(); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200Decoder.java b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200Decoder.java index 3fd46b95..db7574ef 100644 --- a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200Decoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200Decoder.java @@ -1,21 +1,24 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.module.decode.tait; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.bits.IBinarySymbolProcessor; import io.github.dsheirer.bits.MessageFramer; import io.github.dsheirer.bits.SyncPattern; @@ -28,7 +31,6 @@ import io.github.dsheirer.module.decode.afsk.AbstractAFSKDecoder; */ public class Tait1200Decoder extends AbstractAFSKDecoder implements IBinarySymbolProcessor { - /* Message length ... */ private static final int MESSAGE_LENGTH = 440; private MessageFramer mMessageFramerGPS; @@ -36,27 +38,27 @@ public class Tait1200Decoder extends AbstractAFSKDecoder implements IBinarySymbo private Tait1200GPSMessageProcessor mMessageAProcessor; private Tait1200ANIMessageProcessor mMessageBProcessor; - protected Tait1200Decoder(AFSK1200Decoder decoder, AliasList aliasList) + protected Tait1200Decoder(AFSK1200Decoder decoder) { super(decoder); - init(aliasList); + init(); } - public Tait1200Decoder(AliasList aliasList) + public Tait1200Decoder() { super(AFSK1200Decoder.Output.NORMAL); - init(aliasList); + init(); } - private void init(AliasList aliasList) + private void init() { getDecoder().setSymbolProcessor(this); mMessageFramerGPS = new MessageFramer(SyncPattern.TAIT_CCDI_GPS_MESSAGE.getPattern(), MESSAGE_LENGTH); mMessageFramerANI = new MessageFramer(SyncPattern.TAIT_SELCAL_MESSAGE.getPattern(), MESSAGE_LENGTH); - mMessageAProcessor = new Tait1200GPSMessageProcessor(aliasList); - mMessageBProcessor = new Tait1200ANIMessageProcessor(aliasList); + mMessageAProcessor = new Tait1200GPSMessageProcessor(); + mMessageBProcessor = new Tait1200ANIMessageProcessor(); mMessageFramerGPS.addMessageListener(mMessageAProcessor); mMessageFramerANI.addMessageListener(mMessageBProcessor); @@ -81,10 +83,10 @@ public class Tait1200Decoder extends AbstractAFSKDecoder implements IBinarySymbo } @Override - public void receive(boolean symbol) + public void process(boolean symbol) { - mMessageFramerANI.receive(symbol); - mMessageFramerGPS.receive(symbol); + mMessageFramerANI.process(symbol); + mMessageFramerGPS.process(symbol); } public MessageFramer getANIMessageFramer() diff --git a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderInstrumented.java index 66f3100b..a9cfd979 100644 --- a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderInstrumented.java @@ -1,18 +1,22 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.module.decode.tait; import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented; @@ -29,7 +33,7 @@ public class Tait1200DecoderInstrumented extends Tait1200Decoder implements IIns public Tait1200DecoderInstrumented() { - super(new AFSK1200DecoderInstrumented(false), null); + super(new AFSK1200DecoderInstrumented(false)); } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderState.java index e82526bc..c2ce76a2 100644 --- a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderState.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,48 +15,31 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.module.decode.tait; -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.alias.id.AliasIDType; -import io.github.dsheirer.channel.metadata.AliasedStringAttributeMonitor; -import io.github.dsheirer.channel.metadata.Attribute; -import io.github.dsheirer.channel.metadata.AttributeChangeRequest; import io.github.dsheirer.channel.state.DecoderState; import io.github.dsheirer.channel.state.DecoderStateEvent; import io.github.dsheirer.channel.state.DecoderStateEvent.Event; import io.github.dsheirer.channel.state.State; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.MutableIdentifierCollection; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.module.decode.event.CallEvent; -import io.github.dsheirer.util.StringUtils; +import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.PlottableDecodeEvent; +import io.github.dsheirer.module.decode.tait.identifier.TaitIdentifier; import org.jdesktop.swingx.mapviewer.GeoPosition; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import java.util.TreeSet; public class Tait1200DecoderState extends DecoderState { - private TreeSet mIdents = new TreeSet(); + private TreeSet mIdents = new TreeSet<>(); - private AliasedStringAttributeMonitor mFromAttribute; - private AliasedStringAttributeMonitor mToAttribute; - private String mMessage; - private String mMessageType; - - public Tait1200DecoderState(AliasList aliasList) + public Tait1200DecoderState() { - super(aliasList); - - mFromAttribute = new AliasedStringAttributeMonitor(Attribute.SECONDARY_ADDRESS_FROM, - getAttributeChangeRequestListener(), getAliasList(), AliasIDType.TALKGROUP); - mToAttribute = new AliasedStringAttributeMonitor(Attribute.SECONDARY_ADDRESS_TO, - getAttributeChangeRequestListener(), getAliasList(), AliasIDType.TALKGROUP); } @Override @@ -68,7 +52,6 @@ public class Tait1200DecoderState extends DecoderState public void reset() { mIdents.clear(); - resetState(); } @@ -90,56 +73,51 @@ public class Tait1200DecoderState extends DecoderState } - private void resetState() - { - mFromAttribute.reset(); - mToAttribute.reset(); - mMessage = null; - mMessageType = null; - } - @Override - public void receive(Message message) + public void receive(IMessage message) { if(message instanceof Tait1200GPSMessage) { - Tait1200GPSMessage gps = (Tait1200GPSMessage) message; + Tait1200GPSMessage gps = (Tait1200GPSMessage)message; - mFromAttribute.process(gps.getFromID()); - mIdents.add(gps.getFromID()); - - mToAttribute.process(gps.getToID()); - mIdents.add(gps.getToID()); + mIdents.add(gps.getFromIdentifier()); + mIdents.add(gps.getToIdentifier()); GeoPosition position = gps.getGPSLocation(); if(position != null) { - setMessage(gps.getGPSLocation().toString().replace("[", "") - .replace("]", "")); + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(message.getIdentifiers()); + + PlottableDecodeEvent event = PlottableDecodeEvent.plottableBuilder(gps.getTimestamp()) + .eventDescription("GPS") + .identifiers(ic) + .location(position) + .speed(gps.getSpeed()) + .build(); + + broadcast(event); } - setMessageType("GPS"); - - broadcast(new Tait1200CallEvent(CallEvent.CallEventType.GPS, getAliasList(), - gps.getFromID(), gps.getToID(), gps.getGPSLocation().toString())); - broadcast(new DecoderStateEvent(this, Event.DECODE, State.DATA)); } else if(message instanceof Tait1200ANIMessage) { - Tait1200ANIMessage ani = (Tait1200ANIMessage) message; - mFromAttribute.process(ani.getFromID()); - mIdents.add(ani.getFromID()); + Tait1200ANIMessage ani = (Tait1200ANIMessage)message; + mIdents.add(ani.getFromIdentifier()); + mIdents.add(ani.getToIdentifier()); - mToAttribute.process(ani.getToID()); - mIdents.add(ani.getToID()); + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(message.getIdentifiers()); - setMessage(null); - setMessageType("ANI"); - - broadcast(new Tait1200CallEvent(CallEvent.CallEventType.ID_ANI, getAliasList(), - ani.getFromID(), ani.getToID(), "ANI")); + broadcast(DecodeEvent.builder(ani.getTimestamp()) + .eventDescription("ANI") + .identifiers(ic) + .details("Automatic Number Identification") + .build()); broadcast(new DecoderStateEvent(this, Event.DECODE, State.CALL)); } @@ -157,26 +135,9 @@ public class Tait1200DecoderState extends DecoderState { sb.append("Radio Identifiers:\n"); - List idents = new ArrayList(mIdents); - - Collections.sort(idents); - - for(String ident : idents) + for(TaitIdentifier taitIdentifier : mIdents) { - sb.append("\t"); - sb.append(ident); - - if(hasAliasList()) - { - Alias alias = getAliasList().getTalkgroupAlias(ident); - - if(alias != null) - { - sb.append("\t"); - sb.append(alias.getName()); - } - } - sb.append("\n"); + sb.append("\t").append(taitIdentifier).append("\n"); } } @@ -198,32 +159,4 @@ public class Tait1200DecoderState extends DecoderState break; } } - - public String getMessage() - { - return mMessage; - } - - public void setMessage(String message) - { - if(!StringUtils.isEqual(mMessage, message)) - { - mMessage = message; - broadcast(new AttributeChangeRequest(Attribute.MESSAGE, getMessage())); - } - } - - public String getMessageType() - { - return mMessageType; - } - - public void setMessageType(String type) - { - if(!StringUtils.isEqual(mMessageType, type)) - { - mMessageType = type; - broadcast(new AttributeChangeRequest(Attribute.MESSAGE_TYPE, getMessageType())); - } - } } diff --git a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200GPSMessage.java b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200GPSMessage.java index 9d321fac..f2e413ac 100644 --- a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200GPSMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200GPSMessage.java @@ -1,28 +1,30 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.tait; -import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.edac.CRC; -import io.github.dsheirer.map.Plottable; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.message.Message; +import io.github.dsheirer.module.decode.tait.identifier.TaitIdentifier; +import io.github.dsheirer.protocol.Protocol; import org.jdesktop.swingx.mapviewer.GeoPosition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,375 +39,285 @@ import java.util.TimeZone; public class Tait1200GPSMessage extends Message { - private final static Logger mLog = LoggerFactory.getLogger( Tait1200GPSMessage.class ); + private final static Logger mLog = LoggerFactory.getLogger(Tait1200GPSMessage.class); - public static int[] REVS_1 = { 0,1,2,3 }; - public static int[] SYNC = { 4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 }; - public static int[] SIZE = { 20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35 }; - public static int[] FROM_DIGIT_1 = { 36,37,38,39,40,41,42,43 }; - public static int[] FROM_DIGIT_2 = { 44,45,46,47,48,49,50,51 }; - public static int[] FROM_DIGIT_3 = { 52,53,54,55,56,57,58,59 }; - public static int[] FROM_DIGIT_4 = { 60,61,62,63,64,65,66,67 }; - public static int[] FROM_DIGIT_5 = { 68,69,70,71,72,73,74,75 }; - public static int[] FROM_DIGIT_6 = { 76,77,78,79,80,81,82,83 }; - public static int[] FROM_DIGIT_7 = { 84,85,86,87,88,89,90,91 }; - public static int[] FROM_DIGIT_8 = { 92,93,94,95,96,97,98,99 }; - public static int[] CHECKSUM_1 = { 100,101,102,103,104,105,106,107,108,109, - 110,111,112,113,114,115 }; + public static int[] REVS_1 = {0, 1, 2, 3}; + public static int[] SYNC = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; + public static int[] SIZE = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35}; + public static int[] FROM_DIGIT_1 = {36, 37, 38, 39, 40, 41, 42, 43}; + public static int[] FROM_DIGIT_2 = {44, 45, 46, 47, 48, 49, 50, 51}; + public static int[] FROM_DIGIT_3 = {52, 53, 54, 55, 56, 57, 58, 59}; + public static int[] FROM_DIGIT_4 = {60, 61, 62, 63, 64, 65, 66, 67}; + public static int[] FROM_DIGIT_5 = {68, 69, 70, 71, 72, 73, 74, 75}; + public static int[] FROM_DIGIT_6 = {76, 77, 78, 79, 80, 81, 82, 83}; + public static int[] FROM_DIGIT_7 = {84, 85, 86, 87, 88, 89, 90, 91}; + public static int[] FROM_DIGIT_8 = {92, 93, 94, 95, 96, 97, 98, 99}; + public static int[] CHECKSUM_1 = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115}; - public static int[] REVS_2 = { 116,117,118,119,120,121,122,123,124,125,126,127, - 128,129,130,131 }; - public static int[] SIZE_2 = { 188,189,190,191,192,193,194,195,196,197,198, - 199,200,201,202,203 }; - public static int[] TO_DIGIT_1 = { 204,205,206,207,208,209,210,211 }; - public static int[] TO_DIGIT_2 = { 212,213,214,215,216,217,218,219 }; - public static int[] TO_DIGIT_3 = { 220,221,222,223,224,225,226,227 }; - public static int[] TO_DIGIT_4 = { 228,229,230,231,232,233,234,235 }; - public static int[] TO_DIGIT_5 = { 236,237,238,239,240,241,242,243 }; - public static int[] TO_DIGIT_6 = { 244,245,246,247,248,249,250,251 }; - public static int[] TO_DIGIT_7 = { 252,253,254,255,256,257,258,259 }; - public static int[] TO_DIGIT_8 = { 260,261,262,263,264,265,266,267 }; - public static int[] UNKNOWN_1 = { 268,269,270,271,272,273,274,275 }; - public static int[] CHECKSUM_2 = { 276,277,278,279,280,281,282,283,284,285, - 286,287,288,289,290,291 }; - public static int DIVIDER_1 = 292; - public static int[] HOUR_TENS = { 293,294,295 }; - public static int[] HOUR_ONES = { 296,297,298,299 }; - public static int DIVIDER_2 = 300; - public static int[] MINUTES_TENS = { 301,302,303 }; - public static int[] MINUTES_ONES = { 304,305,306,307 }; - public static int DIVIDER_3 = 308; - public static int[] SECONDS_TENS = { 309,310,311 }; - public static int[] SECONDS_ONES = { 312,313,314,315 }; - public static int DIVIDER_4 = 316; - public static int[] LATITUDE_SIGN = { 317,318 }; - public static int DIVIDER_5 = 319; - public static int[] LATITUDE_DEGREES_TENS = { 320,321,322,323 }; - public static int[] LATITUDE_DEGREES_ONES = { 324,325,326,327 }; - public static int DIVIDER_6 = 328; - public static int[] LATITUDE_MINUTES_TENS = { 329,330,331 }; - public static int[] LATITUDE_MINUTES_ONES = { 332,333,334,335 }; - public static int[] LATITUDE_SECONDS_HUND = { 336,337,338,339 }; - public static int[] LATITUDE_SECONDS_TENS = { 340,341,342,344 }; - public static int[] LATITUDE_SECONDS_ONES = { 344,345,346,347 }; - public static int DIVIDER_7 = 348; - public static int[] LONGITUDE_SIGN = { 349,350 }; - public static int LONGITUDE_DEGREES_HUNDREDS = 351; - public static int[] LONGITUDE_DEGREES_TENS = { 352,353,354,355 }; - public static int[] LONGITUDE_DEGREES_ONES = { 356,357,358,359 }; - public static int DIVIDER_9 = 360; - public static int[] LONGITUDE_MINUTES_TENS = { 361,362,363 }; - public static int[] LONGITUDE_MINUTES_ONES = { 364,365,366,367 }; - public static int[] LONGITUDE_SECONDS_HUND = { 368,369,370,371 }; - public static int[] LONGITUDE_SECONDS_TENS = { 372,373,374,375 }; - public static int[] LONGITUDE_SECONDS_ONES = { 376,377,378,379 }; - public static int DIVIDER_10 = 380; - public static int[] DATE_DAY_TENS = { 381,382,383 }; - public static int[] DATE_DAY_ONES = { 384,385,386,387 }; + public static int[] REVS_2 = {116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, + 128, 129, 130, 131}; + public static int[] SIZE_2 = {188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, + 199, 200, 201, 202, 203}; + public static int[] TO_DIGIT_1 = {204, 205, 206, 207, 208, 209, 210, 211}; + public static int[] TO_DIGIT_2 = {212, 213, 214, 215, 216, 217, 218, 219}; + public static int[] TO_DIGIT_3 = {220, 221, 222, 223, 224, 225, 226, 227}; + public static int[] TO_DIGIT_4 = {228, 229, 230, 231, 232, 233, 234, 235}; + public static int[] TO_DIGIT_5 = {236, 237, 238, 239, 240, 241, 242, 243}; + public static int[] TO_DIGIT_6 = {244, 245, 246, 247, 248, 249, 250, 251}; + public static int[] TO_DIGIT_7 = {252, 253, 254, 255, 256, 257, 258, 259}; + public static int[] TO_DIGIT_8 = {260, 261, 262, 263, 264, 265, 266, 267}; + public static int[] UNKNOWN_1 = {268, 269, 270, 271, 272, 273, 274, 275}; + public static int[] CHECKSUM_2 = {276, 277, 278, 279, 280, 281, 282, 283, 284, 285, + 286, 287, 288, 289, 290, 291}; + public static int DIVIDER_1 = 292; + public static int[] HOUR_TENS = {293, 294, 295}; + public static int[] HOUR_ONES = {296, 297, 298, 299}; + public static int DIVIDER_2 = 300; + public static int[] MINUTES_TENS = {301, 302, 303}; + public static int[] MINUTES_ONES = {304, 305, 306, 307}; + public static int DIVIDER_3 = 308; + public static int[] SECONDS_TENS = {309, 310, 311}; + public static int[] SECONDS_ONES = {312, 313, 314, 315}; + public static int DIVIDER_4 = 316; + public static int[] LATITUDE_SIGN = {317, 318}; + public static int DIVIDER_5 = 319; + public static int[] LATITUDE_DEGREES_TENS = {320, 321, 322, 323}; + public static int[] LATITUDE_DEGREES_ONES = {324, 325, 326, 327}; + public static int DIVIDER_6 = 328; + public static int[] LATITUDE_MINUTES_TENS = {329, 330, 331}; + public static int[] LATITUDE_MINUTES_ONES = {332, 333, 334, 335}; + public static int[] LATITUDE_SECONDS_HUND = {336, 337, 338, 339}; + public static int[] LATITUDE_SECONDS_TENS = {340, 341, 342, 344}; + public static int[] LATITUDE_SECONDS_ONES = {344, 345, 346, 347}; + public static int DIVIDER_7 = 348; + public static int[] LONGITUDE_SIGN = {349, 350}; + public static int LONGITUDE_DEGREES_HUNDREDS = 351; + public static int[] LONGITUDE_DEGREES_TENS = {352, 353, 354, 355}; + public static int[] LONGITUDE_DEGREES_ONES = {356, 357, 358, 359}; + public static int DIVIDER_9 = 360; + public static int[] LONGITUDE_MINUTES_TENS = {361, 362, 363}; + public static int[] LONGITUDE_MINUTES_ONES = {364, 365, 366, 367}; + public static int[] LONGITUDE_SECONDS_HUND = {368, 369, 370, 371}; + public static int[] LONGITUDE_SECONDS_TENS = {372, 373, 374, 375}; + public static int[] LONGITUDE_SECONDS_ONES = {376, 377, 378, 379}; + public static int DIVIDER_10 = 380; + public static int[] DATE_DAY_TENS = {381, 382, 383}; + public static int[] DATE_DAY_ONES = {384, 385, 386, 387}; - //TODO: not sure if this field is the month field or the speed field - public static int[] DATE_MONTH = { 388,389,390,391 }; - public static int[] SPEED_HUNDREDS = { 388,389,390,391 }; + //TODO: not sure if this field is the month field or the speed field + public static int[] DATE_MONTH = {388, 389, 390, 391}; + public static int[] SPEED_HUNDREDS = {388, 389, 390, 391}; - public static int[] SPEED_TENS = { 392,393,394,395 }; - public static int[] SPEED_ONES = { 396,397,398,399 }; - public static int[] SPEED_TENTHS = { 400,401,402,403 }; + public static int[] SPEED_TENS = {392, 393, 394, 395}; + public static int[] SPEED_ONES = {396, 397, 398, 399}; + public static int[] SPEED_TENTHS = {400, 401, 402, 403}; - private static SimpleDateFormat mSDF = new SimpleDateFormat( "yyyyMMdd HHmmss" ); + private static SimpleDateFormat mSDF = new SimpleDateFormat("yyyyMMdd HHmmss"); private BinaryMessage mMessage; - private AliasList mAliasList; private CRC mCRC; - - public Tait1200GPSMessage( BinaryMessage message, AliasList list ) + private TaitIdentifier mFromIdentifier; + private TaitIdentifier mToIdentifier; + private List mIdentifiers; + + public Tait1200GPSMessage(BinaryMessage message) { - mMessage = message; - mAliasList = list; + mMessage = message; } - + + public TaitIdentifier getFromIdentifier() + { + if(mFromIdentifier == null) + { + StringBuilder sb = new StringBuilder(); + + sb.append(getCharacter(FROM_DIGIT_1)); + sb.append(getCharacter(FROM_DIGIT_2)); + sb.append(getCharacter(FROM_DIGIT_3)); + sb.append(getCharacter(FROM_DIGIT_4)); + sb.append(getCharacter(FROM_DIGIT_5)); + sb.append(getCharacter(FROM_DIGIT_6)); + sb.append(getCharacter(FROM_DIGIT_7)); + sb.append(getCharacter(FROM_DIGIT_8)); + + mFromIdentifier = TaitIdentifier.createFrom(sb.toString().trim()); + } + + return mFromIdentifier; + } + + public TaitIdentifier getToIdentifier() + { + if(mToIdentifier == null) + { + StringBuilder sb = new StringBuilder(); + + sb.append(getCharacter(TO_DIGIT_1)); + sb.append(getCharacter(TO_DIGIT_2)); + sb.append(getCharacter(TO_DIGIT_3)); + sb.append(getCharacter(TO_DIGIT_4)); + sb.append(getCharacter(TO_DIGIT_5)); + sb.append(getCharacter(TO_DIGIT_6)); + sb.append(getCharacter(TO_DIGIT_7)); + sb.append(getCharacter(TO_DIGIT_8)); + + mToIdentifier = TaitIdentifier.createTo(sb.toString().trim()); + } + + return mToIdentifier; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getFromIdentifier()); + mIdentifiers.add(getToIdentifier()); + } + + return mIdentifiers; + } + public boolean isValid() { - //TODO: Override until we figure out the CRC - return true; + //TODO: Override until we figure out the CRC + return true; } - + public int getMessage1Size() { - return mMessage.getInt( SIZE ); + return mMessage.getInt(SIZE); } - + public int getMessage2Size() { - return mMessage.getInt( SIZE_2 ); + return mMessage.getInt(SIZE_2); } @Override public String toString() { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); - sb.append( "GPS FROM:" ); - sb.append( getFromID() ); - sb.append( " TO:" ); - sb.append( getToID() ); - sb.append( " LOCATION:" ); - - GeoPosition location = getGPSLocation(); - - sb.append( location.getLatitude() ); - sb.append( " " ); - sb.append( location.getLongitude() ); - - sb.append( " SPEED:" ); - sb.append( getSpeed() ); - sb.append( "KPH" ); - - sb.append( " GPS TIME:" ); - sb.append( mSDF.format( new Date( getGPSTime() ) ) ); - - sb.append( " " ); - sb.append( mMessage.toString() ); + sb.append("GPS FROM:").append(getFromIdentifier()); + sb.append(" TO:").append(getToIdentifier()); + sb.append(" LOCATION:"); - return sb.toString(); + GeoPosition location = getGPSLocation(); + + sb.append(location.getLatitude()); + sb.append(" "); + sb.append(location.getLongitude()); + + sb.append(" SPEED:").append(getSpeed()).append("KPH"); + sb.append(" GPS TIME:"); + sb.append(mSDF.format(new Date(getGPSTime()))); + + sb.append(" "); + sb.append(mMessage.toString()); + + return sb.toString(); } - + public GeoPosition getGPSLocation() { - double latitude = mMessage.getInt( LATITUDE_DEGREES_TENS ) * 10.0d; - latitude += mMessage.getInt( LATITUDE_DEGREES_ONES ); - latitude += (double)mMessage.getInt( LATITUDE_MINUTES_TENS ) / 6.0d; - latitude += (double)mMessage.getInt( LATITUDE_MINUTES_ONES ) / 60.0d; - latitude += (double)mMessage.getInt( LATITUDE_SECONDS_HUND ) / 600.0d; - latitude += (double)mMessage.getInt( LATITUDE_SECONDS_TENS ) / 6000.0d; - latitude += (double)mMessage.getInt( LATITUDE_SECONDS_ONES ) / 60000.0d; + double latitude = mMessage.getInt(LATITUDE_DEGREES_TENS) * 10.0d; + latitude += mMessage.getInt(LATITUDE_DEGREES_ONES); + latitude += (double)mMessage.getInt(LATITUDE_MINUTES_TENS) / 6.0d; + latitude += (double)mMessage.getInt(LATITUDE_MINUTES_ONES) / 60.0d; + latitude += (double)mMessage.getInt(LATITUDE_SECONDS_HUND) / 600.0d; + latitude += (double)mMessage.getInt(LATITUDE_SECONDS_TENS) / 6000.0d; + latitude += (double)mMessage.getInt(LATITUDE_SECONDS_ONES) / 60000.0d; - if( mMessage.getInt( LATITUDE_SIGN ) == 0 ) - { - latitude *= -1; - } - - double longitude = mMessage.get( LONGITUDE_DEGREES_HUNDREDS ) ? 100.0d : 0.0d; + if(mMessage.getInt(LATITUDE_SIGN) == 0) + { + latitude *= -1; + } - longitude += mMessage.getInt( LONGITUDE_DEGREES_TENS ) * 10.0d; - longitude += mMessage.getInt( LONGITUDE_DEGREES_ONES ); - longitude += (double)mMessage.getInt( LONGITUDE_MINUTES_TENS ) / 6.0d; - longitude += (double)mMessage.getInt( LONGITUDE_MINUTES_ONES ) / 60.0d; - longitude += (double)mMessage.getInt( LONGITUDE_SECONDS_HUND ) / 600.0d; - longitude += (double)mMessage.getInt( LONGITUDE_SECONDS_TENS ) / 6000.0d; - longitude += (double)mMessage.getInt( LONGITUDE_SECONDS_ONES ) / 60000.0d; - - if( mMessage.getInt( LONGITUDE_SIGN ) == 0 ) - { - longitude = -1; - } - - return new GeoPosition( latitude, longitude ); + double longitude = mMessage.get(LONGITUDE_DEGREES_HUNDREDS) ? 100.0d : 0.0d; + + longitude += mMessage.getInt(LONGITUDE_DEGREES_TENS) * 10.0d; + longitude += mMessage.getInt(LONGITUDE_DEGREES_ONES); + longitude += (double)mMessage.getInt(LONGITUDE_MINUTES_TENS) / 6.0d; + longitude += (double)mMessage.getInt(LONGITUDE_MINUTES_ONES) / 60.0d; + longitude += (double)mMessage.getInt(LONGITUDE_SECONDS_HUND) / 600.0d; + longitude += (double)mMessage.getInt(LONGITUDE_SECONDS_TENS) / 6000.0d; + longitude += (double)mMessage.getInt(LONGITUDE_SECONDS_ONES) / 60000.0d; + + if(mMessage.getInt(LONGITUDE_SIGN) == 0) + { + longitude = -1; + } + + return new GeoPosition(latitude, longitude); } - + public long getGPSTime() { - Calendar cal = new GregorianCalendar( TimeZone.getTimeZone( "UTC" ) ); - - cal.clear(); - - /* Use current time to get current year */ - cal.setTimeInMillis( System.currentTimeMillis() ); + Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); - //TODO: this field is either the month field, or the speed hundredths field - cal.set( Calendar.MONTH, mMessage.getInt( DATE_MONTH ) ); - - int day = mMessage.getInt( DATE_DAY_TENS ) * 10 + - mMessage.getInt( DATE_DAY_ONES ); - cal.set( Calendar.DAY_OF_MONTH, day ); - - cal.set( Calendar.HOUR_OF_DAY, ( mMessage.getInt( HOUR_TENS ) * 10 ) + - mMessage.getInt( HOUR_ONES ) ); - cal.set( Calendar.MINUTE, ( mMessage.getInt( MINUTES_TENS ) * 10 ) + - mMessage.getInt( MINUTES_ONES ) ); - cal.set( Calendar.SECOND, ( mMessage.getInt( SECONDS_TENS ) * 10 ) + - mMessage.getInt( SECONDS_ONES ) ); - cal.set( Calendar.MILLISECOND, 0 ); - - return cal.getTimeInMillis(); - } - - public String getSpeed() - { - StringBuilder sb = new StringBuilder(); + cal.clear(); - sb.append( getDigit( SPEED_HUNDREDS ) ); - sb.append( getDigit( SPEED_TENS ) ); - sb.append( getDigit( SPEED_ONES ) ); - sb.append( "." ); - sb.append( getDigit( SPEED_TENTHS ) ); - - return sb.toString(); - } - - private String getDigit( int[] field ) - { - int value = mMessage.getInt( field ); - return ( 0 <= value && value <= 9 ) ? String.valueOf( value ) : "?"; - } - - @Override - public String getFromID() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getCharacter( FROM_DIGIT_1 ) ); - sb.append( getCharacter( FROM_DIGIT_2 ) ); - sb.append( getCharacter( FROM_DIGIT_3 ) ); - sb.append( getCharacter( FROM_DIGIT_4 ) ); - sb.append( getCharacter( FROM_DIGIT_5 ) ); - sb.append( getCharacter( FROM_DIGIT_6 ) ); - sb.append( getCharacter( FROM_DIGIT_7 ) ); - sb.append( getCharacter( FROM_DIGIT_8 ) ); - - return sb.toString(); - } - - @Override - public Alias getFromIDAlias() - { - if( mAliasList != null ) - { - return mAliasList.getTalkgroupAlias( getFromID() ); - } - - return null; - } - - @Override - public String getToID() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getCharacter( TO_DIGIT_1 ) ); - sb.append( getCharacter( TO_DIGIT_2 ) ); - sb.append( getCharacter( TO_DIGIT_3 ) ); - sb.append( getCharacter( TO_DIGIT_4 ) ); - sb.append( getCharacter( TO_DIGIT_5 ) ); - sb.append( getCharacter( TO_DIGIT_6 ) ); - sb.append( getCharacter( TO_DIGIT_7 ) ); - sb.append( getCharacter( TO_DIGIT_8 ) ); - - return sb.toString(); - } - - @Override - public Alias getToIDAlias() - { - if( mAliasList != null ) - { - return mAliasList.getTalkgroupAlias( getToID() ); - } - - return null; - } - - public char getCharacter( int[] bits ) - { - int value = mMessage.getInt( bits ); - - return (char)value; + /* Use current time to get current year */ + cal.setTimeInMillis(System.currentTimeMillis()); + + //TODO: this field is either the month field, or the speed hundredths field + cal.set(Calendar.MONTH, mMessage.getInt(DATE_MONTH)); + + int day = mMessage.getInt(DATE_DAY_TENS) * 10 + + mMessage.getInt(DATE_DAY_ONES); + cal.set(Calendar.DAY_OF_MONTH, day); + + cal.set(Calendar.HOUR_OF_DAY, (mMessage.getInt(HOUR_TENS) * 10) + + mMessage.getInt(HOUR_ONES)); + cal.set(Calendar.MINUTE, (mMessage.getInt(MINUTES_TENS) * 10) + + mMessage.getInt(MINUTES_ONES)); + cal.set(Calendar.SECOND, (mMessage.getInt(SECONDS_TENS) * 10) + + mMessage.getInt(SECONDS_ONES)); + cal.set(Calendar.MILLISECOND, 0); + + return cal.getTimeInMillis(); } - /** - * Pads spaces onto the end of the value to make it 'places' long - */ - public String pad( String value, int places, String padCharacter ) + public double getSpeed() { - StringBuilder sb = new StringBuilder(); - - sb.append( value ); - - while( sb.length() < places ) - { - sb.append( padCharacter ); - } - - return sb.toString(); - } - - /** - * Pads an integer value with additional zeroes to make it decimalPlaces long - */ - public String format( int number, int decimalPlaces ) - { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); + sb.append(getDigit(SPEED_HUNDREDS)); + sb.append(getDigit(SPEED_TENS)); + sb.append(getDigit(SPEED_ONES)); + sb.append("."); + sb.append(getDigit(SPEED_TENTHS)); - int paddingRequired = decimalPlaces - ( String.valueOf( number ).length() ); + try + { + return Double.parseDouble(sb.toString()); + } + catch(Exception e) + { + //Do nothing, we couldn't parse the value + } - for( int x = 0; x < paddingRequired; x++) - { - sb.append( "0" ); - } - - sb.append( number ); - - return sb.toString(); - } - - @Override - public String getBinaryMessage() - { - return mMessage.toString(); - } - - @Override - public String getProtocol() - { - return "Tait-1200"; + return 0; } - @Override - public String getEventType() + private String getDigit(int[] field) { - return "GPS"; + int value = mMessage.getInt(field); + return (0 <= value && value <= 9) ? String.valueOf(value) : "?"; } - @Override - public String getMessage() + public char getCharacter(int[] bits) { - return toString(); - } - - @Override - public String getErrorStatus() - { - return "UNKNOWN"; -// return mCRC.getDisplayText(); + int value = mMessage.getInt(bits); + return (char)value; } - @Override - public Plottable getPlottable() + @Override + public Protocol getProtocol() { - return new Plottable( getGPSTime(), getGPSLocation(), getFromID(), - getFromIDAlias() ); + return Protocol.TAIT1200; } - - /** - * Provides a listing of aliases contained in the message. - */ - public List getAliases() - { - List aliases = new ArrayList(); - - Alias from = getFromIDAlias(); - - if( from != null ) - { - aliases.add( from ); - } - - Alias to = getToIDAlias(); - - if( to != null ) - { - aliases.add( to ); - } - - return aliases; - } } diff --git a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200GPSMessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200GPSMessageProcessor.java index c9bf06ef..fbb25f2a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200GPSMessageProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200GPSMessageProcessor.java @@ -1,65 +1,61 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.tait; -import io.github.dsheirer.alias.AliasList; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.sample.Listener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class Tait1200GPSMessageProcessor implements Listener +public class Tait1200GPSMessageProcessor implements Listener { - private final static Logger mLog = - LoggerFactory.getLogger( Tait1200GPSMessageProcessor.class ); + private final static Logger mLog = LoggerFactory.getLogger(Tait1200GPSMessageProcessor.class); - private Listener mMessageListener; - - private AliasList mAliasList; - - public Tait1200GPSMessageProcessor( AliasList list ) - { - mAliasList = list; - } - - public void dispose() - { - mMessageListener = null; - mAliasList = null; - } - - @Override - public void receive( BinaryMessage buffer ) + private Listener mMessageListener; + + public Tait1200GPSMessageProcessor() { - if( mMessageListener != null ) - { - mMessageListener.receive( new Tait1200GPSMessage( buffer, mAliasList ) ); - } } - - public void setMessageListener( Listener listener ) + + public void dispose() { - mMessageListener = listener; + mMessageListener = null; + } + + @Override + public void receive(CorrectedBinaryMessage buffer) + { + if(mMessageListener != null) + { + mMessageListener.receive(new Tait1200GPSMessage(buffer)); + } + } + + public void setMessageListener(Listener listener) + { + mMessageListener = listener; } public void removeMessageListener() { - mMessageListener = null; + mMessageListener = null; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/tait/identifier/TaitIdentifier.java b/src/main/java/io/github/dsheirer/module/decode/tait/identifier/TaitIdentifier.java new file mode 100644 index 00000000..32b5148a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/tait/identifier/TaitIdentifier.java @@ -0,0 +1,63 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.tait.identifier; + +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.string.StringIdentifier; +import io.github.dsheirer.protocol.Protocol; + +/** + * Tait-1200 String (ASCII) Identifier + */ +public class TaitIdentifier extends StringIdentifier +{ + /** + * Constructs an identifier with the specified role + */ + public TaitIdentifier(String value, Role role) + { + super(value, IdentifierClass.USER, Form.UNIT_IDENTIFIER, role); + } + + @Override + public Protocol getProtocol() + { + return Protocol.TAIT1200; + } + + /*** + * Creates an identifier with the FROM role + */ + public static TaitIdentifier createFrom(String value) + { + return new TaitIdentifier(value, Role.FROM); + } + + /*** + * Creates an identifier with the TO role + */ + public static TaitIdentifier createTo(String value) + { + return new TaitIdentifier(value, Role.TO); + } +} diff --git a/src/main/java/io/github/dsheirer/module/log/CallEventLogger.java b/src/main/java/io/github/dsheirer/module/log/CallEventLogger.java deleted file mode 100644 index 2f9784f2..00000000 --- a/src/main/java/io/github/dsheirer/module/log/CallEventLogger.java +++ /dev/null @@ -1,62 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.module.log; - -import io.github.dsheirer.module.decode.event.CallEvent; -import io.github.dsheirer.module.decode.event.ICallEventListener; -import io.github.dsheirer.sample.Listener; - -import java.nio.file.Path; - -public class CallEventLogger extends EventLogger implements ICallEventListener, - Listener -{ - public CallEventLogger( Path logDirectory, String fileNameSuffix ) - { - super( logDirectory, fileNameSuffix ); - } - - @Override - public void receive( CallEvent callEvent ) - { - write( callEvent.toCSV() ); - } - - @Override - public String getHeader() - { - return CallEvent.getCSVHeader(); - } - - @Override - public Listener getCallEventListener() - { - return this; - } - - @Override - public void dispose() - { - super.stop(); - } - - @Override - public void reset() - { - } -} diff --git a/src/main/java/io/github/dsheirer/module/log/DecodeEventLogger.java b/src/main/java/io/github/dsheirer/module/log/DecodeEventLogger.java new file mode 100644 index 00000000..68d75393 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/log/DecodeEventLogger.java @@ -0,0 +1,136 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.log; + +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier; +import io.github.dsheirer.module.decode.event.IDecodeEvent; +import io.github.dsheirer.module.decode.event.IDecodeEventListener; +import io.github.dsheirer.preference.TimestampFormat; +import io.github.dsheirer.sample.Listener; + +import java.nio.file.Path; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +public class DecodeEventLogger extends EventLogger implements IDecodeEventListener, Listener +{ + private SimpleDateFormat mTimestampFormat = TimestampFormat.TIMESTAMP_COLONS.getFormatter(); + private DecimalFormat mFrequencyFormat = new DecimalFormat("0.000000"); + + public DecodeEventLogger(Path logDirectory, String fileNameSuffix, long frequency) + { + super(logDirectory, fileNameSuffix, frequency); + } + + @Override + public void receive(IDecodeEvent decodeEvent) + { + write(toCSV(decodeEvent)); + } + + @Override + public String getHeader() + { + return getCSVHeader(); + } + + @Override + public Listener getDecodeEventListener() + { + return this; + } + + @Override + public void dispose() + { + super.stop(); + } + + @Override + public void reset() + { + } + + public static String getCSVHeader() + { + return "TIMESTAMP,DURATION_MS,PROTOCOL,EVENT,FROM,TO,CHANNEL_NUMBER,FREQUENCY,DETAILS"; + } + + private String toCSV(IDecodeEvent event) + { + StringBuilder sb = new StringBuilder(); + + sb.append("\"").append(mTimestampFormat.format(new Date(event.getTimeStart()))).append("\""); + sb.append(",\"").append(event.getDuration() > 0 ? event.getDuration() : "").append("\""); + sb.append(",\"").append(event.getProtocol()).append("\""); + + String description = event.getEventDescription(); + + sb.append(",\"").append(description != null ? description : "").append("\""); + + List fromIdentifiers = event.getIdentifierCollection().getIdentifiers(Role.FROM); + if(fromIdentifiers != null && !fromIdentifiers.isEmpty()) + { + sb.append(",\"").append(fromIdentifiers.get(0)).append("\""); + } + else + { + sb.append(",\"\""); + } + + List toIdentifiers = event.getIdentifierCollection().getIdentifiers(Role.TO); + if(toIdentifiers != null && !toIdentifiers.isEmpty()) + { + sb.append(",\"").append(toIdentifiers.get(0)).append("\""); + } + else + { + sb.append(",\"\""); + } + + IChannelDescriptor descriptor = event.getChannelDescriptor(); + + sb.append(",\"").append(descriptor != null ? descriptor : "").append("\""); + + Identifier frequency = event.getIdentifierCollection().getIdentifier(IdentifierClass.CONFIGURATION, Form.CHANNEL_FREQUENCY, Role.ANY); + + if(frequency instanceof FrequencyConfigurationIdentifier) + { + sb.append(",\"").append(mFrequencyFormat.format(((FrequencyConfigurationIdentifier)frequency).getValue() / 1e6d)).append("\""); + } + else + { + sb.append(",\"\""); + } + + String details = event.getDetails(); + + sb.append(",\"").append(details != null ? details : "").append("\""); + + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/log/EventLogConfigurationEditor.java b/src/main/java/io/github/dsheirer/module/log/EventLogConfigurationEditor.java index 9e9dc0d3..9a8302bc 100644 --- a/src/main/java/io/github/dsheirer/module/log/EventLogConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/module/log/EventLogConfigurationEditor.java @@ -1,20 +1,22 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.module.log; import io.github.dsheirer.controller.channel.Channel; @@ -22,7 +24,7 @@ import io.github.dsheirer.gui.editor.Editor; import io.github.dsheirer.module.log.config.EventLogConfiguration; import net.miginfocom.swing.MigLayout; -import javax.swing.*; +import javax.swing.JCheckBox; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.List; @@ -30,121 +32,144 @@ import java.util.List; public class EventLogConfigurationEditor extends Editor { private static final long serialVersionUID = 1L; - - private JCheckBox mBinaryLogger; - private JCheckBox mDecodedLogger; + private JCheckBox mCallEventLogger; + private JCheckBox mDecodedMessageLogger; + private JCheckBox mTrafficCallEventLogger; + private JCheckBox mTrafficDecodedMessageLogger; public EventLogConfigurationEditor() - { - init(); - } - + { + init(); + } + private void init() { - setLayout( new MigLayout( "fill,wrap 4", "", "[][grow]" ) ); + setLayout(new MigLayout("fill,wrap 2", "", "[][][grow]")); - mBinaryLogger = new JCheckBox( "Binary Messages" ); - mBinaryLogger.setEnabled( false ); - mBinaryLogger.addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent e ) - { - setModified( true ); - } - } ); - add( mBinaryLogger ); - - mDecodedLogger = new JCheckBox( "Decoded Messages" ); - mDecodedLogger.setEnabled( false ); - mDecodedLogger.addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent e ) - { - setModified( true ); - } - } ); - add( mDecodedLogger ); - - mCallEventLogger = new JCheckBox( "Call Events" ); - mCallEventLogger.setEnabled( false ); - mCallEventLogger.addActionListener( new ActionListener() - { - @Override - public void actionPerformed( ActionEvent e ) - { - setModified( true ); - } - } ); - add( mCallEventLogger ); + mCallEventLogger = new JCheckBox("Call Events"); + mCallEventLogger.setEnabled(false); + mCallEventLogger.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + setModified(true); + } + }); + add(mCallEventLogger); + + mDecodedMessageLogger = new JCheckBox("Decoded Messages"); + mDecodedMessageLogger.setEnabled(false); + mDecodedMessageLogger.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + setModified(true); + } + }); + add(mDecodedMessageLogger); + + mTrafficCallEventLogger = new JCheckBox("Traffic Channel Call Events"); + mTrafficCallEventLogger.setEnabled(false); + mTrafficCallEventLogger.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + setModified(true); + } + }); + add(mTrafficCallEventLogger); + + mTrafficDecodedMessageLogger = new JCheckBox("Traffic Channel Decoded Messages"); + mTrafficDecodedMessageLogger.setEnabled(false); + mTrafficDecodedMessageLogger.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + setModified(true); + } + }); + add(mTrafficDecodedMessageLogger); } /** * Saves the current configuration as the stored configuration */ - @Override + @Override public void save() { - if( hasItem() ) - { - EventLogConfiguration config = getItem().getEventLogConfiguration(); + if(hasItem()) + { + EventLogConfiguration config = getItem().getEventLogConfiguration(); - config.clear(); - - if( mBinaryLogger.isSelected() ) - { - config.addLogger( EventLogType.BINARY_MESSAGE ); - } - if( mDecodedLogger.isSelected() ) - { - config.addLogger( EventLogType.DECODED_MESSAGE ); - } - if( mCallEventLogger.isSelected() ) - { - config.addLogger( EventLogType.CALL_EVENT ); - } - } - - setModified( false ); + config.clear(); + + if(mCallEventLogger.isSelected()) + { + config.addLogger(EventLogType.CALL_EVENT); + } + if(mDecodedMessageLogger.isSelected()) + { + config.addLogger(EventLogType.DECODED_MESSAGE); + } + if(mTrafficCallEventLogger.isSelected()) + { + config.addLogger(EventLogType.TRAFFIC_CALL_EVENT); + } + if(mTrafficDecodedMessageLogger.isSelected()) + { + config.addLogger(EventLogType.TRAFFIC_DECODED_MESSAGE); + } + } + + setModified(false); } - @Override - public void setItem( Channel channel ) - { - super.setItem( channel ); + @Override + public void setItem(Channel channel) + { + super.setItem(channel); - if( hasItem() ) - { - setControlsEnabled( true ); + if(hasItem()) + { + setControlsEnabled(true); - List loggers = getItem().getEventLogConfiguration().getLoggers(); - mBinaryLogger.setSelected( loggers.contains( EventLogType.BINARY_MESSAGE ) ); - mDecodedLogger.setSelected( loggers.contains( EventLogType.DECODED_MESSAGE ) ); - mCallEventLogger.setSelected( loggers.contains( EventLogType.CALL_EVENT ) ); - } - else - { - setControlsEnabled( false ); - } - } - - private void setControlsEnabled( boolean enabled ) - { - if( mBinaryLogger.isEnabled() != enabled ) - { - mBinaryLogger.setEnabled( enabled ); - } - - if( mDecodedLogger.isEnabled() != enabled ) - { - mDecodedLogger.setEnabled( enabled ); - } - - if( mCallEventLogger.isEnabled() != enabled ) - { - mCallEventLogger.setEnabled( enabled ); - } - } + List loggers = getItem().getEventLogConfiguration().getLoggers(); + mDecodedMessageLogger.setSelected(loggers.contains(EventLogType.DECODED_MESSAGE)); + mCallEventLogger.setSelected(loggers.contains(EventLogType.CALL_EVENT)); + mTrafficCallEventLogger.setSelected(loggers.contains(EventLogType.TRAFFIC_CALL_EVENT)); + mTrafficDecodedMessageLogger.setSelected(loggers.contains(EventLogType.TRAFFIC_DECODED_MESSAGE)); + } + else + { + setControlsEnabled(false); + } + } + + private void setControlsEnabled(boolean enabled) + { + if(mDecodedMessageLogger.isEnabled() != enabled) + { + mDecodedMessageLogger.setEnabled(enabled); + } + + if(mCallEventLogger.isEnabled() != enabled) + { + mCallEventLogger.setEnabled(enabled); + } + + if(mTrafficDecodedMessageLogger.isEnabled() != enabled) + { + mTrafficDecodedMessageLogger.setEnabled(enabled); + } + + if(mTrafficCallEventLogger.isEnabled() != enabled) + { + mTrafficCallEventLogger.setEnabled(enabled); + } + } } diff --git a/src/main/java/io/github/dsheirer/module/log/EventLogManager.java b/src/main/java/io/github/dsheirer/module/log/EventLogManager.java index 74d29363..b552d4eb 100644 --- a/src/main/java/io/github/dsheirer/module/log/EventLogManager.java +++ b/src/main/java/io/github/dsheirer/module/log/EventLogManager.java @@ -1,6 +1,7 @@ -/******************************************************************************* +/* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 @@ -14,13 +15,16 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.module.log; +import io.github.dsheirer.controller.channel.Channel; import io.github.dsheirer.module.Module; import io.github.dsheirer.module.log.config.EventLogConfiguration; import io.github.dsheirer.properties.SystemProperties; +import io.github.dsheirer.source.config.SourceConfigTuner; +import io.github.dsheirer.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,46 +34,73 @@ import java.util.List; public class EventLogManager { - private final static Logger mLog = LoggerFactory.getLogger(EventLogManager.class); + private final static Logger mLog = LoggerFactory.getLogger(EventLogManager.class); - private Path mDirectory; - - public EventLogManager() - { - mDirectory = SystemProperties.getInstance() - .getApplicationFolder( "event_logs" ); - } + private Path mDirectory; - public List getLoggers(EventLogConfiguration config, String prefix ) - { - List loggers = new ArrayList(); + public EventLogManager() + { + mDirectory = SystemProperties.getInstance() + .getApplicationFolder("event_logs"); + } - for( EventLogType type: config.getLoggers() ) - { - loggers.add( getLogger( type, prefix ) ); - } + public List getLoggers(Channel channel) + { + EventLogConfiguration config = channel.getEventLogConfiguration(); + String prefix = StringUtils.replaceIllegalCharacters(channel.getName()); + long frequency = 0; - return loggers; - } - - public EventLogger getLogger( EventLogType eventLogType, String prefix ) - { - StringBuilder sb = new StringBuilder(); + if(channel.getSourceConfiguration() instanceof SourceConfigTuner) + { + frequency = ((SourceConfigTuner)channel.getSourceConfiguration()).getFrequency(); + } - sb.append( prefix ); - sb.append( eventLogType.getFileSuffix() ); - sb.append( ".log" ); + List loggers = new ArrayList(); - switch( eventLogType ) - { - case BINARY_MESSAGE: - return new MessageEventLogger( mDirectory, sb.toString(), MessageEventLogger.Type.BINARY ); - case DECODED_MESSAGE: - return new MessageEventLogger( mDirectory, sb.toString(), MessageEventLogger.Type.DECODED ); - case CALL_EVENT: - return new CallEventLogger( mDirectory, sb.toString() ); - default: - return null; - } - } + for(EventLogType type : config.getLoggers()) + { + switch(type) + { + case CALL_EVENT: + case DECODED_MESSAGE: + if(channel.getChannelType() == Channel.ChannelType.STANDARD) + { + loggers.add(getLogger(type, prefix, frequency)); + } + break; + case TRAFFIC_CALL_EVENT: + case TRAFFIC_DECODED_MESSAGE: + if(channel.getChannelType() == Channel.ChannelType.TRAFFIC) + { + loggers.add(getLogger(type, prefix, frequency)); + } + break; + } + } + + return loggers; + } + + public EventLogger getLogger(EventLogType eventLogType, String prefix, long frequency) + { + StringBuilder sb = new StringBuilder(); + + sb.append(prefix); + sb.append(eventLogType.getFileSuffix()); + sb.append(".log"); + + switch(eventLogType) + { + case CALL_EVENT: + return new DecodeEventLogger(mDirectory, sb.toString(), frequency); + case DECODED_MESSAGE: + return new MessageEventLogger(mDirectory, sb.toString(), MessageEventLogger.Type.DECODED, frequency); + case TRAFFIC_CALL_EVENT: + return new DecodeEventLogger(mDirectory, sb.toString(), frequency); + case TRAFFIC_DECODED_MESSAGE: + return new MessageEventLogger(mDirectory, sb.toString(), MessageEventLogger.Type.DECODED, frequency); + default: + return null; + } + } } diff --git a/src/main/java/io/github/dsheirer/module/log/EventLogType.java b/src/main/java/io/github/dsheirer/module/log/EventLogType.java index 534e4677..51f2d3c6 100644 --- a/src/main/java/io/github/dsheirer/module/log/EventLogType.java +++ b/src/main/java/io/github/dsheirer/module/log/EventLogType.java @@ -20,10 +20,16 @@ package io.github.dsheirer.module.log; public enum EventLogType { + /** + * Legacy logging type - do not use - superceded by bitstream recorder + */ BINARY_MESSAGE( "Binary Messages", "_binary_messages" ), + DECODED_MESSAGE( "Decoded Messages", "_decoded_messages" ), - CALL_EVENT( "Call Events", "_call_events" ); - + TRAFFIC_DECODED_MESSAGE( "Traffic Channel Decoded Messages", "_decoded_messages" ), + CALL_EVENT( "Call Events", "_call_events" ), + TRAFFIC_CALL_EVENT( "Traffic Channel Call Events", "_call_events" ); + private String mDisplayString; private String mFileSuffix; diff --git a/src/main/java/io/github/dsheirer/module/log/EventLogger.java b/src/main/java/io/github/dsheirer/module/log/EventLogger.java index 242a9ba9..c391c60b 100644 --- a/src/main/java/io/github/dsheirer/module/log/EventLogger.java +++ b/src/main/java/io/github/dsheirer/module/log/EventLogger.java @@ -26,7 +26,6 @@ import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; -import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.file.Path; @@ -38,12 +37,14 @@ public abstract class EventLogger extends Module private Path mLogDirectory; private String mFileNameSuffix; private String mLogFileName; + private long mFrequency; protected Writer mLogFile; - public EventLogger(Path logDirectory, String fileNameSuffix) + public EventLogger(Path logDirectory, String fileNameSuffix, long frequency) { mLogDirectory = logDirectory; mFileNameSuffix = fileNameSuffix; + mFrequency = frequency; } public String toString() @@ -70,8 +71,10 @@ public abstract class EventLogger extends Module StringBuilder sb = new StringBuilder(); sb.append(mLogDirectory); sb.append(File.separator); - sb.append(TimeStamp.getTimeStamp("_")); + sb.append(TimeStamp.getLongTimeStamp("_")); sb.append("_"); + sb.append(mFrequency); + sb.append("_Hz_"); sb.append(mFileNameSuffix); mLogFileName = sb.toString(); @@ -110,10 +113,13 @@ public abstract class EventLogger extends Module { try { - mLogFile.write(eventLogEntry + "\n"); - mLogFile.flush(); + if(mLogFile != null) + { + mLogFile.write((eventLogEntry != null ? eventLogEntry : "") + "\n"); + mLogFile.flush(); + } } - catch(IOException e) + catch(Exception e) { mLog.error("Error writing entry to event log file", e); } diff --git a/src/main/java/io/github/dsheirer/module/log/MessageEventLogger.java b/src/main/java/io/github/dsheirer/module/log/MessageEventLogger.java index 0d1f6b14..9ba3737d 100644 --- a/src/main/java/io/github/dsheirer/module/log/MessageEventLogger.java +++ b/src/main/java/io/github/dsheirer/module/log/MessageEventLogger.java @@ -1,88 +1,77 @@ /******************************************************************************* * 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 io.github.dsheirer.module.log; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.IMessageListener; -import io.github.dsheirer.message.Message; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.util.TimeStamp; import java.nio.file.Path; -public class MessageEventLogger extends EventLogger implements - IMessageListener, Listener +public class MessageEventLogger extends EventLogger implements IMessageListener, Listener { - public enum Type - { - BINARY, DECODED - }; + public enum Type + { + BINARY, DECODED + } - private Type mType; + private Type mType; - public MessageEventLogger( Path logDirectory, - String fileNameSuffix, - Type type ) - { - super( logDirectory, fileNameSuffix ); + public MessageEventLogger(Path logDirectory, String fileNameSuffix, Type type, long frequency) + { + super(logDirectory, fileNameSuffix, frequency); + mType = type; + } - mType = type; - } + @Override + public Listener getMessageListener() + { + return this; + } - @Override - public Listener getMessageListener() - { - return this; - } + @Override + public void dispose() + { + super.stop(); + } - @Override - public void dispose() - { - super.stop(); - } + @Override + public void reset() + { + } - @Override - public void reset() - { - } + @Override + public void receive(IMessage message) + { + StringBuilder sb = new StringBuilder(); + sb.append(TimeStamp.getTimeStamp(message.getTimestamp(), " ")); + sb.append(","); + sb.append((message.isValid() ? "PASSED" : "FAILED")); + sb.append(","); + sb.append(message.toString()); - @Override - public void receive( Message message ) - { - StringBuilder sb = new StringBuilder(); - sb.append( TimeStamp.getTimeStamp( " " ) ); - sb.append( "," ); - sb.append( ( message.isValid() ? "PASSED" : "FAILED" ) ); - sb.append( "," ); + write(sb.toString()); + } - if ( mType == Type.BINARY ) - { - sb.append( message.getBinaryMessage() ); - } else - { - sb.append( message.getMessage() ); - } - - write( sb.toString() ); - } - - @Override - public String getHeader() - { - return mType.toString() + " Message Logger\n"; - } + @Override + public String getHeader() + { + return mType.toString() + " Message Logger\n"; + } } diff --git a/src/main/java/io/github/dsheirer/playlist/PlaylistManager.java b/src/main/java/io/github/dsheirer/playlist/PlaylistManager.java index 472ce4f1..3ee2267a 100644 --- a/src/main/java/io/github/dsheirer/playlist/PlaylistManager.java +++ b/src/main/java/io/github/dsheirer/playlist/PlaylistManager.java @@ -1,18 +1,21 @@ /* - * ********************************************************************************************************************* + * ****************************************************************************** * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 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 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. + * 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 - * ********************************************************************************************************************* + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** */ package io.github.dsheirer.playlist; @@ -26,11 +29,11 @@ import io.github.dsheirer.audio.broadcast.BroadcastEvent; import io.github.dsheirer.audio.broadcast.BroadcastModel; import io.github.dsheirer.controller.channel.Channel.ChannelType; import io.github.dsheirer.controller.channel.ChannelEvent; -import io.github.dsheirer.controller.channel.ChannelEventListener; import io.github.dsheirer.controller.channel.ChannelModel; import io.github.dsheirer.controller.channel.map.ChannelMapEvent; import io.github.dsheirer.controller.channel.map.ChannelMapModel; -import io.github.dsheirer.properties.SystemProperties; +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.preference.playlist.FilePreferences; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.util.ThreadPool; import org.slf4j.Logger; @@ -40,25 +43,21 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -public class PlaylistManager implements ChannelEventListener +public class PlaylistManager implements Listener { private final static Logger mLog = LoggerFactory.getLogger(PlaylistManager.class); + public static final int PLAYLIST_CURRENT_VERSION = 2; + private AliasModel mAliasModel; private BroadcastModel mBroadcastModel; private ChannelModel mChannelModel; private ChannelMapModel mChannelMapModel; - - private Path mPlaylistFolderPath; - private Path mPlaylistCurrentPath; - private Path mPlaylistBackupPath; - private Path mPlaylistLockPath; - + private UserPreferences mUserPreferences; private AtomicBoolean mPlaylistSavePending = new AtomicBoolean(); private boolean mPlaylistLoading = false; @@ -71,12 +70,14 @@ public class PlaylistManager implements ChannelEventListener * * @param channelModel */ - public PlaylistManager(AliasModel aliasModel, BroadcastModel broadcastModel, ChannelModel channelModel, ChannelMapModel channelMapModel) + public PlaylistManager(AliasModel aliasModel, BroadcastModel broadcastModel, ChannelModel channelModel, + ChannelMapModel channelMapModel, UserPreferences userPreferences) { mAliasModel = aliasModel; mBroadcastModel = broadcastModel; mChannelModel = channelModel; mChannelMapModel = channelMapModel; + mUserPreferences = userPreferences; //Register for alias, channel and channel map events so that we can //save the playlist when there are any changes @@ -164,7 +165,7 @@ public class PlaylistManager implements ChannelEventListener * and queues automatic playlist saving. */ @Override - public void channelChanged(ChannelEvent event) + public void receive(ChannelEvent event) { //Only save playlist for changes to standard channels (not traffic) if(event.getChannel().getChannelType() == ChannelType.STANDARD) @@ -180,124 +181,50 @@ public class PlaylistManager implements ChannelEventListener } } - /** - * Folder where playlist, backup and lock file are stored - */ - private Path getPlaylistFolderPath() - { - if(mPlaylistFolderPath == null) - { - SystemProperties props = SystemProperties.getInstance(); - - mPlaylistFolderPath = props.getApplicationFolder("playlist"); - } - - return mPlaylistFolderPath; - } - - /** - * Path to current playlist - */ - private Path getPlaylistPath() - { - if(mPlaylistCurrentPath == null) - { - //Temporarily removed and hard-coding the V2 playlist name. Will be reimplemented with future - //enhancement that allows multiple playlists. -// SystemProperties props = SystemProperties.getInstance(); -// String playlistDefault = props.get("playlist.defaultfilename", "playlist_v2.xml"); -// String playlistCurrent = props.get("playlist.currentfilename", playlistDefault); - - mPlaylistCurrentPath = getPlaylistFolderPath().resolve("playlist_v2.xml"); - } - - return mPlaylistCurrentPath; - } - - /** - * Path to most recent playlist backup - */ - private Path getPlaylistBackupPath() - { - if(mPlaylistBackupPath == null) - { - SystemProperties props = SystemProperties.getInstance(); - - String playlistDefault = props.get("playlist.defaultfilename", "playlist_v2.xml"); - - String playlistCurrent = props.get("playlist.currentfilename", playlistDefault); - - String playlistBackup = playlistCurrent.replace(".xml", ".bak"); - - mPlaylistBackupPath = getPlaylistFolderPath().resolve(playlistBackup); - } - - return mPlaylistBackupPath; - } - - /** - * Path to playlist lock file that is created prior to saving a playlist and removed immediately thereafter. - * Presence of a lock file indicates an incomplete or corrupt playlist file on startup. - */ - private Path getPlaylistLockPath() - { - if(mPlaylistLockPath == null) - { - SystemProperties props = SystemProperties.getInstance(); - - String playlistDefault = props.get("playlist.defaultfilename", "playlist_v2.xml"); - - String playlistCurrent = props.get("playlist.currentfilename", playlistDefault); - - String playlistLock = playlistCurrent.replace(".xml", ".lock"); - - mPlaylistLockPath = getPlaylistFolderPath().resolve(playlistLock); - } - - return mPlaylistLockPath; - } - /** * Saves the current playlist */ private void save() { + FilePreferences files = mUserPreferences.getFilePreferences(); + PlaylistV2 playlist = new PlaylistV2(); playlist.setAliases(mAliasModel.getAliases()); playlist.setBroadcastConfigurations(mBroadcastModel.getBroadcastConfigurations()); playlist.setChannels(mChannelModel.getChannels()); playlist.setChannelMaps(mChannelMapModel.getChannelMaps()); + playlist.setVersion(PLAYLIST_CURRENT_VERSION); //Create a backup copy of the current playlist - if(Files.exists(getPlaylistPath())) + if(Files.exists(files.getPlaylist())) { try { - Files.copy(getPlaylistPath(), getPlaylistBackupPath(), StandardCopyOption.REPLACE_EXISTING); + Files.copy(files.getPlaylist(), files.getPlaylistBackup(), StandardCopyOption.REPLACE_EXISTING); } catch(Exception e) { mLog.error("Error creating backup copy of current playlist prior to saving updates [" + - getPlaylistPath().toString() + "]", e); + files.getPlaylist().toString() + "]", e); } } //Create a temporary lock file to signify that we're in the process of updating the playlist - if(!Files.exists(getPlaylistLockPath())) + if(!Files.exists(files.getPlaylistLock())) { try { - Files.createFile(getPlaylistLockPath()); + Files.createFile(files.getPlaylistLock()); } catch(IOException e) { mLog.error("Error creating temporary lock file prior to saving playlist [" + - getPlaylistLockPath().toString() + "]", e); + files.getPlaylistLock().toString() + "]", e); } } - try(OutputStream out = Files.newOutputStream(getPlaylistPath())) + try(OutputStream out = Files.newOutputStream(files.getPlaylist())) { JacksonXmlModule xmlModule = new JacksonXmlModule(); xmlModule.setDefaultUseWrapper(false); @@ -307,18 +234,20 @@ public class PlaylistManager implements ChannelEventListener out.flush(); //Remove the playlist lock file to indicate that we successfully saved the file - if(Files.exists(getPlaylistLockPath())) + if(Files.exists(files.getPlaylistLock())) { - Files.delete(getPlaylistLockPath()); + Files.delete(files.getPlaylistLock()); } + + mUserPreferences.getFilePreferences().setPlaylistLastAccessedPath(files.getPlaylist()); } catch(IOException ioe) { - mLog.error("IO error while writing the playlist to a file [" + getPlaylistPath().toString() + "]", ioe); + mLog.error("IO error while writing the playlist to a file [" + files.getPlaylist().toString() + "]", ioe); } catch(Exception e) { - mLog.error("Error while saving playlist [" + getPlaylistPath().toString() + "]", e); + mLog.error("Error while saving playlist [" + files.getPlaylist().toString() + "]", e); } } @@ -327,26 +256,28 @@ public class PlaylistManager implements ChannelEventListener */ public PlaylistV2 load() { - mLog.info("Attempting to load version 2 playlist file [" + getPlaylistPath().toString() + "]"); + FilePreferences files = mUserPreferences.getFilePreferences(); PlaylistV2 playlist = null; //Check for a lock file that indicates the previous save attempt was incomplete or had an error - if(Files.exists(getPlaylistLockPath())) + if(Files.exists(files.getPlaylistLock())) { + mLog.info("Previous playlist save was incomplete -- restoring from backup file (if possible)"); + try { //Remove the previous playlist - Files.delete(getPlaylistPath()); + Files.delete(files.getPlaylist()); //Copy the backup file to restore the previous playlist - if(Files.exists(getPlaylistBackupPath())) + if(Files.exists(files.getPlaylistBackup())) { - Files.copy(getPlaylistBackupPath(), getPlaylistPath()); + Files.copy(files.getPlaylistBackup(), files.getPlaylist()); } //Remove the lock file - Files.delete(getPlaylistLockPath()); + Files.delete(files.getPlaylistLock()); } catch(IOException ioe) { @@ -355,13 +286,15 @@ public class PlaylistManager implements ChannelEventListener } } - if(Files.exists(getPlaylistPath())) + if(Files.exists(files.getPlaylist())) { + mLog.info("Loading playlist file [" + files.getPlaylist().toString() + "]"); + JacksonXmlModule xmlModule = new JacksonXmlModule(); xmlModule.setDefaultUseWrapper(false); ObjectMapper objectMapper = new XmlMapper(xmlModule); - try(InputStream in = Files.newInputStream(getPlaylistPath())) + try(InputStream in = Files.newInputStream(files.getPlaylist())) { playlist = objectMapper.readValue(in, PlaylistV2.class); } @@ -370,9 +303,39 @@ public class PlaylistManager implements ChannelEventListener mLog.error("IO error while reading playlist file", ioe); } } + else if(Files.exists(files.getLegacyPlaylist())) + { + mLog.info("Loading legacy playlist file [" + files.getLegacyPlaylist().toString() + "]"); + + JacksonXmlModule xmlModule = new JacksonXmlModule(); + xmlModule.setDefaultUseWrapper(false); + ObjectMapper objectMapper = new XmlMapper(xmlModule); + + try(InputStream in = Files.newInputStream(files.getLegacyPlaylist())) + { + playlist = objectMapper.readValue(in, PlaylistV2.class); + + //Perform any updates that may be needed for the playist. + if(PlaylistUpdater.update(playlist)) + { + mLog.info("Legacy playlist was updated to version [" + PLAYLIST_CURRENT_VERSION + "] - saving"); + schedulePlaylistSave(); + } + } + catch(IOException ioe) + { + mLog.error("IO error while reading playlist file", ioe); + } + } else { - mLog.info("PlaylistManager - playlist not found at [" + getPlaylistPath().toString() + "]"); + mLog.info("PlaylistManager - playlist not found at [" + files.getPlaylist().toString() + "] - creating new (empty) playlist"); + } + + if(playlist == null) + { + playlist = new PlaylistV2(); + schedulePlaylistSave(); } return playlist; diff --git a/src/main/java/io/github/dsheirer/playlist/PlaylistUpdater.java b/src/main/java/io/github/dsheirer/playlist/PlaylistUpdater.java new file mode 100644 index 00000000..0b907961 --- /dev/null +++ b/src/main/java/io/github/dsheirer/playlist/PlaylistUpdater.java @@ -0,0 +1,452 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.playlist; + +import io.github.dsheirer.alias.Alias; +import io.github.dsheirer.alias.id.AliasID; +import io.github.dsheirer.alias.id.AliasIDType; +import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID; +import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID; +import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327ID; +import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID; +import io.github.dsheirer.alias.id.mobileID.Min; +import io.github.dsheirer.alias.id.talkgroup.Talkgroup; +import io.github.dsheirer.controller.channel.Channel; +import io.github.dsheirer.module.log.EventLogType; +import io.github.dsheirer.module.log.config.EventLogConfiguration; +import io.github.dsheirer.protocol.Protocol; +import io.github.dsheirer.record.RecorderType; +import io.github.dsheirer.record.config.RecordConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Playlist updater will automatically update a playlist from version 1 up to the current version specified by the + * Playlist manager + */ +public class PlaylistUpdater +{ + private final static Logger mLog = LoggerFactory.getLogger(PlaylistUpdater.class); + private static final String APCO25_RADIO_ID = "[A-Fa-f\\d\\*]{6}"; + private static final String APCO25_TALKGROUP = "[A-Fa-f\\d\\*]{4}"; + private static final String FLEETSYNC_TALKGROUP = "(\\d{3})-(\\d{4})"; + private static final String LTR_TALKGROUP = "([\\d\\*]{1})-([\\d\\*]{2})-([\\d\\*]{3})"; + private static final String LTR_TALKGROUP_WILDCARD = "\\*-\\*{2}-([\\d]{3})"; + private static final String MDC1200_TALKGROUP = "[A-Fa-f\\d\\*]{4}"; + private static final String MOBILE_ID_NUMBER = "[A-Fa-f\\d\\*]{6}"; + private static final String MPT1327_TALKGROUP = "(\\d{3})-(\\d{4})"; + private static final String PASSPORT_TALKGROUP = "[\\d\\*]{5}"; + private static final Pattern FLEETSYNC_PATTERN = Pattern.compile(FLEETSYNC_TALKGROUP); + private static final Pattern LTR_PATTERN = Pattern.compile(LTR_TALKGROUP); + private static final Pattern MPT1327_PATTERN = Pattern.compile(MPT1327_TALKGROUP); + + /** + * Updates the playlist as necessary. + * + * @param playlist to update + * @return true if the playlist was updated + */ + public static boolean update(PlaylistV2 playlist) + { + boolean updated = false; + + switch(playlist.getVersion()) + { + case 1: + mLog.info("Updating playlist from version [1] to version [" + PlaylistManager.PLAYLIST_CURRENT_VERSION + "]"); + removeVersion1AudioRecordType(playlist); + removeVersion1BinaryMessageLogger(playlist); + removeVersion1NonRecordableAliasIdentifiers(playlist); + updateVersion1Talkgroups(playlist); + updated = true; + break; + } + + + return updated; + } + + /** + * Removes version 1 AUDIO record type from the record configuration + */ + private static void removeVersion1AudioRecordType(PlaylistV2 playlist) + { + int removed = 0; + + for(Channel channel : playlist.getChannels()) + { + RecordConfiguration recordConfiguration = channel.getRecordConfiguration(); + + if(recordConfiguration.contains(RecorderType.AUDIO)) + { + Iterator it = recordConfiguration.getRecorders().iterator(); + + while(it.hasNext()) + { + if(it.next() == RecorderType.AUDIO) + { + it.remove(); + removed++; + } + } + } + } + + if(removed > 0) + { + mLog.info("Removed audio-record setting from [" + removed + "] channels. Audio recording is now a setting for each individual alias"); + } + } + + /** + * Removes version 1 BINARY message event logger + */ + private static void removeVersion1BinaryMessageLogger(PlaylistV2 playlist) + { + int removed = 0; + + for(Channel channel : playlist.getChannels()) + { + EventLogConfiguration eventLogConfiguration = channel.getEventLogConfiguration(); + + if(eventLogConfiguration.getLoggers().contains(EventLogType.BINARY_MESSAGE)) + { + Iterator it = eventLogConfiguration.getLoggers().iterator(); + + while(it.hasNext()) + { + if(it.next() == EventLogType.BINARY_MESSAGE) + { + it.remove(); + removed++; + } + } + } + } + + if(removed > 0) + { + mLog.info("Removed binary message event logging from [" + removed + + "] channels. Use demodulated bitstream recorder instead"); + } + } + + /** + * Removes non-recordable alias identifiers from all aliases. These have been replaced with the 'Record' alias + * identifier. + */ + private static void removeVersion1NonRecordableAliasIdentifiers(PlaylistV2 playlist) + { + int removed = 0; + + for(Alias alias : playlist.getAliases()) + { + Iterator it = alias.getId().iterator(); + + while(it.hasNext()) + { + if(it.next().getType() == AliasIDType.NON_RECORDABLE) + { + it.remove(); + removed++; + } + } + } + + if(removed > 0) + { + mLog.info("Removed [" + removed + "] non-recordable alias identifiers from aliases"); + } + } + + /** + * Converts legacy version 1talkgroups to the new talkgroup format. Previously, each protocol had a talkgroup + * flavor and now there is a generic talkgroup identifier with a protocol specifier. + * + * @param playlist to update + */ + private static void updateVersion1Talkgroups(PlaylistV2 playlist) + { + int updated = 0; + int notUpdated = 0; + + AliasID next = null; + List toAdd = new ArrayList<>(); + + for(Alias alias : playlist.getAliases()) + { + Iterator it = alias.getId().iterator(); + + while(it.hasNext()) + { + next = it.next(); + + switch(next.getType()) + { + case LEGACY_TALKGROUP: + LegacyTalkgroupID legacyTalkgroupId = (LegacyTalkgroupID)next; + + String talkgroup = legacyTalkgroupId.getTalkgroup(); + + if(talkgroup != null) + { + if(talkgroup.matches(APCO25_TALKGROUP) || talkgroup.matches(APCO25_RADIO_ID)) + { + if(talkgroup.contains("*")) + { + notUpdated++; + } + else + { + try + { + Integer value = Integer.parseInt(talkgroup, 16); + it.remove(); + toAdd.add(new Talkgroup(Protocol.APCO25, value)); + updated++; + } + catch(Exception e) + { + //Unable to parse integer value from talkgroup .. do nothing + notUpdated++; + } + } + } + else if(talkgroup.matches(LTR_TALKGROUP)) + { + if(talkgroup.contains("*")) + { + if(talkgroup.matches(LTR_TALKGROUP_WILDCARD)) + { + try + { + Integer group = Integer.parseInt(talkgroup.substring(5)); + + for(int area = 0; area <= 1; area++) + { + for(int channel = 1; channel <= 20; channel++) + { + int value = (area << 13) + (channel << 8) + group; + toAdd.add(new Talkgroup(Protocol.LTR, value)); + mLog.debug("LTR Talkgroup [" + talkgroup + "] updated to [" + value + "]"); + } + } + it.remove(); + updated++; + } + catch(Exception e) + { + notUpdated++; + } + } + else + { + notUpdated++; + } + } + else + { + try + { + Matcher m = LTR_PATTERN.matcher(talkgroup); + + if(m.matches()) + { + int value = Integer.valueOf(m.group(3)); + value += (Integer.valueOf(m.group(2)) << 8); + value += (Integer.valueOf(m.group(1)) << 13); + + toAdd.add(new Talkgroup(Protocol.LTR, value)); + it.remove(); + updated++; + } + } + catch(Exception e) + { + notUpdated++; + } + } + } + else if(talkgroup.matches(PASSPORT_TALKGROUP)) + { + if(talkgroup.contains("*")) + { + notUpdated++; + } + else + { + try + { + Integer value = Integer.parseInt(talkgroup); + it.remove(); + toAdd.add(new Talkgroup(Protocol.PASSPORT, value)); + updated++; + } + catch(Exception e) + { + //Unable to parse integer value from talkgroup .. do nothing + notUpdated++; + } + } + } + } + else + { + //Throw away any legacy talkgroup identifiers with a null value + it.remove(); + } + break; + case FLEETSYNC: + String fleetsync = ((FleetsyncID)next).getIdent(); + + try + { + Matcher matcher = FLEETSYNC_PATTERN.matcher(fleetsync); + + if(matcher.matches()) + { + int value = Integer.valueOf(matcher.group(2)); + value += (Integer.valueOf(matcher.group(1)) << 12); + + toAdd.add(new Talkgroup(Protocol.FLEETSYNC, value)); + it.remove(); + updated++; + } + else + { + notUpdated++; + } + } + catch(Exception e) + { + notUpdated++; + } + break; + case MDC1200: + String mdc = ((MDC1200ID)next).getIdent(); + + if(mdc != null && mdc.matches(MDC1200_TALKGROUP)) + { + try + { + int mdcValue = Integer.parseInt(mdc, 16); + toAdd.add(new Talkgroup(Protocol.MDC1200, mdcValue)); + it.remove(); + updated++; + } + catch(Exception e) + { + notUpdated++; + } + } + else + { + notUpdated++; + } + + break; + case MPT1327: + String mpt = ((MPT1327ID)next).getIdent(); + + if(mpt != null && mpt.matches(MPT1327_TALKGROUP)) + { + try + { + Matcher matcher = MPT1327_PATTERN.matcher(mpt); + + if(matcher.matches()) + { + int value = Integer.valueOf(matcher.group(2)); + value += (Integer.valueOf(matcher.group(1)) << 13); + + toAdd.add(new Talkgroup(Protocol.MPT1327, value)); + it.remove(); + updated++; + + mLog.debug("MPT-1327 Talkgroup [" + mpt + "] updated to [" + value + "]"); + } + else + { + notUpdated++; + } + } + catch(Exception e) + { + notUpdated++; + } + } + break; + case MIN: + String min = ((Min)next).getMin(); + + if(min.matches(MOBILE_ID_NUMBER)) + { + if(min.contains("*")) + { + notUpdated++; + } + else + { + try + { + Integer value = Integer.parseInt(min, 16); + it.remove(); + toAdd.add(new Talkgroup(Protocol.PASSPORT, value)); + updated++; + } + catch(Exception e) + { + //Unable to parse integer value from talkgroup .. do nothing + notUpdated++; + } + } + } + break; + } + } + + //Add any alias identifiers that were converted. We have to do this here in order + //to avoid concurrent modification errors on the alias id list. + for(AliasID aliasID : toAdd) + { + alias.addAliasID(aliasID); + } + + toAdd.clear(); + } + + if(updated > 0) + { + mLog.info("Updated [" + updated + "] legacy talkgroup identifiers to new talkgroup format"); + } + + if(notUpdated > 0) + { + mLog.info("Unable to update [" + notUpdated + "] legacy talkgroup identifiers - please edit and convert to new talkgroup format"); + } + } +} diff --git a/src/main/java/io/github/dsheirer/playlist/PlaylistV2.java b/src/main/java/io/github/dsheirer/playlist/PlaylistV2.java index f132ca3b..1091d59d 100644 --- a/src/main/java/io/github/dsheirer/playlist/PlaylistV2.java +++ b/src/main/java/io/github/dsheirer/playlist/PlaylistV2.java @@ -30,6 +30,7 @@ import java.util.List; @JacksonXmlRootElement(localName = "playlist") public class PlaylistV2 { + private int mVersion = 1; private List mAliases = new ArrayList<>(); private List mBroadcastConfigurations = new ArrayList<>(); private List mChannels = new ArrayList<>(); @@ -39,6 +40,23 @@ public class PlaylistV2 { } + /** + * Version number for this playlist + */ + @JacksonXmlProperty(isAttribute = true, localName = "version") + public int getVersion() + { + return mVersion; + } + + /** + * Sets the version for this playlist. + */ + public void setVersion(int version) + { + mVersion = version; + } + @JacksonXmlProperty(isAttribute = false, localName = "alias") public List getAliases() { diff --git a/src/main/java/io/github/dsheirer/preference/Preference.java b/src/main/java/io/github/dsheirer/preference/Preference.java new file mode 100644 index 00000000..b6003d24 --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/Preference.java @@ -0,0 +1,52 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.preference; + +import io.github.dsheirer.sample.Listener; + +/** + * Preference base class with support for broadcasting preference changes + */ +public abstract class Preference +{ + private Listener mPreferenceUpdateListener; + + public Preference(Listener updateListener) + { + mPreferenceUpdateListener = updateListener; + } + + /** + * Indicates which type of preference for this class + */ + public abstract PreferenceType getPreferenceType(); + + /** + * Notifies any registered listeners that a preference has been updated + */ + public void notifyPreferenceUpdated() + { + if(mPreferenceUpdateListener != null) + { + mPreferenceUpdateListener.receive(getPreferenceType()); + } + } +} diff --git a/src/main/java/io/github/dsheirer/preference/PreferenceType.java b/src/main/java/io/github/dsheirer/preference/PreferenceType.java new file mode 100644 index 00000000..7a92c50e --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/PreferenceType.java @@ -0,0 +1,31 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.preference; + +/** + * Types of preferences + */ +public enum PreferenceType +{ + DECODE_EVENT, + IDENTIFIER, + FILES; +} diff --git a/src/main/java/io/github/dsheirer/preference/TimestampFormat.java b/src/main/java/io/github/dsheirer/preference/TimestampFormat.java new file mode 100644 index 00000000..5950594b --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/TimestampFormat.java @@ -0,0 +1,81 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.preference; + +import java.text.SimpleDateFormat; + +/** + * Timestamp formats + */ +public enum TimestampFormat +{ + TIMESTAMP_DEFAULT("yyyy-MM-dd HH:mm:ss", "Timestamp: yyyy-MM-dd HH:mm:ss"), + TIMESTAMP_COLONS("yyyy:MM:dd:HH:mm:ss", "Timestamp Colons: yyyy:MM:dd:HH:mm:ss"), + TIMESTAMP_MILLIS("yyyy:MM:dd:HH:mm:ss.SSS", "Timestamp Milliseconds: yyyy:MM:dd:HH:mm:ss.SSS"), + TIMESTAMP_COMPACT("yyyyMMddHHmmss", "Timestamp Compact: yyyyMMddHHmmss"), + DATE_DASHES("yyyy-MM-dd", "Date Dashes: YYYY-MM-DD"), + DATE_COLONS("yyyy:MM:dd", "Date Colons: YYYY:MM:DD"), + DATE_COMPACT("yyyyMMdd", "Date Compact: YYYYMMDD"), + TIME_COMPACT("HHmmss", "Time Compact: HHmmss"), + TIME_COLONS("HH:mm:ss", "Time Colons: HH:mm:ss"), + TIME_MILLIS("HH:mm:ss.SSS", "Time Milliseconds: HH:mm:ss.SSS"), + TIME_SECONDS("ss.SSS", "Time Seconds: ss.SSS"), + ; + + private String mFormat; + private String mLabel; + + /** + * Constructs a timestamp format + * @param format for the timestamp + * @param label to describe the format + */ + TimestampFormat(String format, String label) + { + mFormat = format; + mLabel = label; + } + + /** + * Format string for the timestamp format + */ + public String getFormat() + { + return mFormat; + } + + /** + * Creates a new Simple Date Formatter to use format string. + */ + public SimpleDateFormat getFormatter() + { + return new SimpleDateFormat(mFormat); + } + + /** + * Overrides the toString() with a descriptive label + */ + @Override + public String toString() + { + return mLabel; + } +} diff --git a/src/main/java/io/github/dsheirer/preference/UserPreferences.java b/src/main/java/io/github/dsheirer/preference/UserPreferences.java new file mode 100644 index 00000000..370d58f4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/UserPreferences.java @@ -0,0 +1,91 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.preference; + +import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.preference.event.DecodeEventPreference; +import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference; +import io.github.dsheirer.preference.playlist.FilePreferences; +import io.github.dsheirer.sample.Listener; + +/** + * User Preferences. A collection of preferences that can be accessed by preference type. + */ +public class UserPreferences implements Listener +{ + private DecodeEventPreference mDecodeEventPreference; + private FilePreferences mFilePreferences; + private TalkgroupFormatPreference mTalkgroupFormatPreference; + + /** + * Constructs a new user preferences instance + */ + public UserPreferences() + { + loadPreferenceTypes(); + } + + /** + * Decode Event preferences + */ + public DecodeEventPreference getDecodeEventPreference() + { + return mDecodeEventPreference; + } + + /** + * Playlist preferences + */ + public FilePreferences getFilePreferences() + { + return mFilePreferences; + } + + /** + * Identifier preferences + */ + public TalkgroupFormatPreference getTalkgroupFormatPreference() + { + return mTalkgroupFormatPreference; + } + + /** + * Loads the managed preferences + */ + private void loadPreferenceTypes() + { + mDecodeEventPreference = new DecodeEventPreference(this); + mFilePreferences = new FilePreferences(this::receive); + mTalkgroupFormatPreference = new TalkgroupFormatPreference(this); + } + + /** + * Primary method for receiving notification from a managed preference that a preference type + * has been changed/updated. + * + * @param preferenceType that is updated + */ + @Override + public void receive(PreferenceType preferenceType) + { + MyEventBus.getEventBus().post(preferenceType); + } +} diff --git a/src/main/java/io/github/dsheirer/preference/event/DecodeEventPreference.java b/src/main/java/io/github/dsheirer/preference/event/DecodeEventPreference.java new file mode 100644 index 00000000..0b078619 --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/event/DecodeEventPreference.java @@ -0,0 +1,90 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.preference.event; + +import io.github.dsheirer.preference.Preference; +import io.github.dsheirer.preference.PreferenceType; +import io.github.dsheirer.preference.TimestampFormat; +import io.github.dsheirer.sample.Listener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.prefs.Preferences; + +/** + * User preferences for the display of channel decode events + */ +public class DecodeEventPreference extends Preference +{ + private final static Logger mLog = LoggerFactory.getLogger(DecodeEventPreference.class); + private Preferences mPreferences = Preferences.userNodeForPackage(DecodeEventPreference.class); + private TimestampFormat mTimestampFormat = TimestampFormat.TIMESTAMP_DEFAULT; + private static final String TIMESTAMP_FORMAT_KEY = "timestamp.format"; + + public DecodeEventPreference(Listener updateListener) + { + super(updateListener); + loadSettings(); + } + + @Override + public PreferenceType getPreferenceType() + { + return PreferenceType.DECODE_EVENT; + } + + private void loadSettings() + { + String format = mPreferences.get(TIMESTAMP_FORMAT_KEY, TimestampFormat.TIMESTAMP_COLONS.name()); + + if(format != null && !format.isEmpty()) + { + try + { + mTimestampFormat = TimestampFormat.valueOf(format); + } + catch(Exception e) + { + mLog.error("Error loading decode event timestamp format [" + format + "]"); + } + } + } + + /** + * Timestamp format to use for decode event timestamps + */ + public TimestampFormat getTimestampFormat() + { + return mTimestampFormat; + } + + /** + * Updates the timestamp format preference + * + * @param timestampFormat + */ + public void setTimestampFormat(TimestampFormat timestampFormat) + { + mTimestampFormat = timestampFormat; + mPreferences.put(TIMESTAMP_FORMAT_KEY, mTimestampFormat.name()); + notifyPreferenceUpdated(); + } +} diff --git a/src/main/java/io/github/dsheirer/preference/identifier/IntegerFormat.java b/src/main/java/io/github/dsheirer/preference/identifier/IntegerFormat.java new file mode 100644 index 00000000..bdcf1b9d --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/identifier/IntegerFormat.java @@ -0,0 +1,43 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.preference.identifier; + +/** + * Options for formatting of integer values. + */ +public enum IntegerFormat +{ + DECIMAL("Decimal"), + FORMATTED("Formatted"), + HEXADECIMAL("Hexadecimal"); + + private String mLabel; + + IntegerFormat(String label) + { + mLabel = label; + } + + public String toString() + { + return mLabel; + } +} diff --git a/src/main/java/io/github/dsheirer/preference/identifier/TalkgroupFormatPreference.java b/src/main/java/io/github/dsheirer/preference/identifier/TalkgroupFormatPreference.java new file mode 100644 index 00000000..fef1e13d --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/identifier/TalkgroupFormatPreference.java @@ -0,0 +1,335 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.preference.identifier; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.preference.Preference; +import io.github.dsheirer.preference.PreferenceType; +import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter; +import io.github.dsheirer.preference.identifier.talkgroup.FleetsyncTalkgroupFormatter; +import io.github.dsheirer.preference.identifier.talkgroup.LtrNetTalkgroupFormatter; +import io.github.dsheirer.preference.identifier.talkgroup.MPT1327TalkgroupFormatter; +import io.github.dsheirer.protocol.Protocol; +import io.github.dsheirer.sample.Listener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.prefs.Preferences; + +/** + * Specifies and applies user preferences for formatting identifiers + */ +public class TalkgroupFormatPreference extends Preference +{ + private final static Logger mLog = LoggerFactory.getLogger(TalkgroupFormatPreference.class); + private Preferences mPreferences = Preferences.userNodeForPackage(TalkgroupFormatPreference.class); + + public static final String TALKGROUP_FORMAT_PROPERTY = "talkgroup.format."; + public static final String TALKGROUP_FIXED_WIDTH_PROPERTY = "talkgroup.fixed.width."; + + private Map mTalkgroupFormatProtocolMap = new HashMap<>(); + private Map mTalkgroupFixedWidthProtocolMap = new HashMap<>(); + + /** + * Constructs an instance of identifier formatting preference. + * + * @param updateListener to be notified when this preference is updated + */ + public TalkgroupFormatPreference(Listener updateListener) + { + super(updateListener); + loadProperties(); + } + + /** + * Formats an identifier + * + * @param identifier to format + * @return formatted string representing the identifier or null if the identifier is null + */ + public String format(Identifier identifier) + { + if(identifier == null) + { + return null; + } + + switch(identifier.getForm()) + { + case TALKGROUP: + if(identifier instanceof TalkgroupIdentifier) + { + return formatTalkgroupIdentifier((TalkgroupIdentifier)identifier); + } + break; + case PATCH_GROUP: + if(identifier instanceof PatchGroupIdentifier) + { + return formatPatchGroupIdentifier((PatchGroupIdentifier)identifier); + } + break; + } + + return identifier.toString(); + } + + @Override + public PreferenceType getPreferenceType() + { + return PreferenceType.IDENTIFIER; + } + + /** + * Loads persisted properties on startup + */ + private void loadProperties() + { + for(Protocol protocol : Protocol.TALKGROUP_PROTOCOLS) + { + mTalkgroupFixedWidthProtocolMap.put(protocol, mPreferences.getBoolean(getTalkgroupFixedWidthProperty(protocol), + getDefaultFixedWidth(protocol))); + + try + { + String format = mPreferences.get(getTalkgroupFormatProperty(protocol), getDefaultFormat(protocol).name()); + mTalkgroupFormatProtocolMap.put(protocol, IntegerFormat.valueOf(format)); + } + catch(Exception e) + { + //If there is a problem parsing the enum entry, default to DECIMAL + mTalkgroupFormatProtocolMap.put(protocol, getDefaultFormat(protocol)); + } + } + } + + /** + * Default format for each protocol + */ + private IntegerFormat getDefaultFormat(Protocol protocol) + { + switch(protocol) + { + case APCO25: + return IntegerFormat.DECIMAL; + case FLEETSYNC: + return IntegerFormat.FORMATTED; + case IPV4: + return IntegerFormat.DECIMAL; + case LOJACK: + return IntegerFormat.DECIMAL; + case LTR: + return IntegerFormat.FORMATTED; + case LTR_NET: + return IntegerFormat.FORMATTED; + case LTR_STANDARD: + return IntegerFormat.FORMATTED; + case MDC1200: + return IntegerFormat.DECIMAL; + case MPT1327: + return IntegerFormat.FORMATTED; + case PASSPORT: + return IntegerFormat.DECIMAL; + case TAIT1200: + return IntegerFormat.DECIMAL; + case UNKNOWN: + default: + return IntegerFormat.DECIMAL; + } + } + + private boolean getDefaultFixedWidth(Protocol protocol) + { + switch(protocol) + { + case APCO25: + return true; + case FLEETSYNC: + return true; + case IPV4: + return true; + case LOJACK: + return true; + case LTR: + return true; + case LTR_NET: + return true; + case LTR_STANDARD: + return true; + case MDC1200: + return false; + case MPT1327: + return true; + case PASSPORT: + return false; + case TAIT1200: + return false; + case UNKNOWN: + default: + return true; + } + } + + /** + * Property identifier for talkgroup prepad for a specific protocol + */ + private String getTalkgroupFixedWidthProperty(Protocol protocol) + { + return TALKGROUP_FIXED_WIDTH_PROPERTY + protocol.name(); + } + + /** + * Property identifier for talkgroup format for a specific protocol + */ + private String getTalkgroupFormatProperty(Protocol protocol) + { + return TALKGROUP_FORMAT_PROPERTY + protocol.name(); + } + + /** + * User preference for formatting talkgroups for the specified protocol + * + * @param protocol specified + * @return format + */ + public IntegerFormat getTalkgroupFormat(Protocol protocol) + { + if(mTalkgroupFormatProtocolMap.containsKey(protocol)) + { + return mTalkgroupFormatProtocolMap.get(protocol); + } + + return IntegerFormat.DECIMAL; + } + + /** + * Sets the formatting for talkgroup identifiers by protocol + * + * @param protocol for the talkgroup + * @param talkgroupFormat + */ + public void setTalkgroupFormat(Protocol protocol, IntegerFormat talkgroupFormat) + { + IntegerFormat existing = mTalkgroupFormatProtocolMap.get(protocol); + + if(existing == null || existing != talkgroupFormat) + { + mTalkgroupFormatProtocolMap.put(protocol, talkgroupFormat); + mPreferences.put(getTalkgroupFormatProperty(protocol), talkgroupFormat.name()); + notifyPreferenceUpdated(); + } + } + + /** + * Sets the fixed-width for talkgroup identifiers by protocol + * + * @param protocol for the talkgroup + * @param fixedWidth + */ + public void setTalkgroupFormat(Protocol protocol, boolean fixedWidth) + { + mTalkgroupFixedWidthProtocolMap.put(protocol, fixedWidth); + mPreferences.putBoolean(getTalkgroupFixedWidthProperty(protocol), fixedWidth); + notifyPreferenceUpdated(); + } + + /** + * Indicates if talkgroups for the specified protocol should be prepadded to a fixed length using '0' padding characters + * + * @param protocol to prepad + * @return true if the talkgroups should be fixed length/prepadded. + */ + public boolean isTalkgroupFixedWidth(Protocol protocol) + { + Boolean fixedWidth = mTalkgroupFixedWidthProtocolMap.get(protocol); + + if(fixedWidth == null) + { + fixedWidth = false; + } + + return fixedWidth; + } + + /** + * Sets the talkgroup for the specified protocol to the fixed width argument. + * + * @param protocol to set + * @param fixedWidth true for prepadded (0) fixed width values or false for no prepadding. + */ + public void setTalkgroupFixedWidth(Protocol protocol, boolean fixedWidth) + { + if(isTalkgroupFixedWidth(protocol) != fixedWidth) + { + mTalkgroupFixedWidthProtocolMap.put(protocol, fixedWidth); + mPreferences.putBoolean(getTalkgroupFixedWidthProperty(protocol), fixedWidth); + notifyPreferenceUpdated(); + } + } + + /** + * Formats the identifier according to user specified preferences for number format and length. + * + * @param talkgroupIdentifier to format + * @return formatted talkgroups + */ + private String formatTalkgroupIdentifier(TalkgroupIdentifier talkgroupIdentifier) + { + switch(talkgroupIdentifier.getProtocol()) + { + case APCO25: + return APCO25TalkgroupFormatter.format(talkgroupIdentifier, getTalkgroupFormat(Protocol.APCO25), + isTalkgroupFixedWidth(Protocol.APCO25)); + case FLEETSYNC: + return FleetsyncTalkgroupFormatter.format(talkgroupIdentifier, getTalkgroupFormat(Protocol.FLEETSYNC), + isTalkgroupFixedWidth(Protocol.FLEETSYNC)); + case LTR_NET: + return LtrNetTalkgroupFormatter.format(talkgroupIdentifier, getTalkgroupFormat(Protocol.LTR_NET), + isTalkgroupFixedWidth(Protocol.LTR_NET)); + case MPT1327: + return MPT1327TalkgroupFormatter.format(talkgroupIdentifier, getTalkgroupFormat(Protocol.MPT1327), + isTalkgroupFixedWidth(Protocol.MPT1327)); + default: + return talkgroupIdentifier.toString(); + } + } + + /** + * Formats the identifier according to user specified preferences for number format and length. + * + * @param patchGroupIdentifier to format + * @return formatted talkgroups + */ + private String formatPatchGroupIdentifier(PatchGroupIdentifier patchGroupIdentifier) + { + switch(patchGroupIdentifier.getProtocol()) + { + case APCO25: + return APCO25TalkgroupFormatter.format(patchGroupIdentifier, getTalkgroupFormat(Protocol.APCO25), + isTalkgroupFixedWidth(Protocol.APCO25)); + default: + return patchGroupIdentifier.toString(); + } + } +} diff --git a/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/APCO25TalkgroupFormatter.java b/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/APCO25TalkgroupFormatter.java new file mode 100644 index 00000000..ab55c368 --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/APCO25TalkgroupFormatter.java @@ -0,0 +1,98 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.preference.identifier.talkgroup; + +import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.preference.identifier.IntegerFormat; + +public class APCO25TalkgroupFormatter extends IntegerFormatter +{ + public static final int GROUP_DECIMAL_WIDTH = 5; + public static final int UNIT_DECIMAL_WIDTH = 8; + public static final int GROUP_HEXADECIMAL_WIDTH = 4; + public static final int UNIT_HEXADECIMAL_WIDTH = 6; + + /** + * Formats the individual or group identifier to the specified format and width. + */ + public static String format(TalkgroupIdentifier identifier, IntegerFormat format, boolean fixedWidth) + { + if(fixedWidth) + { + switch(format) + { + case DECIMAL: + return toDecimal(identifier.getValue(), + (identifier.isGroup() ? GROUP_DECIMAL_WIDTH : UNIT_DECIMAL_WIDTH)); + case HEXADECIMAL: + return toHex(identifier.getValue(), + (identifier.isGroup() ? GROUP_HEXADECIMAL_WIDTH : UNIT_HEXADECIMAL_WIDTH)); + default: + throw new IllegalArgumentException("Unrecognized integer format: " + format); + } + } + else + { + switch(format) + { + case DECIMAL: + case FORMATTED: + return identifier.getValue().toString(); + case HEXADECIMAL: + return toHex(identifier.getValue()); + default: + throw new IllegalArgumentException("Unrecognized integer format: " + format); + } + } + } + + /** + * Formats the patch group to the specified format and width + */ + public static String format(PatchGroupIdentifier identifier, IntegerFormat format, boolean fixedWidth) + { + PatchGroup patchGroup = identifier.getValue(); + + if(patchGroup != null) + { + StringBuilder sb = new StringBuilder(); + sb.append("P:"); + sb.append(format(patchGroup.getPatchGroup(), format, fixedWidth)); + sb.append("["); + + int counter = 0; + for(TalkgroupIdentifier patchedGroup: patchGroup.getPatchedGroupIdentifiers()) + { + sb.append(format(patchedGroup, format, fixedWidth)); + if(counter++ < patchGroup.getPatchedGroupIdentifiers().size() - 1) + { + sb.append(","); + } + } + sb.append("]"); + return sb.toString(); + } + + return identifier.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/FleetsyncTalkgroupFormatter.java b/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/FleetsyncTalkgroupFormatter.java new file mode 100644 index 00000000..86fdaef9 --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/FleetsyncTalkgroupFormatter.java @@ -0,0 +1,80 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.preference.identifier.talkgroup; + +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.fleetsync2.identifier.FleetsyncIdentifier; +import io.github.dsheirer.preference.identifier.IntegerFormat; + +/** + * Formats Fleetsync 20-bit identifiers into either an integer value or an 8-bit fleet and 12-bit ident + */ +public class FleetsyncTalkgroupFormatter extends IntegerFormatter +{ + private static final int IDENTIFIER_DECIMAL_WIDTH = 7; + private static final int IDENTIFIER_HEXADECIMAL_WIDTH = 5; + private static final int FLEET_DECIMAL_WIDTH = 3; + private static final int IDENT_DECIMAL_WIDTH = 4; + private static final String SEPARATOR = "-"; + + /** + * Formats the individual or group identifier to the specified format and width. + */ + public static String format(TalkgroupIdentifier identifier, IntegerFormat format, boolean fixedWidth) + { + if(identifier instanceof FleetsyncIdentifier) + { + FleetsyncIdentifier fleetsync = (FleetsyncIdentifier)identifier; + + if(fixedWidth) + { + switch(format) + { + case DECIMAL: + return toDecimal(fleetsync.getValue(), IDENTIFIER_DECIMAL_WIDTH); + case FORMATTED: + return toDecimal(fleetsync.getFleet(), FLEET_DECIMAL_WIDTH) + SEPARATOR + + toDecimal(fleetsync.getIdent(), IDENT_DECIMAL_WIDTH); + case HEXADECIMAL: + return toHex(fleetsync.getValue(), IDENTIFIER_HEXADECIMAL_WIDTH); + default: + throw new IllegalArgumentException("Unrecognized integer format: " + format); + } + } + else + { + switch(format) + { + case DECIMAL: + return fleetsync.toString(); + case FORMATTED: + return fleetsync.getFleet() + SEPARATOR + fleetsync.getIdent(); + case HEXADECIMAL: + return toHex(identifier.getValue()); + default: + throw new IllegalArgumentException("Unrecognized integer format: " + format); + } + } + } + + return identifier.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/IntegerFormatter.java b/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/IntegerFormatter.java new file mode 100644 index 00000000..2b5a71ac --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/IntegerFormatter.java @@ -0,0 +1,47 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.preference.identifier.talkgroup; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Base formatter class for integer values + */ +public class IntegerFormatter +{ + private final static Logger mLog = LoggerFactory.getLogger(IntegerFormatter.class); + + public static String toHex(int value) + { + return Integer.toHexString(value).toUpperCase(); + } + + public static String toHex(int value, int width) + { + return String.format("%0" + width + "X", value); + } + + public static String toDecimal(int value, int width) + { + return String.format("%0" + width + "d", value); + } +} diff --git a/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/LtrNetTalkgroupFormatter.java b/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/LtrNetTalkgroupFormatter.java new file mode 100644 index 00000000..f223049f --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/LtrNetTalkgroupFormatter.java @@ -0,0 +1,80 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.preference.identifier.talkgroup; + +import io.github.dsheirer.identifier.talkgroup.LTRTalkgroup; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.preference.identifier.IntegerFormat; + +/** + * Formats Fleetsync 20-bit identifiers into either an integer value or an 8-bit fleet and 12-bit ident + */ +public class LtrNetTalkgroupFormatter extends IntegerFormatter +{ + private static final int IDENTIFIER_DECIMAL_WIDTH = 4; + private static final int IDENTIFIER_HEXADECIMAL_WIDTH = 4; + private static final int HOME_REPEATER_DECIMAL_WIDTH = 2; + private static final int TALKGROUP_DECIMAL_WIDTH = 3; + private static final String SEPARATOR = "-"; + + /** + * Formats the individual or group identifier to the specified format and width. + */ + public static String format(TalkgroupIdentifier identifier, IntegerFormat format, boolean fixedWidth) + { + if(identifier instanceof LTRTalkgroup) + { + LTRTalkgroup ltrnet = (LTRTalkgroup)identifier; + + if(fixedWidth) + { + switch(format) + { + case DECIMAL: + return toDecimal(ltrnet.getValue(), IDENTIFIER_DECIMAL_WIDTH); + case FORMATTED: + return toDecimal(ltrnet.getHomeChannel(), HOME_REPEATER_DECIMAL_WIDTH) + SEPARATOR + + toDecimal(ltrnet.getTalkgroup(), TALKGROUP_DECIMAL_WIDTH); + case HEXADECIMAL: + return toHex(ltrnet.getValue(), IDENTIFIER_HEXADECIMAL_WIDTH); + default: + throw new IllegalArgumentException("Unrecognized integer format: " + format); + } + } + else + { + switch(format) + { + case DECIMAL: + return ltrnet.toString(); + case FORMATTED: + return ltrnet.getHomeChannel() + SEPARATOR + ltrnet.getTalkgroup(); + case HEXADECIMAL: + return toHex(identifier.getValue()); + default: + throw new IllegalArgumentException("Unrecognized integer format: " + format); + } + } + } + + return identifier.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/MPT1327TalkgroupFormatter.java b/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/MPT1327TalkgroupFormatter.java new file mode 100644 index 00000000..6723123e --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/MPT1327TalkgroupFormatter.java @@ -0,0 +1,80 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.preference.identifier.talkgroup; + +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.mpt1327.identifier.MPT1327Talkgroup; +import io.github.dsheirer.preference.identifier.IntegerFormat; + +/** + * Formats Fleetsync 20-bit identifiers into either an integer value or an 8-bit fleet and 12-bit ident + */ +public class MPT1327TalkgroupFormatter extends IntegerFormatter +{ + private static final int IDENTIFIER_DECIMAL_WIDTH = 7; + private static final int IDENTIFIER_HEXADECIMAL_WIDTH = 5; + private static final int FLEET_DECIMAL_WIDTH = 3; + private static final int IDENT_DECIMAL_WIDTH = 4; + private static final String SEPARATOR = "-"; + + /** + * Formats the individual or group identifier to the specified format and width. + */ + public static String format(TalkgroupIdentifier identifier, IntegerFormat format, boolean fixedWidth) + { + if(identifier instanceof MPT1327Talkgroup) + { + MPT1327Talkgroup mpt = (MPT1327Talkgroup)identifier; + + if(fixedWidth) + { + switch(format) + { + case DECIMAL: + return toDecimal(mpt.getValue(), IDENTIFIER_DECIMAL_WIDTH); + case FORMATTED: + return toDecimal(mpt.getPrefix(), FLEET_DECIMAL_WIDTH) + SEPARATOR + + toDecimal(mpt.getIdent(), IDENT_DECIMAL_WIDTH); + case HEXADECIMAL: + return toHex(mpt.getValue(), IDENTIFIER_HEXADECIMAL_WIDTH); + default: + throw new IllegalArgumentException("Unrecognized integer format: " + format); + } + } + else + { + switch(format) + { + case DECIMAL: + return mpt.toString(); + case FORMATTED: + return mpt.getPrefix() + SEPARATOR + mpt.getIdent(); + case HEXADECIMAL: + return toHex(identifier.getValue()); + default: + throw new IllegalArgumentException("Unrecognized integer format: " + format); + } + } + } + + return identifier.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/preference/playlist/FilePreferences.java b/src/main/java/io/github/dsheirer/preference/playlist/FilePreferences.java new file mode 100644 index 00000000..b5e715db --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/playlist/FilePreferences.java @@ -0,0 +1,279 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.preference.playlist; + +import io.github.dsheirer.preference.Preference; +import io.github.dsheirer.preference.PreferenceType; +import io.github.dsheirer.sample.Listener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.prefs.Preferences; + +/** + * User preferences for playlists + */ +public class FilePreferences extends Preference +{ + private final static Logger mLog = LoggerFactory.getLogger(FilePreferences.class); + private Preferences mPreferences = Preferences.userNodeForPackage(FilePreferences.class); + + private static final String FOLDER_APPLICATION_ROOT = "SDRTrunk"; + private static final String FOLDER_PLAYLIST = "playlist"; + private static final String FILE_PLAYLIST = "default.xml"; + private static final String FILE_LEGACY_PLAYLIST = "playlist_v2.xml"; + + private static final String PREFERENCE_KEY_PLAYLIST_FOLDER = "playlist.folder"; + private static final String PREFERENCE_KEY_PLAYLIST_LAST_ACCESSED = "playlist.last.accessed"; + private static final String PREFERENCE_KEY_PLAYLIST_STARTUP = "playlist.default"; + private static final String PREFERENCE_KEY_PLAYLIST_USE_LAST_ACCESSED = "playlist.use.last.accessed"; + + private Path mPlaylistFolder; + private Path mPlaylistStartupPath; + private Path mPlaylistLastAccessedPath; + private Boolean mUsePlaylistLastAccessedByDefault = true; + + /** + * Constructs this preference with an update listener + * @param updateListener to receive notifications whenever these preferences change + */ + public FilePreferences(Listener updateListener) + { + super(updateListener); + } + + @Override + public PreferenceType getPreferenceType() + { + return PreferenceType.FILES; + } + + + /** + * Path to the current playlist + */ + public Path getPlaylist() + { + if(mUsePlaylistLastAccessedByDefault) + { + return getPlaylistLastAccessedPath(); + } + else + { + return getPlaylistStartupPath(); + } + } + + /** + * Legacy (version 2) playlist path. + */ + public Path getLegacyPlaylist() + { + return getPlaylistFolder().resolve(FILE_LEGACY_PLAYLIST); + } + + /** + * Lock file for the playlist when updating the file. + */ + public Path getPlaylistLock() + { + String playlist = getPlaylist().toAbsolutePath().toString(); + return Paths.get(playlist + ".lck"); + } + + /** + * Lock file for the playlist when updating the file. + */ + public Path getPlaylistBackup() + { + String playlist = getPlaylist().toString(); + return Paths.get(playlist + ".backup"); + } + + /** + * Path to the playlist to use at startup when 'use last accessed' is set to false. + */ + public Path getPlaylistStartupPath() + { + if(mPlaylistStartupPath == null) + { + mPlaylistStartupPath = getPath(PREFERENCE_KEY_PLAYLIST_STARTUP, getDefaultPlaylistPath()); + } + + return mPlaylistStartupPath; + } + + /** + * Sets the path to the default playlist + */ + public void setPlaylistStartupPath(Path path) + { + mPlaylistStartupPath = path; + mPreferences.put(PREFERENCE_KEY_PLAYLIST_STARTUP, path.toString()); + notifyPreferenceUpdated(); + } + + /** + * Path for the last accesssed playlist + */ + public Path getPlaylistLastAccessedPath() + { + if(mPlaylistLastAccessedPath == null) + { + mPlaylistLastAccessedPath = getPath(PREFERENCE_KEY_PLAYLIST_LAST_ACCESSED, getDefaultPlaylistPath()); + } + + return mPlaylistLastAccessedPath; + } + + /** + * Sets the path to the last accessed playlist + */ + public void setPlaylistLastAccessedPath(Path path) + { + mPlaylistLastAccessedPath = path; + mPreferences.put(PREFERENCE_KEY_PLAYLIST_LAST_ACCESSED, path.toString()); + notifyPreferenceUpdated(); + } + + /** + * Path to the folder for storing playlist + */ + public Path getPlaylistFolder() + { + if(mPlaylistFolder == null) + { + mPlaylistFolder = getPath(PREFERENCE_KEY_PLAYLIST_FOLDER, getDefaultPlaylistFolder()); + } + + return mPlaylistFolder; + } + + /** + * Sets the path to the playlist folder + */ + public void setPlaylistFolder(Path path) + { + mPlaylistFolder = path; + mPreferences.put(PREFERENCE_KEY_PLAYLIST_FOLDER, path.toString()); + notifyPreferenceUpdated(); + } + + /** + * Indicates the playlist to load on startup, either the last accessed playlist (true) or the default playlist (false). + */ + public boolean usePlaylistLastAccessedByDefault() + { + if(mUsePlaylistLastAccessedByDefault == null) + { + mUsePlaylistLastAccessedByDefault = mPreferences.getBoolean(PREFERENCE_KEY_PLAYLIST_USE_LAST_ACCESSED, true); + } + + return mUsePlaylistLastAccessedByDefault; + } + + /** + * Sets using the last accessed playlist by default (true) or the default playlist (false). + */ + public void setUsePlaylistLastAccessedByDefault(boolean useLastAccessed) + { + mUsePlaylistLastAccessedByDefault = useLastAccessed; + mPreferences.putBoolean(PREFERENCE_KEY_PLAYLIST_USE_LAST_ACCESSED, useLastAccessed); + notifyPreferenceUpdated(); + } + + + /** + * Default application root path + */ + private Path getDefaultApplicationFolder() + { + Path defaultApplicationPath = Paths.get(System.getProperty("user.home"), FOLDER_APPLICATION_ROOT); + createDirectory(defaultApplicationPath); + return defaultApplicationPath; + } + + /** + * Default playlist folder + */ + public Path getDefaultPlaylistFolder() + { + Path defaultPlaylistFolder = getDefaultApplicationFolder().resolve(FOLDER_PLAYLIST); + createDirectory(defaultPlaylistFolder); + return defaultPlaylistFolder; + } + + /** + * Default playlist + */ + private Path getDefaultPlaylistPath() + { + return getDefaultPlaylistFolder().resolve(FILE_PLAYLIST); + } + + /** + * Returns the path stored in preferences and referenced by the key argument if it exists, otherwise returns the + * default path. + * + * Note: the default path is not checked for existence. + * + * @param key to the preferences service for the path + * @param defaultPath to use if the preferred path does not exist + * @return requested path + */ + private Path getPath(String key, Path defaultPath) + { + String stringPath = mPreferences.get(PREFERENCE_KEY_PLAYLIST_STARTUP, defaultPath.toString()); + + if(stringPath != null && !stringPath.isEmpty()) + { + Path temp = Paths.get(stringPath); + if(Files.exists(temp)) + { + return temp; + } + } + + return defaultPath; + } + + /** + * Creates a directory if it does not already exist + */ + private void createDirectory(Path directory) + { + if(!Files.exists(directory) && Files.isDirectory(directory)) + { + try + { + Files.createDirectory(directory); + mLog.info("Created directory [" + directory.toString() + "]"); + } + catch(Exception e) + { + mLog.error("Error creating directory [" + directory.toString() + "]"); + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/protocol/Protocol.java b/src/main/java/io/github/dsheirer/protocol/Protocol.java new file mode 100644 index 00000000..d25e6a93 --- /dev/null +++ b/src/main/java/io/github/dsheirer/protocol/Protocol.java @@ -0,0 +1,72 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.protocol; + +import java.util.EnumSet; + +/** + * Binary protocols supported within this application + */ +public enum Protocol +{ + APCO25("APCO-25", "APCO25PHASE1", 9600), + FLEETSYNC("Fleetsync", "FLEETSYNC", 1200), + IPV4("IPV4", "IPV4", 0), + LOJACK("LoJack", "LOJACK", 1200), + LTR("LTR", "LTR", 300), + LTR_NET("LTR-Net", "LTRNET", 300), + LTR_STANDARD("LTR-Standard", "LTRSTANDARD", 300), + MDC1200("MDC-1200", "MDC1200", 1200), + MPT1327("MPT-1327", "MPT1327", 1200), + PASSPORT("Passport", "PASSPORT", 300), + TAIT1200("Tait 1200", "TAIT1200", 1200), + UNKNOWN("Unknown", "UNKNOWN", 0); + + private String mLabel; + private String mFileNameLabel; + private int mBitRate; + + + Protocol(String label, String fileNameLabel, int bitRate) + { + mLabel = label; + mFileNameLabel = fileNameLabel; + mBitRate = bitRate; + } + + public static EnumSet TALKGROUP_PROTOCOLS = EnumSet.of(APCO25, FLEETSYNC, LTR_NET, LTR_STANDARD, MDC1200, + MPT1327, PASSPORT, TAIT1200); + + @Override + public String toString() + { + return mLabel; + } + + public String getFileNameLabel() + { + return mFileNameLabel; + } + + public int getBitRate() + { + return mBitRate; + } +} diff --git a/src/main/java/io/github/dsheirer/record/AudioRecorder.java b/src/main/java/io/github/dsheirer/record/AudioRecorder.java index b05fea8a..b72f4f25 100644 --- a/src/main/java/io/github/dsheirer/record/AudioRecorder.java +++ b/src/main/java/io/github/dsheirer/record/AudioRecorder.java @@ -19,7 +19,7 @@ package io.github.dsheirer.record; import io.github.dsheirer.audio.IAudioPacketListener; -import io.github.dsheirer.channel.metadata.Metadata; +import io.github.dsheirer.identifier.IdentifierCollection; import io.github.dsheirer.module.Module; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.buffer.ReusableAudioPacket; @@ -49,7 +49,7 @@ public abstract class AudioRecorder extends Module implements Listener { private static final long serialVersionUID = 1L; - private JCheckBox mAudioRecorder; private JCheckBox mBasebandRecorder; private JCheckBox mTrafficBasebandRecorder; private JCheckBox mBitstreamRecorder; @@ -44,18 +43,6 @@ public class RecordConfigurationEditor extends Editor { setLayout(new MigLayout("fill,wrap 2", "", "[][][][grow]")); - mAudioRecorder = new JCheckBox("Audio"); - mAudioRecorder.setEnabled(false); - mAudioRecorder.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - setModified(true); - } - }); - add(mAudioRecorder, "wrap"); - mBasebandRecorder = new JCheckBox("Baseband I/Q"); mBasebandRecorder.setEnabled(false); mBasebandRecorder.addActionListener(new ActionListener() @@ -107,11 +94,6 @@ public class RecordConfigurationEditor extends Editor private void setControlsEnabled(boolean enabled) { - if(mAudioRecorder.isEnabled() != enabled) - { - mAudioRecorder.setEnabled(enabled); - } - if(mBasebandRecorder.isEnabled() != enabled) { mBasebandRecorder.setEnabled(enabled); @@ -142,11 +124,6 @@ public class RecordConfigurationEditor extends Editor config.clearRecorders(); - if(mAudioRecorder.isSelected()) - { - config.addRecorder(RecorderType.AUDIO); - } - if(mBasebandRecorder.isSelected()) { config.addRecorder(RecorderType.BASEBAND); @@ -181,7 +158,6 @@ public class RecordConfigurationEditor extends Editor setControlsEnabled(true); List recorders = getItem().getRecordConfiguration().getRecorders(); - mAudioRecorder.setSelected(recorders.contains(RecorderType.AUDIO)); mBasebandRecorder.setSelected(recorders.contains(RecorderType.BASEBAND)); mTrafficBasebandRecorder.setSelected(recorders.contains(RecorderType.TRAFFIC_BASEBAND)); mBitstreamRecorder.setSelected(recorders.contains(RecorderType.DEMODULATED_BIT_STREAM)); diff --git a/src/main/java/io/github/dsheirer/record/RecorderManager.java b/src/main/java/io/github/dsheirer/record/RecorderManager.java index da8f0fca..59655c61 100644 --- a/src/main/java/io/github/dsheirer/record/RecorderManager.java +++ b/src/main/java/io/github/dsheirer/record/RecorderManager.java @@ -15,16 +15,26 @@ ******************************************************************************/ package io.github.dsheirer.record; -import io.github.dsheirer.channel.metadata.Metadata; +import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.alias.AliasModel; +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.integer.IntegerIdentifier; +import io.github.dsheirer.identifier.string.StringIdentifier; import io.github.dsheirer.properties.SystemProperties; import io.github.dsheirer.record.wave.AudioPacketWaveRecorder; import io.github.dsheirer.record.wave.ComplexBufferWaveRecorder; +import io.github.dsheirer.record.wave.WaveMetadata; import io.github.dsheirer.sample.IOverflowListener; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.OverflowableTransferQueue; import io.github.dsheirer.sample.buffer.ReusableAudioPacket; import io.github.dsheirer.util.StringUtils; import io.github.dsheirer.util.ThreadPool; +import io.github.dsheirer.util.TimeStamp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,10 +55,12 @@ public class RecorderManager implements Listener public static final float BASEBAND_SAMPLE_RATE = 25000.0f; //Default sample rate - source can override public static final long IDLE_RECORDER_REMOVAL_THRESHOLD = 6000; //6 seconds - private Map mRecorders = new HashMap<>(); + private Map mRecorders = new HashMap<>(); private OverflowableTransferQueue mAudioPacketQueue = new OverflowableTransferQueue<>(1000, 100); private List mAudioPackets = new ArrayList<>(); private ScheduledFuture mBufferProcessorFuture; + private AliasModel mAliasModel; + private int mUnknownAudioRecordingIndex = 1; private boolean mCanStartNewRecorders = true; @@ -59,8 +71,9 @@ public class RecorderManager implements Listener * indicating that the call is complete. A separate recording monitor periodically checks for idled recorders to * be stopped for cases when the channel fails to send an end-call audio packet. */ - public RecorderManager() + public RecorderManager(AliasModel aliasModel) { + mAliasModel = aliasModel; mAudioPacketQueue.setOverflowListener(new IOverflowListener() { @Override @@ -78,7 +91,7 @@ public class RecorderManager implements Listener }); mBufferProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new BufferProcessor(), 0, - 1, TimeUnit.SECONDS); + 500, TimeUnit.MILLISECONDS); } /** @@ -100,7 +113,7 @@ public class RecorderManager implements Listener @Override public void receive(ReusableAudioPacket audioPacket) { - if(audioPacket.hasMetadata() && audioPacket.getMetadata().isRecordable()) + if(audioPacket.hasIdentifierCollection() && audioPacket.isRecordable()) { mAudioPacketQueue.offer(audioPacket); } @@ -121,11 +134,11 @@ public class RecorderManager implements Listener { for(ReusableAudioPacket audioPacket : mAudioPackets) { - String identifier = audioPacket.getMetadata().getUniqueIdentifier(); + int audioChannelId = audioPacket.getAudioChannelId(); - if(mRecorders.containsKey(identifier)) + if(mRecorders.containsKey(audioChannelId)) { - AudioPacketWaveRecorder recorder = mRecorders.get(identifier); + AudioPacketWaveRecorder recorder = mRecorders.get(audioChannelId); if(audioPacket.getType() == ReusableAudioPacket.Type.AUDIO) { @@ -134,27 +147,25 @@ public class RecorderManager implements Listener } else if(audioPacket.getType() == ReusableAudioPacket.Type.END) { - AudioPacketWaveRecorder finished = mRecorders.remove(identifier); - finished.stop(); + AudioPacketWaveRecorder finished = mRecorders.remove(audioChannelId); + stopRecorder(finished); } } else if(audioPacket.getType() == ReusableAudioPacket.Type.AUDIO) { if(mCanStartNewRecorders) { - String filePrefix = getFilePrefix(audioPacket); - AudioPacketWaveRecorder recorder = null; try { - recorder = new AudioPacketWaveRecorder(filePrefix, audioPacket.getMetadata()); + recorder = new AudioPacketWaveRecorder(getTemporaryFilePath(audioPacket)); recorder.start(); audioPacket.incrementUserCount(); recorder.receive(audioPacket); - mRecorders.put(identifier, recorder); + mRecorders.put(audioPacket.getAudioChannelId(), recorder); } catch(Exception ioe) { @@ -165,7 +176,7 @@ public class RecorderManager implements Listener if(recorder != null) { - recorder.stop(); + stopRecorder(recorder); } } } @@ -179,54 +190,116 @@ public class RecorderManager implements Listener } } + private void stopRecorder(AudioPacketWaveRecorder recorder) + { + Path rename = getFinalFileName(recorder.getIdentifierCollection()); + + AliasList aliasList = null; + + if(recorder.getIdentifierCollection() != null) + { + aliasList = mAliasModel.getAliasList(recorder.getIdentifierCollection()); + } + + WaveMetadata waveMetadata = WaveMetadata.createFrom(recorder.getIdentifierCollection(), aliasList); + recorder.stop(rename, waveMetadata); + } + /** * Removes recorders that have not received any new audio buffers in the last 6 seconds. */ private void removeIdleRecorders() { - Iterator> it = mRecorders.entrySet().iterator(); + Iterator> it = mRecorders.entrySet().iterator(); while(it.hasNext()) { - Map.Entry entry = it.next(); + Map.Entry entry = it.next(); if(entry.getValue().getLastBufferReceived() + IDLE_RECORDER_REMOVAL_THRESHOLD < System.currentTimeMillis()) { - mLog.info("Removing idle recorder [" + entry.getKey() + "]"); it.remove(); - entry.getValue().stop(); + stopRecorder(entry.getValue()); } } } + private Path getTemporaryFilePath(ReusableAudioPacket packet) + { + StringBuilder sb = new StringBuilder(); + sb.append(TimeStamp.getTimeStamp("-")); + sb.append("_audio_channel_"); + sb.append(packet.getAudioChannelId()); + sb.append(".tmp"); + return getRecordingBasePath().resolve(sb.toString()); + } + /** - * Constructs a file name and path for an audio recording + * Provides a formatted audio recording filename to use as the final audio filename. + * @return */ - private String getFilePrefix(ReusableAudioPacket packet) + private Path getFinalFileName(IdentifierCollection identifierCollection) { - Path baseFilePath = SystemProperties.getInstance().getApplicationFolder("recordings"); - - Metadata metadata = packet.getMetadata(); - StringBuilder sb = new StringBuilder(); - sb.append(metadata.hasChannelConfigurationSystem() ? metadata.getChannelConfigurationSystem() + "_" : ""); - sb.append(metadata.hasChannelConfigurationSite() ? metadata.getChannelConfigurationSite() + "_" : ""); - sb.append(metadata.hasChannelConfigurationName() ? metadata.getChannelConfigurationName() + "_" : ""); - if(metadata.getPrimaryAddressTo().hasIdentifier()) + if(identifierCollection != null) { - sb.append("_TO_").append(metadata.getPrimaryAddressTo().getIdentifier()); + Identifier system = identifierCollection.getIdentifier(IdentifierClass.CONFIGURATION, Form.SYSTEM, Role.ANY); - if(metadata.getPrimaryAddressFrom().hasIdentifier()) + if(system != null) { - sb.append("_FROM_").append(metadata.getPrimaryAddressFrom().getIdentifier()); + sb.append(((StringIdentifier)system).getValue()).append("_"); + } + + Identifier site = identifierCollection.getIdentifier(IdentifierClass.CONFIGURATION, Form.SITE, Role.ANY); + + if(site != null) + { + sb.append(((StringIdentifier)site).getValue()).append("_"); + } + + Identifier channel = identifierCollection.getIdentifier(IdentifierClass.CONFIGURATION, Form.CHANNEL, Role.ANY); + + if(channel != null) + { + sb.append(((StringIdentifier)channel).getValue()).append("_"); + } + + Identifier to = identifierCollection.getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.TO); + + if(to != null) + { + sb.append("_TO_").append(((IntegerIdentifier)to).getValue()); + } + + Identifier from = identifierCollection.getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.FROM); + + if(from != null) + { + sb.append("_FROM_").append(((IntegerIdentifier)from).getValue()); + } + } + else + { + sb.append("audio_recording_no_metadata_").append(mUnknownAudioRecordingIndex++); + + if(mUnknownAudioRecordingIndex < 0) + { + mUnknownAudioRecordingIndex = 1; } } - String cleanedFileName = StringUtils.replaceIllegalCharacters(sb.toString()); - return baseFilePath.resolve(cleanedFileName).toString(); + StringBuilder sbFinal = new StringBuilder(); + sbFinal.append(TimeStamp.getTimeStamp("_")); + + //Remove any illegal filename characters + sbFinal.append(StringUtils.replaceIllegalCharacters(sb.toString())); + sbFinal.append(".wav"); + + return getRecordingBasePath().resolve(sbFinal.toString()); } + public Path getRecordingBasePath() { return SystemProperties.getInstance().getApplicationFolder("recordings"); @@ -253,8 +326,15 @@ public class RecorderManager implements Listener @Override public void run() { - processBuffers(); - removeIdleRecorders(); + try + { + processBuffers(); + removeIdleRecorders(); + } + catch(Throwable t) + { + mLog.error("Error while processing audio buffers", t); + } } } } diff --git a/src/main/java/io/github/dsheirer/record/RecorderType.java b/src/main/java/io/github/dsheirer/record/RecorderType.java index 1921cad4..2bfa12f3 100644 --- a/src/main/java/io/github/dsheirer/record/RecorderType.java +++ b/src/main/java/io/github/dsheirer/record/RecorderType.java @@ -23,7 +23,7 @@ import java.util.EnumSet; public enum RecorderType { /** - * Audio - 8kHz PCM + * Audio - 8kHz PCM -- **DO NOT USE - DEPRECATED - AUDIO RECORDING IS NOT MANAGED BY ALIASES */ AUDIO( "Audio (.wav)"), @@ -52,7 +52,7 @@ public enum RecorderType /** * Recorders available to all decoders */ - public static final EnumSet DEFAULT_RECORDER_TYPES = EnumSet.of(AUDIO, BASEBAND, TRAFFIC_BASEBAND); + public static final EnumSet DEFAULT_RECORDER_TYPES = EnumSet.of(BASEBAND, TRAFFIC_BASEBAND); RecorderType( String displayString ) { diff --git a/src/main/java/io/github/dsheirer/record/binary/BinaryReader.java b/src/main/java/io/github/dsheirer/record/binary/BinaryReader.java new file mode 100644 index 00000000..e4454b9c --- /dev/null +++ b/src/main/java/io/github/dsheirer/record/binary/BinaryReader.java @@ -0,0 +1,215 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.record.binary; + +import io.github.dsheirer.sample.buffer.ReusableByteBuffer; +import io.github.dsheirer.sample.buffer.ReusableByteBufferQueue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.text.SimpleDateFormat; +import java.util.Iterator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class BinaryReader implements Iterator, AutoCloseable +{ + private final static Logger mLog = LoggerFactory.getLogger(BinaryReader.class); + private ReusableByteBufferQueue mBufferQueue = new ReusableByteBufferQueue("Binary Reader"); + private int mBufferSize; + private InputStream mInputStream; + private Path mPath; + private ReusableByteBuffer mNextBuffer; + private TimestampTracker mTimestampTracker = new TimestampTracker(); + + /** + * Constructs a binary reader + * @param path to the binary file + * @param bufferSize for each buffer read from the file + * @throws IOException + */ + public BinaryReader(Path path, int bufferSize) throws IOException + { + mBufferSize = bufferSize; + mPath = path; + mInputStream = new BufferedInputStream(new FileInputStream(path.toFile())); + mTimestampTracker.processFileName(path.toString()); + getNext(); + } + + @Override + public void close() throws Exception + { + if(mInputStream != null) + { + mInputStream.close(); + mInputStream = null; + mPath = null; + } + } + + @Override + public boolean hasNext() + { + return mNextBuffer != null; + } + + /** + * Returns a full reusable byte buffer, or at the end of the file this method may return a + * reusable byte buffer that is less than the requested buffer size, containing the remaining + * bytes from the file. + * + * @return + */ + @Override + public ReusableByteBuffer next() + { + ReusableByteBuffer current = mNextBuffer; + getNext(); + return current; + } + + /** + * Loads the next buffer + */ + private void getNext() + { + try + { + mNextBuffer = mBufferQueue.getBuffer(mBufferSize); + + int bytesRead = mInputStream.read(mNextBuffer.getBytes(), 0, mBufferSize); + + mNextBuffer.setTimestamp(mTimestampTracker.getTimestamp()); + mTimestampTracker.updateBytesProcessed(bytesRead); + + if(bytesRead > 0) + { + if(bytesRead < mBufferSize) + { + ReusableByteBuffer partialBuffer = mBufferQueue.getBuffer(bytesRead); + System.arraycopy(mNextBuffer.getBytes(), 0, partialBuffer.getBytes(), 0, bytesRead); + partialBuffer.setTimestamp(mNextBuffer.getTimestamp()); + mNextBuffer.decrementUserCount(); + mNextBuffer = partialBuffer; + } + } + else + { + mNextBuffer.decrementUserCount(); + mNextBuffer = null; + } + } + catch(IOException e) + { + mLog.error("Error reading binary file [" + mPath.toString() + "]", e); + mNextBuffer.decrementUserCount(); + mNextBuffer = null; + } + } + + public class TimestampTracker + { + private final Pattern TIMESTAMP_BITRATE_PATTERN = Pattern.compile(".*(\\d{8}_\\d{6})_(\\d{4,8})BPS_.*.bits"); + private final SimpleDateFormat SDF = new SimpleDateFormat("yyyyMMdd_HHmmss"); + private Double mBitRate = null; + private long mTimestamp; + + public TimestampTracker() + { + } + + /** + * Processes the file name to attempt to extract the file starting timestamp and bit rate. + * @param filename + */ + public void processFileName(String filename) + { + try + { + Matcher m = TIMESTAMP_BITRATE_PATTERN.matcher(filename); + + if(m.find()) + { + long timestamp = SDF.parse(m.group(1)).getTime(); + int bitRate = Integer.parseInt(m.group(2)); + + setTimestamp(timestamp); + setBitRate(bitRate); + return; + } + } + catch(Exception e) + { + mLog.error("Couldn't parse timestamp and bit rate from filename [" + filename + "]"); + } + } + + /** + * Sets the expected bit rate (ie bits per second) for the tracker + */ + public void setBitRate(int bitRate) + { + mBitRate = (double)bitRate; + } + + /** + * Sets the timestamp for the tracker, otherwise current system time is assumed. + */ + public void setTimestamp(long timestamp) + { + mTimestamp = timestamp; + } + + /** + * Gets the currently tracked timestamp when bits per second has been previously specified, otherwise + * returns current system time. + * + * @return current tracked timestamp or current system time. + */ + public long getTimestamp() + { + if(mBitRate != null) + { + return mTimestamp; + } + + return System.currentTimeMillis(); + } + + /** + * Updates the timestamp based on the number of bytes processed and the specified bit rate. + * + * @param bytesProcessed thus far + */ + public void updateBytesProcessed(int bytesProcessed) + { + if(mBitRate != null) + { + mTimestamp += (long)(((double)bytesProcessed * 8.0) / mBitRate * 1000.0); + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/record/binary/BinaryRecorder.java b/src/main/java/io/github/dsheirer/record/binary/BinaryRecorder.java index 861b7e3c..ede4bddf 100644 --- a/src/main/java/io/github/dsheirer/record/binary/BinaryRecorder.java +++ b/src/main/java/io/github/dsheirer/record/binary/BinaryRecorder.java @@ -1,22 +1,27 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.record.binary; import io.github.dsheirer.dsp.filter.channelizer.ContinuousReusableBufferProcessor; import io.github.dsheirer.module.Module; +import io.github.dsheirer.protocol.Protocol; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.buffer.IReusableByteBufferListener; import io.github.dsheirer.sample.buffer.ReusableByteBuffer; @@ -56,18 +61,21 @@ public class BinaryRecorder extends Module implements IReusableByteBufferListene private String mRecordingIdentifier; private BinaryWriter mBinaryWriter = new BinaryWriter(); private int mBytesRecordedCounter; + private Protocol mProtocol; /** * Constructs a binary recorder. * * @param baseRecordingPath where the recording should be created * @param recordingIdentifier to include in the recording file name. + * @param protocol to include as a values in the recording file name */ - public BinaryRecorder(Path baseRecordingPath, String recordingIdentifier) + public BinaryRecorder(Path baseRecordingPath, String recordingIdentifier, Protocol protocol) { mBaseRecordingPath = baseRecordingPath; mRecordingIdentifier = recordingIdentifier; mBufferProcessor.setListener(mBinaryWriter); + mProtocol = protocol; } public void start() @@ -118,6 +126,8 @@ public class BinaryRecorder extends Module implements IReusableByteBufferListene StringBuilder sb = new StringBuilder(); sb.append(TimeStamp.getTimeStamp("_")); sb.append("_"); + sb.append(mProtocol.getBitRate()).append("BPS_"); + sb.append(mProtocol.getFileNameLabel()).append("_"); sb.append(mRecordingIdentifier.trim()); sb.append(".bits"); String cleaned = StringUtils.replaceIllegalCharacters(sb.toString()); @@ -156,7 +166,7 @@ public class BinaryRecorder extends Module implements IReusableByteBufferListene mCurrentPath = path; mWritableByteChannel = Files.newByteChannel(path, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE)); - mLog.info("Binary (bitstream) recording started: " + mCurrentPath.toString()); +// mLog.info("Binary (bitstream) recording started: " + mCurrentPath.toString()); } } diff --git a/src/main/java/io/github/dsheirer/record/wave/AudioPacketWaveRecorder.java b/src/main/java/io/github/dsheirer/record/wave/AudioPacketWaveRecorder.java index e810c1a1..e41a2041 100644 --- a/src/main/java/io/github/dsheirer/record/wave/AudioPacketWaveRecorder.java +++ b/src/main/java/io/github/dsheirer/record/wave/AudioPacketWaveRecorder.java @@ -17,21 +17,19 @@ package io.github.dsheirer.record.wave; import io.github.dsheirer.audio.AudioFormats; -import io.github.dsheirer.channel.metadata.Metadata; +import io.github.dsheirer.identifier.IdentifierCollection; import io.github.dsheirer.module.Module; import io.github.dsheirer.sample.ConversionUtils; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue; import io.github.dsheirer.sample.buffer.ReusableAudioPacket; import io.github.dsheirer.util.ThreadPool; -import io.github.dsheirer.util.TimeStamp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.sound.sampled.AudioFormat; import java.io.IOException; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ScheduledFuture; @@ -46,32 +44,38 @@ public class AudioPacketWaveRecorder extends Module implements Listener mTransferQueue = new OverflowableReusableBufferTransferQueue<>(500, 100); private BufferProcessor mBufferProcessor; private ScheduledFuture mProcessorHandle; - private Metadata mMetadata; private long mLastBufferReceived; private List mAudioPacketsToProcess = new ArrayList<>(); - + private IdentifierCollection mIdentifierCollection; private AtomicBoolean mRunning = new AtomicBoolean(); /** * Wave audio recorder for AudioPackets - * @param filePrefix - * @param metadata + * + * @param path for the recording file */ - public AudioPacketWaveRecorder(String filePrefix, Metadata metadata) + public AudioPacketWaveRecorder(Path path) { - mMetadata = metadata; - mFilePrefix = filePrefix; + mPath = path; mAudioFormat = AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO; } + /** + * Identifier collection harvested from the most recent audio packet. + * @return identifier collection or null. + */ + public IdentifierCollection getIdentifierCollection() + { + return mIdentifierCollection; + } + /** * Indicates if the recorder is currently running. */ @@ -88,9 +92,9 @@ public class AudioPacketWaveRecorder extends Module implements Listener patchedAliases = patchGroupAlias.getPatchedAliases(); - - if(patchedAliases.size() >= 1) - { - Alias patch = patchedAliases.get(0); - waveMetadata.add(WaveMetadataType.TALKGROUP_PRIMARY_PATCHED_1, patch.getName()); - } - if(patchedAliases.size() >= 2) - { - Alias patch = patchedAliases.get(1); - waveMetadata.add(WaveMetadataType.TALKGROUP_PRIMARY_PATCHED_2, patch.getName()); - } - if(patchedAliases.size() >= 3) - { - Alias patch = patchedAliases.get(2); - waveMetadata.add(WaveMetadataType.TALKGROUP_PRIMARY_PATCHED_3, patch.getName()); - } - if(patchedAliases.size() >= 4) - { - Alias patch = patchedAliases.get(3); - waveMetadata.add(WaveMetadataType.TALKGROUP_PRIMARY_PATCHED_4, patch.getName()); - } - if(patchedAliases.size() >= 5) - { - Alias patch = patchedAliases.get(4); - waveMetadata.add(WaveMetadataType.TALKGROUP_PRIMARY_PATCHED_5, patch.getName()); - } - } - } - } - - AliasedIdentifier secondaryFrom = audioMetadata.getSecondaryAddressFrom(); - - if(secondaryFrom != null) - { - waveMetadata.add(WaveMetadataType.TALKGROUP_SECONDARY_FROM, secondaryFrom.getIdentifier()); - - Alias alias = secondaryFrom.getAlias(); - - if(alias != null) - { - waveMetadata.add(WaveMetadataType.TALKGROUP_SECONDARY_FROM_ALIAS, alias.getName()); - waveMetadata.add(WaveMetadataType.TALKGROUP_SECONDARY_FROM_ICON, alias.getIconName()); - } - } - - AliasedIdentifier secondaryTo = audioMetadata.getSecondaryAddressTo(); - - if(secondaryTo != null) - { - waveMetadata.add(WaveMetadataType.TALKGROUP_SECONDARY_TO, secondaryTo.getIdentifier()); - - Alias alias = secondaryTo.getAlias(); - - if(alias != null) - { - waveMetadata.add(WaveMetadataType.TALKGROUP_SECONDARY_TO_ALIAS, alias.getName()); - waveMetadata.add(WaveMetadataType.TALKGROUP_SECONDARY_TO_ICON, alias.getIconName()); - } - } - - return waveMetadata; + return null; } +// /** +// * Creates a WAVE recording metadata chunk from the audio metadata argument +// * @param audioMetadata to create the wave metadata from +// * @return wave metadata instance +// */ +// public static WaveMetadata createFrom(Metadata audioMetadata) +// { +// WaveMetadata waveMetadata = new WaveMetadata(); +// +// waveMetadata.add(WaveMetadataType.SOFTWARE, SystemProperties.getInstance().getApplicationName()); +// waveMetadata.add(WaveMetadataType.DATE_CREATED, SDF.format(new Date(audioMetadata.getTimestamp()))); +// waveMetadata.add(WaveMetadataType.ARTIST_NAME, audioMetadata.getChannelConfigurationSystem()); +// waveMetadata.add(WaveMetadataType.ALBUM_TITLE, audioMetadata.getChannelConfigurationSite()); +// waveMetadata.add(WaveMetadataType.TRACK_TITLE, audioMetadata.getChannelConfigurationName()); +// waveMetadata.add(WaveMetadataType.COMMENTS, audioMetadata.getPrimaryDecoderType().getDisplayString()); +// waveMetadata.add(WaveMetadataType.CHANNEL_ID, audioMetadata.getChannelFrequencyLabel()); +// waveMetadata.add(WaveMetadataType.CHANNEL_FREQUENCY, String.valueOf(audioMetadata.getChannelFrequency())); +// +// AliasedIdentifier networkID1 = audioMetadata.getNetworkID1(); +// +// if(networkID1 != null) +// { +// waveMetadata.add(WaveMetadataType.NETWORK_ID_1, networkID1.getIdentifier()); +// } +// +// AliasedIdentifier networkID2 = audioMetadata.getNetworkID2(); +// +// if(networkID2 != null) +// { +// waveMetadata.add(WaveMetadataType.NETWORK_ID_2, networkID2.getIdentifier()); +// } +// +// AliasedIdentifier primaryFrom = audioMetadata.getPrimaryAddressFrom(); +// +// if(primaryFrom != null) +// { +// waveMetadata.add(WaveMetadataType.TALKGROUP_PRIMARY_FROM, primaryFrom.getIdentifier()); +// +// Alias alias = primaryFrom.getAlias(); +// +// if(alias != null) +// { +// waveMetadata.add(WaveMetadataType.TALKGROUP_PRIMARY_FROM_ALIAS, alias.getName()); +// waveMetadata.add(WaveMetadataType.TALKGROUP_PRIMARY_FROM_ICON, alias.getIconName()); +// } +// } +// +// AliasedIdentifier primaryTo = audioMetadata.getPrimaryAddressTo(); +// +// if(primaryTo != null) +// { +// waveMetadata.add(WaveMetadataType.TALKGROUP_PRIMARY_TO, primaryTo.getIdentifier()); +// +// Alias alias = primaryTo.getAlias(); +// +// if(alias != null) +// { +// waveMetadata.add(WaveMetadataType.TALKGROUP_PRIMARY_TO_ALIAS, alias.getName()); +// waveMetadata.add(WaveMetadataType.TALKGROUP_PRIMARY_TO_ICON, alias.getIconName()); +// +// if(alias instanceof PatchGroupAlias) +// { +// PatchGroupAlias patchGroupAlias = (PatchGroupAlias)alias; +// +// List patchedAliases = patchGroupAlias.getPatchedAliases(); +// +// if(patchedAliases.size() >= 1) +// { +// Alias patch = patchedAliases.get(0); +// waveMetadata.add(WaveMetadataType.TALKGROUP_PRIMARY_PATCHED_1, patch.getName()); +// } +// if(patchedAliases.size() >= 2) +// { +// Alias patch = patchedAliases.get(1); +// waveMetadata.add(WaveMetadataType.TALKGROUP_PRIMARY_PATCHED_2, patch.getName()); +// } +// if(patchedAliases.size() >= 3) +// { +// Alias patch = patchedAliases.get(2); +// waveMetadata.add(WaveMetadataType.TALKGROUP_PRIMARY_PATCHED_3, patch.getName()); +// } +// if(patchedAliases.size() >= 4) +// { +// Alias patch = patchedAliases.get(3); +// waveMetadata.add(WaveMetadataType.TALKGROUP_PRIMARY_PATCHED_4, patch.getName()); +// } +// if(patchedAliases.size() >= 5) +// { +// Alias patch = patchedAliases.get(4); +// waveMetadata.add(WaveMetadataType.TALKGROUP_PRIMARY_PATCHED_5, patch.getName()); +// } +// } +// } +// } +// +// AliasedIdentifier secondaryFrom = audioMetadata.getSecondaryAddressFrom(); +// +// if(secondaryFrom != null) +// { +// waveMetadata.add(WaveMetadataType.TALKGROUP_SECONDARY_FROM, secondaryFrom.getIdentifier()); +// +// Alias alias = secondaryFrom.getAlias(); +// +// if(alias != null) +// { +// waveMetadata.add(WaveMetadataType.TALKGROUP_SECONDARY_FROM_ALIAS, alias.getName()); +// waveMetadata.add(WaveMetadataType.TALKGROUP_SECONDARY_FROM_ICON, alias.getIconName()); +// } +// } +// +// AliasedIdentifier secondaryTo = audioMetadata.getSecondaryAddressTo(); +// +// if(secondaryTo != null) +// { +// waveMetadata.add(WaveMetadataType.TALKGROUP_SECONDARY_TO, secondaryTo.getIdentifier()); +// +// Alias alias = secondaryTo.getAlias(); +// +// if(alias != null) +// { +// waveMetadata.add(WaveMetadataType.TALKGROUP_SECONDARY_TO_ALIAS, alias.getName()); +// waveMetadata.add(WaveMetadataType.TALKGROUP_SECONDARY_TO_ICON, alias.getIconName()); +// } +// } +// +// return waveMetadata; +// } + /** * Converts the integer length to an ID3 compatible length field where each byte only uses the 7 least significant * bits for a maximum of 28 bits used out of the 32-bit representation. diff --git a/src/main/java/io/github/dsheirer/record/wave/WaveWriter.java b/src/main/java/io/github/dsheirer/record/wave/WaveWriter.java index d6901da9..826af177 100644 --- a/src/main/java/io/github/dsheirer/record/wave/WaveWriter.java +++ b/src/main/java/io/github/dsheirer/record/wave/WaveWriter.java @@ -19,6 +19,8 @@ package io.github.dsheirer.record.wave; import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.sound.sampled.AudioFormat; import java.io.IOException; @@ -35,6 +37,8 @@ import java.util.regex.Pattern; public class WaveWriter implements AutoCloseable { + private final static Logger mLog = LoggerFactory.getLogger(WaveWriter.class); + public static final String RIFF_ID = "RIFF"; public static final int INITIAL_TOTAL_LENGTH = 4; public static final String WAVE_ID = "WAVE"; @@ -127,40 +131,43 @@ public class WaveWriter implements AutoCloseable } /** - * Closes the file + * Implements the auto-closeable interface. Closes the recording without any renaming operation. + * @throws IOException if there is an error closing the file channel. */ public void close() throws IOException + { + close(null); + } + + /** + * Closes the file and renames/moves the contents to the specified path + */ + public void close(Path path) throws IOException { mFileChannel.force(true); mFileChannel.close(); - rename(); + rename(path); } /** - * Renames the file from *.tmp to *.wav after file has been closed. + * Renames the file to the specified filename moving it to the from *.tmp to *.wav after file has been closed. + * * @throws IOException */ - private void rename() throws IOException + private void rename(Path path) throws IOException { - if(mFile != null && Files.exists(mFile)) + if(mFile != null && Files.exists(mFile) && path != null) { - String renamed = mFile.getFileName().toString(); - renamed = renamed.replace(".tmp", ".wav"); - Path renamedPath = mFile.resolveSibling(renamed); - - int suffix = 1; - - while(Files.exists(renamedPath) && suffix < 4) + if(Files.exists(path)) { - String renamedWithSuffix = mFile.getFileName().toString(); - renamedWithSuffix = renamedWithSuffix.replace(".tmp", "_" + suffix + ".wav"); - Path renamedWithSuffixPath = mFile.resolveSibling(renamedWithSuffix); - renamedPath = mFile.resolveSibling(renamedWithSuffixPath); - suffix++; + mLog.warn("Duplicate recording file detected - ignoring [" + path + "]"); + Files.delete(mFile); + } + else + { + Files.move(mFile, path); } - - Files.move(mFile, renamedPath); } } diff --git a/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBuffer.java b/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBuffer.java index c7085f3d..0a8a5ce5 100644 --- a/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBuffer.java +++ b/src/main/java/io/github/dsheirer/sample/buffer/AbstractReusableBuffer.java @@ -115,6 +115,7 @@ public abstract class AbstractReusableBuffer if(mUserCount.get() < 0) { + mUserCount.set(0); throw new IllegalStateException("User count is below zero. This indicates that this buffer's decrement" + " user count was invoked by more than the expected user count"); } diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableAudioPacket.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableAudioPacket.java index c64e6f98..c9daf6a0 100644 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableAudioPacket.java +++ b/src/main/java/io/github/dsheirer/sample/buffer/ReusableAudioPacket.java @@ -15,7 +15,13 @@ ******************************************************************************/ package io.github.dsheirer.sample.buffer; -import io.github.dsheirer.channel.metadata.Metadata; +import io.github.dsheirer.alias.id.broadcast.BroadcastChannel; +import io.github.dsheirer.alias.id.priority.Priority; +import io.github.dsheirer.identifier.IdentifierCollection; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; /** * Reusable audio packet that carries audio sample data, associated metadata and a type to indicate if this @@ -24,9 +30,14 @@ import io.github.dsheirer.channel.metadata.Metadata; public class ReusableAudioPacket extends AbstractReusableBuffer { private Type mType = Type.AUDIO; - private Metadata mMetadata; private float[] mAudioSamples; + private IdentifierCollection mIdentifierCollection; + private int mChannelId = 0; + private int mMonitoringPriority = Priority.DEFAULT_PRIORITY; + private boolean mRecordable = false; + private List mBroadcastChannels = new ArrayList<>(); + /** * Constructs a reusable audio packet. This constructor is package private and should only be used by * a reusable audio packet queue. @@ -41,27 +52,137 @@ public class ReusableAudioPacket extends AbstractReusableBuffer } /** - * Indicates if this packet has associated metadata + * Resets the attributes for this packet to default audio priority, non-recordable and non-streamable. */ - public boolean hasMetadata() + public void resetAttributes() { - return mMetadata != null; + mChannelId = 0; + mMonitoringPriority = Priority.DEFAULT_PRIORITY; + mRecordable = false; + mBroadcastChannels.clear(); } /** - * Associated audio metadata + * Identifier for the channel that produced this audio packet. This ID is used to identify all packets + * within a packet stream that come from the same channel. + * + * @return channel identifier */ - public Metadata getMetadata() + public int getAudioChannelId() { - return mMetadata; + return mChannelId; + } + + /** + * Sets the channel identifier for this audio packet + * + * @param channelId for this audio packet. + */ + public void setAudioChannelId(int channelId) + { + mChannelId = channelId; + } + + /** + * Monitoring (ie local playback) priority. + */ + public int getMonitoringPriority() + { + return mMonitoringPriority; + } + + /** + * Sets the monitoring priority + * + * @param priority for monitoring: + * + * -1 Do Not Monitor + * 0 Selected (monitor override) + * 1 Highest + * ... + * 100 Lowest + */ + public void setMonitoringPriority(int priority) + { + mMonitoringPriority = priority; + } + + /** + * Indicates if the monitoring priority of this audio packet is set to do not monitor. + */ + public boolean isDoNotMonitor() + { + return mMonitoringPriority == Priority.DO_NOT_MONITOR; + } + + /** + * Indicates if this audio packet should be recorded. + */ + public boolean isRecordable() + { + return mRecordable; + } + + /** + * Sets the recordable status for this audio packet. + */ + public void setRecordable(boolean recordable) + { + mRecordable = recordable; + } + + /** + * List of broadcast/streaming channels for this audio packet. + */ + public List getBroadcastChannels() + { + return mBroadcastChannels; + } + + /** + * Adds the broadcast channels to this metadata + */ + public void addBroadcastChannels(Collection channels) + { + for(BroadcastChannel channel: channels) + { + if(!mBroadcastChannels.contains(channel)) + { + mBroadcastChannels.add(channel); + } + } + } + + /** + * Indicates if this audio packet has one or more streaming/broadcast channels assigned. + */ + public boolean isStreamable() + { + return !mBroadcastChannels.isEmpty(); + } + + /** + * Indicates if this packet has associated identifier collection + */ + public boolean hasIdentifierCollection() + { + return mIdentifierCollection != null; + } + + /** + * Associated audio metadata and identifiers + */ + public IdentifierCollection getIdentifierCollection() + { + return mIdentifierCollection; } /** * Sets the metadata associated with this audio packet. */ - public void setMetadata(Metadata metadata) + public void setIdentifierCollection(IdentifierCollection identifierCollection) { - mMetadata = metadata; + mIdentifierCollection = identifierCollection; } /** diff --git a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferAssembler.java b/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferAssembler.java index fc4a8c99..68d434d5 100644 --- a/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferAssembler.java +++ b/src/main/java/io/github/dsheirer/sample/buffer/ReusableComplexBufferAssembler.java @@ -1,18 +1,22 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.sample.buffer; import io.github.dsheirer.dsp.filter.channelizer.SampleTimestampManager; @@ -27,6 +31,7 @@ import java.nio.FloatBuffer; */ public class ReusableComplexBufferAssembler { +// private final static Logger mLog = LoggerFactory.getLogger(ReusableComplexBufferAssembler.class); private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("ReusableComplexBufferAssembler"); private SampleTimestampManager mTimestampManager; private FloatBuffer mBuffer; diff --git a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerEditor.java b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerEditor.java index 253fa784..c67a2340 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerEditor.java +++ b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerEditor.java @@ -79,6 +79,7 @@ public class AirspyTunerEditor extends TunerConfigurationEditor private JCheckBox mLNAAGC; private JCheckBox mMixerAGC; + private FrequencyCorrectionChangeListener mFrequencyCorrectionChangeListener = new FrequencyCorrectionChangeListener(); private AirspyTunerController mController; private boolean mLoading; @@ -194,26 +195,7 @@ public class AirspyTunerEditor extends TunerConfigurationEditor format.setMinimumFractionDigits(1); editor.getTextField().setHorizontalAlignment(SwingConstants.CENTER); - mFrequencyCorrection.addChangeListener(new ChangeListener() - { - @Override - public void stateChanged(ChangeEvent e) - { - final double value = ((SpinnerNumberModel)mFrequencyCorrection - .getModel()).getNumber().doubleValue(); - - try - { - mController.setFrequencyCorrection(value); - } - catch(SourceException e1) - { - mLog.error("Error setting frequency correction value", e1); - } - - save(); - } - }); + mFrequencyCorrection.addChangeListener(mFrequencyCorrectionChangeListener); add(new JLabel("PPM:")); add(mFrequencyCorrection); @@ -713,7 +695,9 @@ public class AirspyTunerEditor extends TunerConfigurationEditor //Update enabled state to reflect when frequency and sample rate controls are locked mSampleRateCombo.setEnabled(!mController.isLocked()); + mFrequencyCorrection.removeChangeListener(mFrequencyCorrectionChangeListener); mFrequencyCorrection.setValue(airspy.getFrequencyCorrection()); + mFrequencyCorrection.addChangeListener(mFrequencyCorrectionChangeListener); GainMode mode = Gain.getGainMode(airspy.getGain()); @@ -793,4 +777,26 @@ public class AirspyTunerEditor extends TunerConfigurationEditor return sb.toString(); } + + private class FrequencyCorrectionChangeListener implements ChangeListener + { + + @Override + public void stateChanged(ChangeEvent e) + { + final double value = ((SpinnerNumberModel)mFrequencyCorrection + .getModel()).getNumber().doubleValue(); + + try + { + mController.setFrequencyCorrection(value); + } + catch(SourceException e1) + { + mLog.error("Error setting frequency correction value", e1); + } + + save(); + } + } } \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java b/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java index 94ee1924..11c90384 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java @@ -1,18 +1,22 @@ -/******************************************************************************* - * sdr-trunk +/* + * ****************************************************************************** + * sdrtrunk * Copyright (C) 2014-2018 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ package io.github.dsheirer.source.tuner.usb; import io.github.dsheirer.sample.Listener; @@ -29,6 +33,8 @@ import org.usb4java.Transfer; import org.usb4java.TransferCallback; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -42,13 +48,12 @@ public class USBTransferProcessor implements TransferCallback private static final long USB_TIMEOUT_MS = 2000l; //milliseconds //Number of native byte buffers to allocate for transferring data from the USB device - private static final int TRANSFER_BUFFER_POOL_SIZE = 20; - private static final int MAX_TRANSFERS_IN_PROGRESS_SIZE = 10; - private int mTransfersToSubmit = 0; + private static final int TRANSFER_BUFFER_POOL_SIZE = 40; private LinkedTransferQueue mAvailableTransfers = new LinkedTransferQueue<>(); private LinkedTransferQueue mInProgressTransfers = new LinkedTransferQueue<>(); private LinkedTransferQueue mCompletedTransfers = new LinkedTransferQueue<>(); + private List mTransfersToSubmit = new ArrayList<>(); //Tuner format-specific byte buffer to IQ float sample converter private NativeBufferConverter mNativeBufferConverter; @@ -67,6 +72,8 @@ public class USBTransferProcessor implements TransferCallback private ScheduledFuture mRestartFuture; private String mDeviceName; + private int mTransferErrorLoggingCount = 0; + /** * Manages stream of USB transfer buffers and converts buffers to complex buffer samples for distribution to @@ -119,9 +126,9 @@ public class USBTransferProcessor implements TransferCallback { if(mRunning.compareAndSet(false, true)) { + prepareDeviceStart(); prepareTransfers(); - mTransfersToSubmit = MAX_TRANSFERS_IN_PROGRESS_SIZE; submitTransfers(); //Start transferred buffer dispatcher @@ -138,6 +145,7 @@ public class USBTransferProcessor implements TransferCallback */ private void stop() { + mLog.debug("stop() was invoked"); if(mRunning.compareAndSet(true, false)) { mBufferDispatcherFuture.cancel(true); @@ -161,15 +169,6 @@ public class USBTransferProcessor implements TransferCallback //Unregister from LibUSB processor so that it auto-stops LibUSB processing TunerManager.LIBUSB_TRANSFER_PROCESSOR.unregisterTransferProcessor(this); - //Directly invoke the timeout handler to ensure that our cancelled transfer buffers are flushed. - int result = LibUsb.handleEventsTimeoutCompleted(null, USB_TIMEOUT_MS, - mLibUsbHandlerStatus.asIntBuffer()); - - if(result != LibUsb.SUCCESS) - { - mLog.error(mDeviceName + " - error while cancelling transfer buffers during shutdown/pause - error code:" + result); - } - mLibUsbHandlerStatus.rewind(); executeDeviceStop(); @@ -182,8 +181,10 @@ public class USBTransferProcessor implements TransferCallback */ private void restart() { + mLog.debug("Stopping for a restart ...."); stop(); + mLog.debug("Attempting to clear halt condition ..."); //Attempt to clear any halt condition LibUsb.clearHalt(mUsbBulkTransferDeviceHandle, USB_BULK_TRANSFER_ENDPOINT); @@ -215,55 +216,50 @@ public class USBTransferProcessor implements TransferCallback { if(mRunning.get()) { - int submitted = 0; + mAvailableTransfers.drainTo(mTransfersToSubmit); - for(int x = 0; x < mTransfersToSubmit; x++) + for(Transfer transfer: mTransfersToSubmit) { - Transfer transfer = mAvailableTransfers.poll(); + int result = LibUsb.submitTransfer(transfer); - if(transfer != null) + if(result == LibUsb.SUCCESS) { - int result = LibUsb.submitTransfer(transfer); + mInProgressTransfers.add(transfer); + } + else if(result == LibUsb.ERROR_PIPE) + { + mLog.warn("USB pipe error - attempting to clear halt on USB device [" + mDeviceName + "]"); - if(result == LibUsb.SUCCESS) + int resetResult = LibUsb.clearHalt(mUsbBulkTransferDeviceHandle, USB_BULK_TRANSFER_ENDPOINT); + + if(resetResult == LibUsb.SUCCESS) { - mInProgressTransfers.add(transfer); - submitted++; - } - else if(result == LibUsb.ERROR_PIPE) - { - mLog.warn("USB pipe error - attempting to clear halt on USB device [" + mDeviceName + "]"); + int resubmitResult = LibUsb.submitTransfer(transfer); - int resetResult = LibUsb.clearHalt(mUsbBulkTransferDeviceHandle, USB_BULK_TRANSFER_ENDPOINT); - - if(resetResult == LibUsb.SUCCESS) + if(resubmitResult == LibUsb.SUCCESS) { - int resubmitResult = LibUsb.submitTransfer(transfer); - - if(resubmitResult == LibUsb.SUCCESS) - { - mInProgressTransfers.add(transfer); - submitted++; - } - else - { - mLog.error(mDeviceName + " - error resubmitting transfer after endpoint clear halt"); - } + mInProgressTransfers.add(transfer); } else { - mLog.error(mDeviceName + " - unable to clear device endpoint halt"); + mAvailableTransfers.add(transfer); + mLog.error(mDeviceName + " - error resubmitting transfer after endpoint clear halt"); } } else { - //TODO: broadcast to each listener that this source has an error and is shutting down - mLog.error(mDeviceName + "- error submitting transfer [" + LibUsb.errorName(result) + "]"); + mAvailableTransfers.add(transfer); + mLog.error(mDeviceName + " - unable to clear device endpoint halt"); } } + else + { + mAvailableTransfers.add(transfer); + mLog.error(mDeviceName + "- error submitting transfer [" + LibUsb.errorName(result) + "]"); + } } - mTransfersToSubmit -= submitted; + mTransfersToSubmit.clear(); } } @@ -355,6 +351,31 @@ public class USBTransferProcessor implements TransferCallback mAvailableTransfers.add(transfer); } break; + case LibUsb.TRANSFER_ERROR: + if(transfer.actualLength() > 0) + { + mCompletedTransfers.add(transfer); + } + else + { + transfer.buffer().rewind(); + mAvailableTransfers.add(transfer); + } + + mTransferErrorLoggingCount++; + + if(mTransferErrorLoggingCount <= 5) + { + //TODO: this error situation should be raised/highlighted to the user for action in the gui ... + mLog.warn("USB buffer transfer error detected. You should try using a different USB port for the usb-based tuner"); + + //Handle integer overflow + if(mTransferErrorLoggingCount < 0) + { + mTransferErrorLoggingCount = 1; + } + } + break; case LibUsb.TRANSFER_CANCELLED: transfer.buffer().rewind(); mAvailableTransfers.add(transfer); @@ -368,11 +389,6 @@ public class USBTransferProcessor implements TransferCallback restart(); return; } - - mTransfersToSubmit++; - - //Dispatch a new transfer - submitTransfers(); } /** @@ -432,6 +448,8 @@ public class USBTransferProcessor implements TransferCallback transfer.buffer().rewind(); mAvailableTransfers.add(transfer); + submitTransfers(); + //Fetch the next transfer to process transfer = mCompletedTransfers.poll(); } diff --git a/src/main/java/io/github/dsheirer/spectrum/OverflowableBufferStream.java b/src/main/java/io/github/dsheirer/spectrum/OverflowableBufferStream.java index af6077d2..46c90006 100644 --- a/src/main/java/io/github/dsheirer/spectrum/OverflowableBufferStream.java +++ b/src/main/java/io/github/dsheirer/spectrum/OverflowableBufferStream.java @@ -209,7 +209,6 @@ public class OverflowableBufferStream extends OverflowableTransferQueue - * - ******************************************************************************/ + * ***************************************************************************** + */ package io.github.dsheirer.spectrum; import io.github.dsheirer.controller.channel.Channel; import io.github.dsheirer.controller.channel.Channel.ChannelType; import io.github.dsheirer.controller.channel.ChannelEvent; -import io.github.dsheirer.controller.channel.ChannelEventListener; import io.github.dsheirer.controller.channel.ChannelModel; +import io.github.dsheirer.controller.channel.ChannelProcessingManager; +import io.github.dsheirer.sample.Listener; import io.github.dsheirer.settings.ColorSetting; import io.github.dsheirer.settings.ColorSetting.ColorSettingName; import io.github.dsheirer.settings.Setting; @@ -31,8 +33,6 @@ import io.github.dsheirer.settings.SettingsManager; import io.github.dsheirer.source.ISourceEventProcessor; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.tuner.channel.TunerChannel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.swing.JPanel; import java.awt.BasicStroke; @@ -50,14 +50,14 @@ import java.awt.geom.Line2D; import java.awt.geom.Rectangle2D; import java.text.DecimalFormat; import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -public class OverlayPanel extends JPanel implements ChannelEventListener, ISourceEventProcessor, - SettingChangeListener +public class OverlayPanel extends JPanel implements Listener, ISourceEventProcessor, SettingChangeListener { private static final long serialVersionUID = 1L; - private final static Logger mLog = LoggerFactory.getLogger(OverlayPanel.class); +// private final static Logger mLog = LoggerFactory.getLogger(OverlayPanel.class); private final DecimalFormat PPM_FORMATTER = new DecimalFormat( "#.0" ); private final static RenderingHints RENDERING_HINTS = new RenderingHints(RenderingHints.KEY_ANTIALIASING, @@ -92,7 +92,9 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, ISourc private Color mColorSpectrumLine; //Currently visible/displayable channels - private CopyOnWriteArrayList mVisibleChannels = new CopyOnWriteArrayList(); + private List mVisibleChannels = new CopyOnWriteArrayList<>(); + private List mTrafficChannels = new CopyOnWriteArrayList<>(); + private ChannelDisplay mChannelDisplay = ChannelDisplay.ALL; //Defines the offset at the bottom of the spectral display to account for @@ -102,13 +104,14 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, ISourc private SettingsManager mSettingsManager; private ChannelModel mChannelModel; + private ChannelProcessingManager mChannelProcessingManager; /** * Translucent overlay panel for displaying channel configurations, * processing channels, selected channels, frequency labels and lines, and * a cursor with a frequency readout. */ - public OverlayPanel(SettingsManager settingsManager, ChannelModel channelModel) + public OverlayPanel(SettingsManager settingsManager, ChannelModel channelModel, ChannelProcessingManager channelProcessingManager) { mSettingsManager = settingsManager; @@ -121,7 +124,14 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, ISourc if(mChannelModel != null) { - mChannelModel.addListener(this); + mChannelModel.addListener(this::receive); + } + + mChannelProcessingManager = channelProcessingManager; + + if(mChannelProcessingManager != null) + { + mChannelProcessingManager.addChannelEventListener(this::receive); } addComponentListener(mLabelSizeMonitor); @@ -668,14 +678,21 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, ISourc */ mVisibleChannels.clear(); mVisibleChannels.addAll(mChannelModel.getChannelsInFrequencyRange(getMinFrequency(), getMaxFrequency())); + + for(Channel trafficChannel: mTrafficChannels) + { + if(trafficChannel.isWithin(getMinFrequency(), getMaxFrequency())) + { + mVisibleChannels.add(trafficChannel); + } + } } /** * Channel change event handler */ @Override - @SuppressWarnings("incomplete-switch") - public void channelChanged(ChannelEvent event) + public void receive(ChannelEvent event) { Channel channel = event.getChannel(); @@ -683,6 +700,10 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, ISourc { case NOTIFICATION_ADD: case NOTIFICATION_PROCESSING_START: + if(channel.getChannelType() == ChannelType.TRAFFIC && !mTrafficChannels.contains(channel)) + { + mTrafficChannels.add(channel); + } if(!mVisibleChannels.contains(channel) && channel.isWithin(getMinFrequency(), getMaxFrequency())) { mVisibleChannels.add(channel); @@ -692,9 +713,11 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, ISourc mVisibleChannels.remove(channel); break; case NOTIFICATION_PROCESSING_STOP: + case NOTIFICATION_PROCESSING_START_REJECTED: if(channel.getChannelType() == ChannelType.TRAFFIC) { mVisibleChannels.remove(channel); + mTrafficChannels.remove(channel); } break; case NOTIFICATION_CONFIGURATION_CHANGE: diff --git a/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java b/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java index c6888e31..963db996 100644 --- a/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java +++ b/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java @@ -126,7 +126,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener - ******************************************************************************/ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 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 io.github.dsheirer.util; import java.text.SimpleDateFormat; @@ -22,97 +24,99 @@ import java.util.Date; public class TimeStamp { - private static SimpleDateFormat mSDFDate = new SimpleDateFormat( "yyyyMMdd" ); - private static SimpleDateFormat mSDFTime = new SimpleDateFormat( "HHmmss" ); - private static SimpleDateFormat mSDFTimeWithMilliseconds = new SimpleDateFormat( "HHmmss.SSS" ); + public static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd"); + public static SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HHmmss"); + public static SimpleDateFormat DATE_WITH_MILLISECONDS_FORMAT = new SimpleDateFormat("HHmmss.SSS"); + public static SimpleDateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss"); + public static SimpleDateFormat DATE_TIME_MILLIS_FORMAT = new SimpleDateFormat("yyyyMMdd HHmmss.SSS"); - /** - * Returns the current system date formatted as yyyy-MM-dd - */ - public static String getFormattedDate() - { - return getFormattedDate( System.currentTimeMillis() ); - } + /** + * Returns the current system date formatted as yyyy-MM-dd + */ + public static synchronized String getFormattedDate() + { + return getFormattedDate(System.currentTimeMillis()); + } - /** - * Returns the timestamp formatted as a date of yyyy-MM-dd - */ - public static String getFormattedDate( long timestamp ) - { - return mSDFDate.format( new Date( timestamp ) ); - } - - /** - * Returns the current system time formatted as HH:mm:ss - */ - public static String getFormattedTime() - { - return getFormattedTime( System.currentTimeMillis() ); - } + /** + * Returns the timestamp formatted as a date of yyyy-MM-dd + */ + public static synchronized String getFormattedDate(long timestamp) + { + return DATE_FORMAT.format(new Date(timestamp)); + } - /** - * Returns the timestamp formatted as a time of HH:mm:ss - */ - public static String getFormattedTime( long timestamp ) - { - return mSDFTime.format( new Date( timestamp ) ); - } + /** + * Returns the current system time formatted as HH:mm:ss + */ + public static synchronized String getFormattedTime() + { + return getFormattedTime(System.currentTimeMillis()); + } - /** - * Returns the timestamp formatted as a time of HH:mm:ss - */ - public static String getFormattedTimeWithMilliseconds( long timestamp ) - { - return mSDFTimeWithMilliseconds.format( new Date( timestamp ) ); - } + /** + * Returns the timestamp formatted as a time of HH:mm:ss + */ + public static synchronized String getFormattedTime(long timestamp) + { + return TIME_FORMAT.format(new Date(timestamp)); + } - /** - * Returns current system time formatted as yyyy-MM-dd*HH:mm:ss - * with the * representing the separator attribute - */ - public static String getTimeStamp( String separator ) - { - return getTimeStamp( System.currentTimeMillis(), separator ); - } - - /** - * Returns timestamp formatted as yyyy-MM-dd*HH:mm:ss - * with the * representing the separator attribute - */ - public static String getTimeStamp( long timestamp, String separator ) - { - StringBuilder sb = new StringBuilder(); - - sb.append( getFormattedDate( timestamp ) ); - sb.append( separator ); - sb.append( getFormattedTime( timestamp ) ); + /** + * Returns the timestamp formatted as a time of HH:mm:ss + */ + public static synchronized String getFormattedTimeWithMilliseconds(long timestamp) + { + return DATE_WITH_MILLISECONDS_FORMAT.format(new Date(timestamp)); + } - return sb.toString(); - } - - /** - * Returns current system time formatted as yyyy-MM-dd*HH:mm:ss.SSS - * with the * representing the separator attribute - */ - public static String getLongTimeStamp( String separator ) - { - return getLongTimeStamp( System.currentTimeMillis(), separator ); - } - - /** - * Returns timestamp formatted as yyyy-MM-dd*HH:mm:ss.SSS - * with the * representing the separator attribute - */ - public static String getLongTimeStamp( long timestamp, String separator ) - { - StringBuilder sb = new StringBuilder(); - - sb.append( getFormattedDate( timestamp ) ); - sb.append( separator ); - sb.append( getFormattedTimeWithMilliseconds( timestamp ) ); + /** + * Returns current system time formatted as yyyy-MM-dd*HH:mm:ss + * with the * representing the separator attribute + */ + public static synchronized String getTimeStamp(String separator) + { + return getTimeStamp(System.currentTimeMillis(), separator); + } + + /** + * Returns timestamp formatted as yyyy-MM-dd*HH:mm:ss + * with the * representing the separator attribute + */ + public static synchronized String getTimeStamp(long timestamp, String separator) + { + StringBuilder sb = new StringBuilder(); + + sb.append(getFormattedDate(timestamp)); + sb.append(separator); + sb.append(getFormattedTime(timestamp)); + + return sb.toString(); + } + + /** + * Returns current system time formatted as yyyy-MM-dd*HH:mm:ss.SSS + * with the * representing the separator attribute + */ + public static synchronized String getLongTimeStamp(String separator) + { + return getLongTimeStamp(System.currentTimeMillis(), separator); + } + + /** + * Returns timestamp formatted as yyyy-MM-dd*HH:mm:ss.SSS + * with the * representing the separator attribute + */ + public static synchronized String getLongTimeStamp(long timestamp, String separator) + { + StringBuilder sb = new StringBuilder(); + + sb.append(getFormattedDate(timestamp)); + sb.append(separator); + sb.append(getFormattedTimeWithMilliseconds(timestamp)); + + return sb.toString(); + } - return sb.toString(); - } - } diff --git a/src/main/java/org/jdesktop/swingx/mapviewer/GeoPosition.java b/src/main/java/org/jdesktop/swingx/mapviewer/GeoPosition.java index de3b1db4..d85c8c02 100644 --- a/src/main/java/org/jdesktop/swingx/mapviewer/GeoPosition.java +++ b/src/main/java/org/jdesktop/swingx/mapviewer/GeoPosition.java @@ -10,43 +10,51 @@ package org.jdesktop.swingx.mapviewer; /** - * An immutable coordinate in the real (geographic) world, + * An immutable coordinate in the real (geographic) world, * composed of a latitude and a longitude. + * * @author rbair */ -public class GeoPosition { +public class GeoPosition +{ private double latitude; private double longitude; - + /** * Creates a new instance of GeoPosition from the specified * latitude and longitude. These are double values in decimal degrees, not * degrees, minutes, and seconds. Use the other constructor for those. + * * @param latitude a latitude value in decmial degrees * @param longitude a longitude value in decimal degrees */ - public GeoPosition(double latitude, double longitude) { + public GeoPosition(double latitude, double longitude) + { this.latitude = latitude; this.longitude = longitude; } // must be an array of length two containing lat then long in that order. + /** * Creates a new instance of GeoPosition from the specified * latitude and longitude as an array of two doubles, with the * latitude first. These are double values in decimal degrees, not * degrees, minutes, and seconds. Use the other constructor for those. + * * @param coords latitude and longitude as a double array of length two */ - public GeoPosition(double [] coords) { + public GeoPosition(double[] coords) + { this.latitude = coords[0]; this.longitude = coords[1]; } - + /** * Creates a new instance of GeoPosition from the specified - * latitude and longitude. + * latitude and longitude. * Each are specified as degrees, minutes, and seconds; not * as decimal degrees. Use the other constructor for those. + * * @param latDegrees the degrees part of the current latitude * @param latMinutes the minutes part of the current latitude * @param latSeconds the seconds part of the current latitude @@ -55,59 +63,80 @@ public class GeoPosition { * @param lonSeconds the seconds part of the current longitude */ public GeoPosition(int latDegrees, int latMinutes, int latSeconds, - int lonDegrees, int lonMinutes, int lonSeconds) { - this(latDegrees + (latMinutes + latSeconds/60.0)/60.0, - lonDegrees + (lonMinutes + lonSeconds/60.0)/60.0); + int lonDegrees, int lonMinutes, int lonSeconds) + { + this(latDegrees + (latMinutes + latSeconds / 60.0) / 60.0, + lonDegrees + (lonMinutes + lonSeconds / 60.0) / 60.0); } - + /** * Get the latitude as decimal degrees + * * @return the latitude as decimal degrees */ - public double getLatitude() { + public double getLatitude() + { return latitude; } - + /** * Get the longitude as decimal degrees + * * @return the longitude as decimal degrees */ - public double getLongitude() { + public double getLongitude() + { return longitude; } - + @Override - public int hashCode() - { - final int prime = 31; - int result = 1; - long temp; - temp = Double.doubleToLongBits(latitude); - result = prime * result + (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(longitude); - result = prime * result + (int) (temp ^ (temp >>> 32)); - return result; - } - - @Override - public boolean equals(Object obj) - { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof GeoPosition)) - return false; - GeoPosition other = (GeoPosition) obj; - if (Double.doubleToLongBits(latitude) != Double.doubleToLongBits(other.latitude)) - return false; - if (Double.doubleToLongBits(longitude) != Double.doubleToLongBits(other.longitude)) - return false; - return true; - } - + public int hashCode() + { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(latitude); + result = prime * result + (int)(temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(longitude); + result = prime * result + (int)(temp ^ (temp >>> 32)); + return result; + } + @Override - public String toString() { + public boolean equals(Object obj) + { + if(this == obj) + { + return true; + } + if(obj == null) + { + return false; + } + if(!(obj instanceof GeoPosition)) + { + return false; + } + GeoPosition other = (GeoPosition)obj; + if(Double.doubleToLongBits(latitude) != Double.doubleToLongBits(other.latitude)) + { + return false; + } + if(Double.doubleToLongBits(longitude) != Double.doubleToLongBits(other.longitude)) + { + return false; + } + return true; + } + + @Override + public String toString() + { return "[" + latitude + ", " + longitude + "]"; } + + public boolean isValid() + { + return -90.0 <= latitude && latitude <= 90.0 && -180.0 <= longitude && longitude <= 180.0; + } } \ No newline at end of file