#1977 P25 FQSUID handling improvements. P25 fully qualified radio/talkgroup identifiers we're being flagged as invalid when ISSI patch assigned a local address/ID of zero. Changes format of FQSUID and FQTGID to use a value of zero for the local alias when the call type is an ISSI patch and suppress display of the 0 alias value. If the identifier represents a roaming user, then the local address value is displayed with the FQSUID appended in parenthesis. (#1978)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2024-09-15 05:37:01 -04:00
committed by GitHub
parent b9068da99c
commit 8bb59d581f
10 changed files with 141 additions and 79 deletions

View File

@@ -21,12 +21,15 @@ package io.github.dsheirer.gui.viewer;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierCollection;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
/**
* JavaFX identifier collection viewer
@@ -82,7 +85,19 @@ public class IdentifierCollectionViewer extends VBox
valueColumn.setText("Value");
valueColumn.setCellValueFactory(new PropertyValueFactory<>("value"));
mIdentifierTableView.getColumns().addAll(classColumn, formColumn, roleColumn, valueColumn);
TableColumn textColumn = new TableColumn();
textColumn.setPrefWidth(160);
textColumn.setText("Text");
textColumn.setCellValueFactory((Callback<TableColumn.CellDataFeatures<Identifier<?>, String>, ObservableValue>) param -> {
if(param.getValue() != null)
{
return new SimpleStringProperty(param.getValue().toString());
}
return null;
});
mIdentifierTableView.getColumns().addAll(classColumn, formColumn, roleColumn, valueColumn, textColumn);
}
return mIdentifierTableView;

View File

@@ -72,12 +72,22 @@ public abstract class FullyQualifiedRadioIdentifier extends RadioIdentifier
}
/**
* Indicates if the radio identify is aliased with a persona value that is different from the radio ID.
* Indicates if the radio identity is aliased with a persona value that is different from the radio ID.
* @return true if aliased.
*/
public boolean isAliased()
{
return getValue() != mRadio;
return getValue() != 0 && getValue() != mRadio;
}
/**
* Override the default behavior of RadioIdentifier.isValid() to allow for fully qualified SUID's with a local
* ID of zero which indicates an ISSI patch.
*/
@Override
public boolean isValid()
{
return true;
}
@Override

View File

@@ -71,12 +71,22 @@ public abstract class FullyQualifiedTalkgroupIdentifier extends TalkgroupIdentif
}
/**
* Indicates if the talkgroup identify is aliased with a persona value that is different from the talkgroup ID.
* Indicates if the talkgroup identity is aliased with a persona value that is different from the talkgroup ID.
* @return true if aliased.
*/
public boolean isAliased()
{
return getValue() != mTalkgroup;
return getValue() != 0 && getValue() != mTalkgroup;
}
/**
* Override the default behavior of TalkgroupIdentifier.isValid() to allow for fully qualified TGID's with a local
* ID of zero which indicates an ISSI patch.
*/
@Override
public boolean isValid()
{
return true;
}
@Override

View File

@@ -32,7 +32,6 @@ import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroupPreLoadDataContent;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.scramble.ScrambleParameterIdentifier;
import io.github.dsheirer.log.LoggingSuppressor;
import io.github.dsheirer.message.IMessage;
@@ -307,7 +306,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
* @param timeslot for the channel.
* @param isIdleNull to indicate if this close event is trigged by an IDLE/NULL message
*/
public void closeP2CallEvent(long frequency, int timeslot, long timestamp, boolean isIdleNull)
public void closeP2CallEvent(long frequency, int timeslot, boolean isIdleNull)
{
/**
* Hack: L3Harris systems can issue a channel grant on control/TS1 which creates an event for TS2 and then the
@@ -325,21 +324,13 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
try
{
DecodeEvent event;
if(timeslot == P25P1Message.TIMESLOT_0 || timeslot == P25P1Message.TIMESLOT_1)
{
event = mTS1ChannelGrantEventMap.remove(frequency);
mTS1ChannelGrantEventMap.remove(frequency);
}
else
{
event = mTS2ChannelGrantEventMap.remove(frequency);
}
if(event != null)
{
event.end(timestamp);
broadcast(event);
mTS2ChannelGrantEventMap.remove(frequency);
}
}
finally
@@ -1410,55 +1401,50 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
* Compares the TO role identifier(s) from each collection for equality. This is normally used to compare a call
* update where we only compare the TO role.
*
* @param collection1 containing a TO identifier
* @param collection2 containing a TO identifier
* @param existingIC containing a TO identifier
* @param nextIC containing a TO identifier
* @return true if both collections contain a TO identifier and the TO identifiers are the same value
*/
private boolean isSameCallUpdate(IdentifierCollection collection1, IdentifierCollection collection2)
private boolean isSameCallUpdate(IdentifierCollection existingIC, IdentifierCollection nextIC)
{
Identifier toIdentifier1 = collection1.getToIdentifier();
Identifier toIdentifier2 = collection2.getToIdentifier();
return toIdentifier1 != null && toIdentifier1.equals(toIdentifier2);
Identifier existingTO = existingIC.getToIdentifier();
Identifier nextTO = nextIC.getToIdentifier();
return existingTO != null && existingTO.equals(nextTO);
}
/**
* Compares the TO role identifier(s) from each collection for equality. This is normally used to compare a call
* update where we only compare the TO role.
*
* @param collection1 containing a TO identifier
* @param collection2 containing a TO identifier
* @param existingIC for the existing/previous event containing a TO identifier
* @param nextIC for the next event containing a TO identifier
* @return true if both collections contain a TO identifier and the TO identifiers are the same value
*/
private boolean isSameCallFull(IdentifierCollection collection1, IdentifierCollection collection2)
private boolean isSameCallFull(IdentifierCollection existingIC, IdentifierCollection nextIC)
{
Identifier to1 = collection1.getToIdentifier();
Identifier to2 = collection2.getToIdentifier();
Identifier existingTO = existingIC.getToIdentifier();
Identifier nextTO = nextIC.getToIdentifier();
if(to1 != null && to1.equals(to2))
if(existingTO != null && existingTO.equals(nextTO))
{
Identifier from1 = collection1.getFromIdentifier();
Identifier existingFROM = existingIC.getFromIdentifier();
//If the FROM identifier hasn't yet been established, then this is the same call. We also ignore the
//talker alias as a call identifier since on L3Harris systems they can transmit the talker alias before
//they transmit the radio ID.
if(from1 == null || from1.getForm() == Form.TALKER_ALIAS)
if(existingFROM == null || existingFROM.getForm() == Form.TALKER_ALIAS)
{
return true;
}
Identifier from2 = collection2.getFromIdentifier();
Identifier nextFROM = nextIC.getFromIdentifier();
if(from2 != null && from2.getForm() == Form.TALKER_ALIAS)
if(nextFROM != null && nextFROM.getForm() == Form.TALKER_ALIAS)
{
return true;
}
if(from2 instanceof RadioIdentifier radio && radio.getValue() == 0)
{
return true;
}
return from1.equals(from2);
return existingFROM.equals(nextFROM);
}
return false;

View File

@@ -188,7 +188,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
protected void resetState()
{
super.resetState();
closeCurrentCallEvent(System.currentTimeMillis(), true, false);
closeCurrentCallEvent(true, false);
mEndPttOnFacchCounter = 0;
}
@@ -1274,7 +1274,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
{
if(mac instanceof EndPushToTalk)
{
closeCurrentCallEvent(message.getTimestamp(), true, false);
closeCurrentCallEvent(true, false);
if(message.getDataUnitID().isFACCH())
{
@@ -1561,7 +1561,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
{
if(mac instanceof MacRelease mr)
{
closeCurrentCallEvent(message.getTimestamp(), true, false);
closeCurrentCallEvent(true, false);
broadcast(message, mac, DecodeEventType.COMMAND,
(mr.isForcedPreemption() ? "FORCED " : "") + "CALL PREEMPTION" +
(mr.isTalkerPreemption() ? " BY USER" : " BY CONTROLLER"));
@@ -1882,13 +1882,12 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
/**
* Ends/closes the current call event.
*
* @param timestamp of the message that indicates the event has ended.
* @param resetIdentifiers to reset the FROM/TO identifiers (true) or reset just the FROM identifiers (false)
* @param isIdleNull to indicate if the calling trigger is an IDLE/NULL message
*/
private void closeCurrentCallEvent(long timestamp, boolean resetIdentifiers, boolean isIdleNull)
private void closeCurrentCallEvent(boolean resetIdentifiers, boolean isIdleNull)
{
mTrafficChannelManager.closeP2CallEvent(getCurrentFrequency(), getTimeslot(), timestamp, isIdleNull);
mTrafficChannelManager.closeP2CallEvent(getCurrentFrequency(), getTimeslot(), isIdleNull);
if(resetIdentifiers)
{

View File

@@ -136,11 +136,11 @@ import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorol
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupChannelGrantUpdate;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupDeleteCommand;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupExtendedFunctionCommand;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupOpcode145;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupTalkerAlias;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupTalkerAliasContinuation;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupVoiceChannelUpdate;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupVoiceChannelUserAbbreviated;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupVoiceChannelUserExtended;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaOpcode149;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaQueuedResponse;
import io.github.dsheirer.module.decode.p25.reference.Vendor;
import java.util.ArrayList;
@@ -585,9 +585,9 @@ public class MacMessageFactory
case MOTOROLA_89_GROUP_REGROUP_DELETE:
return new MotorolaGroupRegroupDeleteCommand(message, offset);
case MOTOROLA_91_GROUP_REGROUP_UNKNOWN:
return new MotorolaGroupRegroupOpcode145(message, offset);
return new MotorolaGroupRegroupTalkerAlias(message, offset);
case MOTOROLA_95_UNKNOWN_149:
return new MotorolaOpcode149(message, offset);
return new MotorolaGroupRegroupTalkerAliasContinuation(message, offset);
case MOTOROLA_A0_GROUP_REGROUP_VOICE_CHANNEL_USER_EXTENDED:
return new MotorolaGroupRegroupVoiceChannelUserExtended(message, offset);
case MOTOROLA_A3_GROUP_REGROUP_CHANNEL_GRANT_IMPLICIT:

View File

@@ -30,9 +30,9 @@ import java.util.ArrayList;
import java.util.List;
/**
* Motorola Group Regroup Unknown Opcode 145 - 17-bytes long
* Motorola Group Regroup Talker Alias - 17-bytes long
*/
public class MotorolaGroupRegroupOpcode145 extends MacStructureVendor
public class MotorolaGroupRegroupTalkerAlias extends MacStructureVendor
{
private static final IntField TALKGROUP = IntField.length16(24);
private static final IntField UNKNOWN = IntField.length32(40);
@@ -49,7 +49,7 @@ public class MotorolaGroupRegroupOpcode145 extends MacStructureVendor
* @param message containing the message bits
* @param offset into the message for this structure
*/
public MotorolaGroupRegroupOpcode145(CorrectedBinaryMessage message, int offset)
public MotorolaGroupRegroupTalkerAlias(CorrectedBinaryMessage message, int offset)
{
super(message, offset);
}
@@ -60,7 +60,7 @@ public class MotorolaGroupRegroupOpcode145 extends MacStructureVendor
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("MOTOROLA GROUP REGROUP UNKNOWN OPCODE 145 TALKGROUP:").append(getTalkgroup());
sb.append("MOTOROLA GROUP REGROUP TALKER ALIAS TALKGROUP:").append(getTalkgroup());
sb.append(" SOURCE SUID RADIO:").append(getRadio());
sb.append(" UNK:").append(Integer.toHexString(getInt(UNKNOWN)).toUpperCase());
sb.append(" MSG:").append(getMessage().get(getOffset(), getMessage().length()).toHexString());

View File

@@ -26,19 +26,30 @@ import java.util.ArrayList;
import java.util.List;
/**
* Motorola Unknown Opcode 149 (0x95)
* Motorola Unknown Opcode 149 (0x95) Talker Alias continuation message.
*
* This was observed at the end of a Group Regroup call sequence during HANGTIME, so it seems more of an announcement
* type message and less as something relevant to the call.
* This was observed at the end of a Group Regroup call sequence during HANGTIME. Each radio doesn't know the alias
* until it receives it once and then queues the alias for subsequent use.
*
* This opcode is observed following an Opcode 145 message and seems to be a continuation message.
*
* Examples:
* 959011 018E58B82BAB4D3B70E9A8457F9D C67
* 959011 0287000000000000000000000000 E26
*
* The opcode, vendor and length octets are consistent. The first octet seems to be an identifier, 01, 02, etc.
* Example 2:
* 9190110BCD02010026BEE004A403151724FD9 Opcode 145
* 959011012436C4C022EC902A9943EDAA69E76 Opcode 149
* 95901102298FAA77683FAE0000000000000AB Opcode 149
* (immediately followed in the same call sequence by a slight variation)
* 9190110BCD02010036BEE004A403151724513 Opcode 145
* 959011013436C4C022EC902A9943EDAA699F6 Opcode 149
* 95901102398FAA77683FAE00000000000072B Opcode 149
*
* The opcode, vendor and length octets are consistent. The first octet seems to be a sequence identifier, 01, 02, etc.
* Nothing else seems to match any of the other identifiers that were active at call time.
*/
public class MotorolaOpcode149 extends MacStructureVendor
public class MotorolaGroupRegroupTalkerAliasContinuation extends MacStructureVendor
{
private List<Identifier> mIdentifiers;
@@ -48,7 +59,7 @@ public class MotorolaOpcode149 extends MacStructureVendor
* @param message containing the message bits
* @param offset into the message for this structure
*/
public MotorolaOpcode149(CorrectedBinaryMessage message, int offset)
public MotorolaGroupRegroupTalkerAliasContinuation(CorrectedBinaryMessage message, int offset)
{
super(message, offset);
}
@@ -59,7 +70,7 @@ public class MotorolaOpcode149 extends MacStructureVendor
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("MOTOROLA UNKNOWN OPCODE 149");
sb.append("MOTOROLA TALKER ALIAS CONTINUATION");
sb.append(" MSG:").append(getMessage().get(getOffset(), getMessage().length()).toHexString());
return sb.toString();
}

View File

@@ -227,16 +227,30 @@ public class APCO25TalkgroupFormatter extends AbstractIntegerFormatter
{
case DECIMAL:
case FORMATTED:
sb.append(toDecimal(id, RADIO_DECIMAL_WIDTH));
sb.append("(").append(toHex(wacn, WACN_HEXADECIMAL_WIDTH));
if(id > 0)
{
sb.append(toDecimal(id, RADIO_DECIMAL_WIDTH)).append(" (");
}
sb.append(toHex(wacn, WACN_HEXADECIMAL_WIDTH));
sb.append(".").append(toHex(system, SYSTEM_HEXADECIMAL_WIDTH));
sb.append(".").append(toDecimal(radio, RADIO_DECIMAL_WIDTH)).append(")");
sb.append(".").append(toDecimal(radio, RADIO_DECIMAL_WIDTH));
if(id > 0)
{
sb.append(")");
}
return sb.toString();
case HEXADECIMAL:
sb.append(toHex(id, RADIO_HEXADECIMAL_WIDTH));
sb.append("(").append(toHex(wacn, WACN_HEXADECIMAL_WIDTH));
if(id > 0)
{
sb.append(toHex(id, RADIO_HEXADECIMAL_WIDTH)).append(" (");
}
sb.append(toHex(wacn, WACN_HEXADECIMAL_WIDTH));
sb.append(".").append(toHex(system, SYSTEM_HEXADECIMAL_WIDTH));
sb.append(".").append(toHex(radio, RADIO_HEXADECIMAL_WIDTH)).append(")");
sb.append(".").append(toHex(radio, RADIO_HEXADECIMAL_WIDTH));
if(id > 0)
{
sb.append(")");
}
return sb.toString();
default:
throw new IllegalArgumentException("Unrecognized integer format: " + format);
@@ -248,16 +262,30 @@ public class APCO25TalkgroupFormatter extends AbstractIntegerFormatter
{
case DECIMAL:
case FORMATTED:
sb.append(id);
if(id > 0)
{
sb.append(id).append(" (");
}
sb.append("(").append(toHex(wacn, WACN_HEXADECIMAL_WIDTH));
sb.append(".").append(toHex(system, SYSTEM_HEXADECIMAL_WIDTH));
sb.append(".").append(radio).append(")");
sb.append(".").append(radio);
if(id > 0)
{
sb.append(")");
}
return sb.toString();
case HEXADECIMAL:
sb.append(toHex(id));
sb.append("(").append(toHex(wacn, WACN_HEXADECIMAL_WIDTH));
if(id > 0)
{
sb.append(toHex(id)).append(" (");
}
sb.append(toHex(wacn, WACN_HEXADECIMAL_WIDTH));
sb.append(".").append(toHex(system, SYSTEM_HEXADECIMAL_WIDTH));
sb.append(".").append(toHex(radio)).append(")");
sb.append(".").append(toHex(radio));
if(id > 0)
{
sb.append(")");
}
return sb.toString();
default:
throw new IllegalArgumentException("Unrecognized integer format: " + format);

View File

@@ -300,15 +300,18 @@ public class FrequencyController
*/
public void removeSourceEventProcessor(ISourceEventProcessor processor)
{
mTunable.getLock().lock();
if(mTunable != null)
{
mTunable.getLock().lock();
try
{
mProcessors.remove(processor);
}
finally
{
mTunable.getLock().unlock();
try
{
mProcessors.remove(processor);
}
finally
{
mTunable.getLock().unlock();
}
}
}