diff --git a/src/main/java/io/github/dsheirer/channel/metadata/NowPlayingPanel.java b/src/main/java/io/github/dsheirer/channel/metadata/NowPlayingPanel.java index 7108c0e76..8200516fb 100644 --- a/src/main/java/io/github/dsheirer/channel/metadata/NowPlayingPanel.java +++ b/src/main/java/io/github/dsheirer/channel/metadata/NowPlayingPanel.java @@ -35,11 +35,11 @@ public class NowPlayingPanel extends JPanel { - private ChannelMetadataPanel mChannelMetadataPanel; - private ChannelDetailPanel mChannelDetailPanel; - private DecodeEventPanel mDecodeEventPanel; - private MessageActivityPanel mMessageActivityPanel; - private ChannelPowerPanel mChannelPowerPanel; + private final ChannelMetadataPanel mChannelMetadataPanel; + private final ChannelDetailPanel mChannelDetailPanel; + private final DecodeEventPanel mDecodeEventPanel; + private final MessageActivityPanel mMessageActivityPanel; + private final ChannelPowerPanel mChannelPowerPanel; /** * GUI panel that combines the currently decoding channels metadata table and viewers for channel details, diff --git a/src/main/java/io/github/dsheirer/filter/AllPassFilter.java b/src/main/java/io/github/dsheirer/filter/AllPassFilter.java index 945c5828c..f94f19edf 100644 --- a/src/main/java/io/github/dsheirer/filter/AllPassFilter.java +++ b/src/main/java/io/github/dsheirer/filter/AllPassFilter.java @@ -1,35 +1,61 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + package io.github.dsheirer.filter; -import java.util.Collections; -import java.util.List; +import java.util.function.Function; /** - * Implements a message filter that passes (true) all messages + * Implements an all-pass filter that automatically passes/allows any presented object. + * + * A key type of String is used, but that makes no difference in how this filter functions. */ -@SuppressWarnings( "rawtypes" ) -public class AllPassFilter extends Filter +public class AllPassFilter extends Filter { - public AllPassFilter() - { - super( "Allow All Messages" ); - } - - @Override - public boolean passes( T t ) + private static final String KEY = "Other/Unlisted"; + private final AllPassKeyExtractor mKeyExtractor = new AllPassKeyExtractor(); + + /** + * Constructor + * @param name to display for this filter + */ + public AllPassFilter(String name) { - return true; + super(name); + add(new FilterElement<>(KEY)); } @Override - public boolean canProcess( T t ) - { - return true; - } + public Function getKeyExtractor() + { + return mKeyExtractor; + } - @Override - @SuppressWarnings( "unchecked" ) - public List getFilterElements() - { - return Collections.EMPTY_LIST; - } + /** + * Key extractor that always returns the same (String)key. + */ + public class AllPassKeyExtractor implements Function + { + @Override + public String apply(T t) + { + return KEY; + } + } } diff --git a/src/main/java/io/github/dsheirer/filter/Filter.java b/src/main/java/io/github/dsheirer/filter/Filter.java index 9e84bd54b..6f99a05b8 100644 --- a/src/main/java/io/github/dsheirer/filter/Filter.java +++ b/src/main/java/io/github/dsheirer/filter/Filter.java @@ -1,55 +1,206 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + package io.github.dsheirer.filter; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.function.Function; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -public abstract class Filter implements IFilter +/** + * Base filter class that uses an extractor function to extract a filter key (K) from an object of type T and then + * lookup the filter element (rule) indicating if objects of this type are enabled. + * + * @param object type that this filter can process. + * @param key type used to lookup filter elements + */ +public abstract class Filter implements IFilter { - protected String mName; - protected boolean mEnabled = true; - - public Filter( String name ) - { - mName = name; - } - - /** - * Indicates if this filter is enabled or disabled - */ - public boolean isEnabled() - { - return mEnabled; - } - - /** - * Enables (true) or disables (false) this filter. An enabled filter will - * evaluate messages and a disabled filter will return false on all message - * test methods. - */ - public void setEnabled( boolean enabled ) - { - mEnabled = enabled; - } - - /** - * List of filter elements managed by this filter - */ - public abstract List> getFilterElements(); - - /** - * Name of this filter - */ - public String getName() - { - return mName; - } - - public void setName( String name ) - { - mName = name; - } - - public String toString() - { - return mName; - } + private static final Logger LOG = LoggerFactory.getLogger(Filter.class); + private String mName; + private Map> mFilterElementMap = new HashMap<>(); + private IFilterChangeListener mFilterChangeListener; + + /** + * Constructs an instance + * @param name of this filter + */ + public Filter(String name) + { + mName = name; + } + + /** + * Indicates if this filter contains a filter element matching the key value. + * @param key to check + * @return true if this filter has a matching filter element. + */ + protected boolean hasKey(K key) + { + return mFilterElementMap.containsKey(key); + } + + /** + * Key extractor function provided by the subclass. + * @return key extractor function + */ + public abstract Function getKeyExtractor(); + + @Override + public boolean passes(T t) + { + K key = getKeyExtractor().apply(t); + + if(mFilterElementMap.containsKey(key)) + { + return mFilterElementMap.get(key).isEnabled(); + } + + return false; + } + + /** + * Indicates if this filter can process the item T, meaning that it has a FilterElement that matches the key + * value for items of type T. + * @param t to test + * @return true if this filter can process item t + */ + @Override + public boolean canProcess(T t) + { + return hasKey(getKeyExtractor().apply(t)); + } + + @Override + public boolean isEnabled() + { + for(FilterElement filterElement: mFilterElementMap.values()) + { + if(filterElement.isEnabled()) + { + return true; + } + } + + return false; + } + + /** + * Adds the child filter element to this filter + * @param filterElement to add + */ + public void add(FilterElement filterElement) + { + if(mFilterElementMap.containsKey(filterElement.getElement())) + { + LOG.warn("Filter element for key [" + filterElement.getElement() + "] named [" + filterElement.getName() + + "] already exists - overwriting existing value"); + } + + mFilterElementMap.put(filterElement.getElement(), filterElement); + filterElement.register(mFilterChangeListener); + } + + /** + * List of filter elements managed by this filter + */ + public final List> getFilterElements() + { + return new ArrayList<>(mFilterElementMap.values()); + } + + /** + * Name of this filter + */ + public String getName() + { + return mName; + } + + /** + * Sets the name of this filter + * + * @param name to set + */ + public void setName(String name) + { + mName = name; + } + + /** + * Pretty version or name of this filter. + * + * @return name + */ + public String toString() + { + return mName; + } + + /** + * Count of enabled child filter elements. + * + * @return count + */ + @Override + public int getEnabledCount() + { + int count = 0; + + for(FilterElement filterElement : mFilterElementMap.values()) + { + if(filterElement.isEnabled()) + { + count++; + } + } + + return count; + } + + /** + * Count of child filter elements + * + * @return count + */ + @Override + public int getElementCount() + { + return mFilterElementMap.size(); + } + + /** + * Registers a filter change listener on this filter and all filter elements. + * @param listener to be notified of any filter changes. + */ + @Override + public void register(IFilterChangeListener listener) + { + mFilterChangeListener = listener; + + for(FilterElement child: mFilterElementMap.values()) + { + child.register(listener); + } + } } diff --git a/src/main/java/io/github/dsheirer/filter/FilterEditor.java b/src/main/java/io/github/dsheirer/filter/FilterEditor.java new file mode 100644 index 000000000..2d5ffec60 --- /dev/null +++ b/src/main/java/io/github/dsheirer/filter/FilterEditor.java @@ -0,0 +1,71 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.filter; + +import java.awt.Component; +import net.miginfocom.swing.MigLayout; + +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JScrollPane; + +/** + * Filter editor + * @param item type for editing + */ +public class FilterEditor extends JFrame +{ + private FilterEditorPanel mEditorPanel; + + /** + * Constructor + * @param title for the editor window frame + * @param owner to register the popup location + * @param filterSet to use initially + */ + public FilterEditor(String title, Component owner, FilterSet filterSet) + { + if(filterSet == null) + { + throw new IllegalArgumentException("Unable to construct FilterEditor - FilterSet cannot be null"); + } + setTitle(title); + setSize(600, 400); + setLocationRelativeTo(owner); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + setLayout(new MigLayout("", "[grow,fill]", "[grow,fill][]")); + mEditorPanel = new FilterEditorPanel<>(filterSet); + JScrollPane scroller = new JScrollPane(mEditorPanel); + scroller.setViewportView(mEditorPanel); + add(scroller, "wrap"); + JButton close = new JButton("Close"); + close.addActionListener(e -> dispose()); + add(close); + } + + /** + * Updates this editor with the filterset. + * @param filterSet to use in this editor. + */ + public void updateFilterSet(FilterSet filterSet) + { + mEditorPanel.updateFilterSet(filterSet); + } +} diff --git a/src/main/java/io/github/dsheirer/filter/FilterEditorPanel.java b/src/main/java/io/github/dsheirer/filter/FilterEditorPanel.java index d3842102e..a1260a7e3 100644 --- a/src/main/java/io/github/dsheirer/filter/FilterEditorPanel.java +++ b/src/main/java/io/github/dsheirer/filter/FilterEditorPanel.java @@ -1,100 +1,128 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + package io.github.dsheirer.filter; +import java.awt.Component; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.EventObject; +import java.util.List; import net.miginfocom.swing.MigLayout; 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.TreeNode; import javax.swing.tree.TreeSelectionModel; -import java.awt.Component; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.util.Collections; -import java.util.Comparator; -import java.util.Enumeration; -import java.util.EventObject; -import java.util.List; +/** + * Editor panel for managing the state of a filter set. + * + * @param element type for the filter set. + */ public class FilterEditorPanel extends JPanel { private static final long serialVersionUID = 1L; - private JTree mTree; private DefaultTreeModel mModel; private FilterSet mFilterSet; + /** + * Constructs an instance + * @param filterSet to manage + */ public FilterEditorPanel(FilterSet filterSet) { setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]")); - mFilterSet = filterSet; - init(); } + /** + * Initializes the panel and the tree model. + */ 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()); - - mTree.getSelectionModel().setSelectionMode( - TreeSelectionModel.SINGLE_TREE_SELECTION); - + mTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); mTree.setCellRenderer(new EditorTreeCellRenderer()); - - mTree.setCellEditor(new FilterTreeNodeEditor()); - + mTree.setCellEditor(new FilterTreeCellEditor()); mTree.setEditable(true); - add(mTree); } + /** + * Updates the filter set for this editor panel + * @param filterSet to use + */ + public void updateFilterSet(FilterSet filterSet) + { + remove(mTree); + mFilterSet = filterSet; + init(); + revalidate(); + } + + /** + * Adds the filter set as a child tree node to the parent. + * @param filterSet to add to the tree + * @param parent node for the filter set child tree node. + */ private void addFilterSet(FilterSet filterSet, DefaultMutableTreeNode parent) { List> filters = filterSet.getFilters(); - /* sort the filters in alphabetical order by name */ - filters.sort(Comparator.comparing(IFilter::getName)); - for(IFilter filter : filters) { - DefaultMutableTreeNode child = new DefaultMutableTreeNode(filter); - - mModel.insertNodeInto(child, parent, parent.getChildCount()); + DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(filter); + mModel.insertNodeInto(childNode, parent, parent.getChildCount()); - if(filter instanceof FilterSet) + if(filter instanceof FilterSet childFilterSet) { - addFilterSet((FilterSet) filter, child); + addFilterSet(childFilterSet, childNode); } - else if(filter instanceof Filter) + else if(filter instanceof Filter childFilter) { - addFilter((Filter) filter, child); + addFilter(childFilter, childNode); } } } - private void addFilter(Filter filter, DefaultMutableTreeNode parent) + /** + * Adds the filter as a child tree node to the parent. + * + * @param filter to add + * @param parent tree node + */ + private void addFilter(Filter filter, DefaultMutableTreeNode parent) { List> elements = filter.getFilterElements(); @@ -103,168 +131,164 @@ private void addFilter(Filter filter, DefaultMutableTreeNode parent) for(FilterElement element : elements) { DefaultMutableTreeNode child = new DefaultMutableTreeNode(element); - mModel.insertNodeInto(child, parent, parent.getChildCount()); } } - public class MouseHandler implements MouseListener + /** + * Accesses the tree's root tree node. + * @return root node. + */ + private DefaultMutableTreeNode getRoot() { - public MouseHandler() + if(mTree.getModel().getRoot() instanceof DefaultMutableTreeNode root) { + return root; } - @Override - public void mouseClicked(MouseEvent event) + return null; + } + + /** + * 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 FilterElement filterElement) + { + filterElement.setEnabled(enabled); + } + + /* Recursively set the children of this node */ + Enumeration children = node.children(); + + while(children.hasMoreElements()) { - if(SwingUtilities.isRightMouseButton(event)) + Object child = children.nextElement(); + + if(child instanceof DefaultMutableTreeNode childNode) { - int row = mTree.getRowForLocation(event.getX(), - event.getY()); + setFilterEnabled(model, childNode, enabled); + } + } - if(row != -1) - { - mTree.setSelectionRow(row); + model.nodeChanged(node); + } - Object selectedNode = - mTree.getLastSelectedPathComponent(); + /** + * Recursively searches the tree to find the tree node containing the specified filter element. + * + * @param node to start search + * @param element to find + */ + private DefaultMutableTreeNode findFilterElementNode(DefaultMutableTreeNode node, FilterElement element) + { + if(node != null) + { + if(node.getUserObject() instanceof FilterElement selfFilterElement && selfFilterElement.equals(element)) + { + return node; + } + + for(int x = 0; x < node.getChildCount(); x++) + { + if(node.getChildAt(x) instanceof DefaultMutableTreeNode child) + { + DefaultMutableTreeNode found = findFilterElementNode(child, element); - if(selectedNode instanceof DefaultMutableTreeNode) + if(found != null) { - 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()); - } + return found; } } } } - @Override - public void mousePressed(MouseEvent e) - { - } + return null; + } - @Override - public void mouseReleased(MouseEvent e) - { - } + /** + * Updates the display value for the specified node and all parents in the nodes parentage. + * @param node to update + */ + private void updateParentage(DefaultMutableTreeNode node) + { + mModel.nodeChanged(node); - @Override - public void mouseEntered(MouseEvent e) - { - } + TreeNode parent = node.getParent(); - @Override - public void mouseExited(MouseEvent e) + while(parent != null) { + mModel.nodeChanged(parent); + parent = parent.getParent(); } } /** - * Recursively sets the enabled state on all filter node children below the - * specified parent node. + * Recursively searches the tree to find the tree node containing the specified filter. * - * @param model - model containing the tree nodes - * @param node - parent node - * @param enabled - true or false to enable or disable the filters + * @param node to start search + * @param filter to find */ - private void setFilterEnabled(DefaultTreeModel model, - DefaultMutableTreeNode node, - boolean enabled) + private DefaultMutableTreeNode findFilterNode(DefaultMutableTreeNode node, IFilter filter) { - 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()) + if(node != null) { - Object child = children.nextElement(); + if(node.getUserObject() instanceof IFilter selfFilter && selfFilter.equals(filter)) + { + return node; + } - if(child instanceof DefaultMutableTreeNode) + for(int x = 0; x < node.getChildCount(); x++) { - setFilterEnabled(model, (DefaultMutableTreeNode) child, enabled); + if(node.getChildAt(x) instanceof DefaultMutableTreeNode child) + { + DefaultMutableTreeNode found = findFilterNode(child, filter); + + if(found != null) + { + return found; + } + } } } + else + { + System.out.println("Can't evaluate - node is null"); + } + + return null; } + /** + * Custom cell renderer + */ 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) + 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) + if(userObject instanceof IFilter filter) { - IFilter filter = (IFilter) userObject; - - checkBox = new JCheckBox(filter.getName()); + checkBox = new FilterCheckBox(filter); checkBox.setSelected(filter.isEnabled()); } - else if(userObject instanceof FilterElement) + else if(userObject instanceof FilterElement element) { - FilterElement element = (FilterElement) userObject; - - checkBox = new JCheckBox(element.getName()); + checkBox = new FilterElementCheckBox(element); checkBox.setSelected(element.isEnabled()); } @@ -285,95 +309,110 @@ else if(userObject instanceof FilterElement) } } - return super.getTreeCellRendererComponent(tree, treeNode, selected, - expanded, leaf, row, hasFocus); + return super.getTreeCellRendererComponent(tree, treeNode, selected, expanded, leaf, row, hasFocus); } } + /** + * Check box used in filter tree nodes + */ public class FilterCheckBox extends JCheckBox { private static final long serialVersionUID = 1L; - - private IFilter mFilter; - - public FilterCheckBox(IFilter filter) + private IFilter mFilter; + + /** + * Constructs an instance + * + * @param filter + */ + public FilterCheckBox(IFilter filter) { - super(filter.getName()); - mFilter = filter; - setSelected(mFilter.isEnabled()); + addItemListener(e -> { + DefaultMutableTreeNode selfNode = findFilterNode(getRoot(), mFilter); - addItemListener(new ItemListener() - { - @Override - public void itemStateChanged(ItemEvent e) + if(selfNode != null) { - mFilter.setEnabled(FilterCheckBox.this.isSelected()); + setFilterEnabled(mModel, selfNode, FilterCheckBox.this.isSelected()); + updateLabel(); + updateParentage(selfNode); } }); } + + @Override + public void setSelected(boolean b) + { + super.setSelected(b); + updateLabel(); + } + + public void updateLabel() + { + setText(mFilter.getName() + " (" + mFilter.getEnabledCount() + "/" + mFilter.getElementCount() + ")"); + } } + /** + * Check box used in filter element tree nodes + */ public class FilterElementCheckBox extends JCheckBox { private static final long serialVersionUID = 1L; - private FilterElement mFilter; + /** + * Constructs an instance + * + * @param filter element + */ public FilterElementCheckBox(FilterElement filter) { super(filter.getName()); - mFilter = filter; - setSelected(mFilter.isEnabled()); - - addItemListener(new ItemListener() - { - @Override - public void itemStateChanged(ItemEvent arg0) + addItemListener(arg0 -> { + mFilter.setEnabled(FilterElementCheckBox.this.isSelected()); + DefaultMutableTreeNode self = findFilterElementNode(getRoot(), mFilter); + if(self != null) { - mFilter.setEnabled(FilterElementCheckBox.this.isSelected()); + updateParentage(self); } }); } } - public class FilterTreeNodeEditor implements TreeCellEditor + /** + * Editor for filter tree nodes + */ + public class FilterTreeCellEditor implements TreeCellEditor { @Override - @SuppressWarnings({"rawtypes", "unchecked"}) - public Component getTreeCellEditorComponent(JTree tree, Object node, - boolean isSelected, boolean expanded, boolean leaf, int row) + public Component getTreeCellEditorComponent(JTree tree, Object node, boolean isSelected, boolean expanded, boolean leaf, int row) { - if(node instanceof DefaultMutableTreeNode) + if(node instanceof DefaultMutableTreeNode mutableTreeNode) { - Object userObject = ((DefaultMutableTreeNode) node).getUserObject(); + Object userObject = mutableTreeNode.getUserObject(); - if(userObject instanceof IFilter) + if(userObject instanceof IFilter filter) { - return new FilterCheckBox((IFilter) userObject); + return new FilterCheckBox(filter); } - else if(userObject instanceof FilterElement) + else if(userObject instanceof FilterElement filterElement) { - return new FilterElementCheckBox((FilterElement) userObject); + return new FilterElementCheckBox(filterElement); } } - return new JLabel(node.toString()); } @Override - public void addCellEditorListener(CellEditorListener l) - { - } + public void addCellEditorListener(CellEditorListener l) {} @Override - public void cancelCellEditing() - { - - } + public void cancelCellEditing() {} @Override public Object getCellEditorValue() @@ -388,9 +427,7 @@ public boolean isCellEditable(EventObject anEvent) } @Override - public void removeCellEditorListener(CellEditorListener l) - { - } + public void removeCellEditorListener(CellEditorListener l) {} @Override public boolean shouldSelectCell(EventObject anEvent) diff --git a/src/main/java/io/github/dsheirer/filter/FilterElement.java b/src/main/java/io/github/dsheirer/filter/FilterElement.java index 394b793c9..9f95e6db9 100644 --- a/src/main/java/io/github/dsheirer/filter/FilterElement.java +++ b/src/main/java/io/github/dsheirer/filter/FilterElement.java @@ -1,55 +1,148 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + package io.github.dsheirer.filter; +/** + * Filter element provides filtering and enabled state tracking for an individual filterable element. + * + * @param element to track and filter. + */ public class FilterElement implements Comparable> { - private boolean mEnabled; - private T mElement; - - public FilterElement( T element, boolean enabled ) - { - mElement = element; - mEnabled = enabled; - } - - public FilterElement( T t ) - { - this( t, true ); - } - - public T getElement() - { - return mElement; - } - - public String toString() - { - return getName(); - } - - public boolean isEnabled() - { - return mEnabled; - } - - public void setEnabled( boolean enabled ) - { - mEnabled = enabled; - } - - public String getName() - { - return mElement.toString(); - } - - @Override - public int compareTo( FilterElement other ) - { - return toString().compareTo( other.toString() ); + private boolean mEnabled; + private final T mElement; + private IFilterChangeListener mFilterChangeListener; + + /** + * Constructs an instance + * + * @param element to track + * @param enabled enabled state of this filter element + */ + public FilterElement(T element, boolean enabled) + { + mElement = element; + mEnabled = enabled; + } + + /** + * Constructs an instance with a default state of enabled (true). + * + * @param t element to track + */ + public FilterElement(T t) + { + this(t, true); + } + + /** + * Registers the listener with each child filter element. + * + * @param listener to register + */ + public void register(IFilterChangeListener listener) + { + mFilterChangeListener = listener; + } + + /** + * Element being filtered. + * + * @return element + */ + public T getElement() + { + return mElement; } + /** + * Name or display string for the element. + * + * @return string version of the element. + */ + public String toString() + { + return getName(); + } + + /** + * Indicates if this filter is enabled indicating that elements of this type will pass this filter. + * + * @return enabled state. + */ + public boolean isEnabled() + { + return mEnabled; + } + + /** + * Sets the enabled state of this filter. + * + * @param enabled true to allow this filter's element type to pass the filter. + */ + public void setEnabled(boolean enabled) + { + mEnabled = enabled; + + if(mFilterChangeListener != null) + { + mFilterChangeListener.filterChanged(); + } + } + + /** + * Name of the tracked element. + * + * @return name + */ + public String getName() + { + return mElement.toString(); + } + + /** + * Implements compareto interface for ordering of filter elements. + * + * @param other the object to be compared. + * @return comparison value + */ @Override - public boolean equals(Object o) { - if (!(o instanceof FilterElement)) return false; - return compareTo((FilterElement) o) == 0; + public int compareTo(FilterElement other) + { + return toString().compareTo(other.toString()); + } + + /** + * Implement equals interface for comparison of filter elements. + * + * @param o to compare + * @return comparison + */ + @Override + public boolean equals(Object o) + { + if(o instanceof FilterElement fe) + { + return compareTo(fe) == 0; + } + + return false; } } diff --git a/src/main/java/io/github/dsheirer/filter/FilterSet.java b/src/main/java/io/github/dsheirer/filter/FilterSet.java index ef8a1f639..8609c491f 100644 --- a/src/main/java/io/github/dsheirer/filter/FilterSet.java +++ b/src/main/java/io/github/dsheirer/filter/FilterSet.java @@ -1,108 +1,239 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + package io.github.dsheirer.filter; +import io.github.dsheirer.log.LoggingSuppressor; import java.util.ArrayList; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +/** + * Filter set is a parent container for filters. + * + * @param type of element tracked/filtered by this filter set. + */ public class FilterSet implements IFilter { - protected List> mFilters = new ArrayList>(); - protected boolean mEnabled = true; - protected String mName; - - public FilterSet( String name ) - { - mName = name; - } - - public FilterSet() - { - mName = "Message Filter"; - } - - public FilterSet(IFilter filter) + private static final Logger LOGGER = LoggerFactory.getLogger(FilterSet.class); + private static LoggingSuppressor sLoggingSuppressor = new LoggingSuppressor(LOGGER); + private List> mFilters = new ArrayList<>(); + private String mName; + private IFilterChangeListener mFilterChangeListener; + + /** + * Constructs an instance of a filter set. + * + * @param name of this filter set. + */ + public FilterSet(String name) { + mName = name; + } + + /** + * Constructs an instance with a single filter using the default 'Filters' name. + * + * @param filter for this filter set. + */ + public FilterSet(IFilter filter) + { + this("Filters"); addFilter(filter); } - - public String getName() - { - return mName; - } - - public void setName( String name ) - { - mName = name; - } - - public String toString() - { - return getName(); - } - + + /** + * Registers the listener with each child filter element. + * @param listener to register + */ + public void register(IFilterChangeListener listener) + { + mFilterChangeListener = listener; + + for(IFilter child: mFilters) + { + child.register(listener); + } + } + + /** + * Name of this filter set. + * + * @return name + */ + public String getName() + { + return mName; + } + + /** + * Sets the name of this filter set. + * + * @param name to set + */ + public void setName(String name) + { + mName = name; + } + + /** + * Name for this filter set. + * + * @return name + */ + public String toString() + { + return getName(); + } + + /** + * Indicates if the element passes at least one of the child filters in this filter set. + * + * @param t to evaluate + * @return true if the argument passes a child filter. + */ @Override - public boolean passes( T t ) - { - if( mEnabled ) + public boolean passes(T t) + { + for(IFilter filter : mFilters) { - for( IFilter filter: mFilters ) + //Stop at the first filter that says that it can process the message type. + if(filter.canProcess(t)) { - // Make sure to loop through all filters. - // Only return if true or all filters have been evaluated. - if( filter.canProcess( t ) && filter.passes( t )) - { - return true; - } + return filter.passes(t); } } + return false; - } + } + /** + * Indicates if this filter set contains at least one filter that can evaluate the type of argument that is presented. + * + * @param t element to evaluate + * @return true if this filter set can evaluate objects of the argument's type. + */ @Override - public boolean canProcess( T t ) - { - if( mEnabled ) + public boolean canProcess(T t) + { + for(IFilter filter : mFilters) { - for( IFilter filter: mFilters ) + if(filter.canProcess(t)) { - if( filter.canProcess( t ) ) - { - return true; - } + return true; } } - + + sLoggingSuppressor.info(t.getClass().getSimpleName(), 1, "FilterSet [" + + this.getClass().getSimpleName() + "] has no filter element for items of type [" + + t.getClass().getSimpleName() + "]"); + return false; - } - + } + + /** + * Child filters + * + * @return filters + */ public List> getFilters() { - return mFilters; + return new ArrayList<>(mFilters); } - - public void addFilters( List> filters ) + + /** + * Adds a list of child filters to this filter set. + * @param filters to add + */ + public void addFilters(List> filters) { - mFilters.addAll( filters ); + for(IFilter filter: filters) + { + addFilter(filter); + } } - - public void addFilter( IFilter filter ) + + /** + * Adds the child filter to this filter set. + * @param filter to add + */ + public void addFilter(IFilter filter) { - mFilters.add( filter ); + if(filter instanceof Filter filterInstance && filterInstance.getKeyExtractor() == null) + { + LOGGER.warn("Filter [" + filter.getClass() + "] has a null key extractor"); + } + + mFilters.add(filter); + filter.register(mFilterChangeListener); } - - public void removeFilter( IFilter filter ) + + /** + * Indicates if any of the child filters contain at least one filter element that is enabled. + * @return enabled state + */ + @Override + public boolean isEnabled() { - mFilters.remove( filter ); + for(IFilter filter: mFilters) + { + if(filter.isEnabled()) + { + return true; + } + } + + return false; } + /** + * Recursive count of enabled child filter elements. + * @return count + */ @Override - public boolean isEnabled() - { - return mEnabled; - } + public int getEnabledCount() + { + int count = 0; + + for(IFilter filter: mFilters) + { + count += filter.getEnabledCount(); + } + + return count; + } + /** + * Recursive count of child filter elements. + * @return count + */ @Override - public void setEnabled( boolean enabled ) - { - mEnabled = enabled; - } + public int getElementCount() + { + int count = 0; + + for(IFilter filter: mFilters) + { + count += filter.getElementCount(); + } + + return count; + } } diff --git a/src/main/java/io/github/dsheirer/filter/IFilter.java b/src/main/java/io/github/dsheirer/filter/IFilter.java index b8596a291..1cba93205 100644 --- a/src/main/java/io/github/dsheirer/filter/IFilter.java +++ b/src/main/java/io/github/dsheirer/filter/IFilter.java @@ -1,36 +1,75 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + package io.github.dsheirer.filter; +/** + * Filter interface + * + * @param type of filter + */ public interface IFilter { - /** - * Generic filter method. - * - * @param message - message to filter - * @return - true if the message passes the filter - */ - public boolean passes( T t ); - - /** - * Indicates if the filter can process (filter) the object - * - * @param message - candidate message for filtering - * - * @return - true if the filter is capable of filtering the message - */ - public boolean canProcess( T t ); - - /** - * Indicates if this filter is enabled to evaluate messages - */ - public boolean isEnabled(); - - /** - * Sets the enabled state of the filter - */ - public void setEnabled( boolean enabled ); - - /** - * Display name for the filter - */ - public String getName(); + /** + * Generic filter method. + * + * @param t - message to filter + * @return - true if the message passes the filter + */ + boolean passes(T t); + + /** + * Indicates if the filter can process (filter) the object + * + * @param t - candidate message for filtering + * @return - true if the filter is capable of filtering the message + */ + boolean canProcess(T t); + + /** + * Indicates if this filter is enabled to evaluate messages + */ + boolean isEnabled(); + + /** + * Display name for the filter + */ + String getName(); + + /** + * (Recursive) count of enabled child filter elements. + * + * @return total enable count. + */ + int getEnabledCount(); + + /** + * (Recursive) count of total child filter elements. + * + * @return total count of child filter elements. + */ + int getElementCount(); + + /** + * Registers the listener to be notified when the enabled state of any child filter elements is updated. + * + * @param listener to be notified + */ + void register(IFilterChangeListener listener); } diff --git a/src/main/java/io/github/dsheirer/filter/IFilterChangeListener.java b/src/main/java/io/github/dsheirer/filter/IFilterChangeListener.java new file mode 100644 index 000000000..db36f7846 --- /dev/null +++ b/src/main/java/io/github/dsheirer/filter/IFilterChangeListener.java @@ -0,0 +1,25 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.filter; + +public interface IFilterChangeListener +{ + void filterChanged(); +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/SyncLossMessageFilter.java b/src/main/java/io/github/dsheirer/filter/SyncLossMessageFilter.java similarity index 55% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/SyncLossMessageFilter.java rename to src/main/java/io/github/dsheirer/filter/SyncLossMessageFilter.java index be973b5ca..e33d53860 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/SyncLossMessageFilter.java +++ b/src/main/java/io/github/dsheirer/filter/SyncLossMessageFilter.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * * This 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,30 +14,36 @@ * * You 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.phase1.message.filter; +package io.github.dsheirer.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.function.Function; -import java.util.Collections; -import java.util.List; - -public class SyncLossMessageFilter extends Filter +/** + * Filter for sync-loss messages + */ +public class SyncLossMessageFilter extends Filter { + private static final String SYNC_LOSS_KEY = "Sync-Loss"; + private KeyExtractor mKeyExtractor = new KeyExtractor(); + + /** + * Constructor + */ public SyncLossMessageFilter() { - super("Sync Loss"); + super("Sync Loss Messages"); + add(new FilterElement<>(SYNC_LOSS_KEY)); } @Override - public boolean passes(IMessage message) + public Function getKeyExtractor() { - return mEnabled && canProcess(message); + return mKeyExtractor; } @Override @@ -47,9 +52,20 @@ public boolean canProcess(IMessage message) return message instanceof SyncLossMessage; } - @Override - public List> getFilterElements() + /** + * Key extractor + */ + private class KeyExtractor implements Function { - return Collections.EMPTY_LIST; + @Override + public String apply(IMessage message) + { + if(message instanceof SyncLossMessage) + { + return SYNC_LOSS_KEY; + } + + return null; + } } } diff --git a/src/main/java/io/github/dsheirer/module/HistoryModule.java b/src/main/java/io/github/dsheirer/module/HistoryModule.java index b79cb36b1..accc47ad5 100644 --- a/src/main/java/io/github/dsheirer/module/HistoryModule.java +++ b/src/main/java/io/github/dsheirer/module/HistoryModule.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2023 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,7 +21,6 @@ import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; - import java.util.ArrayList; import java.util.List; @@ -50,7 +49,7 @@ public HistoryModule(int maximumHistorySize) */ public List getItems() { - return new ArrayList<>(mItems); + return new ArrayList<>(mItems); } @Override diff --git a/src/main/java/io/github/dsheirer/module/ProcessingChain.java b/src/main/java/io/github/dsheirer/module/ProcessingChain.java index cf9935a98..13b6d3e36 100644 --- a/src/main/java/io/github/dsheirer/module/ProcessingChain.java +++ b/src/main/java/io/github/dsheirer/module/ProcessingChain.java @@ -117,8 +117,8 @@ public class ProcessingChain implements Listener private Broadcaster mMessageBroadcaster = new Broadcaster<>(); private Broadcaster mSquelchStateEventBroadcaster = new Broadcaster<>(); private AtomicBoolean mRunning = new AtomicBoolean(); - private DecodeEventHistory mDecodeEventHistory = new DecodeEventHistory(500); - private MessageHistory mMessageHistory = new MessageHistory(500); + private DecodeEventHistory mDecodeEventHistory = new DecodeEventHistory(200); + private MessageHistory mMessageHistory = new MessageHistory(200); private AbstractChannelState mChannelState; private EventBus mEventBus; protected Source mSource; 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 551f59946..20a6729de 100644 --- a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java @@ -47,6 +47,7 @@ import io.github.dsheirer.module.decode.dmr.DMRTrafficChannelManager; import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR; import io.github.dsheirer.module.decode.dmr.audio.DMRAudioModule; +import io.github.dsheirer.module.decode.dmr.message.filter.DmrMessageFilterSet; import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2Decoder; import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2DecoderState; import io.github.dsheirer.module.decode.fleetsync2.FleetsyncMessageFilter; @@ -80,16 +81,18 @@ import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderC4FM; import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderLSM; import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderState; -import io.github.dsheirer.module.decode.p25.phase1.message.filter.P25MessageFilterSet; +import io.github.dsheirer.module.decode.p25.phase1.message.filter.P25P1MessageFilterSet; import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2; import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderHDQPSK; import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderState; +import io.github.dsheirer.module.decode.p25.phase2.message.filter.P25P2MessageFilterSet; import io.github.dsheirer.module.decode.passport.DecodeConfigPassport; import io.github.dsheirer.module.decode.passport.PassportDecoder; import io.github.dsheirer.module.decode.passport.PassportDecoderState; import io.github.dsheirer.module.decode.passport.PassportMessageFilter; import io.github.dsheirer.module.decode.tait.Tait1200Decoder; import io.github.dsheirer.module.decode.tait.Tait1200DecoderState; +import io.github.dsheirer.module.decode.tait.Tait1200MessageFilter; import io.github.dsheirer.module.decode.traffic.TrafficChannelManager; import io.github.dsheirer.module.demodulate.fm.FMDemodulatorModule; import io.github.dsheirer.preference.UserPreferences; @@ -494,7 +497,7 @@ public static List getAuxiliaryDecoders(AuxDecodeConfiguration config) */ public static FilterSet getMessageFilters(List modules) { - FilterSet filterSet = new FilterSet<>(); + FilterSet filterSet = new FilterSet<>("Message Filters"); for(Module module : modules) { @@ -504,28 +507,28 @@ public static FilterSet getMessageFilters(List modules) } } - /* If we don't have any filters, add an ALL-PASS filter */ - if(filterSet.getFilters().isEmpty()) - { - filterSet.addFilter(new AllPassFilter<>()); - } + //Add an all-others filter as a catch-all for anything that isn't handled by the decoder filters. + filterSet.addFilter(new AllPassFilter<>("All Other Messages Filter")); return filterSet; } /** * Returns a set of IMessageFilter objects (FilterSets or Filters) that - * can process all of the messages produced by the specified decoder type. + * can process all messages produced by the specified decoder type. */ public static List> getMessageFilter(DecoderType decoder) { - ArrayList> filters = new ArrayList<>(); + List> filters = new ArrayList<>(); switch(decoder) { case DCS: filters.add(new DCSMessageFilter()); break; + case DMR: + filters.add(new DmrMessageFilterSet()); + break; case FLEETSYNC2: filters.add(new FleetsyncMessageFilter()); break; @@ -545,13 +548,16 @@ public static List> getMessageFilter(DecoderType decoder) filters.add(new MPT1327MessageFilter()); break; case P25_PHASE1: - filters.add(new P25MessageFilterSet()); + filters.add(new P25P1MessageFilterSet()); + break; + case P25_PHASE2: + filters.add(new P25P2MessageFilterSet()); break; case PASSPORT: filters.add(new PassportMessageFilter()); break; - case DMR: - //filters.add(new DMR) //todo: not finished + case TAIT_1200: + filters.add(new Tait1200MessageFilter()); break; default: break; diff --git a/src/main/java/io/github/dsheirer/module/decode/analog/AnalogDecoderState.java b/src/main/java/io/github/dsheirer/module/decode/analog/AnalogDecoderState.java index b07fd1f42..c2bb750a0 100644 --- a/src/main/java/io/github/dsheirer/module/decode/analog/AnalogDecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/analog/AnalogDecoderState.java @@ -26,6 +26,7 @@ import io.github.dsheirer.identifier.IdentifierCollection; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.p25.identifier.channel.StandardChannel; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.source.ISourceEventListener; @@ -100,9 +101,8 @@ private void startCallEvent() if(mDecodeEvent == null) { - mDecodeEvent = DecodeEvent.builder(System.currentTimeMillis()) + mDecodeEvent = DecodeEvent.builder(DecodeEventType.CALL, System.currentTimeMillis()) .channel(mChannelDescriptor) - .eventDescription("CALL") .details(getDecoderType().name()) .identifiers(new IdentifierCollection(getIdentifierCollection().getIdentifiers())) .build(); diff --git a/src/main/java/io/github/dsheirer/module/decode/dcs/DCSMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/dcs/DCSMessageFilter.java index ea5a64f6d..af0812a8e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dcs/DCSMessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/dcs/DCSMessageFilter.java @@ -22,45 +22,55 @@ import io.github.dsheirer.filter.Filter; import io.github.dsheirer.filter.FilterElement; import io.github.dsheirer.message.IMessage; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.function.Function; /** * Message filter for Digital Coded Squelch (DCS) */ -public class DCSMessageFilter extends Filter +public class DCSMessageFilter extends Filter { - private Map> mElements = new HashMap<>(); - private static final String DCS_MESSAGE_FILTER_TAG = "DCS"; + private static final String DCS_KEY = "DCS"; + private final KeyExtractor mKeyExtractor = new KeyExtractor(); + /** + * Constructor + */ public DCSMessageFilter() { - super("DCS Message Filter"); - mElements.put(DCS_MESSAGE_FILTER_TAG, new FilterElement<>("DCS Code", true)); + super("DCS Messages"); + add(new FilterElement(DCS_KEY)); } + /** + * Indicates that this filter can process DCS messages. + * @param message to test + * @return true if the message can be processed + */ @Override - public boolean passes(IMessage message) + public boolean canProcess(IMessage message) { - if(mEnabled && canProcess(message)) - { - return mElements.get(DCS_MESSAGE_FILTER_TAG).isEnabled(); - } - - return false; + return message instanceof DCSMessage && super.canProcess(message); } + /** + * Key extractor that always returns the same key constant. + * @return extractor function. + */ @Override - public boolean canProcess(IMessage message) + public Function getKeyExtractor() { - return message instanceof DCSMessage; + return mKeyExtractor; } - @Override - public List> getFilterElements() + /** + * Key extractor + */ + private class KeyExtractor implements Function { - return new ArrayList>(mElements.values()); + @Override + public String apply(IMessage message) + { + return DCS_KEY; + } } } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java index 57b4fe2c3..4bfd1b65a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java @@ -286,9 +286,7 @@ private void processSMS(UDTShortMessageService sms) { broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot())); - DecodeEvent smsEvent = DMRDecodeEvent.builder(sms.getTimestamp()) - .eventType(DecodeEventType.SMS) - .eventDescription(DecodeEventType.SMS.toString()) + DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, sms.getTimestamp()) .details("MESSAGE: " + sms.getSMS()) .identifiers(new IdentifierCollection(sms.getIdentifiers())) .timeslot(sms.getTimeslot()) @@ -312,8 +310,7 @@ private void processPacket(DMRPacketMessage packet) mic.update(hyteraSmsPacket.getSource()); mic.update(hyteraSmsPacket.getDestination()); - DecodeEvent smsEvent = DMRDecodeEvent.builder(packet.getTimestamp()) - .eventDescription(DecodeEventType.SMS.name()) + DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, packet.getTimestamp()) .identifiers(mic) .timeslot(packet.getTimeslot()) .details("SMS:" + hyteraSmsPacket.getSMS()) @@ -325,8 +322,7 @@ else if(packet.getPacket() instanceof HyteraUnknownPacket hyteraUnknownPacket) { MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers()); - DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(packet.getTimestamp()) - .eventDescription(DecodeEventType.UNKNOWN_PACKET.name()) + DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(DecodeEventType.UNKNOWN_PACKET, packet.getTimestamp()) .identifiers(mic) .timeslot(packet.getTimeslot()) .details("HYTERA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString()) @@ -335,8 +331,7 @@ else if(packet.getPacket() instanceof HyteraUnknownPacket hyteraUnknownPacket) } else { - DecodeEvent packetEvent = DMRDecodeEvent.builder(packet.getTimestamp()) - .eventDescription(DecodeEventType.DATA_PACKET.name()) + DecodeEvent packetEvent = DMRDecodeEvent.builder(DecodeEventType.DATA_PACKET, packet.getTimestamp()) .identifiers(getMergedIdentifierCollection(packet.getIdentifiers())) .timeslot(packet.getTimeslot()) .details(packet.toString()) @@ -347,9 +342,8 @@ else if(packet.getPacket() instanceof HyteraUnknownPacket hyteraUnknownPacket) GeoPosition geoPosition = PacketUtil.extractGeoPosition(packet.getPacket()); if (geoPosition != null) { - PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(packet.getTimestamp()) + PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, packet.getTimestamp()) .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.GPS.toString()) .identifiers(getMergedIdentifierCollection(packet.getIdentifiers())) .protocol(Protocol.LRRP) .location(geoPosition) @@ -505,14 +499,8 @@ private void processCSBK(CSBKMessage csbk) case STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_PAYLOAD: if(csbk instanceof Acknowledge) { - DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.RESPONSE.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details(((Acknowledge)csbk).getReason().toString()) - .build(); - - broadcast(ackEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE, + ((Acknowledge) csbk).getReason().toString())); } broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot())); break; @@ -520,14 +508,8 @@ private void processCSBK(CSBKMessage csbk) case STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_TSCC: if(csbk instanceof Acknowledge) { - DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.RESPONSE.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details(((Acknowledge)csbk).getReason().toString()) - .build(); - - broadcast(ackEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE, + ((Acknowledge) csbk).getReason().toString())); } broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot())); break; @@ -537,33 +519,16 @@ private void processCSBK(CSBKMessage csbk) switch(((Ahoy)csbk).getServiceKind()) { case AUTHENTICATE_REGISTER_RADIO_CHECK_SERVICE: - DecodeEvent registerEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details(DecodeEventType.REGISTER.toString()) - .build(); - broadcast(registerEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND, DecodeEventType.REGISTER.toString())); break; case CANCEL_CALL_SERVICE: - DecodeEvent cancelEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details("CANCEL CALL") - .build(); - broadcast(cancelEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND, "CANCEL CALL")); break; case SUPPLEMENTARY_SERVICE: if(csbk instanceof StunReviveKill) { - DecodeEvent stunEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details(((StunReviveKill)csbk).getCommand() + " RADIO") - .build(); - broadcast(stunEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND, + ((StunReviveKill)csbk).getCommand() + " RADIO")); } break; case FULL_DUPLEX_MS_TO_MS_PACKET_CALL_SERVICE: @@ -577,15 +542,9 @@ private void processCSBK(CSBKMessage csbk) if(csbk instanceof ServiceRadioCheck) { ServiceRadioCheck src = (ServiceRadioCheck)csbk; - - DecodeEvent checkEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.RADIO_CHECK.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details(src.getServiceDescription() + " SERVICE FOR " + - (src.isTalkgroupTarget() ? "TALKGROUP" : "RADIO")) - .build(); - broadcast(checkEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.RADIO_CHECK, + src.getServiceDescription() + " SERVICE FOR " + + (src.isTalkgroupTarget() ? "TALKGROUP" : "RADIO"))); } break; } @@ -599,14 +558,7 @@ private void processCSBK(CSBKMessage csbk) if(aloha.hasRadioIdentifier()) { - DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.RESPONSE.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details("Aloha Acknowledge") - .build(); - - broadcast(ackEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE, "Aloha Acknowledge")); resetState(); } } @@ -618,24 +570,13 @@ private void processCSBK(CSBKMessage csbk) switch(((Announcement)csbk).getAnnouncementType()) { case MASS_REGISTRATION: - DecodeEvent massEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.REGISTER.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details("MASS REGISTRATION") - .build(); - broadcast(massEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.REGISTER, "MASS REGISTRATION")); break; case VOTE_NOW_ADVICE: if(csbk instanceof VoteNowAdvice) { - DecodeEvent voteEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details("VOTE NOW FOR " + ((VoteNowAdvice)csbk).getVotedSystemIdentityCode()) - .build(); - broadcast(voteEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND, + "VOTE NOW FOR " + ((VoteNowAdvice)csbk).getVotedSystemIdentityCode())); } break; } @@ -653,13 +594,8 @@ private void processCSBK(CSBKMessage csbk) case STANDARD_PROTECT: if(csbk instanceof Protect) { - DecodeEvent protectEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details("PROTECT: " + ((Protect)csbk).getProtectKind()) - .build(); - broadcast(protectEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND, + "PROTECT: " + ((Protect)csbk).getProtectKind())); } broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot())); break; @@ -714,13 +650,7 @@ private void processCSBK(CSBKMessage csbk) if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers())) { - event = DMRDecodeEvent.builder(csbk.getTimestamp()) - .channel(channel) - .details(csbk.getOpcode().getLabel()) - .eventDescription(DecodeEventType.DATA_CALL.toString()) - .identifiers(mergedIdentifiers) - .timeslot(channel.getTimeslot()) - .build(); + event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel, mergedIdentifiers); mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event); } else @@ -753,13 +683,7 @@ private void processCSBK(CSBKMessage csbk) if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers())) { - event = DMRDecodeEvent.builder(csbk.getTimestamp()) - .channel(channel) - .details(csbk.getOpcode().getLabel()) - .eventDescription(DecodeEventType.CALL_GROUP.toString()) - .identifiers(mergedIdentifiers) - .timeslot(channel.getTimeslot()) - .build(); + event = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel, mergedIdentifiers); mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event); } else @@ -791,13 +715,7 @@ private void processCSBK(CSBKMessage csbk) if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers())) { - event = DMRDecodeEvent.builder(csbk.getTimestamp()) - .channel(channel) - .details(csbk.getOpcode().getLabel()) - .eventDescription(DecodeEventType.CALL_UNIT_TO_UNIT.toString()) - .identifiers(mergedIdentifiers) - .timeslot(channel.getTimeslot()) - .build(); + event = getDecodeEvent(csbk, DecodeEventType.CALL_UNIT_TO_UNIT, channel, mergedIdentifiers); mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event); } else @@ -817,15 +735,7 @@ private void processCSBK(CSBKMessage csbk) if(cmAloha.hasRadioIdentifier()) { - DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.RESPONSE.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details("Aloha Acknowledge") - .build(); - - broadcast(ackEvent); - + broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE, "Aloha Acknowledge")); resetState(); } } @@ -850,13 +760,7 @@ private void processCSBK(CSBKMessage csbk) if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers())) { - event = DMRDecodeEvent.builder(csbk.getTimestamp()) - .channel(channel) - .details(csbk.getOpcode().getLabel()) - .eventDescription(DecodeEventType.DATA_CALL.toString()) - .identifiers(mergedIdentifiers) - .timeslot(channel.getTimeslot()) - .build(); + event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel, mergedIdentifiers); mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event); } else @@ -871,9 +775,8 @@ private void processCSBK(CSBKMessage csbk) broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot())); break; case MOTOROLA_CONPLUS_REGISTRATION_REQUEST: - DecodeEvent event = DMRDecodeEvent.builder(csbk.getTimestamp()) + DecodeEvent event = DMRDecodeEvent.builder(DecodeEventType.REQUEST, csbk.getTimestamp()) .details("Registration Request") - .eventDescription(DecodeEventType.REGISTER.toString()) .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) .timeslot(csbk.getTimeslot()) .build(); @@ -881,9 +784,8 @@ private void processCSBK(CSBKMessage csbk) broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot())); break; case MOTOROLA_CONPLUS_REGISTRATION_RESPONSE: - DecodeEvent regRespEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) + DecodeEvent regRespEvent = DMRDecodeEvent.builder(DecodeEventType.RESPONSE, csbk.getTimestamp()) .details("Registration Response") - .eventDescription(DecodeEventType.REGISTER.toString()) .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) .timeslot(csbk.getTimeslot()) .build(); @@ -909,13 +811,7 @@ private void processCSBK(CSBKMessage csbk) if(isStale(detectedEvent, csbk.getTimestamp(), csbk.getIdentifiers())) { - detectedEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .channel(channel) - .details(csbk.getOpcode().getLabel()) - .eventDescription(DecodeEventType.CALL_GROUP.toString()) - .identifiers(mergedIdentifiers) - .timeslot(channel.getTimeslot()) - .build(); + detectedEvent = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel, mergedIdentifiers); mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), detectedEvent); } else @@ -930,9 +826,8 @@ private void processCSBK(CSBKMessage csbk) broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot())); break; case MOTOROLA_CONPLUS_TALKGROUP_AFFILIATION: - DecodeEvent affiliateEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) + DecodeEvent affiliateEvent = DMRDecodeEvent.builder(DecodeEventType.AFFILIATE, csbk.getTimestamp()) .details("TALKGROUP AFFILIATION") - .eventDescription(DecodeEventType.AFFILIATE.toString()) .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) .timeslot(csbk.getTimeslot()) .build(); @@ -945,6 +840,23 @@ private void processCSBK(CSBKMessage csbk) } } + private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, DMRChannel channel, IdentifierCollection mergedIdentifiers) { + return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp()) + .channel(channel) + .details(csbk.getOpcode().getLabel()) + .identifiers(mergedIdentifiers) + .timeslot(channel.getTimeslot()) + .build(); + } + + private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, String details) { + return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp()) + .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) + .timeslot(csbk.getTimeslot()) + .details(details) + .build(); + } + /** * Creates a copy of the current identifier collection, removes any USER class identifiers and loads the identifiers * argument values into the collection. @@ -1191,8 +1103,7 @@ private void processLinkControl(LCMessage message, boolean isTerminator) MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); ic.update(gpsInformation.getGPSLocation()); - DecodeEvent gpsEvent = DMRDecodeEvent.builder(message.getTimestamp()) - .eventDescription(DecodeEventType.GPS.toString()) + DecodeEvent gpsEvent = DMRDecodeEvent.builder(DecodeEventType.GPS, message.getTimestamp()) .identifiers(ic) .timeslot(message.getTimeslot()) .details("LOCATION:" + gpsInformation.getGPSLocation()) @@ -1223,9 +1134,8 @@ private void updateCurrentCall(DecodeEventType type, String details, long timest if(mCurrentCallEvent == null) { - mCurrentCallEvent = DMRDecodeEvent.builder(timestamp) + mCurrentCallEvent = DMRDecodeEvent.builder(type, timestamp) .channel(getCurrentChannel()) - .eventDescription(type.toString()) .details(details) .identifiers(getIdentifierCollection().copyOf()) .build(); @@ -1236,7 +1146,6 @@ private void updateCurrentCall(DecodeEventType type, String details, long timest { if(type != DecodeEventType.CALL) { - mCurrentCallEvent.setEventDescription(type.toString()); mCurrentCallEvent.setDetails(details); } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java index ec6577f8b..eea406ff8 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java @@ -325,12 +325,12 @@ public void processChannelGrant(DMRChannel channel, IdentifierCollection identif int lsn = channel.getLogicalSlotNumber(); DMRChannelGrantEvent event = mLSNGrantEventMap.get(lsn); + DecodeEventType decodeEventType = getEventType(opcode, identifierCollection, encrypted); if(isStale(event, timestamp, identifierCollection)) //Create new event { - event = DMRChannelGrantEvent.channelGrantBuilder(timestamp) + event = DMRChannelGrantEvent.channelGrantBuilder(decodeEventType, timestamp) .channel(channel) - .eventDescription(getEventType(opcode, identifierCollection, encrypted).toString()) .details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : "")) .identifiers(identifierCollection) .build(); @@ -349,10 +349,9 @@ public void processChannelGrant(DMRChannel channel, IdentifierCollection identif { event.end(timestamp); - event = DMRChannelGrantEvent.channelGrantBuilder(timestamp) + event = DMRChannelGrantEvent.channelGrantBuilder(decodeEventType, timestamp) .channel(channel) - .eventDescription(getEventType(opcode, identifierCollection, encrypted).toString() + " - Continue") - .details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : "")) + .details("CONTINUE - CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : "")) .identifiers(identifierCollection) .build(); @@ -389,15 +388,6 @@ else if(!event.getDetails().endsWith(NO_FREQUENCY)) { if(mIgnoreDataCalls && opcode.isDataChannelGrantOpcode()) { - if(event.getEventDescription() == null) - { - event.setEventDescription(getEventType(opcode, identifierCollection, encrypted) + IGNORED); - } - else if(!event.getEventDescription().endsWith(IGNORED)) - { - event.setEventDescription(event.getEventDescription() + IGNORED); - } - if(event.getDetails() == null) { event.setDetails(DATA_CALL_IGNORED); diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/event/DMRChannelGrantEvent.java b/src/main/java/io/github/dsheirer/module/decode/dmr/event/DMRChannelGrantEvent.java index 5d1d1e772..c9ecddc11 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/event/DMRChannelGrantEvent.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/event/DMRChannelGrantEvent.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * * This 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,12 +14,13 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.module.decode.dmr.event; import io.github.dsheirer.identifier.IdentifierCollection; import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; +import io.github.dsheirer.module.decode.event.DecodeEventType; /** * DMR Channel Grant Event. @@ -33,11 +33,12 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent /** * Constructs an instance + * @param decodeEventType for the event * @param timestamp for the event */ - public DMRChannelGrantEvent(long timestamp) + public DMRChannelGrantEvent(DecodeEventType decodeEventType, long timestamp) { - super(timestamp); + super(decodeEventType, timestamp); } /** @@ -62,9 +63,9 @@ public void setEncrypted(boolean encrypted) * @param timeStart for the event * @return builder */ - public static DMRChannelGrantDecodeEventBuilder channelGrantBuilder(long timeStart) + public static DMRChannelGrantDecodeEventBuilder channelGrantBuilder(DecodeEventType decodeEventType, long timeStart) { - return new DMRChannelGrantDecodeEventBuilder(timeStart); + return new DMRChannelGrantDecodeEventBuilder(decodeEventType, timeStart); } /** @@ -74,7 +75,7 @@ public static class DMRChannelGrantDecodeEventBuilder { protected long mTimeStart; protected long mDuration; - protected String mEventDescription; + protected DecodeEventType mDecodeEventType; protected IdentifierCollection mIdentifierCollection; protected DMRChannel mChannel; protected String mDetails; @@ -85,8 +86,9 @@ public static class DMRChannelGrantDecodeEventBuilder * * @param timeStart */ - public DMRChannelGrantDecodeEventBuilder(long timeStart) + public DMRChannelGrantDecodeEventBuilder(DecodeEventType decodeEventType, long timeStart) { + mDecodeEventType = decodeEventType; mTimeStart = timeStart; } @@ -121,7 +123,7 @@ public DMRChannelGrantDecodeEventBuilder end(long timestamp) /** * Sets the channel descriptor for this event - * @param channelDescriptor + * @param channel */ public DMRChannelGrantDecodeEventBuilder channel(DMRChannel channel) { @@ -129,16 +131,6 @@ public DMRChannelGrantDecodeEventBuilder channel(DMRChannel channel) return this; } - /** - * Sets the event description text - * @param description of the event - */ - public DMRChannelGrantDecodeEventBuilder eventDescription(String description) - { - mEventDescription = description; - return this; - } - /** * Sets the identifier collection. * @param identifierCollection containing optional identifiers like TO, FROM, frequency and @@ -165,12 +157,11 @@ public DMRChannelGrantDecodeEventBuilder details(String details) */ public DMRChannelGrantEvent build() { - DMRChannelGrantEvent decodeEvent = new DMRChannelGrantEvent(mTimeStart); + DMRChannelGrantEvent decodeEvent = new DMRChannelGrantEvent(mDecodeEventType, mTimeStart); decodeEvent.setChannelDescriptor(mChannel); decodeEvent.setTimeslot(mChannel.getTimeslot()); 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/dmr/event/DMRDecodeEvent.java b/src/main/java/io/github/dsheirer/module/decode/dmr/event/DMRDecodeEvent.java index a69f0ab10..218fd5dde 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/event/DMRDecodeEvent.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/event/DMRDecodeEvent.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * * This 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,12 +14,13 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.module.decode.dmr.event; import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.protocol.Protocol; /** @@ -32,9 +32,9 @@ public class DMRDecodeEvent extends DecodeEvent * Constucts a decode event * @param start */ - public DMRDecodeEvent(long start) + public DMRDecodeEvent(DecodeEventType decodeEventType, long start) { - super(start); + super(decodeEventType, start); setProtocol(Protocol.DMR); } @@ -43,9 +43,9 @@ public DMRDecodeEvent(long start) * @param timeStart for the event * @return builder */ - public static DecodeEventBuilder builder(long timeStart) + public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart) { - DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(timeStart); + DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(decodeEventType, timeStart); decodeEventBuilder.protocol(Protocol.DMR); return decodeEventBuilder; } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/Opcode.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/Opcode.java index cdccf4193..ec8a601c9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/Opcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/Opcode.java @@ -39,7 +39,7 @@ public enum Opcode STANDARD_CHANNEL_TIMING(Vendor.STANDARD, 7, "CHANNEL TIMING"), STANDARD_ALOHA(Vendor.STANDARD, 25, "ALOHA"), STANDARD_UNIFIED_DATA_TRANSPORT_OUTBOUND_HEADER(Vendor.STANDARD, 26, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"), - STANDARD_UNIFIED_DATA_TRANSPORT_INBOUND_HEADER(Vendor.STANDARD, 27, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"), + STANDARD_UNIFIED_DATA_TRANSPORT_INBOUND_HEADER(Vendor.STANDARD, 27, "UNIFIED DATA TRANSPORT INBOUND HEADER"), STANDARD_AHOY(Vendor.STANDARD, 28, "AHOY"), STANDARD_ACTIVATION(Vendor.STANDARD, 30, "ACTIVATION"), STANDARD_RANDOM_ACCESS_SERVICE_REQUEST(Vendor.STANDARD, 31, "RANDOM ACCESS SERVICE REQUEST"), @@ -102,13 +102,18 @@ public enum Opcode HYTERA_68_XPT_PREAMBLE(Vendor.HYTERA_68, 61, "HYTERA 68 XPT PREAMBLE"), HYTERA_68_CSBKO_62(Vendor.HYTERA_68, 62, "HYTERA 68 CSBKO 62"), - UNKNOWN(Vendor.UNKNOWN, -1, "UNKNOWN"); private Vendor mVendor; private int mValue; private String mLabel; + /** + * Constructor + * @param vendor for the opcode + * @param value for the opcode + * @param label to display for the opcode + */ Opcode(Vendor vendor, int value, String label) { mVendor = vendor; @@ -151,19 +156,86 @@ public String toString() */ public boolean isDataChannelGrantOpcode() { - return DATA_CHANNEL_GRANT_OPCODES.contains(this); + return DATA_CHANNEL_GRANTS.contains(this); } /** - * Data Channel Grant opcodes + * Data channel grant opcodes + */ + public static final EnumSet DATA_CHANNEL_GRANTS = EnumSet.of(STANDARD_PRIVATE_DATA_CHANNEL_GRANT_MULTI_ITEM, + STANDARD_PRIVATE_DATA_CHANNEL_GRANT_SINGLE_ITEM, STANDARD_DUPLEX_PRIVATE_DATA_CHANNEL_GRANT, + STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_MULTI_ITEM, STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM); + + /** + * Data opcodes + */ + public static final EnumSet DATA_OPCODES = EnumSet.of(STANDARD_UNIFIED_DATA_TRANSPORT_OUTBOUND_HEADER, + STANDARD_UNIFIED_DATA_TRANSPORT_INBOUND_HEADER, STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_OUTBOUND_HEADER, + STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_INBOUND_HEADER, STANDARD_PREAMBLE); + + /** + * Mobile request and response opcodes + */ + public static final EnumSet MOBILE_REQUEST_RESPONSE = EnumSet.of(STANDARD_UNIT_TO_UNIT_VOICE_SERVICE_REQUEST, + STANDARD_RANDOM_ACCESS_SERVICE_REQUEST); + + /** + * Network request and response and announcement opcodes */ - public static final EnumSet DATA_CHANNEL_GRANT_OPCODES = EnumSet.of( - STANDARD_PRIVATE_DATA_CHANNEL_GRANT_MULTI_ITEM, - STANDARD_PRIVATE_DATA_CHANNEL_GRANT_SINGLE_ITEM, - STANDARD_DUPLEX_PRIVATE_DATA_CHANNEL_GRANT, - STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_MULTI_ITEM, - STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM); + public static final EnumSet NETWORK_REQUEST_RESPONSE = EnumSet.of(STANDARD_FEATURE_NOT_SUPPORTED, + STANDARD_UNIT_TO_UNIT_VOICE_SERVICE_RESPONSE, STANDARD_AHOY, STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_TSCC, + STANDARD_ACKNOWLEDGE_RESPONSE_INBOUND_TSCC, STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_PAYLOAD, + STANDARD_ACKNOWLEDGE_RESPONSE_INBOUND_PAYLOAD, STANDARD_NEGATIVE_ACKNOWLEDGE_RESPONSE, STANDARD_CLEAR, + STANDARD_MOVE_TSCC, STANDARD_CHANNEL_TIMING, + STANDARD_ALOHA, STANDARD_ACTIVATION, STANDARD_ANNOUNCEMENT, STANDARD_MAINTENANCE, STANDARD_PROTECT); + /** + * Voice channel grant opcodes + */ + public static final EnumSet VOICE_CHANNEL_GRANTS = EnumSet.of(STANDARD_PRIVATE_VOICE_CHANNEL_GRANT, + STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT, STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT, + STANDARD_DUPLEX_PRIVATE_VOICE_CHANNEL_GRANT); + + /** + * Hytera opcodes + */ + public static final EnumSet HYTERA = EnumSet.of(HYTERA_08_ACKNOWLEDGE, HYTERA_08_ANNOUNCEMENT, + HYTERA_08_CSBKO_44, HYTERA_08_CSBKO_47, HYTERA_68_XPT_SITE_STATE, HYTERA_68_ALOHA, + HYTERA_68_ACKNOWLEDGE, HYTERA_68_ANNOUNCEMENT, HYTERA_68_XPT_PREAMBLE, HYTERA_68_CSBKO_62); + + /** + * Motorola Capacity Max opcodes + */ + public static final EnumSet MOTOROLA_CAPACITY_MAX = EnumSet.of(MOTOROLA_CAPMAX_ALOHA); + + /** + * Motorola Capacity Plus opcodes + */ + public static final EnumSet MOTOROLA_CAPACITY_PLUS = EnumSet.of(MOTOROLA_CAPPLUS_CALL_ALERT, + MOTOROLA_CAPPLUS_CALL_ALERT_ACK, MOTOROLA_CAPPLUS_DATA_WINDOW_ANNOUNCEMENT, + MOTOROLA_CAPPLUS_DATA_WINDOW_GRANT, MOTOROLA_CAPPLUS_NEIGHBOR_REPORT, MOTOROLA_CAPPLUS_CSBKO_60, + MOTOROLA_CAPPLUS_PREAMBLE, MOTOROLA_CAPPLUS_SITE_STATUS); + + /** + * Motorola Connect Plus opcodes + */ + public static final EnumSet MOTOROLA_CONNECT_PLUS = EnumSet.of(MOTOROLA_CONPLUS_NEIGHBOR_REPORT, + MOTOROLA_CONPLUS_VOICE_CHANNEL_USER, MOTOROLA_CONPLUS_DATA_CHANNEL_GRANT, MOTOROLA_CONPLUS_CSBKO_10, + MOTOROLA_CONPLUS_TERMINATE_CHANNEL_GRANT, MOTOROLA_CONPLUS_CSBKO_16, MOTOROLA_CONPLUS_REGISTRATION_REQUEST, + MOTOROLA_CONPLUS_REGISTRATION_RESPONSE, MOTOROLA_CONPLUS_TALKGROUP_AFFILIATION, + MOTOROLA_CONPLUS_DATA_WINDOW_ANNOUNCEMENT, MOTOROLA_CONPLUS_DATA_WINDOW_GRANT); + + /** + * Indicates if the opcode is included in one of the enumset groupings above + * @return + */ + public boolean isGrouped() + { + return DATA_CHANNEL_GRANTS.contains(this) || DATA_OPCODES.contains(this) || + MOBILE_REQUEST_RESPONSE.contains(this) || NETWORK_REQUEST_RESPONSE.contains(this) || + VOICE_CHANNEL_GRANTS.contains(this) || HYTERA.contains(this) || MOTOROLA_CAPACITY_MAX.contains(this) || + MOTOROLA_CAPACITY_PLUS.contains(this) || MOTOROLA_CONNECT_PLUS.contains(this); + } /** * Lookup map of vendors and opcode value to opcodes diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java index ee1a51f5a..a35dab54c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java @@ -67,14 +67,14 @@ public enum LCOpcode //CAP+ SLCO 15 = Rest Channel https://forums.radioreference.com/threads/understanding-capacity-plus-trunking.209318/page-7 SHORT_CAPACITY_PLUS_REST_CHANNEL_NOTIFICATION(Vendor.MOTOROLA_CAPACITY_PLUS, false,15, "REST CHANNEL NOTIFICATION"), - SHORT_STANDARD_XPT_CHANNEL(Vendor.STANDARD, false, 8, "HYTERA XPT CHANNEL"), + SHORT_STANDARD_XPT_CHANNEL(Vendor.STANDARD, false, 8, "STANDARD XPT CHANNEL"), SHORT_HYTERA_XPT_CHANNEL(Vendor.HYTERA_68, false, 8, "HYTERA XPT CHANNEL"), SHORT_CONNECT_PLUS_TRAFFIC_CHANNEL(Vendor.STANDARD, false, 9, "TRAFFIC CHANNEL INFO"), SHORT_CONNECT_PLUS_CONTROL_CHANNEL(Vendor.STANDARD, false, 10, "CONTROL CHANNEL INFO"), - SHORT_STANDARD_UNKNOWN(Vendor.STANDARD,false,-1, "SHORT UNKNOWN"); + SHORT_STANDARD_UNKNOWN(Vendor.STANDARD,false,-1, "UNKNOWN"); private static final EnumSet TALKER_ALIAS_OPCODES = EnumSet.of(FULL_STANDARD_TALKER_ALIAS_HEADER, FULL_STANDARD_TALKER_ALIAS_BLOCK_1, FULL_STANDARD_TALKER_ALIAS_BLOCK_2, FULL_STANDARD_TALKER_ALIAS_BLOCK_3); diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/packet/PacketSequenceMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/packet/PacketSequenceMessageFactory.java index 667661f71..cb444b37d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/packet/PacketSequenceMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/packet/PacketSequenceMessageFactory.java @@ -63,7 +63,6 @@ public static IMessage create(PacketSequence packetSequence) if(packetSequence.hasPacketSequenceHeader()) { PacketSequenceHeader primaryHeader = packetSequence.getPacketSequenceHeader(); - boolean confirmed = primaryHeader.isConfirmedData(); CorrectedBinaryMessage packet = getPacket(packetSequence, primaryHeader.isConfirmedData()); if(packet != null) diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/ControlMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/ControlMessageFilter.java new file mode 100644 index 000000000..b0eec20ce --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/ControlMessageFilter.java @@ -0,0 +1,74 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.dmr.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.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode; +import java.util.Collection; +import java.util.function.Function; + +/** + * Filter for opcode based control (CSBK) messages. + */ +public class ControlMessageFilter extends Filter +{ + private KeyExtractor mKeyExtractor = new KeyExtractor(); + + /** + * Constructs an instance + * @param name of this control message filter + * @param opcodes to use with this filter + */ + public ControlMessageFilter(String name, Collection opcodes) + { + super(name); + + for(Opcode opcode: opcodes) + { + add(new FilterElement<>(opcode)); + } + } + + @Override + public Function getKeyExtractor() + { + return mKeyExtractor; + } + + /** + * Key extractor + */ + private class KeyExtractor implements Function + { + @Override + public Opcode apply(IMessage message) + { + if(message instanceof CSBKMessage csbk) + { + return csbk.getOpcode(); + } + + return null; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/ControlMessageFilterSet.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/ControlMessageFilterSet.java new file mode 100644 index 000000000..fb8b7ccb2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/ControlMessageFilterSet.java @@ -0,0 +1,68 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.dmr.message.filter; + +import io.github.dsheirer.filter.FilterSet; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode; +import java.util.ArrayList; +import java.util.List; + +/** + * Filter set for DMR control (CSBK) messages using Opcodes for filter elements. + */ +public class ControlMessageFilterSet extends FilterSet +{ + /** + * Constructor + */ + public ControlMessageFilterSet() + { + super("Control (CSBK) Messages"); + addFilter(new ControlMessageFilter("Data Channel Grants", Opcode.DATA_CHANNEL_GRANTS)); + addFilter(new ControlMessageFilter("Data-Related", Opcode.DATA_OPCODES)); + addFilter(new ControlMessageFilter("Mobile Request/Response", Opcode.MOBILE_REQUEST_RESPONSE)); + addFilter(new ControlMessageFilter("Network Request/Response", Opcode.NETWORK_REQUEST_RESPONSE)); + addFilter(new ControlMessageFilter("Voice Channel Grants", Opcode.VOICE_CHANNEL_GRANTS)); + addFilter(new ControlMessageFilter("Hytera", Opcode.HYTERA)); + addFilter(new ControlMessageFilter("Motorola Capacity Max", Opcode.MOTOROLA_CAPACITY_MAX)); + addFilter(new ControlMessageFilter("Motorola Capacity Plus", Opcode.MOTOROLA_CAPACITY_PLUS)); + addFilter(new ControlMessageFilter("Motorola Connect Plus", Opcode.MOTOROLA_CONNECT_PLUS)); + + //Add all remaining opcodes that are not grouped into one of the above enumsets. + List otherOpcodes = new ArrayList<>(); + for(Opcode opcode: Opcode.values()) + { + if(!opcode.isGrouped()) + { + otherOpcodes.add(opcode); + } + } + + addFilter(new ControlMessageFilter("Other/Unknown", otherOpcodes)); + } + + @Override + public boolean canProcess(IMessage message) + { + return message instanceof CSBKMessage && super.canProcess(message); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/DataMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/DataMessageFilter.java new file mode 100644 index 000000000..928ab8a04 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/DataMessageFilter.java @@ -0,0 +1,80 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.dmr.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.dmr.message.data.DataMessage; +import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; +import io.github.dsheirer.module.decode.dmr.message.type.DataType; +import java.util.function.Function; + +/** + * Filter for data messages, excluding CSBK messages. + */ +public class DataMessageFilter extends Filter +{ + private KeyExtractor mKeyExtractor = new KeyExtractor(); + + /** + * Constructs an instance + * + * @param name of this filter + */ + public DataMessageFilter() + { + super("Data Messages"); + + for(DataType dataType: DataType.values()) + { + add(new FilterElement<>(dataType)); + } + } + + @Override + public Function getKeyExtractor() + { + return mKeyExtractor; + } + + @Override + public boolean canProcess(IMessage message) + { + return message instanceof DataMessage && !(message instanceof CSBKMessage) && super.canProcess(message); + } + + /** + * Key extractor + */ + private class KeyExtractor implements Function + { + @Override + public DataType apply(IMessage message) + { + if(message instanceof DataMessage dataMessage) + { + return dataMessage.getSlotType().getDataType(); + } + + return null; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/DmrMessageFilterSet.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/DmrMessageFilterSet.java new file mode 100644 index 000000000..dc852e0c6 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/DmrMessageFilterSet.java @@ -0,0 +1,45 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.dmr.message.filter; + +import io.github.dsheirer.filter.FilterSet; +import io.github.dsheirer.filter.SyncLossMessageFilter; +import io.github.dsheirer.message.IMessage; + +/** + * Filter set for DMR messages + */ +public class DmrMessageFilterSet extends FilterSet +{ + /** + * Constructor + */ + public DmrMessageFilterSet() + { + super("DMR Messages"); + addFilter(new ControlMessageFilterSet()); + addFilter(new VoiceMessageFilter()); + addFilter(new DataMessageFilter()); + addFilter(new DmrPacketSequenceFilter()); + addFilter(new LinkControlMessageFilterSet()); + addFilter(new SyncLossMessageFilter()); + addFilter(new DmrOtherMessageFilter()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/DmrOtherMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/DmrOtherMessageFilter.java new file mode 100644 index 000000000..339edc3e4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/DmrOtherMessageFilter.java @@ -0,0 +1,73 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.dmr.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.dmr.message.DMRMessage; +import java.util.function.Function; + +/** + * Filter for unknown messages, meaning messages not handled by any other filter. + */ +public class DmrOtherMessageFilter extends Filter +{ + private static final String OTHER_KEY = "Other/Unknown Message"; + private KeyExtractor mKeyExtractor = new KeyExtractor(); + + /** + * Constructor + */ + public DmrOtherMessageFilter() + { + super("Other DMR messages"); + add(new FilterElement<>(OTHER_KEY)); + } + + @Override + public Function getKeyExtractor() + { + return mKeyExtractor; + } + + @Override + public boolean canProcess(IMessage message) + { + return message instanceof DMRMessage && super.canProcess(message); + } + + /** + * Key extractor + */ + private class KeyExtractor implements Function + { + @Override + public String apply(IMessage message) + { + if(message instanceof DMRMessage) + { + return OTHER_KEY; + } + + return null; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/DmrPacketSequenceFilter.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/DmrPacketSequenceFilter.java new file mode 100644 index 000000000..83cfc096e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/DmrPacketSequenceFilter.java @@ -0,0 +1,106 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.dmr.message.filter; + +import io.github.dsheirer.filter.Filter; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage; +import io.github.dsheirer.module.decode.dmr.message.data.packet.UDTShortMessageService; +import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket; +import io.github.dsheirer.module.decode.ip.hytera.sms.HyteraSmsPacket; +import io.github.dsheirer.module.decode.ip.mototrbo.ars.ARSPacket; +import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.LRRPPacket; +import io.github.dsheirer.module.decode.ip.mototrbo.xcmp.XCMPPacket; +import java.util.function.Function; + +/** + * Filter for DMR packet sequence messages + */ +public class DmrPacketSequenceFilter extends Filter +{ + private static final String KEY_ARS = "Automatic Registration Service (ARS)"; + private static final String KEY_HYTERA_SMS = "Hytera Short Message Service (SMS)"; + private static final String KEY_HYTERA_UNKNOWN = "Hytera Unknown"; + private static final String KEY_LRRP = "Location Request/Response Protocol (LRRP)"; + private static final String KEY_UDT_SMS = "Unified Data Transport - Short Message Service"; + private static final String KEY_XCMP = "Extensible Command Message Protocol (XCMP)"; + private static final String KEY_UNKNOWN = "Other/Unknown"; + + private KeyExtractor mKeyExtractor = new KeyExtractor(); + + /** + * Constructs an instance + */ + public DmrPacketSequenceFilter() + { + super("Packet Sequence Messages"); + } + + @Override + public boolean canProcess(IMessage message) + { + return message instanceof DMRPacketMessage && super.canProcess(message); + } + + @Override + public Function getKeyExtractor() + { + return mKeyExtractor; + } + + private class KeyExtractor implements Function + { + @Override + public String apply(IMessage message) + { + if(message instanceof UDTShortMessageService) + { + return KEY_UDT_SMS; + } + else if(message instanceof DMRPacketMessage packetMessage) + { + if(packetMessage.getPacket() instanceof ARSPacket) + { + return KEY_ARS; + } + else if(packetMessage.getPacket() instanceof LRRPPacket) + { + return KEY_LRRP; + } + else if(packetMessage.getPacket() instanceof XCMPPacket) + { + return KEY_XCMP; + } + else if(packetMessage.getPacket() instanceof HyteraSmsPacket) + { + return KEY_HYTERA_SMS; + } + else if(packetMessage.getPacket() instanceof HyteraUnknownPacket) + { + return KEY_HYTERA_UNKNOWN; + } + + //TODO: finish this + } + + return KEY_UNKNOWN; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/LinkControlMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/LinkControlMessageFilter.java new file mode 100644 index 000000000..708dc8e2b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/LinkControlMessageFilter.java @@ -0,0 +1,76 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.dmr.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.dmr.message.data.lc.LCMessage; +import io.github.dsheirer.module.decode.dmr.message.data.lc.LCOpcode; +import java.util.Collection; +import java.util.function.Function; + +/** + * Message filter for link control opcodes. + * + * Note: this does not include link control messages carried by data messages with embedded link control. + */ +public class LinkControlMessageFilter extends Filter +{ + private KeyExtractor mKeyExtractor = new KeyExtractor(); + + /** + * Constructs an instance + * + * @param name of this filter + */ + public LinkControlMessageFilter(String name, Collection opcodes) + { + super(name); + + for(LCOpcode opcode: opcodes) + { + add(new FilterElement<>(opcode)); + } + } + + @Override + public Function getKeyExtractor() + { + return mKeyExtractor; + } + + /** + * Key extractor + */ + private class KeyExtractor implements Function + { + @Override + public LCOpcode apply(IMessage message) + { + if(message instanceof LCMessage lcMessage) + { + return lcMessage.getOpcode(); + } + + return null; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/LinkControlMessageFilterSet.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/LinkControlMessageFilterSet.java new file mode 100644 index 000000000..20191f127 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/LinkControlMessageFilterSet.java @@ -0,0 +1,66 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.dmr.message.filter; + +import io.github.dsheirer.filter.FilterSet; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage; +import io.github.dsheirer.module.decode.dmr.message.data.lc.LCOpcode; +import java.util.ArrayList; +import java.util.List; + +/** + * Filter set for short & voice-fragment link control messages + * + * Note: this filter set does NOT work for link control messages in Data Message with Link Control. + */ +public class LinkControlMessageFilterSet extends FilterSet +{ + /** + * Constructor + */ + public LinkControlMessageFilterSet() + { + super("Short/Voice-Fragment Link Control Messages"); + + List fullLinkControl = new ArrayList<>(); + List shortLinkControl = new ArrayList<>(); + for(LCOpcode opcode: LCOpcode.values()) + { + if(opcode.isFull()) + { + fullLinkControl.add(opcode); + } + else + { + shortLinkControl.add(opcode); + } + } + + addFilter(new LinkControlMessageFilter("Full Link Control", fullLinkControl)); + addFilter(new LinkControlMessageFilter("Short Link Control", shortLinkControl)); + } + + @Override + public boolean canProcess(IMessage message) + { + return message instanceof LCMessage && super.canProcess(message); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/VoiceMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/VoiceMessageFilter.java new file mode 100644 index 000000000..8eab4622e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/VoiceMessageFilter.java @@ -0,0 +1,86 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.dmr.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.dmr.DMRSyncPattern; +import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage; +import java.util.function.Function; + +/** + * Filter for voice messages + */ +public class VoiceMessageFilter extends Filter +{ + private KeyExtractor mKeyExtractor = new KeyExtractor(); + + /** + * Constructs an instance + */ + public VoiceMessageFilter() + { + super("Voice Messages"); + add(new FilterElement<>(DMRSyncPattern.BASE_STATION_VOICE)); + add(new FilterElement<>(DMRSyncPattern.BS_VOICE_FRAME_B)); + add(new FilterElement<>(DMRSyncPattern.BS_VOICE_FRAME_C)); + add(new FilterElement<>(DMRSyncPattern.BS_VOICE_FRAME_D)); + add(new FilterElement<>(DMRSyncPattern.BS_VOICE_FRAME_E)); + add(new FilterElement<>(DMRSyncPattern.BS_VOICE_FRAME_F)); + add(new FilterElement<>(DMRSyncPattern.MOBILE_STATION_VOICE)); + add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_B)); + add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_C)); + add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_D)); + add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_E)); + add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_F)); + add(new FilterElement<>(DMRSyncPattern.DIRECT_MODE_VOICE_TIMESLOT_1)); + add(new FilterElement<>(DMRSyncPattern.DIRECT_MODE_VOICE_TIMESLOT_2)); + } + + @Override + public boolean canProcess(IMessage message) + { + return message instanceof VoiceMessage && super.canProcess(message); + } + + @Override + public Function getKeyExtractor() + { + return mKeyExtractor; + } + + /** + * Key extractor + */ + private class KeyExtractor implements Function + { + @Override + public DMRSyncPattern apply(IMessage message) + { + if(message instanceof VoiceMessage voice) + { + return voice.getSyncPattern(); + } + + return null; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/type/DataType.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/type/DataType.java index 29eac064f..9ee71138b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/type/DataType.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/type/DataType.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2023 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,18 +27,18 @@ public enum DataType VOICE_HEADER(1, 288, "VOICE HEADER"), TLC(2, 288, "TERMINATOR"), CSBK(3, 288, "CSBK"), - MBC_HEADER(4, 288, "MBC HEADER"), - MBC_BLOCK(5, 276, "MBC"), + MBC_HEADER(4, 288, "MULTI-BLOCK CONTROL HEADER"), + MBC_BLOCK(5, 276, "MULTI-BLOCK CONTROL BLOCK"), DATA_HEADER(6, 288, "DATA HEADER"), RATE_1_OF_2_DATA(7, 276, "RATE 1/2 PACKET"), RATE_3_OF_4_DATA(8, 276, "RATE 3/4 PACKET"), SLOT_IDLE(9, 276, "IDLE"), RATE_1_DATA(10, 276, "RATE 1/1 PACKET"), USB_DATA(11, 276, "UNIFIED SINGLE BLOCK DATA"), - MBC_ENC_HEADER(12, 276, "MBC ENCRYPTED HEADER"), + MBC_ENC_HEADER(12, 276, "MULTI-BLOCK CONTROL ENCRYPTED HEADER"), DATA_ENC_HEADER(13, 276, "DATA ENCRYPTED HEADER"), CHANNEL_CONTROL_ENC_HEADER(14, 276, "CONTROL CHANNEL ENCRYPTED HEADER"), - RESERVED_15(15, -1, "RESERVED"), + RESERVED_15(15, -1, "RESERVED 15"), UNKNOWN(-1, -1, "UNKNOWN"); private int mValue; diff --git a/src/main/java/io/github/dsheirer/module/decode/event/ClearableHistoryModel.java b/src/main/java/io/github/dsheirer/module/decode/event/ClearableHistoryModel.java new file mode 100644 index 000000000..c1a2af372 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/ClearableHistoryModel.java @@ -0,0 +1,125 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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 java.awt.EventQueue; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.table.AbstractTableModel; + +/** + * AbstractTableModel implementation supporting clearable method options. + */ +public abstract class ClearableHistoryModel extends AbstractTableModel +{ + public static final int DEFAULT_HISTORY_SIZE = 200; + private LinkedList mItems = new LinkedList<>(); + private int mHistorySize = DEFAULT_HISTORY_SIZE; + + /** + * Access an item/row by the model index value. + * @param index to retrieve + * @return item or null + */ + public T getItem(int index) + { + if(index < mItems.size()) + { + return mItems.get(index); + } + + return null; + } + + /** + * Adds the item to the top of the item list and removes any tail items while the item list size exceeds the + * maximum history size for this model. + * @param item to add + */ + public void add(T item) + { + mItems.addFirst(item); + ClearableHistoryModel.this.fireTableRowsInserted(0, 0); + while(mItems.size() > mHistorySize) + { + mItems.removeLast(); + super.fireTableRowsDeleted(mItems.size() - 1, mItems.size() - 1); + } + } + + /** + * Clears all messages from history + */ + public void clear() + { + EventQueue.invokeLater(() -> { + mItems.clear(); + fireTableDataChanged(); + }); + } + + /** + * Clears the current messages and loads the messages argument + */ + public void clearAndSet(List items) + { + EventQueue.invokeLater(() -> { + mItems.clear(); + fireTableDataChanged(); + for(T item: items) + { + add(item); + } + }); + } + + /** + * Current history size + * @return history size + */ + public int getHistorySize() + { + return mHistorySize; + } + + /** + * Resets the history size to the default value. + */ + public void resetHistorySize() + { + setHistorySize(DEFAULT_HISTORY_SIZE); + } + + /** + * Sets the history size + * @param historySize + */ + public void setHistorySize(int historySize) + { + mHistorySize = historySize; + } + + @Override + public int getRowCount() + { + return mItems.size(); + } +} 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 index ae4fa9ed6..4cc9cbf92 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEvent.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEvent.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,27 +32,33 @@ public class DecodeEvent implements IDecodeEvent { private long mTimeStart; private long mTimeEnd; - private String mEventDescription; private DecodeEventType mDecodeEventType; private IdentifierCollection mIdentifierCollection; private IChannelDescriptor mChannelDescriptor; private String mDetails; private Protocol mProtocol; - private Integer mTimeslot; + private int mTimeslot = -1; - public DecodeEvent(long start) + /** + * Constructs an instance + * @param decodeEventType is mandatory to support event filtering by event type. + * @param start time of the event. + */ + public DecodeEvent(DecodeEventType decodeEventType, long start) { + mDecodeEventType = decodeEventType; mTimeStart = start; } /** * Creates a new decode event builder with the specified start timestamp. + * @param decodeEventType for the event * @param timeStart for the event * @return builder */ - public static DecodeEventBuilder builder(long timeStart) + public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart) { - return new DecodeEventBuilder(timeStart); + return new DecodeEventBuilder(decodeEventType, timeStart); } /** @@ -115,23 +121,6 @@ public void setDuration(long duration) mTimeEnd = mTimeStart + duration; } - /** - * Event description - */ - @Override - public String getEventDescription() - { - return mEventDescription; - } - - /** - * Sets the event description text - */ - public void setEventDescription(String description) - { - mEventDescription = description; - } - /** * {@link DecodeEventType} */ @@ -140,13 +129,6 @@ public DecodeEventType getEventType() { return mDecodeEventType; } - /** - * Sets the {@link DecodeEventType} - */ - public void setEventType(DecodeEventType eventType) { - this.mDecodeEventType = eventType; - } - /** * Identifier collection for this event. */ @@ -222,24 +204,24 @@ public void setProtocol(Protocol protocol) * @return timeslot or default value of 0 */ @Override - public Integer getTimeslot() + public int getTimeslot() { return mTimeslot; } /** - * Indicates if this event specifies a timeslot + * Indicates if this event specifies a timeslot that is not negative */ public boolean hasTimeslot() { - return mTimeslot != null; + return mTimeslot >= 0; } /** * Sets the timeslot for this event * @param timeslot of the event */ - public void setTimeslot(Integer timeslot) + public void setTimeslot(int timeslot) { mTimeslot = timeslot; } @@ -249,7 +231,7 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(mProtocol); - sb.append(" DECODE EVENT: ").append(getEventDescription()); + sb.append(" DECODE EVENT: ").append(getEventType()); sb.append(" DETAILS:").append(getDetails()); if(mIdentifierCollection != null) { @@ -267,19 +249,19 @@ public static class DecodeEventBuilder { protected long mTimeStart; protected long mDuration; - protected String mEventDescription; protected DecodeEventType mDecodeEventType; protected IdentifierCollection mIdentifierCollection; protected IChannelDescriptor mChannelDescriptor; protected String mDetails; protected Protocol mProtocol = Protocol.UNKNOWN; - protected Integer mTimeslot; + protected int mTimeslot = -1; /** * Constructs a builder instance with the specified start time in milliseconds */ - public DecodeEventBuilder(long timeStart) + public DecodeEventBuilder(DecodeEventType decodeEventType, long timeStart) { + mDecodeEventType = decodeEventType; mTimeStart = timeStart; } @@ -313,21 +295,6 @@ public DecodeEventBuilder channel(IChannelDescriptor channelDescriptor) return this; } - public DecodeEventBuilder eventType(DecodeEventType eventType) { - mDecodeEventType = eventType; - 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 @@ -359,7 +326,12 @@ public DecodeEventBuilder protocol(Protocol protocol) return this; } - public DecodeEventBuilder timeslot(Integer timeslot) + /** + * Sets the timeslot for this event + * @param timeslot + * @return + */ + public DecodeEventBuilder timeslot(int timeslot) { mTimeslot = timeslot; return this; @@ -370,11 +342,10 @@ public DecodeEventBuilder timeslot(Integer timeslot) */ public DecodeEvent build() { - DecodeEvent decodeEvent = new DecodeEvent(mTimeStart); + DecodeEvent decodeEvent = new DecodeEvent(mDecodeEventType, mTimeStart); decodeEvent.setChannelDescriptor(mChannelDescriptor); decodeEvent.setDetails(mDetails); decodeEvent.setDuration(mDuration); - decodeEvent.setEventDescription(mEventDescription); decodeEvent.setIdentifierCollection(mIdentifierCollection); decodeEvent.setProtocol(mProtocol); decodeEvent.setTimeslot(mTimeslot); 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 index 697391dcd..b677a1254 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventModel.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventModel.java @@ -21,25 +21,20 @@ import com.google.common.eventbus.Subscribe; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.eventbus.MyEventBus; -import io.github.dsheirer.filter.FilterSet; import io.github.dsheirer.identifier.IdentifierCollection; -import io.github.dsheirer.module.decode.event.filter.DecodeEventFilterSet; -import io.github.dsheirer.module.decode.event.filter.EventFilterProvider; import io.github.dsheirer.preference.PreferenceType; import io.github.dsheirer.sample.Listener; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; +import java.awt.EventQueue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.swing.table.AbstractTableModel; - -public class DecodeEventModel extends AbstractTableModel implements Listener, EventFilterProvider +/** + * Decode event table model + */ +public class DecodeEventModel extends ClearableHistoryModel 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; @@ -50,12 +45,6 @@ public class DecodeEventModel extends AbstractTableModel implements Listener mEvents = new ArrayList<>(); - protected FilterSet mEventFilterSet = new DecodeEventFilterSet(); - protected String[] mHeaders = new String[]{"Time", "Duration", "Event", "From", "Alias", "To", "Alias", "Channel", "Frequency", "Details"}; public DecodeEventModel() @@ -70,74 +59,12 @@ public DecodeEventModel() @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.TALKGROUP_FORMAT) - { - for(int row = 0; row < mEvents.size(); row++) - { - fireTableCellUpdated(row, COLUMN_FROM_ID); - fireTableCellUpdated(row, COLUMN_TO_ID); - } - } - } - - /** - * Access the complete list of events managed by this model. - */ - public List getEvents() - { - return new ArrayList<>(mEvents); - } - - public void dispose() - { - MyEventBus.getGlobalEventBus().unregister(this); - Iterator it = mEvents.iterator(); - - while(it.hasNext()) + if(preferenceType == PreferenceType.DECODE_EVENT || preferenceType == PreferenceType.TALKGROUP_FORMAT) { - it.remove(); + fireTableDataChanged(); } } - public void clear() - { - mEvents.clear(); - fireTableDataChanged(); - } - - /** - * Clears all events from this model and loads the events argument - */ - public void clearAndSet(List events) - { - mEvents.clear(); - mEvents.addAll(events); - fireTableDataChanged(); - } - - 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 @@ -146,49 +73,7 @@ public void setMaxMessageCount(int count) */ public void receive(final IDecodeEvent event) { - //Disabled until #1368 decode event is fully implemented -// if (!mEventFilterSet.passes(event)) -// { -// return; -// } - - 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 FilterSet getFilterSet() { - return mEventFilterSet; - } - - @Override - public void setFilterSet(FilterSet filterSet) { - this.mEventFilterSet = filterSet; - } - - @Override - public int getRowCount() - { - return mEvents.size(); + EventQueue.invokeLater(() -> add(event)); } @Override @@ -205,58 +90,55 @@ public String getColumnName(int column) @Override public Object getValueAt(int rowIndex, int columnIndex) { - synchronized(mEvents) - { - IDecodeEvent event = mEvents.get(rowIndex); + IDecodeEvent event = getItem(rowIndex); - if(event != null) + if(event != null) + { + switch(columnIndex) { - 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: - IChannelDescriptor channelDescriptor = event.getChannelDescriptor(); - - if(channelDescriptor != null) + case COLUMN_TIME: + return event.getTimeStart(); + case COLUMN_DURATION: + return event.getDuration(); + case COLUMN_EVENT: + return event.getEventType().getLabel(); + 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: + IChannelDescriptor channelDescriptor = event.getChannelDescriptor(); + + if(channelDescriptor != null) + { + if(event.hasTimeslot()) + { + return channelDescriptor + " TS:" + event.getTimeslot(); + } + else + { + return channelDescriptor.toString(); + } + } + else + { + if(event.hasTimeslot()) { - if(event.hasTimeslot()) - { - return channelDescriptor.toString() + " TS:" + event.getTimeslot(); - } - else - { - return channelDescriptor.toString(); - } + return "TS:" + event.getTimeslot(); } else { - if(event.hasTimeslot()) - { - return "TS:" + event.getTimeslot(); - } - else - { - return null; - } + return null; } - case COLUMN_FREQUENCY: - return event.getChannelDescriptor(); - case COLUMN_DETAILS: - return event.getDetails(); - } + } + case COLUMN_FREQUENCY: + return event.getChannelDescriptor(); + case COLUMN_DETAILS: + return event.getDetails(); } } 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 index 1f543eaf6..bfd1fae32 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventPanel.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventPanel.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,7 +25,6 @@ import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.eventbus.MyEventBus; -import io.github.dsheirer.filter.AllPassFilter; import io.github.dsheirer.filter.FilterSet; import io.github.dsheirer.icon.IconModel; import io.github.dsheirer.identifier.Form; @@ -33,10 +32,7 @@ import io.github.dsheirer.identifier.IdentifierCollection; import io.github.dsheirer.identifier.Role; import io.github.dsheirer.module.ProcessingChain; -import io.github.dsheirer.module.decode.event.filter.EventClearButton; -import io.github.dsheirer.module.decode.event.filter.EventClearHandler; -import io.github.dsheirer.module.decode.event.filter.EventFilterButton; -import io.github.dsheirer.module.decode.event.filter.EventFilterProvider; +import io.github.dsheirer.module.decode.event.filter.DecodeEventFilterSet; import io.github.dsheirer.preference.PreferenceType; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor; @@ -58,7 +54,10 @@ import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; +import javax.swing.RowFilter; import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.TableModel; +import javax.swing.table.TableRowSorter; public class DecodeEventPanel extends JPanel implements Listener { @@ -75,7 +74,10 @@ public class DecodeEventPanel extends JPanel implements Listener mFilterSet = new DecodeEventFilterSet(); + private TableRowSorter mTableRowSorter; + private HistoryManagementPanel mHistoryManagementPanel; + /** * View for call event table @@ -91,17 +93,20 @@ public DecodeEventPanel(IconModel iconModel, UserPreferences userPreferences, Al mUserPreferences = userPreferences; mTimestampCellRenderer = new TimestampCellRenderer(); mTable = new JTable(mEventModel); - mTable.setAutoCreateRowSorter(true); + mTableRowSorter = new TableRowSorter<>(mEventModel); + mTableRowSorter.setRowFilter(new EventRowFilter()); + mTable.setRowSorter(mTableRowSorter); mTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); - mEventManagementPanel = new EventManagementPanel(); mTableColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY); updateCellRenderers(); - - //Disabled until #1368 decode event is fully implemented -// add(mEventManagementPanel, "span,growx"); - + mHistoryManagementPanel = new HistoryManagementPanel<>(mEventModel, "Event Filter Editor"); + mHistoryManagementPanel.updateFilterSet(mFilterSet); + add(mHistoryManagementPanel, "span,growx"); mEmptyScroller = new JScrollPane(mTable); add(mEmptyScroller); + + //Register filter change listener to refresh the table any time the event filters are changed. + mFilterSet.register(() -> mEventModel.fireTableDataChanged()); } public void dispose() @@ -124,22 +129,14 @@ public void preferenceUpdated(PreferenceType preferenceType) 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()); + 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 @@ -156,76 +153,17 @@ public void receive(final ProcessingChain processingChain) mCurrentEventHistory = processingChain.getDecodeEventHistory(); mEventModel.clearAndSet(mCurrentEventHistory.getItems()); processingChain.getDecodeEventHistory().addListener(mEventModel); - mEventManagementPanel.enableButtons(); + mHistoryManagementPanel.setEnabled(true); } else { mCurrentEventHistory = null; mEventModel.clearAndSet(Collections.emptyList()); - mEventManagementPanel.disableButtons(); + mHistoryManagementPanel.setEnabled(false); } }); } - public class EventManagementPanel extends JPanel - { - private static final long serialVersionUID = 1L; - - private EventFilterButton mFilterButton; - private EventClearButton mEventClearButton; - - public EventManagementPanel() - { - setLayout(new MigLayout("insets 2 2 5 5", "[]5[left,grow]", "")); - - initializeFilterButton(); - initializeClearButton(); - disableButtons(); - - add(mFilterButton); - add(mEventClearButton); - } - - public void enableButtons() - { - mFilterButton.setEnabled(true); - mEventClearButton.setEnabled(true); - } - - public void disableButtons() - { - mFilterButton.setEnabled(false); - mEventClearButton.setEnabled(false); - } - - private void initializeFilterButton() - { - EventFilterProvider filterProvider = (EventFilterProvider) mTable.getModel(); - FilterSet filterSet = new FilterSet<>(new AllPassFilter<>()); - if (filterProvider != null) { - filterSet = filterProvider.getFilterSet(); - } - mFilterButton = new EventFilterButton<>("Message Filter Editor", filterSet); - } - - private void initializeClearButton() { - mEventClearButton = new EventClearButton( - ((DecodeEventModel) mTable.getModel()).getMaxMessageCount() - ); - mEventClearButton.setEventClearHandler(new EventClearHandler() { - @Override - public void onHistoryLimitChanged(int newHistoryLimit) { - ((DecodeEventModel) mTable.getModel()).setMaxMessageCount(newHistoryLimit); - } - - @Override - public void onClearHistoryClicked() { - ((DecodeEventModel) mTable.getModel()).clear(); - } - }); - } - } - /** * Custom cell renderer for displaying identifiers from an identifier collection */ @@ -479,4 +417,26 @@ public ChannelDescriptorCellRenderer() setHorizontalAlignment(JLabel.CENTER); } } + + /** + * Row filter for decode events + */ + public class EventRowFilter extends RowFilter + { + @Override + public boolean include(Entry entry) + { + if(entry.getModel() instanceof DecodeEventModel model) + { + IDecodeEvent event = model.getItem(entry.getIdentifier()); + + if(event != null) + { + return mFilterSet.canProcess(event) && mFilterSet.passes(event); + } + } + + return false; + } + } } 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 index a7b1f47ec..51b61733e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java @@ -19,10 +19,17 @@ package io.github.dsheirer.module.decode.event; +import java.util.Arrays; +import java.util.EnumSet; + +/** + * Enumeration of event types for decoded events. + */ public enum DecodeEventType { AFFILIATE("Affiliate"), ANNOUNCEMENT("Announcement"), + ACKNOWLEDGE("Acknowledge"), AUTOMATIC_REGISTRATION_SERVICE("Motorola ARS"), CALL("Call"), CALL_ENCRYPTED("Encrypted Call"), @@ -32,6 +39,7 @@ public enum DecodeEventType CALL_PATCH_GROUP_ENCRYPTED("Encrypted Patch Call"), CALL_ALERT("Call Alert"), CALL_DETECT("Call Detect"), + CALL_IN_PROGRESS("Call In Progress"), CALL_DO_NOT_MONITOR("Call-Do Not Monitor"), CALL_END("Call End"), CALL_INTERCONNECT("Telephone Call"), @@ -41,6 +49,7 @@ public enum DecodeEventType CALL_UNIT_TO_UNIT_ENCRYPTED("Encrypted Unit To Unit Call"), CALL_NO_TUNER("Call - No Tuner"), CALL_TIMEOUT("Call Timeout"), + CELLOCATOR("Cellocator"), COMMAND("Command"), DATA_CALL("Data Call"), DATA_CALL_ENCRYPTED("Encrypted Data Call"), @@ -53,6 +62,7 @@ public enum DecodeEventType ID_ANI("ANI"), ID_UNIQUE("Unique ID"), IP_PACKET("IP Packet"), + LRRP("Motorola LRRP"), NOTIFICATION("Notification"), PAGE("Page"), QUERY("Query"), @@ -72,16 +82,82 @@ public enum DecodeEventType private String mLabel; + /** + * Encrypted voice call event types for filtering + */ + public static final EnumSet VOICE_CALLS_ENCRYPTED = EnumSet.of(DecodeEventType.CALL_ENCRYPTED, + DecodeEventType.CALL_GROUP_ENCRYPTED, DecodeEventType.CALL_PATCH_GROUP_ENCRYPTED, + DecodeEventType.CALL_INTERCONNECT_ENCRYPTED, DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED); + + /** + * Voice call event types for filtering + */ + public static final EnumSet VOICE_CALLS = EnumSet.of(DecodeEventType.CALL_GROUP, + DecodeEventType.CALL_PATCH_GROUP, DecodeEventType.CALL_ALERT, DecodeEventType.CALL_DETECT, + DecodeEventType.CALL_DO_NOT_MONITOR, DecodeEventType.CALL_END, DecodeEventType.CALL_INTERCONNECT, + DecodeEventType.CALL_UNIQUE_ID, DecodeEventType.CALL_UNIT_TO_UNIT, DecodeEventType.CALL_NO_TUNER, + DecodeEventType.CALL_TIMEOUT); + + /** + * Command event types for filtering + */ + public static final EnumSet COMMANDS = EnumSet.of(DecodeEventType.ANNOUNCEMENT, + DecodeEventType.STATION_ID, DecodeEventType.ACKNOWLEDGE, DecodeEventType.PAGE, DecodeEventType.QUERY, + DecodeEventType.RADIO_CHECK, DecodeEventType.STATUS, DecodeEventType.COMMAND, DecodeEventType.EMERGENCY, + DecodeEventType.NOTIFICATION, DecodeEventType.FUNCTION); + + /** + * Data call event types for filtering + */ + public static final EnumSet DATA_CALLS = EnumSet.of(DecodeEventType.DATA_CALL, + DecodeEventType.DATA_CALL_ENCRYPTED, DecodeEventType.DATA_PACKET, DecodeEventType.GPS, + DecodeEventType.IP_PACKET, DecodeEventType.UDP_PACKET, DecodeEventType.SDM, DecodeEventType.ID_ANI, + DecodeEventType.ID_UNIQUE); + + /** + * Registration event types for filtering + */ + public static final EnumSet REGISTRATION = EnumSet.of(DecodeEventType.AFFILIATE, + DecodeEventType.AUTOMATIC_REGISTRATION_SERVICE, DecodeEventType.REGISTER, DecodeEventType.REGISTER_ESN, + DecodeEventType.DEREGISTER, DecodeEventType.REQUEST, DecodeEventType.RESPONSE, DecodeEventType.RESPONSE_PACKET); + + /** + * All other event types of this enumeration that are not included in the groupings above. + */ + public static final EnumSet OTHERS = EnumSet.copyOf(Arrays.stream(DecodeEventType.values()) + .filter(decodeEventType -> !decodeEventType.isGrouped()).toList()); + + /** + * Constructor + * @param label for the element + */ DecodeEventType(String label) { mLabel = label; } + /** + * Indicates if the enumeration element is contained in one of the enumset groupings above. + * @return true if the element is grouped. + */ + public boolean isGrouped() + { + return VOICE_CALLS.contains(this) || VOICE_CALLS_ENCRYPTED.contains(this) || COMMANDS.contains(this) || + DATA_CALLS.contains(this) || REGISTRATION.contains(this); + } + + /** + * Label or pretty value for the element + * @return label + */ public String getLabel() { return mLabel; } + /** + * Uses label as the default string value. + */ public String toString() { return mLabel; diff --git a/src/main/java/io/github/dsheirer/module/decode/event/HistoryManagementPanel.java b/src/main/java/io/github/dsheirer/module/decode/event/HistoryManagementPanel.java new file mode 100644 index 000000000..ad76b60da --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/HistoryManagementPanel.java @@ -0,0 +1,212 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.FilterEditor; +import io.github.dsheirer.filter.FilterSet; +import java.awt.EventQueue; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import net.miginfocom.swing.MigLayout; + +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.SwingUtilities; + +/** + * History management panel with controls for managing item histories. + */ +public class HistoryManagementPanel extends JPanel +{ + private ClearableHistoryModel mModel; + private FilterSet mFilterSet; + private FilterEditor mFilterEditor; + private JButton mClearButton; + private JButton mFilterButton; + private JSlider mHistorySlider; + private JLabel mHistoryTitleLabel; + private JLabel mHistoryValueLabel; + private String mFilterEditorTitle; + + /** + * Constructs an instance + * @param model to manage + */ + public HistoryManagementPanel(ClearableHistoryModel model, String filterEditorTitle) + { + mModel = model; + mFilterEditorTitle = filterEditorTitle; + setLayout(new MigLayout("insets 6 1 5 5", "[]5[]10[]5[]5[][grow]", "")); + add(getFilterButton()); + add(getClearButton()); + add(getHistoryTitleLabel()); + add(getHistorySlider()); + add(getHistoryValueLabel()); + setEnabled(false); + } + + /** + * Updates the filter set + * @param filterSet to use + */ + public void updateFilterSet(FilterSet filterSet) + { + mFilterSet = filterSet; + + if(mFilterEditor != null) + { + getFilterEditor().updateFilterSet(filterSet); + } + } + + /** + * Overrides the panel method to also set the enabled state for the child controls. + * @param enabled true if this component should be enabled, false otherwise + */ + @Override + public void setEnabled(boolean enabled) + { + super.setEnabled(enabled); + getClearButton().setEnabled(enabled); + getFilterButton().setEnabled(enabled); + getHistoryValueLabel().setEnabled(enabled); + getHistoryTitleLabel().setEnabled(enabled); + getHistorySlider().setEnabled(enabled); + } + + /** + * Filter editor + * @return editor lazily constructed + */ + private FilterEditor getFilterEditor() + { + if(mFilterEditor == null) + { + mFilterEditor = new FilterEditor<>(mFilterEditorTitle, getFilterButton(), mFilterSet); + } + + return mFilterEditor; + } + + /** + * Filter button for accessing the filter editor + * @return filter button + */ + private JButton getFilterButton() + { + if(mFilterButton == null) + { + mFilterButton = new JButton("Filters"); + mFilterButton.setToolTipText("Edit filters"); + mFilterButton.addActionListener(arg0 -> EventQueue.invokeLater(() -> getFilterEditor().setVisible(true))); + } + + return mFilterButton; + } + + /** + * Clear button + * @return clear button + */ + private JButton getClearButton() + { + if(mClearButton == null) + { + mClearButton = new JButton("Clear"); + mClearButton.addActionListener(e -> mModel.clear()); + mClearButton.setToolTipText("Clears the history"); + } + + return mClearButton; + } + + /** + * History value label. + * @return label + */ + private JLabel getHistoryValueLabel() + { + if(mHistoryValueLabel == null) + { + mHistoryValueLabel = new JLabel(String.valueOf(ClearableHistoryModel.DEFAULT_HISTORY_SIZE)); + } + + return mHistoryValueLabel; + } + + /** + * History title label + * @return label + */ + private JLabel getHistoryTitleLabel() + { + if(mHistoryTitleLabel == null) + { + mHistoryTitleLabel = new JLabel("History:"); + } + + return mHistoryTitleLabel; + } + + /** + * History value slider control + * @return slider + */ + private JSlider getHistorySlider() + { + if(mHistorySlider == null) + { + mHistorySlider = new JSlider(); + mHistorySlider.setToolTipText("Adjust history size. Double-click to reset to default 200"); + mHistorySlider.setMinimum(0); + mHistorySlider.setMaximum(2000); + mHistorySlider.setMinorTickSpacing(25); + mHistorySlider.setMajorTickSpacing(500); + mHistorySlider.setPaintTicks(false); + mHistorySlider.setPaintLabels(false); + mHistorySlider.addMouseListener(new MouseListener() + { + @Override + public void mouseClicked(MouseEvent arg0) + { + if(SwingUtilities.isLeftMouseButton(arg0) && arg0.getClickCount() == 2) + { + mHistorySlider.setValue(ClearableHistoryModel.DEFAULT_HISTORY_SIZE); + } + } + + public void mouseEntered(MouseEvent arg0) {} + public void mouseExited(MouseEvent arg0) {} + public void mousePressed(MouseEvent arg0) {} + public void mouseReleased(MouseEvent arg0) {} + }); + + mHistorySlider.setValue(mModel.getHistorySize()); + mHistorySlider.addChangeListener(e -> { + mModel.setHistorySize(mHistorySlider.getValue()); + getHistoryValueLabel().setText(String.valueOf(mHistorySlider.getValue())); + }); + } + + return mHistorySlider; + } +} 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 index ea3da208a..7fcf66c50 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/IDecodeEvent.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/IDecodeEvent.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -48,11 +48,6 @@ public interface IDecodeEvent */ 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 @@ -86,7 +81,7 @@ public interface IDecodeEvent * Timeslot for the event. * @return timeslot or default of 0 */ - Integer getTimeslot(); + int getTimeslot(); /** * Indicates if the event has a timeslot specified 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 615b3a45e..f5ecd4f27 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 @@ -18,19 +18,16 @@ */ package io.github.dsheirer.module.decode.event; -import io.github.dsheirer.filter.AllPassFilter; -import io.github.dsheirer.filter.FilterSet; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.StuffBitsMessage; import io.github.dsheirer.sample.Listener; import java.awt.EventQueue; import java.text.SimpleDateFormat; -import java.util.LinkedList; -import java.util.List; -import javax.swing.table.AbstractTableModel; - -public class MessageActivityModel extends AbstractTableModel implements Listener +/** + * Table Model for decoded IMessages. + */ +public class MessageActivityModel extends ClearableHistoryModel implements Listener { private static final long serialVersionUID = 1L; private static final int TIME = 0; @@ -38,95 +35,20 @@ public class MessageActivityModel extends AbstractTableModel implements Listener private static final int TIMESLOT = 2; private static final int MESSAGE = 3; - protected int mMaxMessages = 200; - protected LinkedList mMessageItems = new LinkedList<>(); - protected int[] mColumnWidths = {20, 20, 500}; - protected String[] mHeaders = new String[]{"Time", "Protocol", "Timeslot", "Message"}; - + private String[] mHeaders = new String[]{"Time", "Protocol", "Timeslot", "Message"}; private SimpleDateFormat mSDFTime = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss"); - private FilterSet mMessageFilterSet = new FilterSet<>(new AllPassFilter<>()); - - public MessageActivityModel() - { - } - - /** - * Applies the filter set - */ - public void setFilters(FilterSet filterSet) - { - mMessageFilterSet = filterSet; - } - - public void clearFilters() - { - mMessageFilterSet = new FilterSet<>(new AllPassFilter<>()); - } - /** - * Clears all messages from history + * Constructor */ - public void clear() + public MessageActivityModel() { - EventQueue.invokeLater(() -> { - mMessageItems.clear(); - fireTableDataChanged(); - }); } /** - * Clears the current messages and loads the messages argument + * Implements the listener interface and wraps the IMessage in table-compatible message item wrapper. + * @param message to add to the model */ - public void clearAndSet(List messages) - { - EventQueue.invokeLater(() -> { - mMessageItems.clear(); - fireTableDataChanged(); - for(IMessage message: messages) - { - receive(message); - } - }); - } - - public FilterSet getMessageFilterSet() - { - return mMessageFilterSet; - } - - public void dispose() - { - mMessageItems.clear(); - } - - public int[] getColumnWidths() - { - return mColumnWidths; - } - - 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) { //Don't process tail bits or stuff bits message fragments @@ -135,39 +57,7 @@ public void receive(final IMessage message) return; } - if(mMessageFilterSet.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 mMessageItems.size(); + EventQueue.invokeLater(() -> add(new MessageItem(message))); } @Override @@ -184,20 +74,20 @@ public String getColumnName(int column) @Override public Object getValueAt(int rowIndex, int columnIndex) { - if(0 <= rowIndex && rowIndex < mMessageItems.size()) - { - MessageItem messageItem = mMessageItems.get(rowIndex); + MessageItem item = getItem(rowIndex); + if(item != null) + { switch(columnIndex) { case TIME: - return messageItem.getTimestamp(mSDFTime); + return item.getTimestamp(mSDFTime); case PROTOCOL: - return messageItem.getProtocol(); + return item.getProtocol(); case TIMESLOT: - return messageItem.getTimeslot(); + return item.getTimeslot(); case MESSAGE: - return messageItem.getText(); + return item.getText(); default: break; } 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 4e07d9fe1..479c1d79c 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 @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * * This 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,13 +14,10 @@ * * You 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.jidesoft.swing.JideButton; -import com.jidesoft.swing.JideSplitButton; -import io.github.dsheirer.filter.FilterEditorPanel; import io.github.dsheirer.filter.FilterSet; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.MessageHistory; @@ -30,37 +26,35 @@ import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor; import io.github.dsheirer.sample.Listener; +import java.util.ArrayList; +import java.util.List; import net.miginfocom.swing.MigLayout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.swing.JButton; -import javax.swing.JFrame; -import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; -import javax.swing.JSlider; import javax.swing.JTable; -import javax.swing.SwingUtilities; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import java.awt.EventQueue; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; +import javax.swing.RowFilter; +import javax.swing.table.TableModel; +import javax.swing.table.TableRowSorter; +/** + * Panel to display decoded messages/activity. + */ public class MessageActivityPanel extends JPanel implements Listener { private static final long serialVersionUID = 1L; - private final static Logger mLog = LoggerFactory.getLogger(MessageActivityPanel.class); - private static final String TABLE_PREFERENCE_KEY = "message.activity.panel"; - private static MessageActivityModel mMessageModel = new MessageActivityModel(); + private static final Logger mLog = LoggerFactory.getLogger(MessageActivityPanel.class); + private final String TABLE_PREFERENCE_KEY = "message.activity.panel"; + private MessageActivityModel mMessageModel = new MessageActivityModel(); private MessageHistory mCurrentMessageHistory; private JTable mTable = new JTable(mMessageModel); + private TableRowSorter mTableRowSorter; private JTableColumnWidthMonitor mTableColumnWidthMonitor; private UserPreferences mUserPreferences; - private MessageManagementPanel mManagementPanel = new MessageManagementPanel(); + private FilterSet mMessageFilterSet; + private HistoryManagementPanel mHistoryManagementPanel; /** * Constructs an instance @@ -69,13 +63,14 @@ public class MessageActivityPanel extends JPanel implements Listener(mMessageModel); + mTableRowSorter.setRowFilter(new MessageRowFilter()); + mTable.setRowSorter(mTableRowSorter); + mTableColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY); setLayout(new MigLayout("insets 0 0 0 0", "[][grow,fill]", "[]0[grow,fill]")); - - add(mManagementPanel, "span,growx"); - + mHistoryManagementPanel = new HistoryManagementPanel<>(mMessageModel, "Message Filter Editor"); + add(mHistoryManagementPanel, "span,growx"); add(new JScrollPane(mTable), "span,grow"); - mTableColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY); } /** @@ -89,182 +84,62 @@ public void receive(ProcessingChain processingChain) mCurrentMessageHistory.removeListener(mMessageModel); } + //Unregister from changes made to the filter set + if(mMessageFilterSet != null) + { + mMessageFilterSet.register(null); + } + if(processingChain != null) { mCurrentMessageHistory = processingChain.getMessageHistory(); - mMessageModel.setFilters(DecoderFactory.getMessageFilters(processingChain.getModules())); - mMessageModel.clearAndSet(mCurrentMessageHistory.getItems()); + mMessageFilterSet = DecoderFactory.getMessageFilters(processingChain.getModules()); + //Register filter change listener to refresh the table any time the event filters are changed. + mMessageFilterSet.register(() -> mMessageModel.fireTableDataChanged()); + if(mHistoryManagementPanel != null) + { + mHistoryManagementPanel.updateFilterSet(mMessageFilterSet); + } + + List currentHistory = new ArrayList<>(); + for(IMessage message: mCurrentMessageHistory.getItems()) + { + currentHistory.add(new MessageItem(message)); + } + + mMessageModel.clearAndSet(currentHistory); mCurrentMessageHistory.addListener(mMessageModel); - mManagementPanel.enableButtons(); + mHistoryManagementPanel.setEnabled(true); } else { mCurrentMessageHistory = null; - mMessageModel.clearFilters(); + mMessageFilterSet = null; mMessageModel.clear(); - mManagementPanel.disableButtons(); - } - } - - public class MessageManagementPanel extends JPanel - { - private static final long serialVersionUID = 1L; - - private MessageHistoryButton mHistoryButton = new MessageHistoryButton(); - private MessageFilterButton mFilterButton = new MessageFilterButton(); - - public MessageManagementPanel() - { - setLayout(new MigLayout("insets 2 2 5 5", "[]5[left,grow]", "")); - - disableButtons(); - - add(mFilterButton); - add(mHistoryButton); - } - - public void enableButtons() - { - mHistoryButton.setEnabled(true); - mFilterButton.setEnabled(true); - } - - public void disableButtons() - { - mHistoryButton.setEnabled(false); - mFilterButton.setEnabled(false); + mHistoryManagementPanel.setEnabled(false); } } - public class MessageHistoryButton extends JideSplitButton + /** + * Row visibility filter for messages + */ + public class MessageRowFilter extends RowFilter { - private static final long serialVersionUID = 1L; - - public MessageHistoryButton() + @Override + public boolean include(Entry entry) { - super("Clear"); - - JPanel historyPanel = new JPanel(); - - historyPanel.add(new JLabel("Message History:")); - - final JSlider slider = new JSlider(); - slider.setMinimum(0); - slider.setMaximum(2000); - slider.setMajorTickSpacing(500); - slider.setPaintTicks(true); - slider.setPaintLabels(true); - - slider.addMouseListener(new MouseListener() - { - @Override - public void mouseClicked(MouseEvent arg0) - { - if(SwingUtilities.isLeftMouseButton(arg0) && arg0.getClickCount() == 2) - { - slider.setValue(500); //default - } - } - - public void mouseEntered(MouseEvent arg0) - { - } - - public void mouseExited(MouseEvent arg0) - { - } - - public void mousePressed(MouseEvent arg0) - { - } - - public void mouseReleased(MouseEvent arg0) - { - } - }); - - slider.setValue(((MessageActivityModel) mTable.getModel()).getMaxMessageCount()); - slider.addChangeListener(new ChangeListener() + if(entry.getModel() instanceof MessageActivityModel model) { - @Override - public void stateChanged(ChangeEvent arg0) - { - ((MessageActivityModel) mTable.getModel()).setMaxMessageCount(slider.getValue()); - } - }); - - historyPanel.add(slider); - - add(historyPanel); + MessageItem item = model.getItem(entry.getIdentifier()); - /* Clear messages */ - addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) + if(mMessageFilterSet != null && item != null && item.getMessage() != null) { - ((MessageActivityModel) mTable.getModel()).clear(); + IMessage message = item.getMessage(); + return mMessageFilterSet.canProcess(message) && mMessageFilterSet.passes(message); } - }); - } - } - - public class MessageFilterButton extends JideButton - { - private static final long serialVersionUID = 1L; - - public MessageFilterButton() - { - super("Filter"); - - addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent arg0) - { - final JFrame editor = new JFrame(); + } - editor.setTitle("Message Filter Editor"); - editor.setSize(600, 400); - editor.setLocationRelativeTo(MessageFilterButton.this); - editor.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - editor.setLayout(new MigLayout("", "[grow,fill]", - "[grow,fill][][]")); - - @SuppressWarnings("unchecked") - FilterSet filter = (FilterSet) ((MessageActivityModel) mTable.getModel()) - .getMessageFilterSet(); - - FilterEditorPanel panel = new FilterEditorPanel<>(filter); - - JScrollPane scroller = new JScrollPane(panel); - scroller.setViewportView(panel); - - editor.add(scroller, "wrap"); - - editor.add(new JLabel("Right-click to select/deselect all nodes"), "wrap"); - - JButton close = new JButton("Close"); - close.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - editor.dispose(); - } - }); - - editor.add(close); - - EventQueue.invokeLater(new Runnable() - { - public void run() - { - editor.setVisible(true); - } - }); - } - }); + return false; } } } 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 index 87fca1235..719cf3b7c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/PlottableDecodeEvent.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/PlottableDecodeEvent.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * * This 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,7 +14,7 @@ * * You 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; @@ -31,19 +30,20 @@ public class PlottableDecodeEvent extends DecodeEvent private double mHeading; private double mSpeed; - public PlottableDecodeEvent(long start) + public PlottableDecodeEvent(DecodeEventType decodeEventType, long start) { - super(start); + super(decodeEventType, start); } /** * Creates a new decode event builder with the specified start timestamp. + * @param decodeEventType for the event * @param timeStart for the event * @return builder */ - public static PlottableDecodeEventBuilder plottableBuilder(long timeStart) + public static PlottableDecodeEventBuilder plottableBuilder(DecodeEventType decodeEventType, long timeStart) { - return new PlottableDecodeEventBuilder(timeStart); + return new PlottableDecodeEventBuilder(decodeEventType, timeStart); } /** @@ -102,7 +102,7 @@ public static class PlottableDecodeEventBuilder { private long mTimeStart; private long mDuration; - private String mEventDescription; + private DecodeEventType mDecodeEventType; private IdentifierCollection mIdentifierCollection; private IChannelDescriptor mChannelDescriptor; private String mDetails; @@ -114,8 +114,9 @@ public static class PlottableDecodeEventBuilder /** * Constructs a builder instance with the specified start time in milliseconds */ - public PlottableDecodeEventBuilder(long timeStart) + public PlottableDecodeEventBuilder(DecodeEventType decodeEventType, long timeStart) { + mDecodeEventType = decodeEventType; mTimeStart = timeStart; } @@ -149,16 +150,6 @@ public PlottableDecodeEventBuilder channel(IChannelDescriptor 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 @@ -222,11 +213,10 @@ public PlottableDecodeEventBuilder heading(double heading) */ public PlottableDecodeEvent build() { - PlottableDecodeEvent decodeEvent = new PlottableDecodeEvent(mTimeStart); + PlottableDecodeEvent decodeEvent = new PlottableDecodeEvent(mDecodeEventType, mTimeStart); decodeEvent.setChannelDescriptor(mChannelDescriptor); decodeEvent.setDetails(mDetails); decodeEvent.setDuration(mDuration); - decodeEvent.setEventDescription(mEventDescription); decodeEvent.setIdentifierCollection(mIdentifierCollection); decodeEvent.setProtocol(mProtocol); decodeEvent.setLocation(mGeoPosition); @@ -235,5 +225,4 @@ public PlottableDecodeEvent build() return decodeEvent; } } - } diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/AllOtherEventFilter.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/AllOtherEventFilter.java new file mode 100644 index 000000000..88d24c7b4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/AllOtherEventFilter.java @@ -0,0 +1,39 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.filter; + +import io.github.dsheirer.module.decode.event.DecodeEventType; + +/** + * Event filter for decode event types that are not specified in other event filters. + * + * This is intended as a catch-all for any elements of the DecodeEventType enumeration that may get added in the + * future and are not deliberately added to the various event filter groupings listed in the enumeration. + */ +public class AllOtherEventFilter extends EventFilter +{ + /** + * Constructor + */ + public AllOtherEventFilter() + { + super("Other", DecodeEventType.OTHERS); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodeEventFilterSet.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodeEventFilterSet.java index ed2a8302b..a25055216 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodeEventFilterSet.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodeEventFilterSet.java @@ -1,16 +1,40 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.filter; import io.github.dsheirer.filter.FilterSet; import io.github.dsheirer.module.decode.event.IDecodeEvent; -public class DecodeEventFilterSet extends FilterSet { - public DecodeEventFilterSet() { - super ("All Messages"); - +/** + * Top-level decode event filter set. + */ +public class DecodeEventFilterSet extends FilterSet +{ + public DecodeEventFilterSet() + { + super ("All Events"); addFilter(new DecodedCallEventFilter()); addFilter(new DecodedCallEncryptedEventFilter()); addFilter(new DecodedDataEventFilter()); addFilter(new DecodedCommandEventFilter()); addFilter(new DecodedRegistrationEventFilter()); + addFilter(new AllOtherEventFilter()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedCallEncryptedEventFilter.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedCallEncryptedEventFilter.java index 3afc588a3..27e79bbf7 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedCallEncryptedEventFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedCallEncryptedEventFilter.java @@ -1,19 +1,36 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.filter; import io.github.dsheirer.module.decode.event.DecodeEventType; -import java.util.Arrays; - +/** + * Encrypted call events filter + */ public class DecodedCallEncryptedEventFilter extends EventFilter { + /** + * Constructs an instance + */ public DecodedCallEncryptedEventFilter() { - super("Voice Calls - Encrypted", Arrays.asList( - DecodeEventType.CALL_ENCRYPTED, - DecodeEventType.CALL_GROUP_ENCRYPTED, - DecodeEventType.CALL_PATCH_GROUP_ENCRYPTED, - DecodeEventType.CALL_INTERCONNECT_ENCRYPTED, - DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED - )); + super("Voice Calls - Encrypted", DecodeEventType.VOICE_CALLS_ENCRYPTED); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedCallEventFilter.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedCallEventFilter.java index 930e52a2c..039963c3a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedCallEventFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedCallEventFilter.java @@ -1,25 +1,36 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.filter; import io.github.dsheirer.module.decode.event.DecodeEventType; -import java.util.Arrays; - +/** + * Call events filter + */ public class DecodedCallEventFilter extends EventFilter { + /** + * Constructs an instance + */ public DecodedCallEventFilter() { - super("Voice Calls", Arrays.asList( - DecodeEventType.CALL_GROUP, - DecodeEventType.CALL_PATCH_GROUP, - DecodeEventType.CALL_ALERT, - DecodeEventType.CALL_DETECT, - DecodeEventType.CALL_DO_NOT_MONITOR, - DecodeEventType.CALL_END, - DecodeEventType.CALL_INTERCONNECT, - DecodeEventType.CALL_UNIQUE_ID, - DecodeEventType.CALL_UNIT_TO_UNIT, - DecodeEventType.CALL_NO_TUNER, - DecodeEventType.CALL_TIMEOUT - )); + super("Voice Calls", DecodeEventType.VOICE_CALLS); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedCommandEventFilter.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedCommandEventFilter.java index 8eb385e2c..2b0d5e7f6 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedCommandEventFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedCommandEventFilter.java @@ -1,24 +1,36 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.filter; import io.github.dsheirer.module.decode.event.DecodeEventType; -import java.util.Arrays; - +/** + * Event filter for network commands. + */ public class DecodedCommandEventFilter extends EventFilter { + /** + * Constructs an instance + */ public DecodedCommandEventFilter() { - super("Commands", Arrays.asList( - DecodeEventType.ANNOUNCEMENT, - DecodeEventType.STATION_ID, - DecodeEventType.PAGE, - DecodeEventType.QUERY, - DecodeEventType.RADIO_CHECK, - DecodeEventType.STATUS, - DecodeEventType.COMMAND, - DecodeEventType.EMERGENCY, - DecodeEventType.NOTIFICATION, - DecodeEventType.FUNCTION - )); + super("Commands", DecodeEventType.COMMANDS); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedDataEventFilter.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedDataEventFilter.java index 5b8ea00dc..58ae94ed8 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedDataEventFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedDataEventFilter.java @@ -1,23 +1,36 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.filter; import io.github.dsheirer.module.decode.event.DecodeEventType; -import java.util.Arrays; - +/** + * Event filter for data call event types + */ public class DecodedDataEventFilter extends EventFilter { + /** + * Constructs an instance + */ public DecodedDataEventFilter() { - super("Data Calls", Arrays.asList( - DecodeEventType.DATA_CALL, - DecodeEventType.DATA_CALL_ENCRYPTED, - DecodeEventType.DATA_PACKET, - DecodeEventType.GPS, - DecodeEventType.IP_PACKET, - DecodeEventType.UDP_PACKET, - DecodeEventType.SDM, - DecodeEventType.ID_ANI, - DecodeEventType.ID_UNIQUE - )); + super("Data Calls", DecodeEventType.DATA_CALLS); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedRegistrationEventFilter.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedRegistrationEventFilter.java index d316b6714..d78cafbe5 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedRegistrationEventFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedRegistrationEventFilter.java @@ -1,22 +1,36 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.filter; import io.github.dsheirer.module.decode.event.DecodeEventType; -import java.util.Arrays; - +/** + * Event filter for registration related events + */ public class DecodedRegistrationEventFilter extends EventFilter { + /** + * Constructor + */ public DecodedRegistrationEventFilter() { - super("Registrations", Arrays.asList( - DecodeEventType.AFFILIATE, - DecodeEventType.AUTOMATIC_REGISTRATION_SERVICE, - DecodeEventType.REGISTER, - DecodeEventType.REGISTER_ESN, - DecodeEventType.DEREGISTER, - DecodeEventType.REQUEST, - DecodeEventType.RESPONSE, - DecodeEventType.RESPONSE_PACKET - )); + super("Registrations", DecodeEventType.REGISTRATION); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/EventClearButton.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/EventClearButton.java index 8dba214ea..e6bc6432a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/filter/EventClearButton.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/EventClearButton.java @@ -1,15 +1,33 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.filter; import com.jidesoft.swing.JideSplitButton; - -import javax.swing.*; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.SwingUtilities; + public class EventClearButton extends JideSplitButton { private static final long serialVersionUID = 1L; @@ -21,37 +39,29 @@ public EventClearButton(int maxHistoryCount) JPanel historyPanel = new JPanel(); - historyPanel.add(new JLabel("History Entries:")); - + historyPanel.add(new JLabel("History Size:")); final JSlider historySlider = initializeHistorySlider(); + JLabel valueLabel = new JLabel(String.valueOf(maxHistoryCount)); historySlider.setValue(maxHistoryCount); - historySlider.addChangeListener(new ChangeListener() - { - @Override - public void stateChanged(ChangeEvent arg0) + historySlider.addChangeListener(arg0 -> { + if (mEventClearHandler != null) { - if (mEventClearHandler != null) - { - mEventClearHandler.onHistoryLimitChanged(historySlider.getValue()); - } + mEventClearHandler.onHistoryLimitChanged(historySlider.getValue()); } + + valueLabel.setText(String.valueOf(historySlider.getValue())); }); historyPanel.add(historySlider); - + historyPanel.add(valueLabel); add(historyPanel); /* This handles the click action on the main button. Clear messages */ - addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) + addActionListener(e -> { + if (mEventClearHandler != null) { - if (mEventClearHandler != null) - { - mEventClearHandler.onClearHistoryClicked(); - } + mEventClearHandler.onClearHistoryClicked(); } }); } diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/EventClearHandler.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/EventClearHandler.java index badfd817b..60c664ade 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/filter/EventClearHandler.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/EventClearHandler.java @@ -1,6 +1,37 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.filter; -public interface EventClearHandler { +/** + * Handler interface for changes to the event history size or content. + */ +public interface EventClearHandler +{ + /** + * Invoked when the retained history size limit is changed + * @param newHistoryLimit new size + */ void onHistoryLimitChanged(int newHistoryLimit); + + /** + * Invoked when the history is cleared + */ void onClearHistoryClicked(); } diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/EventFilter.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/EventFilter.java index 53508930d..32588e327 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/filter/EventFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/EventFilter.java @@ -1,56 +1,74 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.filter; import io.github.dsheirer.filter.Filter; import io.github.dsheirer.filter.FilterElement; import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.event.IDecodeEvent; - -import java.util.ArrayList; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; +import java.util.Collection; +import java.util.function.Function; /** * This is used as a base for {@link IDecodeEvent} types. * This will take a list of {@link IDecodeEvent}s and generate filters for the list. * This will default canProcess to TRUE. Override if other value or evaluation is needed. - * */ -public class EventFilter extends Filter +public class EventFilter extends Filter { - private Map> mElements = new EnumMap<>(DecodeEventType.class); + private EventKeyExtractor mKeyExtractor = new EventKeyExtractor(); - public EventFilter(String name, List eventTypes) + /** + * Constructs an instance + * @param name of this filter + * @param decodeEventTypes to filter against + */ + public EventFilter(String name, Collection decodeEventTypes) { super(name); - for (DecodeEventType eventType : eventTypes) { - mElements.put(eventType, new FilterElement<>(eventType)); - } - } - - @Override - public boolean passes(IDecodeEvent iDecodeEvent) - { - if (mEnabled && canProcess(iDecodeEvent)) + for (DecodeEventType decodeEventType : decodeEventTypes) { - if (mElements.containsKey(iDecodeEvent.getEventType())) - { - return mElements.get(iDecodeEvent.getEventType()).isEnabled(); - } + add(new FilterElement<>(decodeEventType)); } - return false; } + /** + * Key extractor function + * @return function. + */ @Override - public boolean canProcess(IDecodeEvent iDecodeEvent) + public Function getKeyExtractor() { - return true; + return mKeyExtractor; } - @Override - public List> getFilterElements() + /** + * Key extractor function for decode events to extract the decode event type + */ + public static class EventKeyExtractor implements Function { - return new ArrayList<>(mElements.values()); + @Override + public DecodeEventType apply(IDecodeEvent iDecodeEvent) + { + return iDecodeEvent.getEventType(); + } } } diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/EventFilterButton.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/EventFilterButton.java index 235aa1be0..2b338c472 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/filter/EventFilterButton.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/EventFilterButton.java @@ -1,84 +1,103 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.filter; import com.jidesoft.swing.JideButton; import io.github.dsheirer.filter.FilterEditorPanel; import io.github.dsheirer.filter.FilterSet; -import net.miginfocom.swing.MigLayout; - -import javax.swing.*; -import java.awt.*; +import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import net.miginfocom.swing.MigLayout; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JScrollPane; + +/** + * Event filter button that includes split button functionality to allow user to select filter items. + * + * @param type of filter. + */ public class EventFilterButton extends JideButton { private static final long serialVersionUID = 1L; + /** + * Constructs an instance + * @param dialogTitle for the dialog/panel that appears + * @param filterSet to include in the editor panel. + */ public EventFilterButton(String dialogTitle, FilterSet filterSet) { this("Filter", dialogTitle, filterSet); } + /** + * Constructs an instance + * @param buttonLabel to use on the button + * @param dialogTitle for the dialog/panel that appears + * @param filterSet to include in the editor panel. + */ public EventFilterButton(String buttonLabel, String dialogTitle, FilterSet filterSet) { super(buttonLabel); - - addActionListener( - new EventFilterActionHandler(dialogTitle, filterSet) - ); + addActionListener(new EventFilterActionHandler(dialogTitle, filterSet)); } + /** + * Action handler for the button + */ public class EventFilterActionHandler implements ActionListener { - private String title; - private FilterSet filterSet; - - public EventFilterActionHandler( String title, FilterSet filterSet) + private String mTitle; + private FilterSet mFilterSet; + + /** + * Constructs an instance + * @param title for this panel + * @param filterSet to edit + */ + public EventFilterActionHandler(String title, FilterSet filterSet) { - this.title = title; - this.filterSet = filterSet; + mTitle = title; + mFilterSet = filterSet; } @Override public void actionPerformed(ActionEvent e) { final JFrame editor = new JFrame(); - - editor.setTitle(title); + editor.setTitle(mTitle); editor.setLocationRelativeTo(EventFilterButton.this); editor.setSize(600, 400); editor.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - editor.setLayout(new MigLayout("", "[grow,fill]", - "[grow,fill][][]")); - - FilterEditorPanel panel = new FilterEditorPanel(filterSet); - + editor.setLayout(new MigLayout("", "[grow,fill]", "[grow,fill][][]")); + FilterEditorPanel panel = new FilterEditorPanel(mFilterSet); JScrollPane scroller = new JScrollPane(panel); scroller.setViewportView(panel); - editor.add(scroller, "wrap"); - - editor.add(new JLabel("Right-click to select/deselect all nodes"), "wrap"); - JButton close = new JButton("Close"); - close.addActionListener(new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - editor.dispose(); - } - }); - + close.addActionListener(e1 -> editor.dispose()); editor.add(close); - - EventQueue.invokeLater(new Runnable() - { - public void run() - { - editor.setVisible(true); - } - }); + EventQueue.invokeLater(() -> editor.setVisible(true)); } } } \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/EventFilterProvider.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/EventFilterProvider.java deleted file mode 100644 index d09810103..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/event/filter/EventFilterProvider.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.github.dsheirer.module.decode.event.filter; - -import io.github.dsheirer.filter.FilterSet; - -public interface EventFilterProvider { - FilterSet getFilterSet(); - void setFilterSet(FilterSet filterSet); -} 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 2a6ba768b..70817ff13 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 @@ -107,13 +107,7 @@ public void receive(IMessage message) 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(); - + DecodeEvent aniEvent = getDecodeEvent(fleetsync, getDecodeEventType(fleetsync.getMessageType())); broadcast(aniEvent); broadcast(new DecoderStateEvent(this, DecoderStateEvent.Event.DECODE, State.CALL)); break; @@ -121,19 +115,13 @@ public void receive(IMessage message) 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(); + DecodeEvent statusEvent = getDecodeEvent(fleetsync, getDecodeEventType(fleetsync.getMessageType())); broadcast(statusEvent); broadcast(new DecoderStateEvent(this, DecoderStateEvent.Event.DECODE, State.DATA)); break; case GPS: - PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(fleetsync.getTimestamp()) + PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, fleetsync.getTimestamp()) .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.GPS.toString()) .details(fleetsync.toString()) .identifiers(getIdentifierCollection().copyOf()) .protocol(Protocol.FLEETSYNC) @@ -147,6 +135,35 @@ public void receive(IMessage message) } } + private DecodeEvent getDecodeEvent(Fleetsync2Message fleetsync, DecodeEventType eventType) { + return DecodeEvent.builder(eventType, fleetsync.getTimestamp()) + .channel(getCurrentChannel()) + .details(fleetsync.getMessageType() + " " + fleetsync) + .identifiers(getIdentifierCollection().copyOf()) + .protocol(Protocol.FLEETSYNC) + .build(); + } + + private DecodeEventType getDecodeEventType(FleetsyncMessageType fleetsyncMessageType) { + switch (fleetsyncMessageType) { + case ANI: + return DecodeEventType.ID_ANI; + case EMERGENCY: + case LONE_WORKER_EMERGENCY: + return DecodeEventType.EMERGENCY; + case ACKNOWLEDGE: + return DecodeEventType.ACKNOWLEDGE; + case PAGING: + return DecodeEventType.PAGE; + case STATUS: + return DecodeEventType.STATUS; + case GPS: + return DecodeEventType.GPS; + default: + return DecodeEventType.UNKNOWN; + } + } + /** * Responds to reset events issued by the channel state */ 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 c2b75b0bd..10b9b8b35 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,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * * This 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,7 +14,7 @@ * * You 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; @@ -24,52 +23,63 @@ import io.github.dsheirer.filter.FilterElement; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.fleetsync2.message.Fleetsync2Message; -import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType; - -import java.util.*; +import java.util.function.Function; -public class FleetsyncMessageFilter extends Filter +/** + * Filter for Fleetsync messages. + */ +public class FleetsyncMessageFilter extends Filter { - private Map> mElements = new EnumMap<>(FleetsyncMessageType.class); + private final KeyExtractor mKeyExtractor = new KeyExtractor(); + /** + * Constructor + */ public FleetsyncMessageFilter() { - super("Fleetsync Message Filter"); + super("Fleetsync Messages"); for(FleetsyncMessageType type : FleetsyncMessageType.values()) { - if(type != FleetsyncMessageType.UNKNOWN) - { - mElements.put(type, new FilterElement(type)); - } + add(new FilterElement<>(type)); } } + /** + * Limits processing to only fleetsync messages. + * @param message to test + * @return true if message type is correct. + */ @Override - public boolean passes(IMessage message) + public boolean canProcess(IMessage message) { - if(mEnabled && canProcess(message)) - { - Fleetsync2Message fleet = (Fleetsync2Message)message; - - if(mElements.containsKey(fleet.getMessageType())) - { - return mElements.get(fleet.getMessageType()).isEnabled(); - } - } - - return false; + return message instanceof Fleetsync2Message && super.canProcess(message); } + /** + * Key extractor for fleetsync messages + * @return key extractor + */ @Override - public boolean canProcess(IMessage message) + public Function getKeyExtractor() { - return message instanceof Fleetsync2Message; + return mKeyExtractor; } - @Override - public List> getFilterElements() + /** + * Key extractor for Fleetsync 2 Messages. + */ + private class KeyExtractor implements Function { - return new ArrayList>(mElements.values()); + @Override + public FleetsyncMessageType apply(IMessage message) + { + if(message instanceof Fleetsync2Message f2m) + { + return f2m.getMessageType(); + } + + return null; + } } } 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 9763f4637..bc54188eb 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,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 Dennis Sheirer - * * - * * This program is free software: you can redistribute it and/or modify - * * it under the terms of the GNU General Public License as published by - * * the Free Software Foundation, either version 3 of the License, or - * * (at your option) any later version. - * * - * * This program is distributed in the hope that it will be useful, - * * but WITHOUT ANY WARRANTY; without even the implied warranty of - * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * * GNU General Public License for more details. - * * - * * You should have received a copy of the GNU General Public License - * * along with this program. If not, see - * * ***************************************************************************** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * + * You 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; @@ -30,14 +27,15 @@ 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; - +import io.github.dsheirer.module.decode.event.DecodeEventType; +import io.github.dsheirer.protocol.Protocol; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.TreeSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class LJ1200DecoderState extends DecoderState { @@ -72,11 +70,11 @@ public void receive(IMessage message) ic.remove(IdentifierClass.USER); ic.update(lj.getIdentifiers()); - DecodeEvent event = DecodeEvent.builder(System.currentTimeMillis()) + DecodeEvent event = DecodeEvent.builder(DecodeEventType.DATA_PACKET, System.currentTimeMillis()) + .protocol(Protocol.LOJACK) .identifiers(ic) .channel(getCurrentChannel()) - .details("LOJACK") - .eventDescription(lj.toString()) + .details("LOJACK " + lj) .build(); broadcast(event); @@ -91,11 +89,11 @@ else if(message instanceof LJ1200TransponderMessage) ic.remove(IdentifierClass.USER); ic.update(transponder.getIdentifiers()); - DecodeEvent transponderEvent = DecodeEvent.builder(System.currentTimeMillis()) + DecodeEvent transponderEvent = DecodeEvent.builder(DecodeEventType.GPS, System.currentTimeMillis()) + .protocol(Protocol.LOJACK) .identifiers(ic) .channel(getCurrentChannel()) - .details("LOJACK TRANSPONDER") - .eventDescription(transponder.toString()) + .details("LOJACK TRANSPONDER " + transponder) .build(); broadcast(transponderEvent); @@ -114,7 +112,7 @@ public String getActivitySummary() { sb.append("Transponder Addresses:\n"); - List addresses = new ArrayList(mAddresses); + List addresses = new ArrayList<>(mAddresses); Collections.sort(addresses); 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 96c4916f1..85a7467a0 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,52 +1,66 @@ -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,2015 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public 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.filter.Filter; import io.github.dsheirer.filter.FilterElement; import io.github.dsheirer.message.IMessage; +import java.util.function.Function; -import java.util.Collections; -import java.util.List; - -public class LJ1200MessageFilter extends Filter +/** + * Filter for LJ1200 messages + */ +public class LJ1200MessageFilter extends Filter { + private static final String LJ1200_KEY = "LJ-1200"; + private final LJ1200KeyExtractor mKeyExtractor = new LJ1200KeyExtractor(); + /** + * Constructor + */ public LJ1200MessageFilter() { - super("LJ-1200 Message Filter"); + super("LJ-1200 Messages"); + add(new FilterElement<>(LJ1200_KEY)); } @Override - public boolean passes(IMessage message) + public boolean canProcess(IMessage message) { - return mEnabled && canProcess(message); + return (message instanceof LJ1200Message || message instanceof LJ1200TransponderMessage); } @Override - public boolean canProcess(IMessage message) + public Function getKeyExtractor() { - return message instanceof LJ1200Message || message instanceof LJ1200TransponderMessage; + return mKeyExtractor; } - @Override - public List> getFilterElements() + /** + * Key extractor + */ + public class LJ1200KeyExtractor implements Function { - return Collections.EMPTY_LIST; + @Override + public String apply(IMessage message) + { + return LJ1200_KEY; + } } } diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecodeEvent.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecodeEvent.java new file mode 100644 index 000000000..dfe3c4e0e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecodeEvent.java @@ -0,0 +1,52 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; +import io.github.dsheirer.protocol.Protocol; + +/** + * LTRNet decode event + */ +public class LTRNetDecodeEvent extends DecodeEvent +{ + /** + * Constructs an LTR NET decode event + * @param start + */ + public LTRNetDecodeEvent(DecodeEventType decodeEventType, long start) + { + super(decodeEventType, start); + setProtocol(Protocol.LTR_NET); + } + + /** + * Creates a new decode event builder with the specified start timestamp. + * @param timeStart for the event + * @return builder + */ + public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart) + { + DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(decodeEventType, timeStart); + decodeEventBuilder.protocol(Protocol.LTR_NET); + return decodeEventBuilder; + } +} 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 c29402ae1..e3bba893d 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,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 Dennis Sheirer - * * - * * This program is free software: you can redistribute it and/or modify - * * it under the terms of the GNU General Public License as published by - * * the Free Software Foundation, either version 3 of the License, or - * * (at your option) any later version. - * * - * * This program is distributed in the hope that it will be useful, - * * but WITHOUT ANY WARRANTY; without even the implied warranty of - * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * * GNU General Public License for more details. - * * - * * You should have received a copy of the GNU General Public License - * * along with this program. If not, see - * * ***************************************************************************** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * + * You 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; @@ -38,6 +35,7 @@ import io.github.dsheirer.message.MessageDirection; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.ltrnet.channel.LtrNetChannel; import io.github.dsheirer.module.decode.ltrnet.identifier.LtrNetRadioIdentifier; import io.github.dsheirer.module.decode.ltrnet.message.LtrNetMessage; @@ -60,9 +58,6 @@ 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.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -71,6 +66,8 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class LTRNetDecoderState extends DecoderState { @@ -166,10 +163,24 @@ private void processCallStart(int channel, LTRTalkgroup talkgroup, long timestam if(mCurrentCallTalkgroup == null || !mCurrentCallTalkgroup.equals(talkgroup)) { + DecodeEventType decodeEventType = null; + + if(talkgroup.getTalkgroup() == 253) + { + decodeEventType = DecodeEventType.REGISTER; + } + else if(talkgroup.getTalkgroup() == 254) + { + decodeEventType = DecodeEventType.STATION_ID; + } + else + { + decodeEventType = DecodeEventType.CALL; + } + getIdentifierCollection().remove(IdentifierClass.USER); getIdentifierCollection().update(talkgroup); - mCurrentCallEvent = DecodeEvent.builder(timestamp) - .protocol(Protocol.LTR_NET) + mCurrentCallEvent = LTRNetDecodeEvent.builder(decodeEventType, timestamp) .channel(getCurrentChannel()) .identifiers(getIdentifierCollection().copyOf()) .build(); @@ -181,17 +192,14 @@ private void processCallStart(int channel, LTRTalkgroup talkgroup, long timestam 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)); } @@ -203,16 +211,7 @@ else if(talkgroup.getTalkgroup() == 254) 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(); + decodeEvent = getDecodeEvent(talkgroup, channel, timestamp, DecodeEventType.CALL_DETECT); mCallDetectMap.put(channel, decodeEvent); } else @@ -223,16 +222,7 @@ else if(talkgroup.getTalkgroup() == 254) if(eventTalkgroup == null || !eventTalkgroup.equals(talkgroup) || (timestamp - decodeEvent.getTimeStart() - decodeEvent.getDuration() > 2000)) { - 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(); + decodeEvent = getDecodeEvent(talkgroup, channel, timestamp, DecodeEventType.CALL_DETECT); mCallDetectMap.put(channel, decodeEvent); } } @@ -307,89 +297,59 @@ public void receive(IMessage message) switch(((LtrNetMessage)message).getLtrNetMessageType()) { case ISW_CALL_END: - if(message instanceof IswCallEnd) + if(message instanceof IswCallEnd callEnd) { - IswCallEnd callEnd = (IswCallEnd)message; mTalkgroups.add(callEnd.getTalkgroup()); processCallEnd(callEnd.getChannel(), callEnd.getTalkgroup(), callEnd.getTimestamp()); } break; case ISW_CALL_START: - if(message instanceof IswCallStart) + if(message instanceof IswCallStart callStart) { - 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) + if(message instanceof RegistrationRequestEsnHigh registrationRequestEsn) { - 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(getDecodeEvent(message, DecodeEventType.REQUEST, "REGISTER: " + registrationRequestEsn)); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA)); } break; case ISW_REGISTRATION_REQUEST_ESN_LOW: - if(message instanceof RegistrationRequestEsnLow) + if(message instanceof RegistrationRequestEsnLow registrationRequestEsn) { - 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(getDecodeEvent(message, DecodeEventType.REQUEST, "REGISTER: " + registrationRequestEsn)); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA)); } break; case ISW_REQUEST_ACCESS: - if(message instanceof RequestAccess) + if(message instanceof RequestAccess 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(getDecodeEvent(message, DecodeEventType.REQUEST, "ACCESS: " + requestAccess)); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA)); } break; case ISW_UNIQUE_ID: - if(message instanceof IswUniqueId) + if(message instanceof IswUniqueId iswUniqueId) { - IswUniqueId iswUniqueId = (IswUniqueId)message; getIdentifierCollection().update(iswUniqueId.getUniqueID()); mLtrNetRadioIdentifiers.add(iswUniqueId.getUniqueID()); updateCurrentCallIdentifiers(); @@ -400,104 +360,90 @@ public void receive(IMessage message) broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); break; case OSW_CALL_END: - if(message instanceof OswCallEnd) + if(message instanceof OswCallEnd callEnd) { - OswCallEnd callEnd = (OswCallEnd)message; mTalkgroups.add(callEnd.getTalkgroup()); processCallEnd(callEnd.getChannel(), callEnd.getTalkgroup(), callEnd.getTimestamp()); } break; case OSW_CALL_START: - if(message instanceof OswCallStart) + if(message instanceof OswCallStart callStart) { - 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) + if(message instanceof ChannelMapHigh channelMapHigh) { - mChannelMapHigh = (ChannelMapHigh)message; + mChannelMapHigh = channelMapHigh; } broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); break; case OSW_CHANNEL_MAP_LOW: - if(message instanceof ChannelMapLow) + if(message instanceof ChannelMapLow channelMapLow) { - mChannelMapLow = (ChannelMapLow)message; + mChannelMapLow = channelMapLow; } broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); break; case OSW_SYSTEM_IDLE: - if(message instanceof SystemIdle) + if(message instanceof SystemIdle systemIdle) { - setCurrentChannelNumber(((SystemIdle)message).getChannel()); + setCurrentChannelNumber(systemIdle.getChannel()); } broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); break; case OSW_NEIGHBOR_ID: - if(message instanceof NeighborId) + if(message instanceof NeighborId 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) + if(message instanceof ReceiveFrequencyHigh receiveFrequency) { - 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) + if(message instanceof ReceiveFrequencyLow receiveFrequency) { - 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) + if(message instanceof RegistrationAccept registrationAccept) { - RegistrationAccept registrationAccept = (RegistrationAccept)message; mLtrNetRadioIdentifiers.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()); + broadcast(getDecodeEvent(message, DecodeEventType.RESPONSE, "REGISTRATION ACCEPT: " + registrationAccept)); } break; case OSW_SITE_ID: - if(message instanceof SiteId) + if(message instanceof SiteId siteId) { - mCurrentSite = (SiteId)message; + mCurrentSite = siteId; } broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); break; case OSW_TRANSMIT_FREQUENCY_HIGH: - if(message instanceof TransmitFrequencyHigh) + if(message instanceof TransmitFrequencyHigh transmitFrequency) { - 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) + if(message instanceof TransmitFrequencyLow transmitFrequency) { - TransmitFrequencyLow transmitFrequency = (TransmitFrequencyLow)message; updateTransmitFrequency(transmitFrequency.getChannel(), transmitFrequency.getFrequency()); } broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE)); @@ -509,6 +455,27 @@ public void receive(IMessage message) } } + private DecodeEvent getDecodeEvent(IMessage message, DecodeEventType decodeEventType, String details) + { + return LTRNetDecodeEvent.builder(decodeEventType, message.getTimestamp()) + .details(details) + .identifiers(getIdentifierCollection().copyOf()) + .channel(getCurrentChannel()) + .build(); + } + + private DecodeEvent getDecodeEvent(LTRTalkgroup talkgroup, int channel, long timestamp, DecodeEventType decodeEventType) + { + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(talkgroup); + + return LTRNetDecodeEvent.builder(decodeEventType, timestamp) + .channel(mChannelMap.get(channel)) + .identifiers(ic) + .build(); + } + @Override public String getActivitySummary() { diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetMessageFilter.java index ab9182bc6..0b1226619 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetMessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetMessageFilter.java @@ -1,52 +1,85 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.filter.Filter; import io.github.dsheirer.filter.FilterElement; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.ltrnet.message.LtrNetMessage; -import io.github.dsheirer.module.decode.ltrstandard.LtrStandardMessageType; - -import java.util.*; +import java.util.function.Function; -public class LTRNetMessageFilter extends Filter +/** + * Filter for LTR-Net messages + */ +public class LTRNetMessageFilter extends Filter { - private Map> mElements = new EnumMap<>(LtrNetMessageType.class); + private final KeyExtractor mKeyExtractor = new KeyExtractor(); + /** + * Constructor + */ public LTRNetMessageFilter() { - super("LTR-Net Message Filter"); + super("LTR-Net Messages"); for(LtrNetMessageType type: LtrNetMessageType.values()) { - mElements.put(type, new FilterElement<>(type)); + add(new FilterElement<>(type)); } } + /** + * Tests for instance of LTR-Net message + * @param message to test + * @return true if correct type. + */ @Override - public boolean passes(IMessage message) + public boolean canProcess(IMessage message) { - if(mEnabled && canProcess(message)) - { - LtrNetMessage ltr = (LtrNetMessage) message; - - if(mElements.containsKey(ltr.getLtrNetMessageType())) - { - return mElements.get(ltr.getLtrNetMessageType()).isEnabled(); - } - } - - return false; + return message instanceof LtrNetMessage && super.canProcess(message); } + /** + * Key extractor for LTR-Net messages + * @return extractor + */ @Override - public boolean canProcess(IMessage message) + public Function getKeyExtractor() { - return message instanceof LtrNetMessage; + return mKeyExtractor; } - @Override - public List> getFilterElements() + /** + * Key extractor + */ + private class KeyExtractor implements Function { - return new ArrayList>(mElements.values()); + @Override + public LtrNetMessageType apply(IMessage message) + { + if(message instanceof LtrNetMessage ltr) + { + return ltr.getLtrNetMessageType(); + } + + return null; + } } } diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecodeEvent.java b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecodeEvent.java new file mode 100644 index 000000000..b98ad009e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecodeEvent.java @@ -0,0 +1,52 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; +import io.github.dsheirer.protocol.Protocol; + +/** + * LTR decode event + */ +public class LTRStandardDecodeEvent extends DecodeEvent +{ + /** + * Constructs an LTR Standard decode event + * @param start + */ + public LTRStandardDecodeEvent(DecodeEventType decodeEventType, long start) + { + super(decodeEventType, start); + setProtocol(Protocol.LTR); + } + + /** + * Creates a new decode event builder with the specified start timestamp. + * @param timeStart for the event + * @return builder + */ + public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart) + { + DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(decodeEventType, timeStart); + decodeEventBuilder.protocol(Protocol.LTR); + return decodeEventBuilder; + } +} 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 f5539d212..a28aa6dae 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,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 Dennis Sheirer - * * - * * This program is free software: you can redistribute it and/or modify - * * it under the terms of the GNU General Public License as published by - * * the Free Software Foundation, either version 3 of the License, or - * * (at your option) any later version. - * * - * * This program is distributed in the hope that it will be useful, - * * but WITHOUT ANY WARRANTY; without even the implied warranty of - * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * * GNU General Public License for more details. - * * - * * You should have received a copy of the GNU General Public License - * * along with this program. If not, see - * * ***************************************************************************** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * + * You 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; @@ -35,15 +32,13 @@ import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; 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.LTRMessage; import io.github.dsheirer.protocol.Protocol; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -51,6 +46,8 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class LTRStandardDecoderState extends DecoderState { @@ -81,10 +78,8 @@ public void receive(IMessage message) switch(((LTRMessage)message).getMessageType()) { case CALL: - if(message instanceof Call) + if(message instanceof Call start) { - Call start = (Call)message; - int channel = start.getChannel(); setChannelNumber(channel); mLCNTracker.processFreeChannel(start.getFree()); @@ -97,11 +92,9 @@ public void receive(IMessage message) mCurrentTalkgroup = start.getTalkgroup(); getIdentifierCollection().remove(IdentifierClass.USER); getIdentifierCollection().update(start.getTalkgroup()); - mCurrentCallEvent = DecodeEvent.builder(start.getTimestamp()) - .protocol(Protocol.LTR) + mCurrentCallEvent = LTRStandardDecodeEvent.builder(DecodeEventType.CALL, start.getTimestamp()) .identifiers(getIdentifierCollection().copyOf()) .channel(getCurrentChannel()) - .eventDescription("Call") .build(); } else @@ -114,10 +107,8 @@ public void receive(IMessage message) } break; case CALL_END: - if(message instanceof CallEnd) + if(message instanceof CallEnd end) { - CallEnd end = (CallEnd)message; - mCurrentTalkgroup = null; //Home channel is 31 for call end -- use the free channel as the call end channel @@ -135,10 +126,10 @@ public void receive(IMessage message) } break; case IDLE: - if(message instanceof Idle) + if(message instanceof Idle idle) { mCurrentTalkgroup = null; - mLCNTracker.processCallChannel(((Idle)message).getChannel()); + mLCNTracker.processCallChannel(idle.getChannel()); } break; default: 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 af409e5e7..7d27e8357 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,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * * This 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,7 +14,7 @@ * * You 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; @@ -24,47 +23,53 @@ import io.github.dsheirer.filter.FilterElement; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.ltrstandard.message.LTRMessage; +import java.util.function.Function; -import java.util.*; - -public class LTRStandardMessageFilter extends Filter +/** + * Filter for LTR standard messages. + */ +public class LTRStandardMessageFilter extends Filter { - private Map> mElements = new EnumMap<>(LtrStandardMessageType.class); + private final KeyExtractor mKeyExtractor = new KeyExtractor(); + /** + * Constructor + */ public LTRStandardMessageFilter() { - 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)); - } + super("LTR Messages"); - @Override - public boolean passes(IMessage message) - { - if(mEnabled && canProcess(message)) + for(LtrStandardMessageType type: LtrStandardMessageType.values()) { - LTRMessage ltr = (LTRMessage)message; - - if(mElements.containsKey(ltr.getMessageType())) - { - return mElements.get(ltr.getMessageType()).isEnabled(); - } + add(new FilterElement<>(type)); } - - return false; } public boolean canProcess(IMessage message) { - return message instanceof LTRMessage; + return message instanceof LTRMessage && super.canProcess(message); } @Override - public List> getFilterElements() + public Function getKeyExtractor() + { + return mKeyExtractor; + } + + /** + * Key extractor + */ + private class KeyExtractor implements Function { - return new ArrayList>(mElements.values()); + @Override + public LtrStandardMessageType apply(IMessage message) + { + if(message instanceof LTRMessage ltr) + { + return ltr.getMessageType(); + } + + return null; + } } } diff --git a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecodeEvent.java b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecodeEvent.java new file mode 100644 index 000000000..8187c7178 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecodeEvent.java @@ -0,0 +1,53 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; +import io.github.dsheirer.protocol.Protocol; + +/** + * MDC-1200 decode event + */ +public class MDCDecodeEvent extends DecodeEvent +{ + /** + * Constucts an MDC1200 decode event + * @param decodeEventType for the event + * @param start for the event + */ + public MDCDecodeEvent(DecodeEventType decodeEventType, long start) + { + super(decodeEventType, start); + setProtocol(Protocol.MDC1200); + } + + /** + * Creates a new decode event builder with the specified start timestamp. + * @param timeStart for the event + * @return builder + */ + public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart) + { + DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(decodeEventType, timeStart); + decodeEventBuilder.protocol(Protocol.MDC1200); + return decodeEventBuilder; + } +} 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 f79fefd8c..f9a079053 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,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 Dennis Sheirer - * * - * * This program is free software: you can redistribute it and/or modify - * * it under the terms of the GNU General Public License as published by - * * the Free Software Foundation, either version 3 of the License, or - * * (at your option) any later version. - * * - * * This program is distributed in the hope that it will be useful, - * * but WITHOUT ANY WARRANTY; without even the implied warranty of - * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * * GNU General Public License for more details. - * * - * * You should have received a copy of the GNU General Public License - * * along with this program. If not, see - * * ***************************************************************************** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * + * You 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; @@ -29,9 +26,8 @@ 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 io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.mdc1200.identifier.MDC1200Identifier; - import java.util.Iterator; import java.util.Set; import java.util.TreeSet; @@ -114,8 +110,7 @@ public void receive(IMessage message) ic.remove(IdentifierClass.USER); ic.update(message.getIdentifiers()); - broadcast(DecodeEvent.builder(mdc.getTimestamp()) - .eventDescription(type.getLabel()) + broadcast(MDCDecodeEvent.builder(getDecodeEventType(type), mdc.getTimestamp()) .details(mdc.toString()) .identifiers(ic) .build()); @@ -195,4 +190,22 @@ public void receiveDecoderStateEvent(DecoderStateEvent event) break; } } + + private DecodeEventType getDecodeEventType(MDCMessageType messageType) { + switch(messageType) + { + case ANI: + return DecodeEventType.ID_ANI; + case ACKNOWLEDGE: + return DecodeEventType.RESPONSE; + case EMERGENCY: + return DecodeEventType.EMERGENCY; + case PAGING: + return DecodeEventType.PAGE; + case STATUS: + return DecodeEventType.STATUS; + default: + return DecodeEventType.UNKNOWN; + } + } } 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 45b4d3afe..795f5a46a 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 @@ -1,55 +1,75 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.filter.Filter; import io.github.dsheirer.filter.FilterElement; import io.github.dsheirer.message.IMessage; -import io.github.dsheirer.message.Message; -import io.github.dsheirer.module.decode.mpt1327.MPT1327Message; - -import java.util.*; +import java.util.function.Function; -public class MDCMessageFilter extends Filter +/** + * Message filter for MDC-1200 messages + */ +public class MDCMessageFilter extends Filter { - private Map> mElements = new EnumMap<>(MDCMessageType.class); + private final KeyExtractor mKeyExtractor = new KeyExtractor(); + /** + * Constructor + */ public MDCMessageFilter() { - super("MDC-1200 Message Filter"); + super("MDC-1200 Messages"); for(MDCMessageType type : MDCMessageType.values()) { - if(type != MDCMessageType.UNKNOWN) - { - mElements.put(type, new FilterElement(type)); - } + add(new FilterElement<>(type)); } } @Override - public boolean passes(IMessage message) + public boolean canProcess(IMessage message) { - if(mEnabled && canProcess(message)) - { - MDCMessage mdc = (MDCMessage) message; - - if(mElements.containsKey(mdc.getMessageType())) - { - return mElements.get(mdc.getMessageType()).isEnabled(); - } - } - - return false; + return message instanceof MDCMessage && super.canProcess(message); } @Override - public boolean canProcess(IMessage message) + public Function getKeyExtractor() { - return message instanceof MDCMessage; + return mKeyExtractor; } - @Override - public List> getFilterElements() + /** + * Key extractor + */ + private class KeyExtractor implements Function { - return new ArrayList>(mElements.values()); + @Override + public MDCMessageType apply(IMessage message) + { + if(message instanceof MDCMessage mdc) + { + return mdc.getMessageType(); + } + + return null; + } } } 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 index 18506fcc3..7e249d071 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327ChannelGrantEvent.java +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327ChannelGrantEvent.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * * This 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,20 +14,23 @@ * * You 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.module.decode.event.DecodeEventType; import io.github.dsheirer.protocol.Protocol; -public class MPT1327ChannelGrantEvent extends P25DecodeEvent +/** + * MPT1327 Channel Grant Event + */ +public class MPT1327ChannelGrantEvent extends MPT1327DecodeEvent { - public MPT1327ChannelGrantEvent(long timestamp) + public MPT1327ChannelGrantEvent(DecodeEventType decodeEventType, long timestamp) { - super(timestamp); + super(decodeEventType, timestamp); } /** @@ -36,9 +38,9 @@ public MPT1327ChannelGrantEvent(long timestamp) * @param timeStart for the event * @return builder */ - public static MPT1327ChannelGrantDecodeEventBuilder mpt1327Builder(long timeStart) + public static MPT1327ChannelGrantDecodeEventBuilder mpt1327Builder(DecodeEventType decodeEventType, long timeStart) { - return new MPT1327ChannelGrantDecodeEventBuilder(timeStart); + return new MPT1327ChannelGrantDecodeEventBuilder(decodeEventType, timeStart); } /** @@ -48,7 +50,7 @@ public static class MPT1327ChannelGrantDecodeEventBuilder { protected long mTimeStart; protected long mDuration; - protected String mEventDescription; + protected DecodeEventType mDecodeEventType; protected IdentifierCollection mIdentifierCollection; protected IChannelDescriptor mChannelDescriptor; protected String mDetails; @@ -58,8 +60,9 @@ public static class MPT1327ChannelGrantDecodeEventBuilder * * @param timeStart */ - public MPT1327ChannelGrantDecodeEventBuilder(long timeStart) + public MPT1327ChannelGrantDecodeEventBuilder(DecodeEventType decodeEventType, long timeStart) { + mDecodeEventType = decodeEventType; mTimeStart = timeStart; } @@ -93,16 +96,6 @@ public MPT1327ChannelGrantDecodeEventBuilder channel(IChannelDescriptor channelD 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 @@ -129,12 +122,11 @@ public MPT1327ChannelGrantDecodeEventBuilder details(String details) */ public MPT1327ChannelGrantEvent build() { - MPT1327ChannelGrantEvent decodeEvent = new MPT1327ChannelGrantEvent(mTimeStart); + MPT1327ChannelGrantEvent decodeEvent = new MPT1327ChannelGrantEvent(mDecodeEventType, 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/MPT1327DecodeEvent.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecodeEvent.java new file mode 100644 index 000000000..57afa5905 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecodeEvent.java @@ -0,0 +1,52 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; +import io.github.dsheirer.protocol.Protocol; + +/** + * MPT1327 Decode Event + */ +public class MPT1327DecodeEvent extends DecodeEvent +{ + /** + * Constructs a MPT1327 decode event + * @param start + */ + public MPT1327DecodeEvent(DecodeEventType decodeEventType, long start) + { + super(decodeEventType, start); + setProtocol(Protocol.MPT1327); + } + + /** + * Creates a new decode event builder with the specified start timestamp. + * @param timeStart for the event + * @return builder + */ + public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart) + { + DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(decodeEventType, timeStart); + decodeEventBuilder.protocol(Protocol.MPT1327); + return decodeEventBuilder; + } +} 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 6cdf80184..95b1fc63b 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,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,17 +30,16 @@ import io.github.dsheirer.module.decode.DecoderType; 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.mpt1327.channel.MPT1327Channel; -import io.github.dsheirer.protocol.Protocol; import io.github.dsheirer.util.StringUtils; -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.TreeSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class MPT1327DecoderState extends DecoderState { @@ -110,28 +109,11 @@ public void receive(IMessage message) if(identType == MPT1327Message.IdentType.REGI) { - 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()); + broadcast(getDecodeEvent(message, mpt, DecodeEventType.REGISTER, null)); } else { - 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()) - .identifiers(ic) - .build()); + broadcast(getDecodeEvent(message, mpt, DecodeEventType.RESPONSE, "ACK " + identType.getLabel())); } broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); @@ -143,45 +125,15 @@ public void receive(IMessage message) 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(getDecodeEvent(message, mpt, DecodeEventType.ACKNOWLEDGE, mpt.getMessageType().getDescription())); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); break; case AHYC: - MutableIdentifierCollection icAHY = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icAHY.remove(IdentifierClass.USER); - icAHY.update(mpt.getIdentifiers()); - - broadcast(DecodeEvent.builder(message.getTimestamp()) - .protocol(Protocol.MPT1327) - .eventDescription("Command") - .details("Send Short Data Message") - .identifiers(icAHY) - .build()); - + broadcast(getDecodeEvent(message, mpt, DecodeEventType.COMMAND, "Send Short Data Message")); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); break; case AHYQ: - 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(getDecodeEvent(message, mpt, DecodeEventType.COMMAND, "Send Status Message")); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); break; case ALH: @@ -197,41 +149,22 @@ public void receive(IMessage message) case GTC: if(mMPT1327TrafficChannelManager != null) { - MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - ic.remove(IdentifierClass.USER); - ic.update(mpt.getIdentifiers()); + MutableIdentifierCollection ic = getUpdatedMutableIdentifierCollection(mpt); mMPT1327TrafficChannelManager.processChannelGrant(mpt, ic); } else { 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()); + DecodeEvent decodeEvent = getDecodeEvent(message, mpt, DecodeEventType.CALL_DETECT, mpt.getMessage()); + decodeEvent.setChannelDescriptor(channel); + broadcast(decodeEvent); } break; case HEAD_PLUS1: case HEAD_PLUS2: case HEAD_PLUS3: case HEAD_PLUS4: - 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()) - .identifiers(icHEAD) - .build()); - + broadcast(getDecodeEvent(message, mpt, DecodeEventType.SDM, mpt.getMessage())); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); break; @@ -250,14 +183,7 @@ public void receive(IMessage message) // timeout specified by the user. Otherwise we'll be using the shorter default call timeout broadcast(new ChangeChannelTimeoutEvent(this, mChannelType, mCallTimeoutMilliseconds)); - 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(getDecodeEvent(message, mpt, DecodeEventType.CALL_IN_PROGRESS, null)); broadcast(new DecoderStateEvent(this, Event.START, State.CALL)); } break; @@ -268,6 +194,22 @@ public void receive(IMessage message) } } + private DecodeEvent getDecodeEvent(IMessage message, MPT1327Message mpt, DecodeEventType decodeEventType, String details) + { + MutableIdentifierCollection ic = getUpdatedMutableIdentifierCollection(mpt); + return MPT1327DecodeEvent.builder(decodeEventType, message.getTimestamp()) + .details(details) + .identifiers(ic) + .build(); + } + + private MutableIdentifierCollection getUpdatedMutableIdentifierCollection(MPT1327Message mpt) { + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(mpt.getIdentifiers()); + return ic; + } + public void reset() { super.reset(); 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 bfd596aa4..159f57eed 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,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * * This 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,7 +14,7 @@ * * You 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; @@ -23,56 +22,55 @@ import io.github.dsheirer.filter.Filter; import io.github.dsheirer.filter.FilterElement; import io.github.dsheirer.message.IMessage; -import io.github.dsheirer.message.MessageType; - -import java.util.*; +import java.util.function.Function; -public class MPT1327MessageFilter extends Filter +/** + * Filter for MPT1327 messages + */ +public class MPT1327MessageFilter extends Filter { - private Map> mFilterElements = new EnumMap<>(MPT1327Message.MPTMessageType.class); + private final KeyExtractor mKeyExtractor = new KeyExtractor(); + /** + * Constructor + */ public MPT1327MessageFilter() { - super("MPT1327 Message Type Filter"); + super("MPT1327 Messages"); for(MPT1327Message.MPTMessageType type : MPT1327Message.MPTMessageType.values()) { - if(type != MPT1327Message.MPTMessageType.UNKN) - { - mFilterElements.put(type, new FilterElement(type)); - } + add(new FilterElement<>(type)); } } @Override - public boolean passes(IMessage message) + public boolean canProcess(IMessage message) { - if(mEnabled && canProcess(message)) - { - MPT1327Message mpt = (MPT1327Message) message; - - FilterElement element = - mFilterElements.get(mpt.getMessageType()); - - if(element != null) - { - return element.isEnabled(); - } - } - - return false; + return message instanceof MPT1327Message && super.canProcess(message); } @Override - public boolean canProcess(IMessage message) + public Function getKeyExtractor() { - return message instanceof MPT1327Message; + return mKeyExtractor; } - @Override - public List> getFilterElements() + /** + * Key extractor + */ + private class KeyExtractor implements Function { - return new ArrayList>(mFilterElements.values()); + @Override + public MPT1327Message.MPTMessageType apply(IMessage message) + { + if(message instanceof MPT1327Message mpt) + { + return mpt.getMessageType(); + } + + return 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 index 713600f02..9e1f45ce2 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327TrafficChannelManager.java +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327TrafficChannelManager.java @@ -30,6 +30,7 @@ import io.github.dsheirer.identifier.Role; 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.mpt1327.channel.MPT1327Channel; @@ -103,9 +104,9 @@ else if(mAllocatedTrafficChannelMap.containsKey(mpt1327Channel)) } } - MPT1327ChannelGrantEvent channelGrantEvent = MPT1327ChannelGrantEvent.mpt1327Builder(mpt1327Message.getTimestamp()) + MPT1327ChannelGrantEvent channelGrantEvent = MPT1327ChannelGrantEvent + .mpt1327Builder(DecodeEventType.CALL, mpt1327Message.getTimestamp()) .channel(mpt1327Channel) - .eventDescription("Call") .details("Traffic Channel Grant") .identifiers(identifierCollection) .build(); @@ -123,7 +124,7 @@ else if(mAllocatedTrafficChannelMap.containsKey(mpt1327Channel)) if(trafficChannel == null) { channelGrantEvent.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED); - channelGrantEvent.setEventDescription("Detect:" + channelGrantEvent.getEventDescription()); + channelGrantEvent.setDetails("Detect:" + channelGrantEvent.getDetails()); return; } @@ -150,10 +151,8 @@ private void createTrafficChannels(Channel parentChannel) DecodeConfiguration decodeConfiguration = parentChannel.getDecodeConfiguration(); List trafficChannelList = new ArrayList<>(); - if(decodeConfiguration instanceof DecodeConfigMPT1327) + if(decodeConfiguration instanceof DecodeConfigMPT1327 decodeConfigMPT1327) { - DecodeConfigMPT1327 decodeConfigMPT1327 = (DecodeConfigMPT1327)decodeConfiguration; - int maxTrafficChannels = decodeConfigMPT1327.getTrafficChannelPoolSize(); for(int x = 0; x < maxTrafficChannels; x++) 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 index d6f55655a..4b15afadb 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25ChannelGrantEvent.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25ChannelGrantEvent.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * * This 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,7 +14,7 @@ * * You 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; @@ -29,9 +28,9 @@ public class P25ChannelGrantEvent extends P25DecodeEvent { private ServiceOptions mServiceOptions; - public P25ChannelGrantEvent(long timestamp) + public P25ChannelGrantEvent(DecodeEventType decodeEventType, long timestamp) { - super(timestamp); + super(decodeEventType, timestamp); } /** @@ -39,9 +38,9 @@ public P25ChannelGrantEvent(long timestamp) * @param timeStart for the event * @return builder */ - public static P25ChannelGrantDecodeEventBuilder builder(long timeStart, ServiceOptions serviceOptions) + public static P25ChannelGrantDecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart, ServiceOptions serviceOptions) { - return new P25ChannelGrantDecodeEventBuilder(timeStart, serviceOptions); + return new P25ChannelGrantDecodeEventBuilder(decodeEventType, timeStart, serviceOptions); } /** @@ -76,7 +75,6 @@ public static class P25ChannelGrantDecodeEventBuilder { protected long mTimeStart; protected long mDuration; - protected String mEventDescription; protected DecodeEventType mDecodeEventType; protected IdentifierCollection mIdentifierCollection; protected IChannelDescriptor mChannelDescriptor; @@ -88,8 +86,9 @@ public static class P25ChannelGrantDecodeEventBuilder * * @param timeStart */ - public P25ChannelGrantDecodeEventBuilder(long timeStart, ServiceOptions serviceOptions) + public P25ChannelGrantDecodeEventBuilder(DecodeEventType decodeEventType, long timeStart, ServiceOptions serviceOptions) { + mDecodeEventType = decodeEventType; mTimeStart = timeStart; mServiceOptions = serviceOptions; } @@ -124,26 +123,6 @@ public P25ChannelGrantDecodeEventBuilder channel(IChannelDescriptor channelDescr return this; } - /** - * Sets the event description text - * @param description of the event - */ - public P25ChannelGrantDecodeEventBuilder eventDescription(String description) - { - mEventDescription = description; - return this; - } - - /** - * Sets the {@link DecodeEventType} - * @param decodeEventType of the event - */ - public P25ChannelGrantDecodeEventBuilder eventType(DecodeEventType decodeEventType) - { - mDecodeEventType = decodeEventType; - return this; - } - /** * Sets the identifier collection. * @param identifierCollection containing optional identifiers like TO, FROM, frequency and @@ -170,13 +149,11 @@ public P25ChannelGrantDecodeEventBuilder details(String details) */ public P25ChannelGrantEvent build() { - P25ChannelGrantEvent decodeEvent = new P25ChannelGrantEvent(mTimeStart); + P25ChannelGrantEvent decodeEvent = new P25ChannelGrantEvent(mDecodeEventType, mTimeStart); decodeEvent.setProtocol(Protocol.APCO25); decodeEvent.setChannelDescriptor(mChannelDescriptor); decodeEvent.setDetails(mDetails); decodeEvent.setDuration(mDuration); - decodeEvent.setEventType(mDecodeEventType); - decodeEvent.setEventDescription(mEventDescription); decodeEvent.setIdentifierCollection(mIdentifierCollection); decodeEvent.setServiceOptions(mServiceOptions); return decodeEvent; 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 index 8189e25d0..d22aeb4e5 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecodeEvent.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecodeEvent.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * * This 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,23 +14,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.p25; import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.protocol.Protocol; public class P25DecodeEvent extends DecodeEvent { /** - * Constucts a P25 decode event + * Constructs a P25 decode event * @param start */ - public P25DecodeEvent(long start) + public P25DecodeEvent(DecodeEventType decodeEventType, long start) { - super(start); + super(decodeEventType, start); setProtocol(Protocol.APCO25); } @@ -40,9 +40,9 @@ public P25DecodeEvent(long start) * @param timeStart for the event * @return builder */ - public static DecodeEventBuilder builder(long timeStart) + public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart) { - DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(timeStart); + DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(decodeEventType, timeStart); decodeEventBuilder.protocol(Protocol.APCO25); return decodeEventBuilder; } 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 index ff3bcb849..5018f079d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25TrafficChannelManager.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25TrafficChannelManager.java @@ -281,11 +281,9 @@ private void processPhase1ChannelGrant(APCO25Channel apco25Channel, ServiceOptio { event.end(timestamp); - P25ChannelGrantEvent continuationGrantEvent = P25ChannelGrantEvent.builder(timestamp, serviceOptions) + P25ChannelGrantEvent continuationGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) .channel(apco25Channel) - .eventType(decodeEventType) - .eventDescription(decodeEventType.toString() + " - Continue") - .details("PHASE 1 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")) + .details("CONTINUE - PHASE 1 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")) .identifiers(identifierCollection) .build(); @@ -306,7 +304,6 @@ private void processPhase1ChannelGrant(APCO25Channel apco25Channel, ServiceOptio if(trafficChannel != null) { - event.setEventDescription(decodeEventType.toString()); event.setDetails("PHASE 1 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")); event.setChannelDescriptor(apco25Channel); broadcast(event); @@ -327,10 +324,8 @@ private void processPhase1ChannelGrant(APCO25Channel apco25Channel, ServiceOptio if(mIgnoreDataCalls && opcode.isDataChannelGrant()) { - P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(timestamp, serviceOptions) + P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) .channel(apco25Channel) - .eventType(decodeEventType) - .eventDescription(decodeEventType.toString() + " - Ignored") .details("DATA CALL IGNORED: " + (serviceOptions != null ? serviceOptions : "")) .identifiers(identifierCollection) .build(); @@ -340,10 +335,8 @@ private void processPhase1ChannelGrant(APCO25Channel apco25Channel, ServiceOptio return; } - P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(timestamp, serviceOptions) + P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) .channel(apco25Channel) - .eventType(decodeEventType) - .eventDescription(decodeEventType.toString()) .details("PHASE 1 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")) .identifiers(identifierCollection) .build(); @@ -357,8 +350,7 @@ private void processPhase1ChannelGrant(APCO25Channel apco25Channel, ServiceOptio if(trafficChannel == null) { - channelGrantEvent.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED); - channelGrantEvent.setEventDescription(channelGrantEvent.getEventDescription() + " - Ignored"); + channelGrantEvent.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED + " - IGNORED"); return; } @@ -427,11 +419,9 @@ else if(timeslot == 1) { event.end(timestamp); - P25ChannelGrantEvent continuationGrantEvent = P25ChannelGrantEvent.builder(timestamp, serviceOptions) + P25ChannelGrantEvent continuationGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) .channel(apco25Channel) - .eventType(decodeEventType) - .eventDescription(decodeEventType.toString() + " - Continue") - .details("PHASE 2 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")) + .details("CONTINUE - PHASE 2 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")) .identifiers(identifierCollection) .build(); @@ -460,7 +450,6 @@ else if(timeslot == 1) if(trafficChannel != null) { - event.setEventDescription(decodeEventType.toString()); event.setDetails("PHASE 2 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")); event.setChannelDescriptor(apco25Channel); broadcast(event); @@ -488,10 +477,8 @@ else if(timeslot == 1) if(mIgnoreDataCalls && opcode.isDataChannelGrant()) { - P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(timestamp, serviceOptions) + P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) .channel(apco25Channel) - .eventType(decodeEventType) - .eventDescription(decodeEventType.toString() + " - Ignored") .details("PHASE 2 DATA CALL IGNORED: " + (serviceOptions != null ? serviceOptions : "")) .identifiers(identifierCollection) .build(); @@ -502,10 +489,8 @@ else if(timeslot == 1) return; } - P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(timestamp, serviceOptions) + P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) .channel(apco25Channel) - .eventType(decodeEventType) - .eventDescription(decodeEventType.toString()) .details("PHASE 2 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")) .identifiers(identifierCollection) .build(); @@ -526,8 +511,7 @@ else if(timeslot == 1) if(trafficChannel == null) { - channelGrantEvent.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED); - channelGrantEvent.setEventDescription(channelGrantEvent.getEventDescription() + " - Ignored"); + channelGrantEvent.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED + " - IGNORED"); return; } @@ -836,17 +820,7 @@ public synchronized void receive(ChannelEvent channelEvent) 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()); - } - + event.setDetails(CHANNEL_START_REJECTED + " - " + event.getDetails()); broadcast(event); } }); diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java index 28bbfb512..1b4019bcb 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java @@ -654,10 +654,8 @@ private void processBroadcast(TSBKMessage tsbkMessage, DecodeEventType request, private void processBroadcast(List identifiers, long timestamp, DecodeEventType request, String s) { MutableIdentifierCollection requestCollection = getMutableIdentifierCollection(identifiers); - broadcast(P25DecodeEvent.builder(timestamp) + broadcast(P25DecodeEvent.builder(request, timestamp) .channel(getCurrentChannel()) - .eventType(request) - .eventDescription(request.toString()) .details(s) .identifiers(requestCollection) .build()); @@ -767,17 +765,16 @@ private void processTDULC(P25Message message) /** * Updates or creates a current call event. * - * @param type of call that will be used as an event description + * @param decodeEventType 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) + private void updateCurrentCall(DecodeEventType decodeEventType, String details, long timestamp) { if(mCurrentCallEvent == null) { - mCurrentCallEvent = P25DecodeEvent.builder(timestamp) + mCurrentCallEvent = P25DecodeEvent.builder(DecodeEventType.CALL, timestamp) .channel(getCurrentChannel()) - .eventDescription(type.toString()) .details(details) .identifiers(getIdentifierCollection().copyOf()) .build(); @@ -791,9 +788,8 @@ private void updateCurrentCall(DecodeEventType type, String details, long timest mCurrentCallEvent.end(timestamp); broadcast(mCurrentCallEvent); - if(type == DecodeEventType.CALL_ENCRYPTED) + if(decodeEventType == DecodeEventType.CALL_ENCRYPTED) { - mCurrentCallEvent.setEventDescription(type.toString()); mCurrentCallEvent.setDetails(details); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ENCRYPTED)); } @@ -838,12 +834,9 @@ private void processTDU(P25Message message) */ private void processPDU(P25Message message) { - if(message.isValid() && message instanceof PDUMessage) + if(message.isValid() && message instanceof PDUMessage pdu) { - PDUMessage pdu = (PDUMessage)message; - processBroadcast(pdu.getIdentifiers(), message.getTimestamp(), DecodeEventType.DATA_PACKET, pdu.toString()); - } broadcast(new DecoderStateEvent(this, Event.DECODE, State.DATA)); @@ -856,10 +849,8 @@ private void processPDU(P25Message message) */ private void processUMBTC(P25Message message) { - if(message.isValid() && message instanceof UMBTCTelephoneInterconnectRequestExplicitDialing) + if(message.isValid() && message instanceof UMBTCTelephoneInterconnectRequestExplicitDialing tired) { - UMBTCTelephoneInterconnectRequestExplicitDialing tired = (UMBTCTelephoneInterconnectRequestExplicitDialing)message; - processBroadcast(tired.getIdentifiers(), tired.getTimestamp(), DecodeEventType.REQUEST, "TELEPHONE INTERCONNECT:" + tired.getTelephoneNumber()); } @@ -910,11 +901,10 @@ else if(message instanceof PacketMessage) ic.update(identifier); } - DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp()) + DecodeEvent packetEvent = P25DecodeEvent.builder(DecodeEventType.AUTOMATIC_REGISTRATION_SERVICE, message.getTimestamp()) .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.AUTOMATIC_REGISTRATION_SERVICE.toString()) .identifiers(ic) - .details(arsPacket.toString() + " " + ipv4.toString()) + .details(arsPacket + " " + ipv4) .build(); broadcast(packetEvent); @@ -929,11 +919,10 @@ else if(udpPayload instanceof MCGPPacket) ic.update(identifier); } - DecodeEvent cellocatorEvent = P25DecodeEvent.builder(message.getTimestamp()) + DecodeEvent cellocatorEvent = P25DecodeEvent.builder(DecodeEventType.CELLOCATOR, message.getTimestamp()) .channel(getCurrentChannel()) - .eventDescription("Cellocator") .identifiers(ic) - .details(mcgpPacket.toString() + " " + ipv4.toString()) + .details(mcgpPacket + " " + ipv4) .build(); broadcast(cellocatorEvent); @@ -942,10 +931,9 @@ else if(udpPayload instanceof LRRPPacket lrrpPacket) { MutableIdentifierCollection ic = new MutableIdentifierCollection(packet.getIdentifiers()); - DecodeEvent lrrpEvent = P25DecodeEvent.builder(message.getTimestamp()) + DecodeEvent lrrpEvent = P25DecodeEvent.builder(DecodeEventType.LRRP, message.getTimestamp()) .channel(getCurrentChannel()) .details(lrrpPacket + " " + ipv4) - .eventDescription("LRRP") .identifiers(ic) .protocol(Protocol.LRRP) .build(); @@ -956,9 +944,9 @@ else if(udpPayload instanceof LRRPPacket lrrpPacket) if(geoPosition != null) { - PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(message.getTimestamp()) + PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent + .plottableBuilder(DecodeEventType.GPS, message.getTimestamp()) .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.GPS.toString()) .identifiers(ic) .protocol(Protocol.LRRP) .location(geoPosition) @@ -975,9 +963,8 @@ else if(udpPayload instanceof LRRPPacket lrrpPacket) ic.update(identifier); } - DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp()) + DecodeEvent packetEvent = P25DecodeEvent.builder(DecodeEventType.UDP_PACKET, message.getTimestamp()) .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.UDP_PACKET.toString()) .identifiers(ic) .details(ipv4.toString()) .build(); @@ -993,9 +980,8 @@ else if(ipPayload instanceof ICMPPacket) ic.update(identifier); } - DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp()) + DecodeEvent packetEvent = P25DecodeEvent.builder(DecodeEventType.ICMP_PACKET, message.getTimestamp()) .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.ICMP_PACKET.toString()) .identifiers(ic) .details(ipv4.toString()) .build(); @@ -1010,9 +996,8 @@ else if(ipPayload instanceof ICMPPacket) ic.update(identifier); } - DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp()) + DecodeEvent packetEvent = P25DecodeEvent.builder(DecodeEventType.IP_PACKET, message.getTimestamp()) .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.IP_PACKET.toString()) .identifiers(ic) .details(ipv4.toString()) .build(); @@ -1028,9 +1013,8 @@ else if(packet instanceof UnknownPacket) ic.update(identifier); } - DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp()) + DecodeEvent packetEvent = P25DecodeEvent.builder(DecodeEventType.UNKNOWN_PACKET, message.getTimestamp()) .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.UNKNOWN_PACKET.toString()) .identifiers(ic) .details(packet.toString()) .build(); @@ -1376,9 +1360,8 @@ private void processTSBKMotorolaOspDenyResponse(TSBKMessage tsbk) { private void processTSBKRoamingAddressRequest(TSBKMessage tsbk) { //TODO: not sure if this should be used or not. - broadcast(P25DecodeEvent.builder(tsbk.getTimestamp()) + broadcast(P25DecodeEvent.builder(DecodeEventType.REQUEST, tsbk.getTimestamp()) .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.REQUEST.toString()) .details("ROAMING ADDRESS") // TODO: This identifierCollection is different from all the others. .identifiers(new IdentifierCollection(tsbk.getIdentifiers())) diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/AMBTCMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/AMBTCMessageFilter.java deleted file mode 100644 index fe065b3cb..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/AMBTCMessageFilter.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You 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.phase1.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.phase1.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/phase1/message/filter/HDUMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/HeaderMessageFilter.java similarity index 55% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/HDUMessageFilter.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/HeaderMessageFilter.java index ccc837014..7e91bd051 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/HDUMessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/HeaderMessageFilter.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * * This 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,7 +14,7 @@ * * You 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.phase1.message.filter; @@ -24,32 +23,51 @@ import io.github.dsheirer.filter.FilterElement; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HDUMessage; +import java.util.function.Function; -import java.util.Collections; -import java.util.List; - -public class HDUMessageFilter extends Filter +/** + * Filter for HDU header messages + */ +public class HeaderMessageFilter extends Filter { - public HDUMessageFilter() + private static final String HDU_KEY = "Header Data Unit (HDU)"; + private KeyExtractor mKeyExtractor = new KeyExtractor(); + + /** + * Constructor + */ + public HeaderMessageFilter() { - super("Header Data Unit"); + super("Header Messages"); + add(new FilterElement<>(HDU_KEY)); } @Override - public boolean passes(IMessage message) + public boolean canProcess(IMessage message) { - return mEnabled && canProcess(message); + return message instanceof HDUMessage && super.canProcess(message); } @Override - public boolean canProcess(IMessage message) + public Function getKeyExtractor() { - return message instanceof HDUMessage; + return mKeyExtractor; } - @Override - public List> getFilterElements() + /** + * Key extractor + */ + private class KeyExtractor implements Function { - return Collections.EMPTY_LIST; + @Override + public String apply(IMessage message) + { + if(message instanceof HDUMessage) + { + return HDU_KEY; + } + + return null; + } } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/LDUMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/P25OtherMessageFilter.java similarity index 51% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/LDUMessageFilter.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/P25OtherMessageFilter.java index 6f7fb6cd5..d130370e5 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/LDUMessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/P25OtherMessageFilter.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * * This 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,7 +14,7 @@ * * You 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.phase1.message.filter; @@ -23,33 +22,52 @@ 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.phase1.message.ldu.LDUMessage; +import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; +import java.util.function.Function; -import java.util.Collections; -import java.util.List; - -public class LDUMessageFilter extends Filter +/** + * Filter for unknown messages, meaning messages not handled by any other filter. + */ +public class P25OtherMessageFilter extends Filter { - public LDUMessageFilter() + private static final String OTHER_KEY = "Other/Unknown Message"; + private KeyExtractor mKeyExtractor = new KeyExtractor(); + + /** + * Constructor + */ + public P25OtherMessageFilter() { - super("Link Data Unit"); + super("Other P25 messages"); + add(new FilterElement<>(OTHER_KEY)); } @Override - public boolean passes(IMessage message) + public Function getKeyExtractor() { - return mEnabled && canProcess(message); + return mKeyExtractor; } @Override public boolean canProcess(IMessage message) { - return message instanceof LDUMessage; + return message instanceof P25Message && super.canProcess(message); } - @Override - public List> getFilterElements() + /** + * Key extractor + */ + private class KeyExtractor implements Function { - return Collections.EMPTY_LIST; + @Override + public String apply(IMessage message) + { + if(message instanceof P25Message) + { + return OTHER_KEY; + } + + return null; + } } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/P25MessageFilterSet.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/P25P1MessageFilterSet.java similarity index 66% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/P25MessageFilterSet.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/P25P1MessageFilterSet.java index 28dd12c2a..faa5f842c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/P25MessageFilterSet.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/P25P1MessageFilterSet.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * * This 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,35 +14,42 @@ * * You 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.phase1.message.filter; import io.github.dsheirer.filter.FilterSet; +import io.github.dsheirer.filter.SyncLossMessageFilter; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.SyncLossMessage; import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; -public class P25MessageFilterSet extends FilterSet +/** + * Filter set for P25 messages + */ +public class P25P1MessageFilterSet extends FilterSet { - public P25MessageFilterSet() + public P25P1MessageFilterSet() { super("P25 Message Filter"); - addFilter(new AMBTCMessageFilter()); - addFilter(new HDUMessageFilter()); - addFilter(new IPPacketMessageFilter()); - addFilter(new LDUMessageFilter()); + addFilter(new HeaderMessageFilter()); + addFilter(new PacketMessageFilter()); addFilter(new PDUMessageFilter()); addFilter(new SNDCPMessageFilter()); addFilter(new SyncLossMessageFilter()); - addFilter(new TDUMessageFilter()); - addFilter(new TDULCMessageFilter()); - addFilter(new TSBKMessageFilterSet()); - addFilter(new UMBTCMessageFilter()); + addFilter(new TerminatorMessageFilterSet()); + addFilter(new TrunkingOpcodeMessageFilterSet()); + addFilter(new VoiceMessageFilter()); + addFilter(new P25OtherMessageFilter()); } + /** + * Override default to descope handling to P25 or sync-loss messages. + * @param message to test + * @return true if the message can be processed + */ @Override public boolean canProcess(IMessage message) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/PDUMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/PDUMessageFilter.java index 1f4470707..7a0eaec96 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/PDUMessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/PDUMessageFilter.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * * This 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,7 +14,7 @@ * * You 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.phase1.message.filter; @@ -24,49 +23,52 @@ import io.github.dsheirer.filter.FilterElement; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUMessage; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode; -import io.github.dsheirer.protocol.Protocol; +import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDUMessage; +import java.util.function.Function; -import java.util.*; - -public class PDUMessageFilter extends Filter +/** + * Filter for PDU packet data unit messages + */ +public class PDUMessageFilter extends Filter { - private Map> mOpcodeFilterElements = new EnumMap<>(Opcode.class); + private static final String PDU_KEY = "Packet Data Unit (PDU)"; + private KeyExtractor mKeyExtractor = new KeyExtractor(); + /** + * Constructor + */ public PDUMessageFilter() { - super("PDU - Packet Data Unit"); - - for(Opcode opcode : Opcode.values()) - { - mOpcodeFilterElements.put(opcode, new FilterElement(opcode)); - } + super("Packet Data Unit messages"); + add(new FilterElement<>(PDU_KEY)); } @Override - public boolean passes(IMessage message) + public Function getKeyExtractor() { - if(mEnabled && message instanceof PDUMessage) - { - PDUMessage pdu = (PDUMessage) message; - - Opcode opcode = pdu.getOpcode(); - - return mOpcodeFilterElements.get(opcode).isEnabled(); - } - - return false; + return mKeyExtractor; } @Override public boolean canProcess(IMessage message) { - return message instanceof PDUMessage; + return message instanceof TDUMessage && super.canProcess(message); } - @Override - public List> getFilterElements() + /** + * Key extractor + */ + private class KeyExtractor implements Function { - return new ArrayList>(mOpcodeFilterElements.values()); + @Override + public String apply(IMessage message) + { + if(message instanceof PDUMessage) + { + return PDU_KEY; + } + + return null; + } } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/PacketMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/PacketMessageFilter.java new file mode 100644 index 000000000..0e6034736 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/PacketMessageFilter.java @@ -0,0 +1,135 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.phase1.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.ip.IPacket; +import io.github.dsheirer.module.decode.ip.cellocator.MCGPPacket; +import io.github.dsheirer.module.decode.ip.icmp.ICMPPacket; +import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet; +import io.github.dsheirer.module.decode.ip.mototrbo.ars.ARSPacket; +import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.LRRPPacket; +import io.github.dsheirer.module.decode.ip.mototrbo.xcmp.XCMPPacket; +import io.github.dsheirer.module.decode.ip.udp.UDPPacket; +import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.PacketMessage; +import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.sndcp.SNDCPPacketMessage; +import java.util.function.Function; + +/** + * Filter for packet messages + */ +public class PacketMessageFilter extends Filter +{ + private static final String KEY_CELLOCATOR = "Cellocator Packet"; + private static final String KEY_IPV4_OTHER = "IPV4 Other Packet"; + private static final String KEY_IPV4_UDP_ICMP = "IPV4 UDP/IP ICMP Packet"; + private static final String KEY_IPV4_UDP_OTHER = "IPV4 UDP/IP Other Packet"; + private static final String KEY_MOTOROLA_ARS = "Motorola Registration (ARS) Packet"; + private static final String KEY_MOTOROLA_LRRP = "Motorola Location Report (LRRP) Packet"; + private static final String KEY_MOTOROLA_XCMP = "Motorola Extensible (XCMP) Packet"; + private static final String KEY_SNDCP = "SNDCP Data Packet"; + private static final String KEY_UNKNOWN = "Unknown Packet"; + private KeyExtractor mKeyExtractor = new KeyExtractor(); + + /** + * Constructor + */ + public PacketMessageFilter() + { + super("Packet Messages"); + add(new FilterElement<>(KEY_CELLOCATOR)); + add(new FilterElement<>(KEY_IPV4_OTHER)); + add(new FilterElement<>(KEY_IPV4_UDP_ICMP)); + add(new FilterElement<>(KEY_IPV4_UDP_OTHER)); + add(new FilterElement<>(KEY_MOTOROLA_ARS)); + add(new FilterElement<>(KEY_MOTOROLA_LRRP)); + add(new FilterElement<>(KEY_MOTOROLA_XCMP)); + add(new FilterElement<>(KEY_SNDCP)); + add(new FilterElement<>(KEY_UNKNOWN)); + } + + @Override + public Function getKeyExtractor() + { + return mKeyExtractor; + } + + @Override + public boolean canProcess(IMessage message) + { + return message instanceof PacketMessage && super.canProcess(message); + } + + /** + * Key extractor + */ + private class KeyExtractor implements Function + { + @Override + public String apply(IMessage message) + { + if(message instanceof PacketMessage packetMessage) + { + IPacket packet = packetMessage.getPacket(); + + if(packet instanceof MCGPPacket) + { + return KEY_CELLOCATOR; + } + else if(packet instanceof ICMPPacket) + { + return KEY_IPV4_UDP_ICMP; + } + else if(packet instanceof UDPPacket) + { + return KEY_IPV4_UDP_OTHER; + } + else if(packet instanceof IPV4Packet) + { + return KEY_IPV4_OTHER; + } + else if(packet instanceof SNDCPPacketMessage) + { + return KEY_SNDCP; + } + else if(packet instanceof ARSPacket) + { + return KEY_MOTOROLA_ARS; + } + else if(packet instanceof LRRPPacket) + { + return KEY_MOTOROLA_LRRP; + } + else if(packet instanceof XCMPPacket) + { + return KEY_MOTOROLA_XCMP; + } + + else + { + return KEY_UNKNOWN; + } + } + return null; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/SNDCPMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/SNDCPMessageFilter.java index 00bb34691..ecae7ce1a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/SNDCPMessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/SNDCPMessageFilter.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * * This 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,7 +14,7 @@ * * You 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.phase1.message.filter; @@ -24,32 +23,55 @@ import io.github.dsheirer.filter.FilterElement; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.sndcp.SNDCPMessage; +import io.github.dsheirer.module.decode.p25.reference.PDUType; +import java.util.function.Function; -import java.util.Collections; -import java.util.List; - -public class SNDCPMessageFilter extends Filter +/** + * Filter for SNDCP messages + */ +public class SNDCPMessageFilter extends Filter { + private KeyExtractor mKeyExtractor = new KeyExtractor(); + + /** + * Constructor + */ public SNDCPMessageFilter() { - super("Sub-Network Dependent Convergence Protocol"); + super("Sub-Network Dependent Convergence Protocol (SNDCP) Messages"); + + for(PDUType pduType: PDUType.values()) + { + add(new FilterElement<>(pduType)); + } } @Override - public boolean passes(IMessage message) + public boolean canProcess(IMessage message) { - return mEnabled && canProcess(message); + return message instanceof SNDCPMessage && super.canProcess(message); } @Override - public boolean canProcess(IMessage message) + public Function getKeyExtractor() { - return message instanceof SNDCPMessage; + return mKeyExtractor; } - @Override - public List> getFilterElements() + /** + * Key extractor + */ + private class KeyExtractor implements Function { - return Collections.EMPTY_LIST; + @Override + public PDUType apply(IMessage message) + { + if(message instanceof SNDCPMessage sndcp) + { + return sndcp.getPDUType(); + } + + return null; + } } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TDULCMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TDULCMessageFilter.java deleted file mode 100644 index c1c6b04f5..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TDULCMessageFilter.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You 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.phase1.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.phase1.message.tdu.TDULinkControlMessage; - -import java.util.Collections; -import java.util.List; - -public class TDULCMessageFilter extends Filter -{ - public TDULCMessageFilter() - { - super("TDU Terminator Data Unit with Link Control"); - } - - @Override - public boolean passes(IMessage message) - { - return mEnabled && canProcess(message); - } - - @Override - public boolean canProcess(IMessage message) - { - return message instanceof TDULinkControlMessage; - } - - @Override - public List> getFilterElements() - { - return Collections.EMPTY_LIST; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TDUMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TDUMessageFilter.java index f47bf304e..44794e832 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TDUMessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TDUMessageFilter.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * * This 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,7 +14,7 @@ * * You 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.phase1.message.filter; @@ -24,32 +23,51 @@ import io.github.dsheirer.filter.FilterElement; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDUMessage; +import java.util.function.Function; -import java.util.Collections; -import java.util.List; - -public class TDUMessageFilter extends Filter +/** + * Filter for TDU terminator (no link control) messages + */ +public class TDUMessageFilter extends Filter { + private static final String TDU_KEY = "Terminator Data Unit (TDU)"; + private KeyExtractor mKeyExtractor = new KeyExtractor(); + + /** + * Constructor + */ public TDUMessageFilter() { - super("TDU Terminator Data Unit"); + super("Terminator/No Link Control messages"); + add(new FilterElement<>(TDU_KEY)); } @Override - public boolean passes(IMessage message) + public Function getKeyExtractor() { - return mEnabled && canProcess(message); + return mKeyExtractor; } @Override public boolean canProcess(IMessage message) { - return message instanceof TDUMessage; + return message instanceof TDUMessage && super.canProcess(message); } - @Override - public List> getFilterElements() + /** + * Key extractor + */ + private class KeyExtractor implements Function { - return Collections.EMPTY_LIST; + @Override + public String apply(IMessage message) + { + if(message instanceof TDUMessage) + { + return TDU_KEY; + } + + return null; + } } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TSBKMessageFilterSet.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TSBKMessageFilterSet.java deleted file mode 100644 index 2efe15ce5..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TSBKMessageFilterSet.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You 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.phase1.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.IMessage; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.reference.Vendor; - -import java.util.*; - -public class TSBKMessageFilterSet extends FilterSet -{ - public TSBKMessageFilterSet() - { - super("TSBK Trunking Signalling Block"); - - addFilter(new StandardOpcodeFilter()); - } - -// @Override -// public boolean passes( Message message ) -// { -// if( mEnabled && canProcess( message ) ) -// { -// TSBKMessage tsbk = (TSBKMessage) message; -// -// switch( tsbk.getVendor() ) -// { -// case STANDARD: -// return -// } -// Opcode opcode = tsbk.getOpcode(); -// -// return mFilterElements.get( opcode ).isEnabled(); -// } -// -// return false; -// } - - @Override - public boolean canProcess(IMessage message) - { - return message instanceof TSBKMessage; - } - - public class StandardOpcodeFilter extends Filter - { - private Map> mStandardElements = new EnumMap<>(Opcode.class); - - 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 passes(IMessage message) - { - if(mEnabled && canProcess(message)) - { - Opcode opcode = ((TSBKMessage) message).getOpcode(); - - if(mStandardElements.containsKey(opcode)) - { - return mStandardElements.get(opcode).isEnabled(); - } - } - - return false; - } - - @Override - public boolean canProcess(IMessage message) - { - return ((TSBKMessage) message).getVendor() == Vendor.STANDARD; - } - - @Override - public List> getFilterElements() - { - return new ArrayList>(mStandardElements.values()); - } - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TerminatorMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TerminatorMessageFilter.java new file mode 100644 index 000000000..be1df793b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TerminatorMessageFilter.java @@ -0,0 +1,74 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.phase1.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.phase1.message.lc.LinkControlOpcode; +import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULinkControlMessage; +import java.util.Collection; +import java.util.function.Function; + +/** + * Filter for TDU terminator and TDULC terminator with link control messages + */ +public class TerminatorMessageFilter extends Filter +{ + private KeyExtractor mKeyExtractor = new KeyExtractor(); + + /** + * Constructs an instance + * + * @param name of this filter + */ + public TerminatorMessageFilter(String name, Collection opcodes) + { + super(name); + + for(LinkControlOpcode opcode: opcodes) + { + add(new FilterElement<>(opcode)); + } + } + + @Override + public Function getKeyExtractor() + { + return mKeyExtractor; + } + + /** + * Key extractor + */ + private class KeyExtractor implements Function + { + @Override + public LinkControlOpcode apply(IMessage message) + { + if(message instanceof TDULinkControlMessage tdulc) + { + return tdulc.getLinkControlWord().getOpcode(); + } + + return null; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TerminatorMessageFilterSet.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TerminatorMessageFilterSet.java new file mode 100644 index 000000000..b87a9a35b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TerminatorMessageFilterSet.java @@ -0,0 +1,63 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.phase1.message.filter; + +import io.github.dsheirer.filter.FilterSet; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlOpcode; +import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULinkControlMessage; +import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDUMessage; +import java.util.ArrayList; +import java.util.List; + +/** + * Filter set for terminator/link control messages + */ +public class TerminatorMessageFilterSet extends FilterSet +{ + /** + * Constructor + */ + public TerminatorMessageFilterSet() + { + super("Terminator Messages"); + addFilter(new TDUMessageFilter()); + addFilter(new TerminatorMessageFilter("Command/Status", LinkControlOpcode.COMMAND_STATUS_OPCODES)); + addFilter(new TerminatorMessageFilter("Network/Channel", LinkControlOpcode.NETWORK_OPCODES)); + addFilter(new TerminatorMessageFilter("Voice", LinkControlOpcode.VOICE_OPCODES)); + addFilter(new TerminatorMessageFilter("Motorola", LinkControlOpcode.MOTOROLA_OPCODES)); + + List others = new ArrayList<>(); + for(LinkControlOpcode opcode: LinkControlOpcode.values()) + { + if(!opcode.isGrouped()) + { + others.add(opcode); + } + } + addFilter(new TerminatorMessageFilter("Other/Reserved", others)); + } + + @Override + public boolean canProcess(IMessage message) + { + return (message instanceof TDUMessage || message instanceof TDULinkControlMessage) && super.canProcess(message); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TrunkingOpcodeMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TrunkingOpcodeMessageFilter.java new file mode 100644 index 000000000..bee5291b3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TrunkingOpcodeMessageFilter.java @@ -0,0 +1,91 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.phase1.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.phase1.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.phase1.message.pdu.umbtc.UMBTCMessage; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessage; +import java.util.Collection; +import java.util.function.Function; + +/** + * Message filter for P25 TSBK, AMBTC and UMBTC trunking control messages. + */ +public class TrunkingOpcodeMessageFilter extends Filter +{ + private KeyExtractor mKeyExtractor = new KeyExtractor(); + private Collection mOpcodes; + + /** + * Constructs an instance + * + * @param name of this filter + */ + public TrunkingOpcodeMessageFilter(String name, Collection opcodes) + { + super(name); + + mOpcodes = opcodes; + + for(Opcode opcode: opcodes) + { + add(new FilterElement<>(opcode)); + } + } + + /** + * Key extractor + * @return opcode extractor + */ + @Override + public Function getKeyExtractor() + { + return mKeyExtractor; + } + + /** + * Key extractor for Opcode values from TSBK, AMBTC and UMBTC messages. + */ + private class KeyExtractor implements Function + { + @Override + public Opcode apply(IMessage message) + { + if(message instanceof TSBKMessage tsbk) + { + return tsbk.getOpcode(); + } + else if(message instanceof AMBTCMessage ambtc) + { + return ambtc.getHeader().getOpcode(); + } + else if(message instanceof UMBTCMessage umbtc) + { + return umbtc.getOpcode(); + } + + return null; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TrunkingOpcodeMessageFilterSet.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TrunkingOpcodeMessageFilterSet.java new file mode 100644 index 000000000..ed7be3ba8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TrunkingOpcodeMessageFilterSet.java @@ -0,0 +1,65 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.phase1.message.filter; + +import io.github.dsheirer.filter.FilterSet; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.phase1.message.pdu.umbtc.UMBTCMessage; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessage; +import java.util.List; + +/** + * Filter set for trunking messages + */ +public class TrunkingOpcodeMessageFilterSet extends FilterSet +{ + /** + * Constructor + */ + public TrunkingOpcodeMessageFilterSet() + { + super("Trunking Messages"); + addFilter(new TrunkingOpcodeMessageFilter("Mobile Data Requests", Opcode.MOBILE_DATA_REQUESTS)); + addFilter(new TrunkingOpcodeMessageFilter("Mobile Request/Response", Opcode.MOBILE_REQUEST_RESPONSE)); + addFilter(new TrunkingOpcodeMessageFilter("Mobile Voice Requests", Opcode.MOBILE_VOICE_REQUESTS)); + addFilter(new TrunkingOpcodeMessageFilter("Network/Channel Status", Opcode.NETWORK_CHANNEL_STATUS)); + addFilter(new TrunkingOpcodeMessageFilter("Network Data Grants", Opcode.NETWORK_DATA_GRANTS)); + addFilter(new TrunkingOpcodeMessageFilter("Network Command/Request/Response", Opcode.NETWORK_COMMAND_REQUEST_RESPONSE)); + addFilter(new TrunkingOpcodeMessageFilter("Network Voice Grants", Opcode.NETWORK_VOICE_GRANTS)); + addFilter(new TrunkingOpcodeMessageFilter("Vendor-Harris Messages", Opcode.HARRIS)); + addFilter(new TrunkingOpcodeMessageFilter("Vendor-Motorola Messages", Opcode.MOTOROLA)); + addFilter(new TrunkingOpcodeMessageFilter("Vendor-Unknown Messages", Opcode.UNKNOWN)); + + List ungrouped = Opcode.getUngrouped(); + if(!ungrouped.isEmpty()) + { + addFilter(new TrunkingOpcodeMessageFilter("Unknown Opcode Messages", ungrouped)); + } + } + + @Override + public boolean canProcess(IMessage message) + { + return (message instanceof TSBKMessage || message instanceof AMBTCMessage || message instanceof UMBTCMessage) && + super.canProcess(message); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/UMBTCMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/UMBTCMessageFilter.java deleted file mode 100644 index e355a9cea..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/UMBTCMessageFilter.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You 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.phase1.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.phase1.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/phase1/message/filter/VoiceMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/VoiceMessageFilter.java new file mode 100644 index 000000000..70788207b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/VoiceMessageFilter.java @@ -0,0 +1,83 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.phase1.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.phase1.message.ldu.LDU1Message; +import io.github.dsheirer.module.decode.p25.phase1.message.ldu.LDU2Message; +import io.github.dsheirer.module.decode.p25.phase1.message.ldu.LDUMessage; +import java.util.function.Function; + +/** + * Filter for LDU1 and LDU2 voice messages + */ +public class VoiceMessageFilter extends Filter +{ + private static final String LDU1_KEY = "LDU1 Voice Message"; + private static final String LDU2_KEY = "LDU2 Voice Message"; + private KeyExtractor mKeyExtractor = new KeyExtractor(); + + /** + * Constructs an instance + * + * @param name of this filter + */ + public VoiceMessageFilter() + { + super("Voice Messages"); + add(new FilterElement<>(LDU1_KEY)); + add(new FilterElement<>(LDU2_KEY)); + } + + @Override + public Function getKeyExtractor() + { + return mKeyExtractor; + } + + @Override + public boolean canProcess(IMessage message) + { + return message instanceof LDUMessage && super.canProcess(message); + } + + /** + * Key extractor + */ + private class KeyExtractor implements Function + { + @Override + public String apply(IMessage message) + { + if(message instanceof LDU1Message) + { + return LDU1_KEY; + } + else if(message instanceof LDU2Message) + { + return LDU2_KEY; + } + + return null; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlOpcode.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlOpcode.java index 6a8fddddf..0d039e781 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlOpcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlOpcode.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * * This 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,13 +14,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.p25.phase1.message.lc; import io.github.dsheirer.module.decode.p25.reference.Vendor; +import java.util.EnumSet; +/** + * Opcodes used for Link Control Words. + */ public enum LinkControlOpcode { GROUP_VOICE_CHANNEL_USER("GROUP VOICE CHANNEL USER", 0), @@ -101,22 +104,82 @@ public enum LinkControlOpcode private String mLabel; private int mCode; + /** + * Constructor + * @param label to display for the opcode + * @param code value. + */ LinkControlOpcode(String label, int code) { mLabel = label; mCode = code; } + /** + * Command, response and status opcodes + */ + public static final EnumSet COMMAND_STATUS_OPCODES = EnumSet.of(UNIT_TO_UNIT_ANSWER_REQUEST, + TELEPHONE_INTERCONNECT_ANSWER_REQUEST, CALL_TERMINATION_OR_CANCELLATION, GROUP_AFFILIATION_QUERY, + UNIT_AUTHENTICATION_COMMAND, UNIT_REGISTRATION_COMMAND, STATUS_QUERY, STATUS_UPDATE, MESSAGE_UPDATE, + EXTENDED_FUNCTION_COMMAND, CALL_ALERT); + + /** + * Motorola Opcodes + */ + public static final EnumSet MOTOROLA_OPCODES = + EnumSet.range(MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_USER, MOTOROLA_UNKNOWN); + + + /** + * Network/channel related opcodes + */ + public static final EnumSet NETWORK_OPCODES = EnumSet.of(CHANNEL_IDENTIFIER_UPDATE, + CHANNEL_IDENTIFIER_UPDATE_EXPLICIT, SYSTEM_SERVICE_BROADCAST, SECONDARY_CONTROL_CHANNEL_BROADCAST, + SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT, ADJACENT_SITE_STATUS_BROADCAST, + ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT, RFSS_STATUS_BROADCAST, RFSS_STATUS_BROADCAST_EXPLICIT, + NETWORK_STATUS_BROADCAST, NETWORK_STATUS_BROADCAST_EXPLICIT, PROTECTION_PARAMETER_BROADCAST); + + /** + * Voice/call opcodes + */ + public static final EnumSet VOICE_OPCODES = EnumSet.of(GROUP_VOICE_CHANNEL_USER, + GROUP_VOICE_CHANNEL_UPDATE, GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT, UNIT_TO_UNIT_VOICE_CHANNEL_USER, + TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER); + + /** + * Indicates if the enumeration element is contained in one of the enumset groupings above. + * @return true if the element is grouped. + */ + public boolean isGrouped() + { + return COMMAND_STATUS_OPCODES.contains(this) || MOTOROLA_OPCODES.contains(this) || + NETWORK_OPCODES.contains(this) || VOICE_OPCODES.contains(this); + } + + /** + * Pretty label/string for the element + * @return label + */ public String getLabel() { return mLabel; } + /** + * Numeric value of the opcode. + * @return code value. + */ public int getCode() { return mCode; } + /** + * Lookup method for finding an opcode for a given vendor and value combination. + * @param value of the opcode + * @param vendor for the opcode + * @return matching element or UNKNOWN. + */ public static LinkControlOpcode fromValue(int value, Vendor vendor) { switch(vendor) diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/Opcode.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/Opcode.java index 09ccf88d3..a2f2f446b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/Opcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/Opcode.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2021 Dennis Sheirer + * Copyright (C) 2014-2023 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,8 +21,9 @@ 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.EnumSet; +import java.util.List; public enum Opcode { @@ -192,18 +193,146 @@ public enum Opcode private String mLabel; private String mDescription; + /** + * Constructor + * @param code value + * @param label or pretty value. + * @param description of the opcode + */ + Opcode(int code, String label, String description) + { + mCode = code; + mLabel = label; + mDescription = description; + } + + /** + * OSP standard opcodes + */ public static final EnumSet STANDARD_OUTBOUND_OPCODES = EnumSet.range(OSP_GROUP_VOICE_CHANNEL_GRANT, OSP_PROTECTION_PARAMETER_UPDATE); + + /** + * ISP standard opcodes + */ public static final EnumSet STANDARD_INBOUND_OPCODES = EnumSet.range(ISP_GROUP_VOICE_SERVICE_REQUEST, ISP_RESERVED_3F); - public static final EnumSet DATA_CHANNEL_GRANT_OPCODES = EnumSet.of(OSP_SNDCP_DATA_CHANNEL_GRANT, - OSP_INDIVIDUAL_DATA_CHANNEL_GRANT, OSP_GROUP_DATA_CHANNEL_GRANT); - Opcode(int code, String label, String description) + /** + * ISP mobile data requests + */ + public static final EnumSet MOBILE_DATA_REQUESTS = EnumSet.of(ISP_INDIVIDUAL_DATA_SERVICE_REQUEST, + ISP_GROUP_DATA_SERVICE_REQUEST, ISP_SNDCP_DATA_CHANNEL_REQUEST, ISP_SNDCP_RECONNECT_REQUEST); + + /** + * ISP mobile voice requests + */ + public static final EnumSet MOBILE_VOICE_REQUESTS = EnumSet.of(ISP_GROUP_VOICE_SERVICE_REQUEST, + ISP_UNIT_TO_UNIT_VOICE_SERVICE_REQUEST, ISP_TELEPHONE_INTERCONNECT_EXPLICIT_DIAL_REQUEST, + ISP_TELEPHONE_INTERCONNECT_PSTN_REQUEST); + + /** + * ISP mobile request, response and status. + */ + public static final EnumSet MOBILE_REQUEST_RESPONSE = EnumSet.of(ISP_UNIT_TO_UNIT_ANSWER_RESPONSE, + ISP_TELEPHONE_INTERCONNECT_ANSWER_RESPONSE, ISP_SNDCP_DATA_PAGE_RESPONSE, ISP_STATUS_UPDATE_REQUEST, + ISP_STATUS_QUERY_REQUEST, ISP_STATUS_QUERY_RESPONSE, ISP_MESSAGE_UPDATE_REQUEST, + ISP_RADIO_UNIT_MONITOR_REQUEST, ISP_CALL_ALERT_REQUEST, ISP_UNIT_ACKNOWLEDGE_RESPONSE, + ISP_CANCEL_SERVICE_REQUEST, ISP_EXTENDED_FUNCTION_RESPONSE, ISP_EMERGENCY_ALARM_REQUEST, + ISP_GROUP_AFFILIATION_QUERY_RESPONSE, ISP_GROUP_AFFILIATION_REQUEST, ISP_UNIT_DE_REGISTRATION_REQUEST, + ISP_UNIT_REGISTRATION_REQUEST, ISP_LOCATION_REGISTRATION_REQUEST, ISP_AUTHENTICATION_QUERY_OBSOLETE, + ISP_AUTHENTICATION_RESPONSE_OBSOLETE, ISP_PROTECTION_PARAMETER_REQUEST, ISP_IDENTIFIER_UPDATE_REQUEST, + ISP_ROAMING_ADDRESS_REQUEST, ISP_ROAMING_ADDRESS_RESPONSE, ISP_AUTHENTICATION_RESPONSE, + ISP_AUTHENTICATION_RESPONSE_MUTUAL, ISP_AUTHENTICATION_FNE_RESULT, ISP_AUTHENTICATION_SU_DEMAND); + + /** + * OSP network data channel grants. + */ + public static final EnumSet NETWORK_DATA_GRANTS = EnumSet.of(OSP_SNDCP_DATA_CHANNEL_GRANT, + OSP_INDIVIDUAL_DATA_CHANNEL_GRANT, OSP_GROUP_DATA_CHANNEL_GRANT); + + /** + * OSP network status + */ + public static final EnumSet NETWORK_CHANNEL_STATUS = EnumSet.of(OSP_GROUP_DATA_CHANNEL_ANNOUNCEMENT, + OSP_GROUP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT, OSP_SNDCP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT, + OSP_SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT, OSP_TDMA_SYNC_BROADCAST, OSP_IDENTIFIER_UPDATE_TDMA, + OSP_IDENTIFIER_UPDATE_VHF_UHF_BANDS, OSP_TIME_DATE_ANNOUNCEMENT, OSP_SYSTEM_SERVICE_BROADCAST, + OSP_SECONDARY_CONTROL_CHANNEL_BROADCAST, OSP_RFSS_STATUS_BROADCAST, OSP_NETWORK_STATUS_BROADCAST, + OSP_ADJACENT_STATUS_BROADCAST, OSP_IDENTIFIER_UPDATE, OSP_PROTECTION_PARAMETER_BROADCAST, + OSP_PROTECTION_PARAMETER_UPDATE); + + /** + * OSP network command, request and response + */ + public static final EnumSet NETWORK_COMMAND_REQUEST_RESPONSE = EnumSet.of(OSP_UNIT_TO_UNIT_ANSWER_REQUEST, + OSP_TELEPHONE_INTERCONNECT_ANSWER_REQUEST, OSP_SNDCP_DATA_PAGE_REQUEST, OSP_STATUS_UPDATE, + OSP_STATUS_QUERY, OSP_MESSAGE_UPDATE, OSP_RADIO_UNIT_MONITOR_COMMAND, OSP_CALL_ALERT, + OSP_ACKNOWLEDGE_RESPONSE, OSP_QUEUED_RESPONSE, OSP_EXTENDED_FUNCTION_COMMAND, OSP_DENY_RESPONSE, + OSP_GROUP_AFFILIATION_QUERY, OSP_GROUP_AFFILIATION_RESPONSE, OSP_LOCATION_REGISTRATION_RESPONSE, + OSP_UNIT_REGISTRATION_COMMAND, OSP_UNIT_REGISTRATION_RESPONSE, OSP_AUTHENTICATION_COMMAND, + OSP_UNIT_DEREGISTRATION_ACKNOWLEDGE, OSP_AUTHENTICATION_DEMAND, OSP_AUTHENTICATION_FNE_RESPONSE, + OSP_ROAMING_ADDRESS_COMMAND, OSP_ROAMING_ADDRESS_UPDATE); + + /** + * OSP network voice channel grants. + */ + public static final EnumSet NETWORK_VOICE_GRANTS = EnumSet.of(OSP_GROUP_VOICE_CHANNEL_GRANT, + OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE, OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT, + OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT, OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE, + OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT, OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE); + + /** + * Motorola opcodes + */ + public static final EnumSet MOTOROLA = EnumSet.of(MOTOROLA_ISP_UNKNOWN, MOTOROLA_OSP_PATCH_GROUP_ADD, + MOTOROLA_OSP_PATCH_GROUP_DELETE, MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT, + MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT_UPDATE, MOTOROLA_OSP_TRAFFIC_CHANNEL_ID, + MOTOROLA_OSP_DENY_RESPONSE, MOTOROLA_OSP_SYSTEM_LOADING, MOTOROLA_OSP_BASE_STATION_ID, + MOTOROLA_OSP_CONTROL_CHANNEL_PLANNED_SHUTDOWN, MOTOROLA_OSP_UNKNOWN); + + /** + * Harris opcodes + */ + public static final EnumSet HARRIS = EnumSet.of(HARRIS_ISP_UNKNOWN, HARRIS_OSP_TDMA_SYNC, + HARRIS_OSP_UNKNOWN); + + /** + * Unknown vendor ISP/OSP opcodes + */ + public static final EnumSet UNKNOWN = EnumSet.of(UNKNOWN_VENDOR_ISP, UNKNOWN_VENDOR_OSP); + + /** + * Indicates if the enumeration element is contained in one of the enumset groupings above. + * @return true if the element is grouped. + */ + public boolean isGrouped() { - mCode = code; - mLabel = label; - mDescription = description; + return MOBILE_DATA_REQUESTS.contains(this) || MOBILE_VOICE_REQUESTS.contains(this) || + MOBILE_REQUEST_RESPONSE.contains(this) || NETWORK_DATA_GRANTS.contains(this) || + NETWORK_CHANNEL_STATUS.contains(this) || NETWORK_COMMAND_REQUEST_RESPONSE.contains(this) || + NETWORK_VOICE_GRANTS.contains(this) || MOTOROLA.contains(this) || HARRIS.contains(this) || + UNKNOWN.contains(this); + } + + /** + * List of opcodes that are not grouped. This will catch any opcodes that are added to this enumeration where + * the opcode is not added to one of the existing groups. This is used for message filtering to ensure that all + * opcodes and handled by the filter/filterset. + * @return list of ungrouped opcodes. + */ + public static List getUngrouped() + { + List ungrouped = new ArrayList<>(); + for(Opcode opcode: ungrouped) + { + if(!opcode.isGrouped()) + { + ungrouped.add(opcode); + } + } + + return ungrouped; } /** @@ -235,7 +364,7 @@ public int getCode() */ public boolean isDataChannelGrant() { - return DATA_CHANNEL_GRANT_OPCODES.contains(this); + return NETWORK_DATA_GRANTS.contains(this); } @Override @@ -244,17 +373,13 @@ 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; - } - + /** + * Lookup an opcode from a numeric value, vendor, and inbound/outbound direction. + * @param value for the opcode + * @param direction indicator - inbound (mobile) or outbound (tower) + * @param vendor identifier + * @return opcode or UNKNOWN + */ public static Opcode fromValue(int value, Direction direction, Vendor vendor) { switch(vendor) diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java index 19daf59ea..c7e252448 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -169,9 +169,8 @@ public void receive(IMessage message) { if(message.isValid() && message.getTimeslot() == getTimeslot()) { - if(message instanceof MacMessage) + if(message instanceof MacMessage macMessage) { - MacMessage macMessage = (MacMessage)message; processMacMessage(macMessage); MacPduType macPduType = macMessage.getMacPduType(); @@ -315,31 +314,26 @@ private void processMacMessage(MacMessage message) processMacRelease(message, mac); break; case PHASE1_65_GROUP_VOICE_SERVICE_REQUEST: - if(mac instanceof GroupVoiceServiceRequest) + if(mac instanceof GroupVoiceServiceRequest gvsr) { - GroupVoiceServiceRequest gvsr = (GroupVoiceServiceRequest)mac; - broadcast(message, mac, getCurrentChannel(), DecodeEventType.REQUEST, "GROUP VOICE SERVICE " + gvsr.getServiceOptions()); } break; case PHASE1_68_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_ABBREVIATED: - if(mac instanceof UnitToUnitVoiceChannelGrantAbbreviated) + if(mac instanceof UnitToUnitVoiceChannelGrantAbbreviated uuvcga) { - UnitToUnitVoiceChannelGrantAbbreviated uuvcga = (UnitToUnitVoiceChannelGrantAbbreviated)mac; broadcast(message, mac, uuvcga.getChannel(), DecodeEventType.CALL_UNIT_TO_UNIT, "VOICE CHANNEL GRANT"); } break; case PHASE1_69_UNIT_TO_UNIT_ANSWER_REQUEST_ABBREVIATED: - if(mac instanceof UnitToUnitAnswerRequestAbbreviated) + if(mac instanceof UnitToUnitAnswerRequestAbbreviated uuara) { - UnitToUnitAnswerRequestAbbreviated uuara = (UnitToUnitAnswerRequestAbbreviated)mac; broadcast(message, mac, getCurrentChannel(), DecodeEventType.REQUEST, "UNIT-TO-UNIT ANSWER REQUEST - " + uuara.getServiceOptions()); } break; case PHASE1_74_TELEPHONE_INTERCONNECT_ANSWER_REQUEST: - if(mac instanceof TelephoneInterconnectAnswerRequest) + if(mac instanceof TelephoneInterconnectAnswerRequest tiar) { - TelephoneInterconnectAnswerRequest tiar = (TelephoneInterconnectAnswerRequest)mac; broadcast(message, mac, getCurrentChannel(), DecodeEventType.REQUEST, "TELEPHONE INTERCONNECT ANSWER REQUEST"); } break; @@ -350,9 +344,8 @@ private void processMacMessage(MacMessage message) processSDCG(message, mac); break; case PHASE1_85_SNDCP_DATA_PAGE_REQUEST: - if(mac instanceof SNDCPDataPageRequest) + if(mac instanceof SNDCPDataPageRequest sdpr) { - SNDCPDataPageRequest sdpr = (SNDCPDataPageRequest)mac; broadcast(message, mac, getCurrentChannel(), DecodeEventType.PAGE, "SNDCP DATA PAGE " + sdpr.getServiceOptions()); } break; @@ -366,9 +359,8 @@ private void processMacMessage(MacMessage message) } break; case PHASE1_92_MESSAGE_UPDATE_ABBREVIATED: - if(mac instanceof MessageUpdateAbbreviated) + if(mac instanceof MessageUpdateAbbreviated mua) { - MessageUpdateAbbreviated mua = (MessageUpdateAbbreviated)mac; broadcast(message, mac, getCurrentChannel(), DecodeEventType.SDM, "MESSAGE UPDATE - " + mua.getShortDataMessage()); } break; @@ -379,9 +371,8 @@ private void processMacMessage(MacMessage message) processCallAlert(message, mac); break; case PHASE1_96_ACK_RESPONSE: - if(mac instanceof AcknowledgeResponse) + if(mac instanceof AcknowledgeResponse ar) { - AcknowledgeResponse ar = (AcknowledgeResponse)mac; broadcast(message, mac, getCurrentChannel(), DecodeEventType.RESPONSE, "ACKNOWLEDGE: " + ar.getServiceType()); } break; @@ -401,10 +392,8 @@ private void processMacMessage(MacMessage message) broadcast(message, mac, getCurrentChannel(), DecodeEventType.COMMAND, "UNIT REGISTRATION"); break; case PHASE1_197_UNIT_TO_UNIT_ANSWER_REQUEST_EXTENDED: - if(mac instanceof UnitToUnitAnswerRequestExtended) + if(mac instanceof UnitToUnitAnswerRequestExtended uuare) { - UnitToUnitAnswerRequestExtended uuare = (UnitToUnitAnswerRequestExtended)mac; - broadcast(message, mac, getCurrentChannel(), DecodeEventType.REQUEST, "UNIT-TO-UNIT ANSWER REQUEST " + uuare.getServiceOptions()); } break; @@ -418,9 +407,8 @@ private void processMacMessage(MacMessage message) broadcast(message, mac, getCurrentChannel(), DecodeEventType.STATUS, "STATUS QUERY"); break; case PHASE1_220_MESSAGE_UPDATE_EXTENDED: - if(mac instanceof MessageUpdateExtended) + if(mac instanceof MessageUpdateExtended mue) { - MessageUpdateExtended mue = (MessageUpdateExtended)mac; broadcast(message, mac, getCurrentChannel(), DecodeEventType.SDM, "MESSAGE UPDATE - " + mue.getShortDataMessage()); } break; @@ -468,270 +456,153 @@ private void processMacMessage(MacMessage message) } private void processQueuedResponse(MacMessage message, MacStructure mac) { - if(mac instanceof QueuedResponse) + if(mac instanceof QueuedResponse qr) { - QueuedResponse qr = (QueuedResponse)mac; - - MutableIdentifierCollection icQueuedResponse = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icQueuedResponse.remove(IdentifierClass.USER); - icQueuedResponse.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.RESPONSE.toString()) - .details("QUEUED - " + qr.getQueuedResponseServiceType() + - " REASON:" + qr.getQueuedResponseReason() + " ADDL:" + qr.getAdditionalInfo()) - .identifiers(icQueuedResponse) - .build()); + broadcast(message, mac, DecodeEventType.RESPONSE, + "QUEUED - " + qr.getQueuedResponseServiceType() + + " REASON:" + qr.getQueuedResponseReason() + " ADDL:" + qr.getAdditionalInfo()); } } + private void processEFCA(MacMessage message, MacStructure mac) { - if(mac instanceof ExtendedFunctionCommand) + if(mac instanceof ExtendedFunctionCommand efc) { - ExtendedFunctionCommand efc = (ExtendedFunctionCommand)mac; - - MutableIdentifierCollection icExtendedFunction = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icExtendedFunction.remove(IdentifierClass.USER); - icExtendedFunction.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .details("EXTENDED FUNCTION: " + efc.getExtendedFunction() + " ARGUMENTS:" + efc.getArguments()) - .identifiers(icExtendedFunction) - .build()); + broadcast(message, mac, DecodeEventType.COMMAND, + "EXTENDED FUNCTION: " + efc.getExtendedFunction() + " ARGUMENTS:" + efc.getArguments()); } } private void processDenyResponse(MacMessage message, MacStructure mac) { - if(mac instanceof DenyResponse) + if(mac instanceof DenyResponse dr) { - DenyResponse dr = (DenyResponse)mac; - MutableIdentifierCollection icDenyResponse = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icDenyResponse.remove(IdentifierClass.USER); - icDenyResponse.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.RESPONSE.toString()) - .details("DENY: " + dr.getDeniedServiceType() + " REASON:" + dr.getDenyReason() + " ADDL:" + dr.getAdditionalInfo()) - .identifiers(icDenyResponse) - .build()); + broadcast(message, mac, DecodeEventType.RESPONSE, + "DENY: " + dr.getDeniedServiceType() + " REASON:" + dr.getDenyReason() + " ADDL:" + dr.getAdditionalInfo()); } } private void processRUMCExtended(MacMessage message, MacStructure mac) { - if(mac instanceof RadioUnitMonitorCommandExtended) + if(mac instanceof RadioUnitMonitorCommandExtended rumce) { - RadioUnitMonitorCommandExtended rumce = (RadioUnitMonitorCommandExtended)mac; - MutableIdentifierCollection icRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icRequest.remove(IdentifierClass.USER); - icRequest.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .details("RADIO UNIT MONITOR" + (rumce.isSilentMonitor() ? " (STEALTH)" : "") + - " TIME:" + rumce.getTransmitTime() + "MULTIPLIER:" + rumce.getTransmitMultiplier()) - .identifiers(icRequest) - .build()); + broadcast(message, mac, DecodeEventType.COMMAND, + "RADIO UNIT MONITOR" + (rumce.isSilentMonitor() ? " (STEALTH)" : "") + + " TIME:" + rumce.getTransmitTime() + "MULTIPLIER:" + rumce.getTransmitMultiplier()); } } private void processSUE(MacMessage message, MacStructure mac) { - if(mac instanceof StatusUpdateExtended) + if(mac instanceof StatusUpdateExtended sue) { - StatusUpdateExtended sue = (StatusUpdateExtended)mac; - MutableIdentifierCollection icStatusUpdate = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icStatusUpdate.remove(IdentifierClass.USER); - icStatusUpdate.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.STATUS.toString()) - .details("STATUS UPDATE - UNIT:" + sue.getUnitStatus() + " USER:" + sue.getUserStatus()) - .identifiers(icStatusUpdate) - .build()); + broadcast(message, mac, DecodeEventType.STATUS, + "STATUS UPDATE - UNIT:" + sue.getUnitStatus() + " USER:" + sue.getUserStatus()); } } private void processEFCE(MacMessage message, MacStructure mac) { - if(mac instanceof ExtendedFunctionCommandExtended) + if(mac instanceof ExtendedFunctionCommandExtended efce) { - ExtendedFunctionCommandExtended efce = (ExtendedFunctionCommandExtended)mac; - - MutableIdentifierCollection icExtendedFunction = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icExtendedFunction.remove(IdentifierClass.USER); - icExtendedFunction.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .details("EXTENDED FUNCTION: " + efce.getExtendedFunction() + " ARGUMENTS:" + efce.getArguments()) - .identifiers(icExtendedFunction) - .build()); + broadcast(message, mac, DecodeEventType.COMMAND, + "EXTENDED FUNCTION: " + efce.getExtendedFunction() + " ARGUMENTS:" + efce.getArguments()); } } private void processCallAlert(MacMessage message, MacStructure mac) { - MutableIdentifierCollection icCallAlert = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icCallAlert.remove(IdentifierClass.USER); - icCallAlert.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.CALL_ALERT.toString()) - .identifiers(icCallAlert) - .build()); + broadcast(message, mac, DecodeEventType.CALL_ALERT, null); } private void processRUMCE(MacMessage message, MacStructure mac) { - if(mac instanceof RadioUnitMonitorCommandEnhanced) + if(mac instanceof RadioUnitMonitorCommandEnhanced rumc) { - RadioUnitMonitorCommandEnhanced rumc = (RadioUnitMonitorCommandEnhanced)mac; - MutableIdentifierCollection icRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icRequest.remove(IdentifierClass.USER); - icRequest.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .details("RADIO UNIT MONITOR" + (rumc.isStealthMode() ? " (STEALTH)" : "") + + broadcast(message, mac, DecodeEventType.COMMAND, + "RADIO UNIT MONITOR" + (rumc.isStealthMode() ? " (STEALTH)" : "") + " ENCRYPTION:" + rumc.getEncryption() + - " TIME:" + rumc.getTransmitTime()) - .identifiers(icRequest) - .build()); + " TIME:" + rumc.getTransmitTime()); } } private void processSUA(MacMessage message, MacStructure mac) { - if(mac instanceof StatusUpdateAbbreviated) + if(mac instanceof StatusUpdateAbbreviated sua) { - StatusUpdateAbbreviated sua = (StatusUpdateAbbreviated)mac; - MutableIdentifierCollection icStatusUpdate = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icStatusUpdate.remove(IdentifierClass.USER); - icStatusUpdate.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.STATUS.toString()) - .details("STATUS UPDATE - UNIT:" + sua.getUnitStatus() + " USER:" + sua.getUserStatus()) - .identifiers(icStatusUpdate) - .build()); + broadcast(message, mac, DecodeEventType.STATUS, + "STATUS UPDATE - UNIT:" + sua.getUnitStatus() + " USER:" + sua.getUserStatus()); } } private void processSDCG(MacMessage message, MacStructure mac) { - if(mac instanceof SNDCPDataChannelGrant) + if(mac instanceof SNDCPDataChannelGrant sdcg) { - SNDCPDataChannelGrant sdcg = (SNDCPDataChannelGrant)mac; - MutableIdentifierCollection icGrant = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icGrant.remove(IdentifierClass.USER); - icGrant.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(sdcg.getChannel()) - .eventDescription(sdcg.getServiceOptions().isEncrypted() ? DecodeEventType.DATA_CALL_ENCRYPTED.toString() : DecodeEventType.DATA_CALL.toString()) - .details("SNDCP CHANNEL GRANT " + sdcg.getServiceOptions()) - .identifiers(icGrant) - .build()); + broadcast(message, mac, + sdcg.getServiceOptions().isEncrypted() ? DecodeEventType.DATA_CALL_ENCRYPTED : DecodeEventType.DATA_CALL, + "SNDCP CHANNEL GRANT " + sdcg.getServiceOptions()); } } private void processRUMCA(MacMessage message, MacStructure mac) { - if(mac instanceof RadioUnitMonitorCommand) + if(mac instanceof RadioUnitMonitorCommand rumc) { - RadioUnitMonitorCommand rumc = (RadioUnitMonitorCommand)mac; - MutableIdentifierCollection icRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icRequest.remove(IdentifierClass.USER); - icRequest.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .details("RADIO UNIT MONITOR" + (rumc.isSilentMonitor() ? " (STEALTH)" : "") + - " TIME:" + rumc.getTransmitTime() + " MULTIPLIER:" + rumc.getTransmitMultiplier()) - .identifiers(icRequest) - .build()); + broadcast(message, mac, DecodeEventType.COMMAND, + "RADIO UNIT MONITOR" + (rumc.isSilentMonitor() ? " (STEALTH)" : "") + + " TIME:" + rumc.getTransmitTime() + " MULTIPLIER:" + rumc.getTransmitMultiplier()); } } private void processMacRelease(MacMessage message, MacStructure mac) { - if(mac instanceof MacRelease) + if(mac instanceof MacRelease mr) { - MacRelease mr = (MacRelease)mac; - - MutableIdentifierCollection icMacRelease = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icMacRelease.remove(IdentifierClass.USER); - icMacRelease.update(mac.getIdentifiers()); - closeCurrentCallEvent(message.getTimestamp(), true, message.getMacPduType()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .details((mr.isForcedPreemption() ? "FORCED " : "") + "CALL PREEMPTION" + (mr.isTalkerPreemption() ? " BY USER" : "")) - .identifiers(icMacRelease) - .build()); + broadcast(message, mac, DecodeEventType.COMMAND, + (mr.isForcedPreemption() ? "FORCED " : "") + "CALL PREEMPTION" + (mr.isTalkerPreemption() ? " BY USER" : "")); } } private void processPCSQ(MacMessage message, MacStructure mac) { - if(mac instanceof PowerControlSignalQuality) + if(mac instanceof PowerControlSignalQuality pcsq) { - PowerControlSignalQuality pcsq = (PowerControlSignalQuality)mac; - - MutableIdentifierCollection icPowerControl = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icPowerControl.remove(IdentifierClass.USER); - icPowerControl.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .details("ADJUST TRANSMIT POWER - RF:" + pcsq.getRFLevel() + " BER:" + pcsq.getBitErrorRate()) - .identifiers(icPowerControl) - .build()); + broadcast(message, mac, DecodeEventType.COMMAND, + "ADJUST TRANSMIT POWER - RF:" + pcsq.getRFLevel() + " BER:" + pcsq.getBitErrorRate()); } } private void processIPMWP(MacMessage message, MacStructure mac) { - if(mac instanceof IndividualPagingMessage) + if(mac instanceof IndividualPagingMessage ipm) { - IndividualPagingMessage ipm = (IndividualPagingMessage)mac; - MutableIdentifierCollection icIndividualPaging = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icIndividualPaging.remove(IdentifierClass.USER); - icIndividualPaging.update(mac.getIdentifiers()); boolean priority = ipm.isTalkgroupPriority1() || ipm.isTalkgroupPriority2() || ipm.isTalkgroupPriority3() || ipm.isTalkgroupPriority4(); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.PAGE.toString()) - .details((priority ? "PRIORITY " : "") + "USER PAGE") - .identifiers(icIndividualPaging) - .build()); + broadcast(message, mac, DecodeEventType.PAGE, + (priority ? "PRIORITY " : "") + "USER PAGE"); } } - private void broadcast(MacMessage message, MacStructure mac, IChannelDescriptor currentChannel, DecodeEventType page, String s) { - MutableIdentifierCollection collection = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - collection.remove(IdentifierClass.USER); - collection.update(mac.getIdentifiers()); + private void broadcast(MacMessage message, MacStructure mac, IChannelDescriptor currentChannel, DecodeEventType eventType, String details) { + MutableIdentifierCollection collection = getUpdatedMutableIdentifierCollection(mac); - broadcast(P25DecodeEvent.builder(message.getTimestamp()) + broadcast(P25DecodeEvent.builder(eventType, message.getTimestamp()) .channel(currentChannel) - .eventDescription(page.toString()) - .details(s) + .details(details) .identifiers(collection) .build()); } + private void broadcast(MacMessage message, MacStructure structure, DecodeEventType eventType, String details) { + MutableIdentifierCollection icQueuedResponse = getUpdatedMutableIdentifierCollection(structure); + + broadcast(P25DecodeEvent.builder(eventType, message.getTimestamp()) + .channel(getCurrentChannel()) + .details(details) + .identifiers(icQueuedResponse) + .build()); + } + + private MutableIdentifierCollection getUpdatedMutableIdentifierCollection(MacStructure mac) { + MutableIdentifierCollection icQueuedResponse = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icQueuedResponse.remove(IdentifierClass.USER); + icQueuedResponse.update(mac.getIdentifiers()); + return icQueuedResponse; + } + private void processUTUVCGUExtended(MacStructure mac) { - if(getCurrentChannel() == null && mac instanceof UnitToUnitVoiceChannelGrantUpdateExtended) + if(getCurrentChannel() == null && mac instanceof UnitToUnitVoiceChannelGrantUpdateExtended uuvcgue) { - UnitToUnitVoiceChannelGrantUpdateExtended uuvcgue = (UnitToUnitVoiceChannelGrantUpdateExtended)mac; - if(isCurrentGroup(uuvcgue.getTargetAddress())) { broadcastCurrentChannel(uuvcgue.getChannel()); @@ -740,10 +611,8 @@ private void processUTUVCGUExtended(MacStructure mac) { } private void processGVCGUExplicit(MacStructure mac) { - if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdateExplicit) + if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdateExplicit gvcgue) { - GroupVoiceChannelGrantUpdateExplicit gvcgue = (GroupVoiceChannelGrantUpdateExplicit)mac; - if(isCurrentGroup(gvcgue.getGroupAddress())) { broadcastCurrentChannel(gvcgue.getChannel()); @@ -752,10 +621,8 @@ private void processGVCGUExplicit(MacStructure mac) { } private void processUTUVCGUAbbreviated(MacStructure mac) { - if(getCurrentChannel() == null && mac instanceof UnitToUnitVoiceChannelGrantAbbreviated) + if(getCurrentChannel() == null && mac instanceof UnitToUnitVoiceChannelGrantAbbreviated uuvcga) { - UnitToUnitVoiceChannelGrantAbbreviated uuvcga = (UnitToUnitVoiceChannelGrantAbbreviated)mac; - if(isCurrentGroup(uuvcga.getSourceAddress()) || isCurrentGroup(uuvcga.getTargetAddress())) { broadcastCurrentChannel(uuvcga.getChannel()); @@ -764,10 +631,8 @@ private void processUTUVCGUAbbreviated(MacStructure mac) { } private void processGVCGUpdate(MacStructure mac) { - if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdate) + if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdate gvcgu) { - GroupVoiceChannelGrantUpdate gvcgu = (GroupVoiceChannelGrantUpdate)mac; - if(isCurrentGroup(gvcgu.getGroupAddressA())) { broadcastCurrentChannel(gvcgu.getChannelA()); @@ -781,10 +646,8 @@ private void processGVCGUpdate(MacStructure mac) { } private void processGVCGAbbreviated(MacStructure mac) { - if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantAbbreviated) + if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantAbbreviated gvcga) { - GroupVoiceChannelGrantAbbreviated gvcga = (GroupVoiceChannelGrantAbbreviated)mac; - if(isCurrentGroup(gvcga.getGroupAddress())) { broadcastCurrentChannel(gvcga.getChannel()); @@ -793,10 +656,8 @@ private void processGVCGAbbreviated(MacStructure mac) { } private void processGVCGUMExplicit(MacStructure mac) { - if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdateMultipleExplicit) + if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdateMultipleExplicit gvcgume) { - GroupVoiceChannelGrantUpdateMultipleExplicit gvcgume = (GroupVoiceChannelGrantUpdateMultipleExplicit)mac; - if(isCurrentGroup(gvcgume.getGroupAddressA())) { broadcastCurrentChannel(gvcgume.getChannelA()); @@ -810,10 +671,8 @@ private void processGVCGUMExplicit(MacStructure mac) { } private void processGVCGUM(MacStructure mac) { - if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdateMultiple) + if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdateMultiple gvcgum) { - GroupVoiceChannelGrantUpdateMultiple gvcgum = (GroupVoiceChannelGrantUpdateMultiple)mac; - if(isCurrentGroup(gvcgum.getGroupAddressA())) { broadcastCurrentChannel(gvcgum.getChannelA()); @@ -850,10 +709,8 @@ private void processTIVCU(MacMessage message, MacStructure mac) { getIdentifierCollection().update(mPatchGroupManager.update(identifier)); } - if(mac instanceof TelephoneInterconnectVoiceChannelUser) + if(mac instanceof TelephoneInterconnectVoiceChannelUser tivcu) { - TelephoneInterconnectVoiceChannelUser tivcu = (TelephoneInterconnectVoiceChannelUser)mac; - if(tivcu.getServiceOptions().isEncrypted()) { updateCurrentCall(DecodeEventType.CALL_INTERCONNECT_ENCRYPTED, null, message.getTimestamp()); @@ -885,10 +742,8 @@ private void processUTUVCUExtended(MacMessage message, MacStructure mac) { getIdentifierCollection().update(mPatchGroupManager.update(identifier)); } - if(mac instanceof UnitToUnitVoiceChannelUserExtended) + if(mac instanceof UnitToUnitVoiceChannelUserExtended uuvcue) { - UnitToUnitVoiceChannelUserExtended uuvcue = (UnitToUnitVoiceChannelUserExtended)mac; - if(uuvcue.getServiceOptions().isEncrypted()) { updateCurrentCall(DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED, null, message.getTimestamp()); @@ -1144,9 +999,13 @@ private void updateCurrentCall(DecodeEventType type, String details, long timest { if(mCurrentCallEvent == null) { - mCurrentCallEvent = P25DecodeEvent.builder(timestamp) + if(type == null) + { + type = DecodeEventType.CALL; + } + + mCurrentCallEvent = P25DecodeEvent.builder(type, timestamp) .channel(getCurrentChannel()) - .eventDescription(type != null ? type.toString() : DecodeEventType.CALL.toString()) .details(details) .identifiers(getIdentifierCollection().copyOf()) .build(); @@ -1164,11 +1023,6 @@ private void updateCurrentCall(DecodeEventType type, String details, long timest } else { - if(type != null) - { - mCurrentCallEvent.setEventDescription(type.toString()); - } - if(details != null) { mCurrentCallEvent.setDetails(details); diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/P25MessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/P25MessageFactory.java deleted file mode 100644 index 134ee59f0..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/P25MessageFactory.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 Dennis Sheirer - * * - * * This program is free software: you can redistribute it and/or modify - * * it under the terms of the GNU General Public License as published by - * * the Free Software Foundation, either version 3 of the License, or - * * (at your option) any later version. - * * - * * This program is distributed in the hope that it will be useful, - * * but WITHOUT ANY WARRANTY; without even the implied warranty of - * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * * GNU General Public License for more details. - * * - * * You 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.phase2.message; - -public class P25MessageFactory -{ - -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/filter/MacOpcodeMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/filter/MacOpcodeMessageFilter.java new file mode 100644 index 000000000..f878583f6 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/filter/MacOpcodeMessageFilter.java @@ -0,0 +1,81 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.phase2.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.phase2.message.mac.MacMessage; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode; +import java.util.Collection; +import java.util.function.Function; + +/** + * Message filter for P25 Phase 2 MAC opcode messages. + */ +public class MacOpcodeMessageFilter extends Filter +{ + private KeyExtractor mKeyExtractor = new KeyExtractor(); + private Collection mOpcodes; + + /** + * Constructs an instance + * + * @param name of this filter + */ + public MacOpcodeMessageFilter(String name, Collection opcodes) + { + super(name); + + mOpcodes = opcodes; + + for(MacOpcode opcode: opcodes) + { + add(new FilterElement<>(opcode)); + } + } + + /** + * Key extractor + * @return opcode extractor + */ + @Override + public Function getKeyExtractor() + { + return mKeyExtractor; + } + + /** + * Key extractor for MacOpcode values from MacMessage. + */ + private class KeyExtractor implements Function + { + @Override + public MacOpcode apply(IMessage message) + { + if(message instanceof MacMessage mac) + { + return mac.getMacStructure().getOpcode(); + } + + return null; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/filter/MacOpcodeMessageFilterSet.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/filter/MacOpcodeMessageFilterSet.java new file mode 100644 index 000000000..a2d2f82b5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/filter/MacOpcodeMessageFilterSet.java @@ -0,0 +1,64 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.phase2.message.filter; + +import io.github.dsheirer.filter.FilterSet; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode; +import java.util.ArrayList; +import java.util.List; + +/** + * Filter set for P25 Phase 2 MAC messages + */ +public class MacOpcodeMessageFilterSet extends FilterSet +{ + /** + * Constructor + */ + public MacOpcodeMessageFilterSet() + { + super("Trunking Messages"); + + addFilter(new MacOpcodeMessageFilter("Call Maintenance", MacOpcode.CALL_MAINTENANCE)); + addFilter(new MacOpcodeMessageFilter("Data Channel Grants", MacOpcode.DATA_GRANTS)); + addFilter(new MacOpcodeMessageFilter("Mobile Request & Response", MacOpcode.MOBILE_REQUEST_RESPONSE)); + addFilter(new MacOpcodeMessageFilter("Network Request & Response", MacOpcode.NETWORK_REQUEST_RESPONSE)); + addFilter(new MacOpcodeMessageFilter("Network Status & Announcements", MacOpcode.NETWORK_STATUS)); + addFilter(new MacOpcodeMessageFilter("Voice Channel Grants", MacOpcode.VOICE_GRANTS)); + + List ungrouped = new ArrayList<>(); + for(MacOpcode opcode: ungrouped) + { + if(!opcode.isGrouped()) + { + ungrouped.add(opcode); + } + } + addFilter(new MacOpcodeMessageFilter("Other/Unknown", ungrouped)); + } + + @Override + public boolean canProcess(IMessage message) + { + return (message instanceof MacMessage) && super.canProcess(message); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/IPPacketMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/filter/P25P2MessageFilterSet.java similarity index 51% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/IPPacketMessageFilter.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/filter/P25P2MessageFilterSet.java index f22d0c858..112267d45 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/IPPacketMessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/filter/P25P2MessageFilterSet.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * * This 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,41 +14,38 @@ * * You 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.phase1.message.filter; +package io.github.dsheirer.module.decode.p25.phase2.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.IMessage; -import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.PacketMessage; - -import java.util.Collections; -import java.util.List; +import io.github.dsheirer.message.SyncLossMessage; +import io.github.dsheirer.module.decode.p25.phase2.message.P25P2Message; -public class IPPacketMessageFilter extends Filter +/** + * Filter set for P25 Phase 2 messages + */ +public class P25P2MessageFilterSet extends FilterSet { - public IPPacketMessageFilter() + public P25P2MessageFilterSet() { - super("Packet Message"); - } + super("P25 Phase 2 Message Filter"); - @Override - public boolean passes(IMessage message) - { - return mEnabled && canProcess(message); + addFilter(new MacOpcodeMessageFilterSet()); + addFilter(new VoiceMessageFilter()); + addFilter(new P25P2OtherMessageFilter()); } + /** + * Override default to descope handling to P25P2 or sync-loss messages. + * @param message to test + * @return true if the message can be processed + */ @Override public boolean canProcess(IMessage message) { - return message instanceof PacketMessage; - } - - @Override - public List> getFilterElements() - { - return Collections.EMPTY_LIST; + return message instanceof P25P2Message || message instanceof SyncLossMessage; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/filter/P25P2OtherMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/filter/P25P2OtherMessageFilter.java new file mode 100644 index 000000000..8dacb354d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/filter/P25P2OtherMessageFilter.java @@ -0,0 +1,73 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.phase2.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.phase2.message.P25P2Message; +import java.util.function.Function; + +/** + * Filter for unknown messages, meaning messages not handled by any other filter. + */ +public class P25P2OtherMessageFilter extends Filter +{ + private static final String OTHER_KEY = "Other/Unknown Message"; + private KeyExtractor mKeyExtractor = new KeyExtractor(); + + /** + * Constructor + */ + public P25P2OtherMessageFilter() + { + super("Other P25 messages"); + add(new FilterElement<>(OTHER_KEY)); + } + + @Override + public Function getKeyExtractor() + { + return mKeyExtractor; + } + + @Override + public boolean canProcess(IMessage message) + { + return message instanceof P25P2Message && super.canProcess(message); + } + + /** + * Key extractor + */ + private class KeyExtractor implements Function + { + @Override + public String apply(IMessage message) + { + if(message instanceof P25P2Message) + { + return OTHER_KEY; + } + + return null; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/filter/VoiceMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/filter/VoiceMessageFilter.java new file mode 100644 index 000000000..164f5beb5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/filter/VoiceMessageFilter.java @@ -0,0 +1,76 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.phase2.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.phase2.enumeration.DataUnitID; +import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractVoiceTimeslot; +import java.util.function.Function; + +/** + * Filter for P25 Phase 2 Voice (2/4) messages + */ +public class VoiceMessageFilter extends Filter +{ + private KeyExtractor mKeyExtractor = new KeyExtractor(); + + /** + * Constructs an instance + * + * @param name of this filter + */ + public VoiceMessageFilter() + { + super("Voice Timeslot Messages"); + add(new FilterElement<>(DataUnitID.VOICE_2)); + add(new FilterElement<>(DataUnitID.VOICE_4)); + } + + @Override + public Function getKeyExtractor() + { + return mKeyExtractor; + } + + @Override + public boolean canProcess(IMessage message) + { + return message instanceof AbstractVoiceTimeslot && super.canProcess(message); + } + + /** + * Key extractor + */ + private class KeyExtractor implements Function + { + @Override + public DataUnitID apply(IMessage message) + { + if(message instanceof AbstractVoiceTimeslot voice) + { + return voice.getDataUnitID(); + } + + return null; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java index e80c927a6..d267395c5 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java @@ -1,27 +1,25 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 Dennis Sheirer - * * - * * This program is free software: you can redistribute it and/or modify - * * it under the terms of the GNU General Public License as published by - * * the Free Software Foundation, either version 3 of the License, or - * * (at your option) any later version. - * * - * * This program is distributed in the hope that it will be useful, - * * but WITHOUT ANY WARRANTY; without even the implied warranty of - * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * * GNU General Public License for more details. - * * - * * You should have received a copy of the GNU General Public License - * * along with this program. If not, see - * * ***************************************************************************** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * + * You 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.phase2.message.mac; +import java.util.EnumSet; import java.util.Map; import java.util.TreeMap; @@ -120,6 +118,12 @@ public enum MacOpcode private String mLabel; private int mLength; + /** + * Constructor + * @param value of the opcode + * @param label for display + * @param length of the message in bytes. + */ MacOpcode(int value, String label, int length) { mValue = value; @@ -127,6 +131,75 @@ public enum MacOpcode mLength = length; } + /** + * Call maintenance + */ + public static EnumSet CALL_MAINTENANCE = EnumSet.of(PUSH_TO_TALK, END_PUSH_TO_TALK, + TDMA_1_GROUP_VOICE_CHANNEL_USER_ABBREVIATED, TDMA_2_UNIT_TO_UNIT_VOICE_CHANNEL_USER, + TDMA_3_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER, TDMA_5_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE, + TDMA_33_GROUP_VOICE_CHANNEL_USER_EXTENDED, TDMA_34_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED, + TDMA_37_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_EXPLICIT); + + /** + * Data channel grants + */ + public static EnumSet DATA_GRANTS = EnumSet.of(PHASE1_84_SNDCP_DATA_CHANNEL_GRANT); + + /** + * Mobile requests and responses + */ + public static EnumSet MOBILE_REQUEST_RESPONSE = EnumSet.of(PHASE1_65_GROUP_VOICE_SERVICE_REQUEST, + PHASE1_88_STATUS_UPDATE_ABBREVIATED, PHASE1_92_MESSAGE_UPDATE_ABBREVIATED, + PHASE1_216_STATUS_UPDATE_EXTENDED, PHASE1_220_MESSAGE_UPDATE_EXTENDED); + + /** + * Network requests and responses + */ + public static EnumSet NETWORK_REQUEST_RESPONSE = EnumSet.of(TDMA_0_NULL_INFORMATION_MESSAGE, + TDMA_17_INDIRECT_GROUP_PAGING, TDMA_18_INDIVIDUAL_PAGING_MESSAGE_WITH_PRIORITY, + TDMA_48_POWER_CONTROL_SIGNAL_QUALITY, TDMA_49_MAC_RELEASE, PHASE1_69_UNIT_TO_UNIT_ANSWER_REQUEST_ABBREVIATED, + PHASE1_74_TELEPHONE_INTERCONNECT_ANSWER_REQUEST, PHASE1_76_RADIO_UNIT_MONITOR_COMMAND_ABBREVIATED, + PHASE1_85_SNDCP_DATA_PAGE_REQUEST, PHASE1_90_STATUS_QUERY_ABBREVIATED, + OBSOLETE_PHASE1_93_RADIO_UNIT_MONITOR_COMMAND, PHASE1_94_RADIO_UNIT_MONITOR_COMMAND_ENHANCED, + PHASE1_95_CALL_ALERT_ABBREVIATED, PHASE1_96_ACK_RESPONSE, PHASE1_97_QUEUED_RESPONSE, + PHASE1_100_EXTENDED_FUNCTION_COMMAND_ABBREVIATED, PHASE1_103_DENY_RESPONSE, + PHASE1_106_GROUP_AFFILIATION_QUERY_ABBREVIATED, PHASE1_109_UNIT_REGISTRATION_COMMAND_ABBREVIATED, + PHASE1_197_UNIT_TO_UNIT_ANSWER_REQUEST_EXTENDED, PHASE1_204_RADIO_UNIT_MONITOR_COMMAND_EXTENDED, + PHASE1_218_STATUS_QUERY_EXTENDED, PHASE1_223_CALL_ALERT_EXTENDED, + PHASE1_228_EXTENDED_FUNCTION_COMMAND_EXTENDED, PHASE1_234_GROUP_AFFILIATION_QUERY_EXTENDED); + + /** + * Network and channel status and announcements + */ + public static EnumSet NETWORK_STATUS = EnumSet.of(PHASE1_115_IDENTIFIER_UPDATE_TDMA, + PHASE1_116_IDENTIFIER_UPDATE_V_UHF, PHASE1_117_TIME_AND_DATE_ANNOUNCEMENT, + PHASE1_120_SYSTEM_SERVICE_BROADCAST, PHASE1_121_SECONDARY_CONTROL_CHANNEL_BROADCAST_ABBREVIATED, + PHASE1_122_RFSS_STATUS_BROADCAST_ABBREVIATED, PHASE1_123_NETWORK_STATUS_BROADCAST_ABBREVIATED, + PHASE1_124_ADJACENT_STATUS_BROADCAST_ABBREVIATED, PHASE1_125_IDENTIFIER_UPDATE, + PHASE1_214_SNDCP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT, PHASE1_233_SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT, + PHASE1_250_RFSS_STATUS_BROADCAST_EXTENDED, PHASE1_251_NETWORK_STATUS_BROADCAST_EXTENDED, + PHASE1_252_ADJACENT_STATUS_BROADCAST_EXTENDED); + + /** + * Voice channel grants + */ + public static EnumSet VOICE_GRANTS = EnumSet.of(PHASE1_64_GROUP_VOICE_CHANNEL_GRANT_ABBREVIATED, + PHASE1_66_GROUP_VOICE_CHANNEL_GRANT_UPDATE, PHASE1_68_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_ABBREVIATED, + PHASE1_70_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_ABBREVIATED, PHASE1_192_GROUP_VOICE_CHANNEL_GRANT_EXTENDED, + PHASE1_195_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT, PHASE1_196_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_EXTENDED, + PHASE1_198_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED); + + /** + * Indicates if the enumeration element is contained in one of the enumset groupings above. + * @return true if the element is grouped. + */ + public boolean isGrouped() + { + return CALL_MAINTENANCE.contains(this) || DATA_GRANTS.contains(this) || + MOBILE_REQUEST_RESPONSE.contains(this) || NETWORK_REQUEST_RESPONSE.contains(this) || + NETWORK_STATUS.contains(this) || VOICE_GRANTS.contains(this); + } + @Override public String toString() { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/FacchTimeslot.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/FacchTimeslot.java index a959d08be..5320b011b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/FacchTimeslot.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/FacchTimeslot.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 Dennis Sheirer - * * - * * This program is free software: you can redistribute it and/or modify - * * it under the terms of the GNU General Public License as published by - * * the Free Software Foundation, either version 3 of the License, or - * * (at your option) any later version. - * * - * * This program is distributed in the hope that it will be useful, - * * but WITHOUT ANY WARRANTY; without even the implied warranty of - * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * * GNU General Public License for more details. - * * - * * You should have received a copy of the GNU General Public License - * * along with this program. If not, see - * * ***************************************************************************** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * + * You 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.phase2.timeslot; @@ -29,11 +26,10 @@ import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage; import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessageFactory; import io.github.dsheirer.module.decode.p25.phase2.message.mac.UnknownMacMessage; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; - /** * Fast Associated Control CHannel (FACCH) timeslot carrying a S-OEMI Message */ @@ -257,33 +253,4 @@ public List getMacMessages() return mMacMessages; } - - public static void main(String[] args) - { -// String raw = "575D57F7FFD8000000000000000030000000000000003FD55DDF5F500082919EB24B903F6A5B0BF9831985D29B"; - String raw = "575D57F7FFD8010020000000000030000000000000003FD55DDF5F500082919EB24B903F6A5B0BF9831985D29B"; - CorrectedBinaryMessage m = new CorrectedBinaryMessage(360); - - int pointer = 0; - - for(int x = 0; x < raw.length(); x += 2) - { - String braw = raw.substring(x, x + 2); - int parsed = Integer.parseInt(braw, 16); - m.load(pointer, 8, parsed); - pointer += 8; - } - - mLog.debug(" IN:" + raw); - mLog.debug("OUT:" + m.toHexString()); - - FacchTimeslot facch = new FacchTimeslot(m, 0, System.currentTimeMillis()); - - List macs = facch.getMacMessages(); - - for(MacMessage mac: macs) - { - mLog.debug(mac.toString()); - } - } } diff --git a/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecodeEvent.java b/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecodeEvent.java new file mode 100644 index 000000000..3328657d2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecodeEvent.java @@ -0,0 +1,52 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; +import io.github.dsheirer.protocol.Protocol; + +/** + * Passport decode event + */ +public class PassportDecodeEvent extends DecodeEvent +{ + /** + * Constructs a Passport decode event + * @param start + */ + public PassportDecodeEvent(DecodeEventType decodeEventType, long start) + { + super(decodeEventType, start); + setProtocol(Protocol.PASSPORT); + } + + /** + * Creates a new decode event builder with the specified start timestamp. + * @param timeStart for the event + * @return builder + */ + public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart) + { + DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(decodeEventType, timeStart); + decodeEventBuilder.protocol(Protocol.PASSPORT); + return decodeEventBuilder; + } +} 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 9663b9092..d792d398f 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,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 Dennis Sheirer - * * - * * This program is free software: you can redistribute it and/or modify - * * it under the terms of the GNU General Public License as published by - * * the Free Software Foundation, either version 3 of the License, or - * * (at your option) any later version. - * * - * * This program is distributed in the hope that it will be useful, - * * but WITHOUT ANY WARRANTY; without even the implied warranty of - * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * * GNU General Public License for more details. - * * - * * You should have received a copy of the GNU General Public License - * * along with this program. If not, see - * * ***************************************************************************** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * + * You 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; @@ -34,12 +31,10 @@ import io.github.dsheirer.message.MessageType; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.passport.identifier.PassportRadioId; import io.github.dsheirer.module.decode.passport.identifier.PassportTalkgroup; import io.github.dsheirer.protocol.Protocol; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -47,6 +42,8 @@ import java.util.Objects; import java.util.Set; import java.util.TreeSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class PassportDecoderState extends DecoderState { @@ -158,8 +155,10 @@ public void receive(IMessage message) } } - mCurrentDecodeEvent = DecodeEvent.builder(passport.getTimestamp()) - .eventDescription(passport.getMessageType() == MessageType.CA_STRT ? "Call" : "Data") + DecodeEventType decodeEventType = passport.getMessageType() == MessageType.CA_STRT ? + DecodeEventType.CALL : DecodeEventType.DATA_CALL; + + mCurrentDecodeEvent = PassportDecodeEvent.builder(decodeEventType, passport.getTimestamp()) .identifiers(getIdentifierCollection().copyOf()) .details(passport.toString()) .build(); @@ -177,8 +176,7 @@ public void receive(IMessage message) !isSameTalkgroup(to, getToIdentifier(callDetect.getIdentifierCollection())) || callDetect.getTimeStart() < (passport.getTimestamp() - 45000)) { - callDetect = DecodeEvent.builder(passport.getTimestamp()) - .eventDescription("Call Detect") + callDetect = PassportDecodeEvent.builder(DecodeEventType.CALL_DETECT, passport.getTimestamp()) .identifiers(new IdentifierCollection(passport.getIdentifiers())) // .channel(...) .build(); @@ -218,8 +216,7 @@ public void receive(IMessage message) } break; case RA_REGI: - broadcast(DecodeEvent.builder(passport.getTimestamp()) - .eventDescription("Register") + broadcast(PassportDecodeEvent.builder(DecodeEventType.REGISTER, passport.getTimestamp()) .identifiers(new IdentifierCollection(passport.getIdentifiers())) .build()); 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 f4ea669bd..e81bbfd6a 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 @@ -1,57 +1,79 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.filter.Filter; import io.github.dsheirer.filter.FilterElement; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.MessageType; -import io.github.dsheirer.protocol.Protocol; - -import java.util.*; +import java.util.function.Function; -public class PassportMessageFilter extends Filter +/** + * Filter for Passport messages + */ +public class PassportMessageFilter extends Filter { - private Map> mElements = new EnumMap<>(MessageType.class); + private KeyExtractor mKeyExtractor = new KeyExtractor(); + /** + * Constructor + */ public PassportMessageFilter() { - 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)); + super("Passport Messages"); + add(new FilterElement<>(MessageType.CA_STRT)); + add(new FilterElement<>(MessageType.SY_IDLE)); + add(new FilterElement<>(MessageType.ID_TGAS)); + add(new FilterElement<>(MessageType.ID_ESNH)); + add(new FilterElement<>(MessageType.CA_PAGE)); + add(new FilterElement<>(MessageType.ID_RDIO)); + add(new FilterElement<>(MessageType.DA_STRT)); + add(new FilterElement<>(MessageType.RA_REGI)); } @Override - public boolean passes(IMessage message) + public boolean canProcess(IMessage message) { - if(mEnabled && canProcess(message)) - { - PassportMessage passport = (PassportMessage) message; - - if(mElements.containsKey(passport.getMessageType())) - { - return mElements.get(passport.getMessageType()).isEnabled(); - } - } - - return false; + return message instanceof PassportMessage && super.canProcess(message); } @Override - public boolean canProcess(IMessage message) + public Function getKeyExtractor() { - return message instanceof PassportMessage; + return mKeyExtractor; } - @Override - public List> getFilterElements() + /** + * Key extractor + */ + private class KeyExtractor implements Function { - return new ArrayList>(mElements.values()); + @Override + public MessageType apply(IMessage message) + { + if(message instanceof PassportMessage passport) + { + return passport.getMessageType(); + } + + return null; + } } } 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 f0380ba03..6bdddec04 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,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 Dennis Sheirer - * * - * * This program is free software: you can redistribute it and/or modify - * * it under the terms of the GNU General Public License as published by - * * the Free Software Foundation, either version 3 of the License, or - * * (at your option) any later version. - * * - * * This program is distributed in the hope that it will be useful, - * * but WITHOUT ANY WARRANTY; without even the implied warranty of - * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * * GNU General Public License for more details. - * * - * * You should have received a copy of the GNU General Public License - * * along with this program. If not, see - * * ***************************************************************************** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * + * You 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; @@ -30,14 +27,16 @@ import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; 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.tait.identifier.TaitIdentifier; -import org.jdesktop.swingx.mapviewer.GeoPosition; - +import io.github.dsheirer.protocol.Protocol; import java.util.TreeSet; +import org.jdesktop.swingx.mapviewer.GeoPosition; public class Tait1200DecoderState extends DecoderState { + public static final Protocol PROTOCOL_TAIT_1200 = Protocol.TAIT1200; private TreeSet mIdents = new TreeSet<>(); public Tait1200DecoderState() @@ -94,8 +93,8 @@ public void receive(IMessage message) ic.remove(IdentifierClass.USER); ic.update(message.getIdentifiers()); - PlottableDecodeEvent event = PlottableDecodeEvent.plottableBuilder(gps.getTimestamp()) - .eventDescription("GPS") + PlottableDecodeEvent event = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, gps.getTimestamp()) + .protocol(PROTOCOL_TAIT_1200) .identifiers(ic) .location(position) .speed(gps.getSpeed()) @@ -106,9 +105,8 @@ public void receive(IMessage message) broadcast(new DecoderStateEvent(this, Event.DECODE, State.DATA)); } - else if(message instanceof Tait1200ANIMessage) + else if(message instanceof Tait1200ANIMessage ani) { - Tait1200ANIMessage ani = (Tait1200ANIMessage)message; mIdents.add(ani.getFromIdentifier()); mIdents.add(ani.getToIdentifier()); @@ -116,8 +114,8 @@ else if(message instanceof Tait1200ANIMessage) ic.remove(IdentifierClass.USER); ic.update(message.getIdentifiers()); - broadcast(DecodeEvent.builder(ani.getTimestamp()) - .eventDescription("ANI") + broadcast(DecodeEvent.builder(DecodeEventType.ID_ANI, ani.getTimestamp()) + .protocol(PROTOCOL_TAIT_1200) .identifiers(ic) .details("Automatic Number Identification") .build()); diff --git a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200MessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200MessageFilter.java index 72677dac3..2da9bdfce 100644 --- a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200MessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200MessageFilter.java @@ -1,52 +1,64 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You 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.filter.Filter; -import io.github.dsheirer.filter.FilterElement; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; +import java.util.function.Function; -import java.util.Collections; -import java.util.List; - -public class Tait1200MessageFilter extends Filter +/** + * Filter for Tait1200 messages + */ +public class Tait1200MessageFilter extends Filter { - - public Tait1200MessageFilter() - { - super( "Tait-1200 Message Filter" ); - } - - @Override - public boolean passes( Message message ) + private static final String TAIT1200_KEY = "Tait-1200"; + private final KeyExtractor mKeyExtractor = new KeyExtractor(); + + /** + * Constructor + */ + public Tait1200MessageFilter() + { + super("Tait-1200 Messages"); + } + + @Override + public boolean canProcess(IMessage message) { - return mEnabled && canProcess( message ); + return message instanceof Tait1200GPSMessage; } - @Override - public boolean canProcess( Message message ) + @Override + public Function getKeyExtractor() { - return message instanceof Tait1200GPSMessage; + return mKeyExtractor; } - @Override - public List> getFilterElements() + /** + * Key extractor + */ + private class KeyExtractor implements Function { - return Collections.EMPTY_LIST; + @Override + public String apply(IMessage message) + { + return TAIT1200_KEY; + } } } diff --git a/src/main/java/io/github/dsheirer/module/log/DecodeEventLogger.java b/src/main/java/io/github/dsheirer/module/log/DecodeEventLogger.java index 129dda562..b23f8a2f1 100644 --- a/src/main/java/io/github/dsheirer/module/log/DecodeEventLogger.java +++ b/src/main/java/io/github/dsheirer/module/log/DecodeEventLogger.java @@ -1,25 +1,23 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 Dennis Sheirer - * * - * * This program is free software: you can redistribute it and/or modify - * * it under the terms of the GNU General Public License as published by - * * the Free Software Foundation, either version 3 of the License, or - * * (at your option) any later version. - * * - * * This program is distributed in the hope that it will be useful, - * * but WITHOUT ANY WARRANTY; without even the implied warranty of - * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * * GNU General Public License for more details. - * * - * * You should have received a copy of the GNU General Public License - * * along with this program. If not, see - * * ***************************************************************************** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * + * 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.alias.AliasList; import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.channel.IChannelDescriptor; @@ -33,15 +31,14 @@ import io.github.dsheirer.module.decode.event.IDecodeEventListener; import io.github.dsheirer.preference.TimestampFormat; import io.github.dsheirer.sample.Listener; -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.QuoteMode; - import java.nio.file.Path; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.QuoteMode; public class DecodeEventLogger extends EventLogger implements IDecodeEventListener, Listener { @@ -101,9 +98,7 @@ private String toCSV(IDecodeEvent event) cells.add(mTimestampFormat.format(new Date(event.getTimeStart()))); cells.add(event.getDuration() > 0 ? event.getDuration() : ""); cells.add(event.getProtocol()); - - String description = event.getEventDescription(); - cells.add(description != null ? description : ""); + cells.add(event.getEventType()); List fromIdentifiers = event.getIdentifierCollection().getIdentifiers(Role.FROM); if(fromIdentifiers != null && !fromIdentifiers.isEmpty()) diff --git a/src/test/java/io/github/dsheirer/module/log/DecodeEventLoggerTest.java b/src/test/java/io/github/dsheirer/module/log/DecodeEventLoggerTest.java deleted file mode 100644 index 75fb76b1c..000000000 --- a/src/test/java/io/github/dsheirer/module/log/DecodeEventLoggerTest.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2021 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * 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.alias.Alias; -import io.github.dsheirer.alias.AliasModel; -import io.github.dsheirer.channel.IChannelDescriptor; -import io.github.dsheirer.identifier.IdentifierCollection; -import io.github.dsheirer.identifier.Role; -import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier; -import io.github.dsheirer.identifier.configuration.DecoderTypeConfigurationIdentifier; -import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier; -import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.module.decode.event.DecodeEvent; -import io.github.dsheirer.module.decode.event.IDecodeEvent; -import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; -import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; -import io.github.dsheirer.protocol.Protocol; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -class DecodeEventLoggerTest { - IChannelDescriptor channelDescriptor = APCO25Channel.create(98765, 1); - - APCO25Talkgroup fromIdentifier = new APCO25Talkgroup(123, Role.FROM); - APCO25Talkgroup toIdentifier = new APCO25Talkgroup(456, Role.TO); - FrequencyConfigurationIdentifier freqIdentifier = new FrequencyConfigurationIdentifier(859562500L); - DecoderTypeConfigurationIdentifier decoderIdentifier = new DecoderTypeConfigurationIdentifier(DecoderType.P25_PHASE1); - AliasListConfigurationIdentifier aliasListIdentifier = new AliasListConfigurationIdentifier("My Alias List"); - AliasModel aliasModel = new AliasModel(); - - Path logDirectory = Paths.get(System.getProperty("java.io.tmpdir"), "sdr_trunk_tests"); - - @BeforeEach - void setUp() { - aliasModel.addAliasList(aliasListIdentifier.getValue()); - } - - @AfterEach - void tearDown() { - } - - IdentifierCollection buildIdentifierCollection() { - return new IdentifierCollection(Arrays.asList( - fromIdentifier, - toIdentifier, - decoderIdentifier, - freqIdentifier, - aliasListIdentifier - )); - } - - DecodeEvent.DecodeEventBuilder decodeEventBuilder() { - return DecodeEvent.builder(1634428994000L) - .channel(channelDescriptor) - .identifiers(buildIdentifierCollection()) - .duration(111) - .protocol(Protocol.APCO25) - .eventDescription("DATA_PACKET") - .timeslot(2) - .details("Some details"); - } - -// @Test -// void test_receive_writesToCsv() { -// IDecodeEvent decodeEvent = decodeEventBuilder().build(); -// -// DecodeEventLogger decodeEventLogger = new DecodeEventLogger(aliasModel, logDirectory, "_foo.txt", 859562500); -// DecodeEventLogger spy = spy(decodeEventLogger); -// -// doNothing().when(spy).write(anyString()); -// -// spy.receive(decodeEvent); -// -// String expectedToCsvString = -// "\"2021:10:16:20:03:14\",\"111\",\"APCO-25\",\"DATA_PACKET\",\"123\",\" (456)\",\"98765-1\",\"859.562500\",\"TS:2\",\"Some details\""; -// verify(spy).write(expectedToCsvString); -// } - -// @Test -// void test_receive_withQuotesInDetails_writesToCsv() { -// IDecodeEvent decodeEvent = decodeEventBuilder() -// .details("Some details now with \"quotes\"!") -// .build(); -// -// DecodeEventLogger decodeEventLogger = new DecodeEventLogger(aliasModel, logDirectory, "_foo.txt", 859562500); -// DecodeEventLogger spy = spy(decodeEventLogger); -// -// doNothing().when(spy).write(anyString()); -// -// spy.receive(decodeEvent); -// -// String expectedToCsvString = -// "\"2021:10:16:20:03:14\",\"111\",\"APCO-25\",\"DATA_PACKET\",\"123\",\" (456)\",\"98765-1\",\"859.562500\",\"TS:2\",\"Some details now with \"\"quotes\"\"!\""; -// verify(spy).write(expectedToCsvString); -// } - -// @Test -// void test_receive_withCommasInDetails_writesToCsv() { -// IDecodeEvent decodeEvent = decodeEventBuilder() -// .details("Some details now with, to an extent, commas!") -// .build(); -// -// DecodeEventLogger decodeEventLogger = new DecodeEventLogger(aliasModel, logDirectory, "_foo.txt", 859562500); -// DecodeEventLogger spy = spy(decodeEventLogger); -// -// doNothing().when(spy).write(anyString()); -// -// spy.receive(decodeEvent); -// -// String expectedToCsvString = -// "\"2021:10:16:20:03:14\",\"111\",\"APCO-25\",\"DATA_PACKET\",\"123\",\" (456)\",\"98765-1\",\"859.562500\",\"TS:2\",\"Some details now with, to an extent, commas!\""; -// verify(spy).write(expectedToCsvString); -// } -} \ No newline at end of file