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:
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package io.github.dsheirer.identifier;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* Identifier form. Indicates the type of identifier.
|
||||
*/
|
||||
@@ -64,4 +66,21 @@ public enum Form
|
||||
UNIQUE_ID,
|
||||
WACN,
|
||||
ANY;
|
||||
|
||||
Form()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Entity forms that are used to identify entities in P25 call events.
|
||||
*/
|
||||
public static EnumSet<Form> ENTITY_FORMS = EnumSet.of(Form.RADIO, Form.TALKGROUP, Form.PATCH_GROUP, Form.TELEPHONE_NUMBER);
|
||||
|
||||
/**
|
||||
* Indicates if this form is a form that can be used to identify an entity
|
||||
*/
|
||||
public boolean isEntityForm()
|
||||
{
|
||||
return ENTITY_FORMS.contains(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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
|
||||
@@ -32,6 +32,12 @@ public enum IdentifierClass
|
||||
//Communication network identifier
|
||||
NETWORK,
|
||||
|
||||
//User network address
|
||||
USER_NETWORK_ADDRESS,
|
||||
|
||||
//User network port
|
||||
USER_NETWORK_PORT,
|
||||
|
||||
//Communication user identifier
|
||||
USER;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 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
|
||||
@@ -20,14 +20,13 @@
|
||||
package io.github.dsheirer.identifier;
|
||||
|
||||
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* (Immutable) Collection of identifiers with convenient accessor methods
|
||||
@@ -274,43 +273,90 @@ public class IdentifierCollection
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first identifier in this collection that is assigned a FROM role
|
||||
* Indicates if this collection has an identifier that matches the specified identiifer.
|
||||
* @param toCheck identifier
|
||||
* @return true if the identifier already exists in the collection.
|
||||
*/
|
||||
public boolean hasIdentifier(Identifier toCheck)
|
||||
{
|
||||
if(toCheck == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for(Identifier identifier : mIdentifiers)
|
||||
{
|
||||
if(identifier.equals(toCheck))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first entity identifier in this collection that is assigned a FROM role
|
||||
*/
|
||||
public Identifier getFromIdentifier()
|
||||
{
|
||||
Identifier from = getIdentifier(IdentifierClass.USER, Form.RADIO, Role.FROM);
|
||||
|
||||
if(from == null)
|
||||
if(from != null)
|
||||
{
|
||||
List<Identifier> fromIdentifiers = getIdentifiers(Role.FROM);
|
||||
|
||||
if(!fromIdentifiers.isEmpty())
|
||||
{
|
||||
from = fromIdentifiers.get(0);
|
||||
}
|
||||
return from;
|
||||
}
|
||||
|
||||
return from;
|
||||
List<Identifier> identifiers = getIdentifiers(Form.TELEPHONE_NUMBER);
|
||||
|
||||
if(!identifiers.isEmpty())
|
||||
{
|
||||
return identifiers.get(0);
|
||||
}
|
||||
|
||||
List<Identifier> fromIdentifiers = getIdentifiers(Role.FROM);
|
||||
|
||||
if(!fromIdentifiers.isEmpty())
|
||||
{
|
||||
return fromIdentifiers.get(0);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first identifier in this collection that is assigned a TO role
|
||||
* Returns the first entity identifier in this collection that is assigned a TO role
|
||||
*/
|
||||
public Identifier getToIdentifier()
|
||||
{
|
||||
Identifier to = getIdentifier(IdentifierClass.USER, Form.PATCH_GROUP, Role.TO);
|
||||
|
||||
if(to == null)
|
||||
if(to != null)
|
||||
{
|
||||
to = getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.TO);
|
||||
return to;
|
||||
}
|
||||
|
||||
if(to == null)
|
||||
to = getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.TO);
|
||||
|
||||
if(to != null)
|
||||
{
|
||||
List<Identifier> toIdentifiers = getIdentifiers(Role.TO);
|
||||
if(!toIdentifiers.isEmpty())
|
||||
return to;
|
||||
}
|
||||
|
||||
to = getIdentifier(IdentifierClass.USER, Form.RADIO, Role.TO);
|
||||
|
||||
if(to != null)
|
||||
{
|
||||
return to;
|
||||
}
|
||||
|
||||
List<Identifier> toIdentifiers = getIdentifiers(Role.TO);
|
||||
|
||||
for(Identifier identifier: toIdentifiers)
|
||||
{
|
||||
if(identifier.getForm() != Form.ENCRYPTION_KEY)
|
||||
{
|
||||
to = toIdentifiers.get(0);
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ public abstract class IntegerIdentifier extends Identifier<Integer>
|
||||
super(value, identifierClass, form, role);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 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
|
||||
@@ -15,7 +14,7 @@
|
||||
*
|
||||
* 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.identifier.ipv4;
|
||||
@@ -29,6 +28,6 @@ public abstract class IPV4Identifier extends Identifier<IPV4Address>
|
||||
{
|
||||
public IPV4Identifier(IPV4Address value, Role role)
|
||||
{
|
||||
super(value, IdentifierClass.USER, Form.IPV4_ADDRESS, role);
|
||||
super(value, IdentifierClass.USER_NETWORK_ADDRESS, Form.IPV4_ADDRESS, role);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -95,25 +95,44 @@ public class PatchGroup
|
||||
/**
|
||||
* Adds the talkgroup identifier to this patched group.
|
||||
* @param patchedGroupIdentifier to add
|
||||
* @return true if the patched group identifier is new and was added
|
||||
*/
|
||||
public void addPatchedTalkgroup(TalkgroupIdentifier patchedGroupIdentifier)
|
||||
public boolean addPatchedTalkgroup(TalkgroupIdentifier patchedGroupIdentifier)
|
||||
{
|
||||
boolean added = false;
|
||||
|
||||
if(!mPatchedTalkgroupIdentifiers.contains(patchedGroupIdentifier))
|
||||
{
|
||||
mPatchedTalkgroupIdentifiers.add(patchedGroupIdentifier);
|
||||
added |= mPatchedTalkgroupIdentifiers.add(patchedGroupIdentifier);
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the patched talkgroup from this patch group.
|
||||
* @param talkgroup to remove
|
||||
*/
|
||||
public void removePatchedTalkgroup(TalkgroupIdentifier talkgroup)
|
||||
{
|
||||
mPatchedTalkgroupIdentifiers.remove(talkgroup);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a list of talkgroup identifiers to this patched group.
|
||||
* @param patchedGroupIdentifiers to add
|
||||
*/
|
||||
public void addPatchedTalkgroups(List<TalkgroupIdentifier> patchedGroupIdentifiers)
|
||||
public boolean addPatchedTalkgroups(List<TalkgroupIdentifier> patchedGroupIdentifiers)
|
||||
{
|
||||
boolean added = false;
|
||||
|
||||
for(TalkgroupIdentifier identifier : patchedGroupIdentifiers)
|
||||
{
|
||||
addPatchedTalkgroup(identifier);
|
||||
added |= addPatchedTalkgroup(identifier);
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,24 +148,41 @@ public class PatchGroup
|
||||
* Adds the radio identifier to this patched group.
|
||||
* @param patchedRadioIdentifier to add
|
||||
*/
|
||||
public void addPatchedRadio(RadioIdentifier patchedRadioIdentifier)
|
||||
public boolean addPatchedRadio(RadioIdentifier patchedRadioIdentifier)
|
||||
{
|
||||
if(!mPatchedRadioIdentifiers.contains(patchedRadioIdentifier))
|
||||
{
|
||||
mPatchedRadioIdentifiers.add(patchedRadioIdentifier);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the patched radio from this patch group.
|
||||
* @param radioIdentifier to remove
|
||||
*/
|
||||
public void removePatchedRadio(RadioIdentifier radioIdentifier)
|
||||
{
|
||||
mPatchedRadioIdentifiers.remove(radioIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a list of radio identifiers to this patched group.
|
||||
* @param patchedRadioIdentifiers to add
|
||||
* @return true if any new identifiers were added
|
||||
*/
|
||||
public void addPatchedRadios(List<RadioIdentifier> patchedRadioIdentifiers)
|
||||
public boolean addPatchedRadios(List<RadioIdentifier> patchedRadioIdentifiers)
|
||||
{
|
||||
boolean added = false;
|
||||
|
||||
for(RadioIdentifier identifier : patchedRadioIdentifiers)
|
||||
{
|
||||
addPatchedRadio(identifier);
|
||||
added |= addPatchedRadio(identifier);
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
@@ -22,11 +22,14 @@ package io.github.dsheirer.identifier.patch;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Manager for (temporary) patch groups aka super groups. This manager monitors patch group additions and deletions and
|
||||
@@ -39,7 +42,8 @@ import java.util.Map;
|
||||
*/
|
||||
public class PatchGroupManager
|
||||
{
|
||||
private Map<Integer,PatchGroupIdentifier> mPatchGroupMap = new HashMap<>();
|
||||
private static final long PATCH_GROUP_FRESHNESS_THRESHOLD_MS = Duration.ofSeconds(30).toMillis();
|
||||
private Map<Integer,PatchGroupTracker> mPatchGroupTrackerMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -48,12 +52,36 @@ public class PatchGroupManager
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary listing of active patch groups
|
||||
*/
|
||||
public String getPatchGroupSummary()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Active Patch Groups\n");
|
||||
List<PatchGroupTracker> trackers = new ArrayList<>(mPatchGroupTrackerMap.values());
|
||||
|
||||
if(trackers.isEmpty())
|
||||
{
|
||||
sb.append("None");
|
||||
}
|
||||
else
|
||||
{
|
||||
for(PatchGroupTracker tracker : trackers)
|
||||
{
|
||||
sb.append(" ").append(tracker.mPatchGroupIdentifier).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any existing patch groups
|
||||
*/
|
||||
public void clear()
|
||||
{
|
||||
mPatchGroupMap.clear();
|
||||
mPatchGroupTrackerMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,46 +91,53 @@ public class PatchGroupManager
|
||||
* existing patch group is discarded and replaced with the updated version of the patch group.
|
||||
*
|
||||
* @param patchGroupIdentifier to add or update
|
||||
* @return true if the group was added or updated
|
||||
*/
|
||||
public synchronized void addPatchGroup(PatchGroupIdentifier patchGroupIdentifier)
|
||||
public synchronized boolean addPatchGroup(PatchGroupIdentifier patchGroupIdentifier, long timestamp)
|
||||
{
|
||||
PatchGroup update = patchGroupIdentifier.getValue();
|
||||
|
||||
if(update.getPatchGroup().getValue() > 0)
|
||||
{
|
||||
if(mPatchGroupMap.containsKey(update.getPatchGroup().getValue()))
|
||||
{
|
||||
PatchGroup existingPatchGroup = mPatchGroupMap.get(update.getPatchGroup().getValue()).getValue();
|
||||
int patchGroup = update.getPatchGroup().getValue();
|
||||
|
||||
//If the patch group version number is the same, this is an update
|
||||
if(existingPatchGroup.getVersion() == update.getVersion())
|
||||
if(patchGroup > 0)
|
||||
{
|
||||
if(mPatchGroupTrackerMap.containsKey(patchGroup))
|
||||
{
|
||||
PatchGroupTracker tracker = mPatchGroupTrackerMap.get(patchGroup);
|
||||
|
||||
if(tracker.isStale(timestamp))
|
||||
{
|
||||
existingPatchGroup.addPatchedTalkgroups(update.getPatchedTalkgroupIdentifiers());
|
||||
existingPatchGroup.addPatchedRadios(update.getPatchedRadioIdentifiers());
|
||||
//Replace the existing patch group if it is stale.
|
||||
mPatchGroupTrackerMap.put(patchGroup, new PatchGroupTracker(patchGroupIdentifier, timestamp));
|
||||
return true;
|
||||
}
|
||||
//Otherwise, this is a replace operation.
|
||||
else
|
||||
{
|
||||
mPatchGroupMap.put(update.getPatchGroup().getValue(), patchGroupIdentifier);
|
||||
//Update the existing patch group.
|
||||
return tracker.add(patchGroupIdentifier, timestamp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mPatchGroupMap.put(update.getPatchGroup().getValue(), patchGroupIdentifier);
|
||||
mPatchGroupTrackerMap.put(patchGroup, new PatchGroupTracker(patchGroupIdentifier, timestamp));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds any patch group identifiers contained in the list.
|
||||
* @param referenceTimestamp as a reference for checking staleness of existing patch groups.
|
||||
*/
|
||||
public synchronized void addPatchGroups(List<Identifier> identifiers)
|
||||
public synchronized void addPatchGroups(List<Identifier> identifiers, long referenceTimestamp)
|
||||
{
|
||||
for(Identifier identifier : identifiers)
|
||||
{
|
||||
if(identifier instanceof PatchGroupIdentifier)
|
||||
{
|
||||
addPatchGroup((PatchGroupIdentifier)identifier);
|
||||
addPatchGroup((PatchGroupIdentifier)identifier, referenceTimestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,11 +146,12 @@ public class PatchGroupManager
|
||||
* Removes the patch group from this manager if it is currently being managed.
|
||||
*
|
||||
* @param patchGroupIdentifier to remove
|
||||
* @return true if the patch group was removed.
|
||||
*/
|
||||
public synchronized void removePatchGroup(PatchGroupIdentifier patchGroupIdentifier)
|
||||
public synchronized boolean removePatchGroup(PatchGroupIdentifier patchGroupIdentifier)
|
||||
{
|
||||
int id = patchGroupIdentifier.getValue().getPatchGroup().getValue();
|
||||
mPatchGroupMap.remove(id);
|
||||
return mPatchGroupTrackerMap.remove(id) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,15 +172,16 @@ public class PatchGroupManager
|
||||
* Updates the list of identifiers by replacing any talkgroups or patch groups with the current
|
||||
* version of the patch group and a complete listing of the patched talkgroups.
|
||||
* @param identifiers to update
|
||||
* @param referenceTimestamp as a reference for checking staleness of existing patch groups.
|
||||
* @return list of identifiers
|
||||
*/
|
||||
public List<Identifier> update(List<Identifier> identifiers)
|
||||
public List<Identifier> update(List<Identifier> identifiers, long referenceTimestamp)
|
||||
{
|
||||
List<Identifier> updated = new ArrayList<>();
|
||||
|
||||
for(Identifier identifier: identifiers)
|
||||
{
|
||||
updated.add(update(identifier));
|
||||
updated.add(update(identifier, referenceTimestamp));
|
||||
}
|
||||
|
||||
return updated;
|
||||
@@ -155,9 +192,10 @@ public class PatchGroupManager
|
||||
* if the identifier matches a currently managed patch group.
|
||||
*
|
||||
* @param identifier for a talkgroup or a patch group.
|
||||
* @param referenceTimestamp as a reference for checking staleness of existing patch groups.
|
||||
* @return current patch group or the original identifier
|
||||
*/
|
||||
public Identifier update(Identifier identifier)
|
||||
public Identifier update(Identifier identifier, long referenceTimestamp)
|
||||
{
|
||||
if(identifier != null && identifier.getIdentifierClass() == IdentifierClass.USER && identifier.getRole() == Role.TO)
|
||||
{
|
||||
@@ -166,22 +204,49 @@ public class PatchGroupManager
|
||||
case TALKGROUP:
|
||||
if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
|
||||
{
|
||||
Identifier mapValue = mPatchGroupMap.get(talkgroupIdentifier.getValue());
|
||||
if (mapValue != null)
|
||||
int id = talkgroupIdentifier.getValue();
|
||||
|
||||
PatchGroupTracker tracker = mPatchGroupTrackerMap.get(id);
|
||||
|
||||
if(tracker != null)
|
||||
{
|
||||
return mapValue;
|
||||
if(tracker.isStale(referenceTimestamp))
|
||||
{
|
||||
mPatchGroupTrackerMap.remove(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Perform substitution - return patch group instead of the original talkgroup
|
||||
return tracker.getPatchGroupIdentifier(referenceTimestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PATCH_GROUP:
|
||||
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
|
||||
{
|
||||
int patchGroupId = patchGroupIdentifier.getValue().getPatchGroup().getValue();
|
||||
int id = patchGroupIdentifier.getValue().getPatchGroup().getValue();
|
||||
|
||||
Identifier mapValue = mPatchGroupMap.get(patchGroupId);
|
||||
if (mapValue != null)
|
||||
PatchGroupTracker tracker = mPatchGroupTrackerMap.get(id);
|
||||
|
||||
if(tracker != null)
|
||||
{
|
||||
return mapValue;
|
||||
if(tracker.isStale(referenceTimestamp))
|
||||
{
|
||||
mPatchGroupTrackerMap.put(id, new PatchGroupTracker(patchGroupIdentifier, referenceTimestamp));
|
||||
mPatchGroupTrackerMap.remove(id);
|
||||
}
|
||||
else if(tracker.getPatchGroupIdentifier(referenceTimestamp).getValue().getVersion() !=
|
||||
patchGroupIdentifier.getValue().getVersion())
|
||||
{
|
||||
mPatchGroupTrackerMap.put(id, new PatchGroupTracker(patchGroupIdentifier, referenceTimestamp));
|
||||
mPatchGroupTrackerMap.remove(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Perform substitution - return patch group instead of the original talkgroup
|
||||
return tracker.getPatchGroupIdentifier(referenceTimestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -190,4 +255,107 @@ public class PatchGroupManager
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the freshness of a patch group and any member talkgroup and radio identifiers to ensure that stale
|
||||
* patch group identifiers are removed.
|
||||
*/
|
||||
public class PatchGroupTracker
|
||||
{
|
||||
private PatchGroupIdentifier mPatchGroupIdentifier;
|
||||
private long mLastUpdateTimestamp;
|
||||
private Map<TalkgroupIdentifier,Long> mTalkgroupTimestampMap = new HashMap<>();
|
||||
private Map<RadioIdentifier,Long> mRadioTimestampMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param patchGroupIdentifier
|
||||
*/
|
||||
public PatchGroupTracker(PatchGroupIdentifier patchGroupIdentifier, long timestamp)
|
||||
{
|
||||
mPatchGroupIdentifier = patchGroupIdentifier;
|
||||
add(patchGroupIdentifier, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch group monitored by this tracker. This method will cleanup and remove any stale radio or talkgroup
|
||||
* identifiers on access.
|
||||
*
|
||||
* Note: this tracker should be checked for freshness by first checking the isStale() method before accessing
|
||||
* the tracked patch group by this method.
|
||||
*
|
||||
* @param referenceTimestamp for comparing stored radio and talkgroup values for staleness.
|
||||
* @return current version of the talkgroup
|
||||
*/
|
||||
public PatchGroupIdentifier getPatchGroupIdentifier(long referenceTimestamp)
|
||||
{
|
||||
//Remove stale talkgroups
|
||||
List<Map.Entry<TalkgroupIdentifier,Long>> toRemove = mTalkgroupTimestampMap.entrySet().stream()
|
||||
.filter(entry -> isStale(entry.getValue(), referenceTimestamp)).collect(Collectors.toList());
|
||||
|
||||
for(Map.Entry<TalkgroupIdentifier,Long> entry: toRemove)
|
||||
{
|
||||
mTalkgroupTimestampMap.remove(entry.getKey());
|
||||
mPatchGroupIdentifier.getValue().removePatchedTalkgroup(entry.getKey());
|
||||
}
|
||||
|
||||
//Remove stale radios
|
||||
List<Map.Entry<RadioIdentifier,Long>> toRemove2 = mRadioTimestampMap.entrySet().stream()
|
||||
.filter(entry -> isStale(entry.getValue(), referenceTimestamp)).collect(Collectors.toList());
|
||||
|
||||
for(Map.Entry<RadioIdentifier,Long> entry: toRemove2)
|
||||
{
|
||||
mRadioTimestampMap.remove(entry.getKey());
|
||||
mPatchGroupIdentifier.getValue().removePatchedRadio(entry.getKey());
|
||||
}
|
||||
|
||||
return mPatchGroupIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the stored timestamp is stale relative to the reference timestamp.
|
||||
* @param storedTimestamp to compare
|
||||
* @param referenceTimestamp representing the current time.
|
||||
* @return true of the stored timestamp is stale.
|
||||
*/
|
||||
private boolean isStale(long storedTimestamp, long referenceTimestamp)
|
||||
{
|
||||
return referenceTimestamp - storedTimestamp > PATCH_GROUP_FRESHNESS_THRESHOLD_MS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the tracked patch group is stale and hasn't received any add/updates within a period of time.
|
||||
* @param referenceTimestamp representing now for comparison against the patch groups update timestamp.
|
||||
* @return true if the tracked patch group is stale.
|
||||
*/
|
||||
public boolean isStale(long referenceTimestamp)
|
||||
{
|
||||
return isStale(mLastUpdateTimestamp, referenceTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or update the patch group and update the freshness timestamp.
|
||||
* @param patchGroupIdentifier containing an add value.
|
||||
*/
|
||||
public boolean add(PatchGroupIdentifier patchGroupIdentifier, long timestamp)
|
||||
{
|
||||
mLastUpdateTimestamp = timestamp;
|
||||
|
||||
boolean added = false;
|
||||
|
||||
for(TalkgroupIdentifier talkgroup: patchGroupIdentifier.getValue().getPatchedTalkgroupIdentifiers())
|
||||
{
|
||||
mTalkgroupTimestampMap.put(talkgroup, timestamp);
|
||||
added |= mPatchGroupIdentifier.getValue().addPatchedTalkgroup(talkgroup);
|
||||
}
|
||||
|
||||
for(RadioIdentifier radio: patchGroupIdentifier.getValue().getPatchedRadioIdentifiers())
|
||||
{
|
||||
mRadioTimestampMap.put(radio, timestamp);
|
||||
added |= mPatchGroupIdentifier.getValue().addPatchedRadio(radio);
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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
|
||||
@@ -27,13 +27,25 @@ import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
*/
|
||||
public class PatchGroupPreLoadDataContent extends PreloadDataContent<IdentifierCollection>
|
||||
{
|
||||
private long mTimestamp;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param data to preload
|
||||
*/
|
||||
public PatchGroupPreLoadDataContent(IdentifierCollection data)
|
||||
public PatchGroupPreLoadDataContent(IdentifierCollection data, long timestamp)
|
||||
{
|
||||
super(data);
|
||||
mTimestamp = timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Freshness timestamp for the preload data.
|
||||
* @return timestamp milliseconds.
|
||||
*/
|
||||
public long getTimestamp()
|
||||
{
|
||||
return mTimestamp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General 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.identifier.radio;
|
||||
@@ -25,29 +22,74 @@ package io.github.dsheirer.identifier.radio;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
|
||||
/**
|
||||
* Fully qualified radio identifier
|
||||
* Fully qualified radio identifier. This is used for roaming radios or any time there is a need to fully qualify a
|
||||
* radio.
|
||||
*/
|
||||
public abstract class FullyQualifiedRadioIdentifier extends RadioIdentifier
|
||||
{
|
||||
private int mWacn;
|
||||
private int mSystem;
|
||||
private int mRadio;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param wacn
|
||||
* @param system
|
||||
* @param id
|
||||
* @param localAddress radio identifier, aka alias. This can be the same as the radio ID when the fully qualified radio
|
||||
* is not being aliased on a local radio system.
|
||||
* @param wacn of the home network for the radio.
|
||||
* @param system of the home network for the radio.
|
||||
* @param id of the radio within the home network.
|
||||
*/
|
||||
public FullyQualifiedRadioIdentifier(int wacn, int system, int id, Role role)
|
||||
public FullyQualifiedRadioIdentifier(int localAddress, int wacn, int system, int id, Role role)
|
||||
{
|
||||
super(id, role);
|
||||
super(localAddress > 0 ? localAddress : id, role);
|
||||
mWacn = wacn;
|
||||
mSystem = system;
|
||||
mRadio = id;
|
||||
}
|
||||
|
||||
public int getWacn()
|
||||
{
|
||||
return mWacn;
|
||||
}
|
||||
|
||||
public int getSystem()
|
||||
{
|
||||
return mSystem;
|
||||
}
|
||||
|
||||
public int getRadio()
|
||||
{
|
||||
return mRadio;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully qualified radio identity.
|
||||
* @return radio identity
|
||||
*/
|
||||
public String getFullyQualifiedRadioAddress()
|
||||
{
|
||||
return mWacn + "." + mSystem + "." + mRadio;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the radio identify is aliased with a persona value that is different from the radio ID.
|
||||
* @return true if aliased.
|
||||
*/
|
||||
public boolean isAliased()
|
||||
{
|
||||
return getValue() != mRadio;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mWacn + "." + mSystem + "." + super.toString();
|
||||
if(isAliased())
|
||||
{
|
||||
return super.toString() + "(" + getFullyQualifiedRadioAddress() + ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
return getFullyQualifiedRadioAddress();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,14 @@ import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||
|
||||
/**
|
||||
* Radio identifier.
|
||||
*
|
||||
* Note: this class overrides the .equals method to ensure that all radios and subclasses can be compared using
|
||||
* the radio value, regardless if it is a simple radio or a fully qualified radio identifier.
|
||||
*/
|
||||
public abstract class RadioIdentifier extends IntegerIdentifier
|
||||
{
|
||||
public RadioIdentifier(Integer value, Role role)
|
||||
@@ -39,4 +46,28 @@ public abstract class RadioIdentifier extends IntegerIdentifier
|
||||
{
|
||||
return getValue() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides to compare just the radio value, class, form, role and protocol. This allows a fully qualified
|
||||
* radio identifier to be equivalent to a standard radio identifier for the traffic channel manager.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if(this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if(o instanceof RadioIdentifier radio)
|
||||
{
|
||||
return (getValue().intValue() == radio.getValue().intValue()) &&
|
||||
getIdentifierClass() == radio.getIdentifierClass() &&
|
||||
getForm() == radio.getForm() &&
|
||||
getRole() == radio.getRole() &&
|
||||
getProtocol() == radio.getProtocol();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General 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.identifier.talkgroup;
|
||||
@@ -31,23 +28,67 @@ public abstract class FullyQualifiedTalkgroupIdentifier extends TalkgroupIdentif
|
||||
{
|
||||
private int mWacn;
|
||||
private int mSystem;
|
||||
private int mTalkgroup;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param wacn
|
||||
* @param system
|
||||
* @param id
|
||||
* @param localAddress used on the local system as an alias to the fully qualified talkgroup.
|
||||
* @param wacn for the talkgroup home system.
|
||||
* @param system for the talkgroup home system.
|
||||
* @param id for the talkgroup within the home system.
|
||||
* @param role played by the talkgroup.
|
||||
*/
|
||||
public FullyQualifiedTalkgroupIdentifier(int wacn, int system, int id, Role role)
|
||||
public FullyQualifiedTalkgroupIdentifier(int localAddress, int wacn, int system, int id, Role role)
|
||||
{
|
||||
super(id, role);
|
||||
super(localAddress > 0 ? localAddress : id, role);
|
||||
mWacn = wacn;
|
||||
mSystem = system;
|
||||
mTalkgroup = id;
|
||||
}
|
||||
|
||||
public int getWacn()
|
||||
{
|
||||
return mWacn;
|
||||
}
|
||||
|
||||
public int getSystem()
|
||||
{
|
||||
return mSystem;
|
||||
}
|
||||
|
||||
public int getTalkgroup()
|
||||
{
|
||||
return mTalkgroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully qualified talkgroup identity.
|
||||
* @return talkgroup identity
|
||||
*/
|
||||
public String getFullyQualifiedTalkgroupAddress()
|
||||
{
|
||||
return mWacn + "." + mSystem + "." + mTalkgroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the talkgroup identify is aliased with a persona value that is different from the talkgroup ID.
|
||||
* @return true if aliased.
|
||||
*/
|
||||
public boolean isAliased()
|
||||
{
|
||||
return getValue() != mTalkgroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mWacn + "." + mSystem + "." + super.toString();
|
||||
if(isAliased())
|
||||
{
|
||||
return super.toString() + "(" + getFullyQualifiedTalkgroupAddress() + ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
return getFullyQualifiedTalkgroupAddress();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,19 @@ import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
|
||||
/**
|
||||
* Talkgroup identifier.
|
||||
*
|
||||
* Note: this class overrides the .equals method to ensure that all talkgroups and subclasses can be compared using
|
||||
* the talkgroup value, regardless if it is a simple talkgroup or a fully qualified talkgroup identifier.
|
||||
*/
|
||||
public abstract class TalkgroupIdentifier extends IntegerIdentifier
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param value for the talkgroup
|
||||
* @param role for the talkgroup
|
||||
*/
|
||||
public TalkgroupIdentifier(Integer value, Role role)
|
||||
{
|
||||
super(value, IdentifierClass.USER, Form.TALKGROUP, role);
|
||||
@@ -39,4 +50,28 @@ public abstract class TalkgroupIdentifier extends IntegerIdentifier
|
||||
{
|
||||
return getValue() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides to compare just the talkgroup value, class, form, role and protocol. This allows a fully qualified
|
||||
* talkgroup identifier to be equivalent to a standard talkgroup identifier for the traffic channel manager.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if(this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if(o instanceof TalkgroupIdentifier tg)
|
||||
{
|
||||
return (getValue().intValue() == tg.getValue().intValue()) &&
|
||||
getIdentifierClass() == tg.getIdentifierClass() &&
|
||||
getForm() == tg.getForm() &&
|
||||
getRole() == tg.getRole() &&
|
||||
getProtocol() == tg.getProtocol();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user