mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
* P25 Phase 2 now supports control channel decoding and decoder was enhanced to improve decodes. * Radio reference site editor updated for phase 2 sites to allow user to select FDMA or TDMA control. * Decode events are now minimized and de-duplicated. * Fully parsing all phase 1/2 messages from the latest ICD. * Vendor specific messaging recoveries (Moto & Harris). * Patch group management enhancements with stale patch detection. * Tuner editor now fully resets the min/max frequency values * Default traffic channel count increased to 20 (from 3). * ISSI roaming radio and talkgroup values now displayed and aliasable as fully qualified values (e.g. 1234(123.456.7890) * Moto emergency alarm activation messaging support * Expanded list of P25 encryption algorithms * Message (bits) viewer enhanced for P25 phase 1 and phase 2 * IPV4 addresses and UDP ports no longer classified as user values - won't display in event views. * New framework for describing message binary fields that should reduce overall memory footprint and enhance code readability Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -50,6 +50,28 @@ public class DecodeEvent implements IDecodeEvent
|
||||
mTimeStart = start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or changes the decode event type.
|
||||
* @param type to set.
|
||||
*/
|
||||
public void setDecodeEventType(DecodeEventType type)
|
||||
{
|
||||
if(type == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Decode event type cannot be null");
|
||||
}
|
||||
|
||||
mDecodeEventType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a snapshot of this decode event for offline analysis
|
||||
*/
|
||||
public DecodeEventSnapshot getSnapshot()
|
||||
{
|
||||
return new DecodeEventSnapshot(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new decode event builder with the specified start timestamp.
|
||||
* @param decodeEventType for the event
|
||||
@@ -238,7 +260,8 @@ public class DecodeEvent implements IDecodeEvent
|
||||
sb.append(" IDS:").append(Joiner.on(",").join(mIdentifierCollection.getIdentifiers()));
|
||||
}
|
||||
sb.append(" DURATION:").append(getDuration());
|
||||
sb.append(" CHANNEL:").append(mChannelDescriptor);
|
||||
sb.append(" CHANNEL:").append(getChannelDescriptor());
|
||||
sb.append(" TIMESLOT:").append(getTimeslot());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.event;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Provides duplicate decode event detection support
|
||||
*/
|
||||
public class DecodeEventDuplicateDetector
|
||||
{
|
||||
private static final long EVENT_MAX_AGE_MILLISECONDS = Duration.ofMinutes(1).toMillis();
|
||||
private Map<DecodeEventType,DecodeEventTracker> mTrackerMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Indicates if the event is a duplicate event.
|
||||
* @param event to check
|
||||
* @param timestamp of a current message to trigger time-based event age off.
|
||||
* @return true if the event is a duplicate.
|
||||
*/
|
||||
public synchronized boolean isDuplicate(IDecodeEvent event, long timestamp)
|
||||
{
|
||||
//Null event types and voice call event types are not tracked by this detector
|
||||
if(event.getEventType() == null || event.getEventType().isVoiceCallEvent())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!mTrackerMap.containsKey(event.getEventType()))
|
||||
{
|
||||
mTrackerMap.put(event.getEventType(), new DecodeEventTracker());
|
||||
}
|
||||
|
||||
boolean duplicate = mTrackerMap.get(event.getEventType()).isDuplicate(event);
|
||||
|
||||
for(DecodeEventTracker tracker: mTrackerMap.values())
|
||||
{
|
||||
tracker.ageOff(timestamp);
|
||||
}
|
||||
|
||||
return duplicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks all decode events for a given decode event type
|
||||
*/
|
||||
private class DecodeEventTracker
|
||||
{
|
||||
private Map<String,IDecodeEvent> mDecodeEventMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Checks the event for duplicate
|
||||
* @param event to check
|
||||
* @return true if duplicate
|
||||
*/
|
||||
public boolean isDuplicate(IDecodeEvent event)
|
||||
{
|
||||
if(event.getIdentifierCollection().getToIdentifier() == null || event.getDetails() == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
String key = getKey(event);
|
||||
|
||||
if(key == null || key.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(mDecodeEventMap.containsKey(key))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
mDecodeEventMap.put(key, event);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique event key that is a combinatio of the TO identifier value and the details of the event.
|
||||
* @param event for the key
|
||||
* @return generated key.
|
||||
*/
|
||||
private static String getKey(IDecodeEvent event)
|
||||
{
|
||||
return event.getIdentifierCollection().getToIdentifier().toString() + event.getDetails();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all decode events from the duplicate detection map that are too old.
|
||||
* @param timestamp for the current messaging.
|
||||
*/
|
||||
public void ageOff(long timestamp)
|
||||
{
|
||||
long threshold = timestamp - EVENT_MAX_AGE_MILLISECONDS;
|
||||
List<String> toRemove = new ArrayList<>();
|
||||
|
||||
for(Map.Entry<String,IDecodeEvent> entry: mDecodeEventMap.entrySet())
|
||||
{
|
||||
if(entry.getValue() == null || entry.getValue().getTimeStart() < threshold)
|
||||
{
|
||||
toRemove.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
for(String key: toRemove)
|
||||
{
|
||||
mDecodeEventMap.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -115,9 +115,9 @@ public class DecodeEventModel extends ClearableHistoryModel<IDecodeEvent> implem
|
||||
|
||||
if(channelDescriptor != null)
|
||||
{
|
||||
if(event.hasTimeslot())
|
||||
if(event.hasTimeslot() && !event.toString().contains("TS"))
|
||||
{
|
||||
return channelDescriptor + " TS:" + event.getTimeslot();
|
||||
return channelDescriptor + " TS" + event.getTimeslot();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -126,9 +126,9 @@ public class DecodeEventModel extends ClearableHistoryModel<IDecodeEvent> implem
|
||||
}
|
||||
else
|
||||
{
|
||||
if(event.hasTimeslot())
|
||||
if(event.hasTimeslot() && !event.toString().contains("TS"))
|
||||
{
|
||||
return "TS:" + event.getTimeslot();
|
||||
return "TS" + event.getTimeslot();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package io.github.dsheirer.module.decode.event;
|
||||
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
|
||||
/**
|
||||
* Snapshot of a decode event that also preserves the object instance hash code.
|
||||
*/
|
||||
public class DecodeEventSnapshot extends DecodeEvent
|
||||
{
|
||||
private int mOriginalHashCode;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param decodeEvent to snapshot
|
||||
*/
|
||||
public DecodeEventSnapshot(DecodeEvent decodeEvent)
|
||||
{
|
||||
super(decodeEvent.getEventType(), decodeEvent.getTimeStart());
|
||||
mOriginalHashCode = decodeEvent.hashCode();
|
||||
setDetails(decodeEvent.getDetails());
|
||||
setDuration(decodeEvent.getDuration());
|
||||
setProtocol(decodeEvent.getProtocol());
|
||||
setTimeslot(decodeEvent.getTimeslot());
|
||||
setChannelDescriptor(decodeEvent.getChannelDescriptor());
|
||||
if(decodeEvent.getIdentifierCollection() != null)
|
||||
{
|
||||
setIdentifierCollection(new IdentifierCollection(decodeEvent.getIdentifierCollection().getIdentifiers()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashcode of the original decode event for this snapshot (not this snapshot instance's hash code).
|
||||
* @return
|
||||
*/
|
||||
public int getOriginalHashCode()
|
||||
{
|
||||
return mOriginalHashCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Frequency for the channel descriptor
|
||||
* @return frequency or zero.
|
||||
*/
|
||||
public long getFrequency()
|
||||
{
|
||||
return getChannelDescriptor() != null ? getChannelDescriptor().getDownlinkFrequency() : 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -55,6 +55,7 @@ public enum DecodeEventType
|
||||
DATA_CALL_ENCRYPTED("Encrypted Data Call"),
|
||||
DATA_PACKET("Data Packet"),
|
||||
DEREGISTER("Deregister"),
|
||||
DYNAMIC_REGROUP("Dynamic Regroup"),
|
||||
EMERGENCY("EMERGENCY"),
|
||||
FUNCTION("Function"),
|
||||
GPS("GPS"),
|
||||
@@ -106,7 +107,7 @@ public enum DecodeEventType
|
||||
public static final EnumSet<DecodeEventType> 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);
|
||||
DecodeEventType.NOTIFICATION, DecodeEventType.FUNCTION, DecodeEventType.DYNAMIC_REGROUP);
|
||||
|
||||
/**
|
||||
* Data call event types for filtering
|
||||
@@ -129,6 +130,13 @@ public enum DecodeEventType
|
||||
public static final EnumSet<DecodeEventType> OTHERS = EnumSet.copyOf(Arrays.stream(DecodeEventType.values())
|
||||
.filter(decodeEventType -> !decodeEventType.isGrouped()).toList());
|
||||
|
||||
/**
|
||||
* Voice call event types.
|
||||
*/
|
||||
public static final EnumSet<DecodeEventType> VOICE_CALL_EVENTS = EnumSet.of(CALL, CALL_ENCRYPTED, CALL_GROUP,
|
||||
CALL_GROUP_ENCRYPTED, CALL_PATCH_GROUP, CALL_PATCH_GROUP_ENCRYPTED, CALL_INTERCONNECT,
|
||||
CALL_INTERCONNECT_ENCRYPTED, CALL_UNIT_TO_UNIT, CALL_UNIT_TO_UNIT_ENCRYPTED);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param label for the element
|
||||
@@ -149,12 +157,12 @@ public enum DecodeEventType
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this is a call event.
|
||||
* @return true if this is a call event type.
|
||||
* Indicates if this is a voice call event.
|
||||
* @return true if this is a voice call event type.
|
||||
*/
|
||||
public boolean isCallEvent()
|
||||
public boolean isVoiceCallEvent()
|
||||
{
|
||||
return VOICE_CALLS.contains(this) || VOICE_CALLS_ENCRYPTED.contains(this) || DATA_CALLS.contains(this);
|
||||
return VOICE_CALL_EVENTS.contains(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user