mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-09 15:17:24 -05:00
Co-authored-by: denny <dennis.sheirer@gmail.com>
This commit is contained in:
@@ -76,8 +76,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasComp
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxTalkerAlias;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxTalkerAliasContinuation;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
||||
@@ -1193,73 +1191,6 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS:
|
||||
if(message instanceof CapacityMaxTalkerAlias alias)
|
||||
{
|
||||
//If we have a talker alias identifier, append this value.
|
||||
Identifier existing = getIdentifierCollection().getIdentifier(IdentifierClass.USER, Form.TALKER_ALIAS, Role.FROM);
|
||||
|
||||
if(existing instanceof DmrTalkerAliasIdentifier talkerAlias &&
|
||||
!talkerAlias.equals(alias.getTalkerAliasIdentifier()) &&
|
||||
!talkerAlias.getValue().contains(alias.getTalkerAliasIdentifier().getValue()))
|
||||
{
|
||||
//Concatenate the existing talker alias fragment with the base alias value.
|
||||
DmrTalkerAliasIdentifier updated = DmrTalkerAliasIdentifier
|
||||
.create(alias.getTalkerAliasIdentifier().getValue() + talkerAlias.getValue());
|
||||
getIdentifierCollection().update(updated);
|
||||
|
||||
Identifier fromRadio = getIdentifierCollection().getFromIdentifier();
|
||||
|
||||
if(hasTrafficChannelManager() && fromRadio instanceof RadioIdentifier radio)
|
||||
{
|
||||
mTrafficChannelManager.getTalkerAliasManager().update(radio, updated);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
getIdentifierCollection().update(alias.getTalkerAliasIdentifier());
|
||||
}
|
||||
|
||||
if(mCurrentCallEvent != null)
|
||||
{
|
||||
broadcast(mCurrentCallEvent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS_CONTINUATION:
|
||||
if(message instanceof CapacityMaxTalkerAliasContinuation alias)
|
||||
{
|
||||
//If we have a talker alias identifier, append this value.
|
||||
Identifier existing = getIdentifierCollection().getIdentifier(IdentifierClass.USER, Form.TALKER_ALIAS, Role.FROM);
|
||||
|
||||
if(existing instanceof DmrTalkerAliasIdentifier talkerAlias &&
|
||||
!talkerAlias.equals(alias.getTalkerAliasIdentifier()) &&
|
||||
!talkerAlias.getValue().contains(alias.getTalkerAliasIdentifier().getValue()))
|
||||
{
|
||||
//Concatenate the existing talker alias value with the updated continuation fragment.
|
||||
DmrTalkerAliasIdentifier updated = DmrTalkerAliasIdentifier.create(talkerAlias.getValue() +
|
||||
alias.getTalkerAliasIdentifier().getValue());
|
||||
getIdentifierCollection().update(updated);
|
||||
|
||||
Identifier fromRadio = getIdentifierCollection().getFromIdentifier();
|
||||
|
||||
if(hasTrafficChannelManager() && fromRadio instanceof RadioIdentifier radio)
|
||||
{
|
||||
mTrafficChannelManager.getTalkerAliasManager().update(radio, updated);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Temporarily place the continuation fragment alias into the identifier collection.
|
||||
getIdentifierCollection().update(alias.getTalkerAliasIdentifier());
|
||||
}
|
||||
|
||||
if(mCurrentCallEvent != null)
|
||||
{
|
||||
broadcast(mCurrentCallEvent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER:
|
||||
if(message instanceof CapacityPlusWideAreaVoiceChannelUser cpwavcu)
|
||||
{
|
||||
@@ -1382,6 +1313,18 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
if(message instanceof TalkerAliasComplete tac && tac.hasTalkerAliasIdentifier())
|
||||
{
|
||||
getIdentifierCollection().update(tac.getTalkerAliasIdentifier());
|
||||
|
||||
Identifier fromRadio = getIdentifierCollection().getFromIdentifier();
|
||||
|
||||
if(hasTrafficChannelManager() && fromRadio instanceof RadioIdentifier radio)
|
||||
{
|
||||
mTrafficChannelManager.getTalkerAliasManager().update(radio, tac.getTalkerAliasIdentifier());
|
||||
}
|
||||
|
||||
if(mCurrentCallEvent != null)
|
||||
{
|
||||
broadcast(mCurrentCallEvent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -144,8 +144,10 @@ public class DMRNetworkConfigurationMonitor
|
||||
switch(linkControl.getOpcode())
|
||||
{
|
||||
case FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER:
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS:
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS_CONTINUATION:
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS_HEADER:
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS_BLOCK_1:
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS_BLOCK_2:
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS_BLOCK_3:
|
||||
mBrand = BRAND_MOTOROLA_CAPACITY_MAX_TIER_3_TRUNKING;
|
||||
break;
|
||||
|
||||
|
||||
@@ -39,8 +39,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGr
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraTerminator;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraXptChannelGrant;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxTalkerAlias;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxTalkerAliasContinuation;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
||||
@@ -170,12 +168,6 @@ public class LCMessageFactory
|
||||
case FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER:
|
||||
flc = new CapacityMaxVoiceChannelUser(message, timestamp, timeslot);
|
||||
break;
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS:
|
||||
flc = new CapacityMaxTalkerAlias(message, timestamp, timeslot);
|
||||
break;
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS_CONTINUATION:
|
||||
flc = new CapacityMaxTalkerAliasContinuation(message, timestamp, timeslot);
|
||||
break;
|
||||
case FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER:
|
||||
flc = new CapacityPlusEncryptedVoiceChannelUser(message, timestamp, timeslot);
|
||||
break;
|
||||
@@ -197,18 +189,22 @@ public class LCMessageFactory
|
||||
break;
|
||||
case FULL_STANDARD_TALKER_ALIAS_HEADER:
|
||||
case FULL_HYTERA_TALKER_ALIAS_HEADER:
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS_HEADER:
|
||||
flc = new TalkerAliasHeader(message, timestamp, timeslot);
|
||||
break;
|
||||
case FULL_STANDARD_TALKER_ALIAS_BLOCK_1:
|
||||
case FULL_HYTERA_TALKER_ALIAS_BLOCK_1:
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS_BLOCK_1:
|
||||
flc = new TalkerAliasBlock1(message, timestamp, timeslot);
|
||||
break;
|
||||
case FULL_STANDARD_TALKER_ALIAS_BLOCK_2:
|
||||
case FULL_HYTERA_TALKER_ALIAS_BLOCK_2:
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS_BLOCK_2:
|
||||
flc = new TalkerAliasBlock2(message, timestamp, timeslot);
|
||||
break;
|
||||
case FULL_STANDARD_TALKER_ALIAS_BLOCK_3:
|
||||
case FULL_HYTERA_TALKER_ALIAS_BLOCK_3:
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS_BLOCK_3:
|
||||
flc = new TalkerAliasBlock3(message, timestamp, timeslot);
|
||||
break;
|
||||
case FULL_HYTERA_XPT_CHANNEL_GRANT:
|
||||
|
||||
@@ -47,8 +47,10 @@ public enum LCOpcode
|
||||
FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 4, "WAN GROUP VOICE CHANNEL USER"),
|
||||
|
||||
FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 16, "CAPMAX GROUP VOICE CHANNEL USER"),
|
||||
FULL_CAPACITY_MAX_TALKER_ALIAS(Vendor.MOTOROLA_CAPACITY_PLUS, true, 20, "CAPMAX TALKER ALIAS"),
|
||||
FULL_CAPACITY_MAX_TALKER_ALIAS_CONTINUATION(Vendor.MOTOROLA_CAPACITY_PLUS, true, 21, "CAPMAX TALKER ALIAS CONTINUATION"),
|
||||
FULL_CAPACITY_MAX_TALKER_ALIAS_HEADER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 20, "CAPMAX TALKER ALIAS HEADER"),
|
||||
FULL_CAPACITY_MAX_TALKER_ALIAS_BLOCK_1(Vendor.MOTOROLA_CAPACITY_PLUS, true, 21, "CAPMAX TALKER ALIAS BLOCK 1"),
|
||||
FULL_CAPACITY_MAX_TALKER_ALIAS_BLOCK_2(Vendor.MOTOROLA_CAPACITY_PLUS, true, 22, "CAPMAX TALKER ALIAS BLOCK 2"),
|
||||
FULL_CAPACITY_MAX_TALKER_ALIAS_BLOCK_3(Vendor.MOTOROLA_CAPACITY_PLUS, true, 23, "CAPMAX TALKER ALIAS BLOCK 3"),
|
||||
//Observed on Cap+ Multi-Site System during an encrypted voice call
|
||||
FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 32, "ENCRYPTED VOICE CHANNEL USER"),
|
||||
//Observed on Cap+ Multi-Site System during an encrypted voice call
|
||||
@@ -90,7 +92,9 @@ public enum LCOpcode
|
||||
private static final EnumSet<LCOpcode> 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,
|
||||
FULL_HYTERA_TALKER_ALIAS_HEADER, FULL_HYTERA_TALKER_ALIAS_BLOCK_1, FULL_HYTERA_TALKER_ALIAS_BLOCK_2,
|
||||
FULL_HYTERA_TALKER_ALIAS_BLOCK_3);
|
||||
FULL_HYTERA_TALKER_ALIAS_BLOCK_3, FULL_CAPACITY_MAX_TALKER_ALIAS_HEADER,
|
||||
FULL_CAPACITY_MAX_TALKER_ALIAS_BLOCK_1, FULL_CAPACITY_MAX_TALKER_ALIAS_BLOCK_2,
|
||||
FULL_CAPACITY_MAX_TALKER_ALIAS_BLOCK_3);
|
||||
|
||||
private final Vendor mVendor;
|
||||
private final boolean mFull;
|
||||
|
||||
@@ -26,6 +26,9 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
*/
|
||||
public class TalkerAliasAssembler
|
||||
{
|
||||
//Header (49) plus three blocks (56 each)
|
||||
private static final int MAX_BIT_LENGTH = 217;
|
||||
|
||||
//Timeslot 1 fragments
|
||||
private TalkerAliasHeader mHeaderTs1;
|
||||
private TalkerAliasBlock1 mBlock1Ts1;
|
||||
@@ -84,6 +87,7 @@ public class TalkerAliasAssembler
|
||||
{
|
||||
case FULL_STANDARD_TALKER_ALIAS_HEADER:
|
||||
case FULL_HYTERA_TALKER_ALIAS_HEADER:
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS_HEADER:
|
||||
if(message instanceof TalkerAliasHeader tah)
|
||||
{
|
||||
if(message.getTimeslot() == 1)
|
||||
@@ -101,6 +105,7 @@ public class TalkerAliasAssembler
|
||||
}
|
||||
case FULL_STANDARD_TALKER_ALIAS_BLOCK_1:
|
||||
case FULL_HYTERA_TALKER_ALIAS_BLOCK_1:
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS_BLOCK_1:
|
||||
if(message instanceof TalkerAliasBlock1 tab1)
|
||||
{
|
||||
if(message.getTimeslot() == 1)
|
||||
@@ -118,6 +123,7 @@ public class TalkerAliasAssembler
|
||||
}
|
||||
case FULL_STANDARD_TALKER_ALIAS_BLOCK_2:
|
||||
case FULL_HYTERA_TALKER_ALIAS_BLOCK_2:
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS_BLOCK_2:
|
||||
if(message instanceof TalkerAliasBlock2 tab2)
|
||||
{
|
||||
if(message.getTimeslot() == 1)
|
||||
@@ -135,6 +141,7 @@ public class TalkerAliasAssembler
|
||||
}
|
||||
case FULL_STANDARD_TALKER_ALIAS_BLOCK_3:
|
||||
case FULL_HYTERA_TALKER_ALIAS_BLOCK_3:
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS_BLOCK_3:
|
||||
if(message instanceof TalkerAliasBlock3 tab3)
|
||||
{
|
||||
if(message.getTimeslot() == 1)
|
||||
@@ -187,39 +194,37 @@ public class TalkerAliasAssembler
|
||||
{
|
||||
if(header != null)
|
||||
{
|
||||
CorrectedBinaryMessage message = new CorrectedBinaryMessage(MAX_BIT_LENGTH);
|
||||
message.load(0, header.getPayloadFragment());
|
||||
int availableBits = 49;
|
||||
|
||||
//Load the contiguous fragments that are available. UTF-8 aliases need all of them because the header's
|
||||
//character length under-counts the bytes when characters are multi-byte, so a longer alias replaces a
|
||||
//shorter one as the remaining fragments arrive.
|
||||
if(block1 != null)
|
||||
{
|
||||
message.load(49, block1.getPayloadFragment());
|
||||
availableBits = 105;
|
||||
|
||||
if(block2 != null)
|
||||
{
|
||||
message.load(105, block2.getPayloadFragment());
|
||||
availableBits = 161;
|
||||
|
||||
if(block3 != null)
|
||||
{
|
||||
message.load(161, block3.getPayloadFragment());
|
||||
availableBits = MAX_BIT_LENGTH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int bitLength = header.getTotalBitLength();
|
||||
|
||||
if(1 <= bitLength && bitLength <= 49)
|
||||
if(1 <= bitLength && bitLength <= availableBits)
|
||||
{
|
||||
return new TalkerAliasComplete(header.getPayloadFragment(), header.getFormat(), header.getCharacterLength(),
|
||||
header.getTimestamp(), header.getTimeslot());
|
||||
}
|
||||
else if(50 <= bitLength && bitLength <= 105 && block1 != null)
|
||||
{
|
||||
CorrectedBinaryMessage message = new CorrectedBinaryMessage(105);
|
||||
message.load(0, header.getPayloadFragment());
|
||||
message.load(49, block1.getPayloadFragment());
|
||||
return new TalkerAliasComplete(message, header.getFormat(), header.getCharacterLength(),
|
||||
header.getTimestamp(), header.getTimeslot());
|
||||
}
|
||||
else if(106 <= bitLength && bitLength <= 161 && block1 != null && block2 != null)
|
||||
{
|
||||
CorrectedBinaryMessage message = new CorrectedBinaryMessage(161);
|
||||
message.load(0, header.getPayloadFragment());
|
||||
message.load(49, block1.getPayloadFragment());
|
||||
message.load(105, block2.getPayloadFragment());
|
||||
return new TalkerAliasComplete(message, header.getFormat(), header.getCharacterLength(),
|
||||
header.getTimestamp(), header.getTimeslot());
|
||||
}
|
||||
else if(162 <= bitLength && bitLength <= 217 && block1 != null && block2 != null && block3 != null)
|
||||
{
|
||||
CorrectedBinaryMessage message = new CorrectedBinaryMessage(217);
|
||||
message.load(0, header.getPayloadFragment());
|
||||
message.load(49, block1.getPayloadFragment());
|
||||
message.load(105, block2.getPayloadFragment());
|
||||
message.load(161, block3.getPayloadFragment());
|
||||
return new TalkerAliasComplete(message, header.getFormat(), header.getCharacterLength(),
|
||||
header.getTimestamp(), header.getTimeslot());
|
||||
return new TalkerAliasComplete(message.getSubMessage(0, availableBits), header.getFormat(),
|
||||
header.getCharacterLength(), header.getTimestamp(), header.getTimeslot());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.alias.DmrTalkerAliasIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCOpcode;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.TalkerAliasDataFormat;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -98,7 +99,7 @@ public class TalkerAliasComplete extends FullLCMessage
|
||||
{
|
||||
try
|
||||
{
|
||||
mAlias = getMessage().parseUTF8(1, mCharacterCount);
|
||||
mAlias = parseUTF8();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@@ -124,6 +125,35 @@ public class TalkerAliasComplete extends FullLCMessage
|
||||
return mAlias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses all of the assembled bytes as UTF-8 and limits the result to the character count, instead of limiting the
|
||||
* bytes to the character count, since characters outside of the ASCII range are encoded in two or more bytes.
|
||||
* @return alias, excluding null padding and any trailing character that is missing its remaining bytes.
|
||||
*/
|
||||
private String parseUTF8() throws UnsupportedEncodingException
|
||||
{
|
||||
String alias = getMessage().parseUTF8(1, (getMessage().size() - 1) / 8);
|
||||
|
||||
int nullIndex = alias.indexOf('\0');
|
||||
|
||||
if(nullIndex >= 0)
|
||||
{
|
||||
alias = alias.substring(0, nullIndex);
|
||||
}
|
||||
|
||||
if(alias.endsWith("\uFFFD"))
|
||||
{
|
||||
alias = alias.substring(0, alias.length() - 1);
|
||||
}
|
||||
|
||||
if(alias.codePointCount(0, alias.length()) > mCharacterCount)
|
||||
{
|
||||
alias = alias.substring(0, alias.offsetByCodePoints(0, mCharacterCount));
|
||||
}
|
||||
|
||||
return alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Talker Alias identifier.
|
||||
* @return identifier or null if the alias string is null or empty, meaning it could not be decoded.
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.alias.DmrTalkerAliasIdentifier;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Capacity Max Talker Alias (FLCO 20 / FID 0x10)
|
||||
* <p>
|
||||
* See also: FLCO 21 - Talker Alias Continuation
|
||||
*/
|
||||
public class CapacityMaxTalkerAlias extends CapacityPlusVoiceChannelUser
|
||||
{
|
||||
private static final int[] ENCODING_TYPE = new int[]{16, 17, 18};
|
||||
private static final int[] LENGTH = new int[]{19, 20, 21, 22};
|
||||
private static final int ALIAS_START = 24;
|
||||
private static final int ALIAS_END = ALIAS_START + (6 * 8); //6 characters?
|
||||
private DmrTalkerAliasIdentifier mTalkerAliasIdentifier;
|
||||
private List<Identifier> mIdentifiers;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param message for the link control payload
|
||||
* @param timestamp
|
||||
* @param timeslot
|
||||
*/
|
||||
public CapacityMaxTalkerAlias(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||
{
|
||||
super(message, timestamp, timeslot);
|
||||
}
|
||||
|
||||
public int getLength()
|
||||
{
|
||||
return getMessage().getInt(LENGTH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append("[CRC-ERROR] ");
|
||||
}
|
||||
if(isEncrypted())
|
||||
{
|
||||
sb.append(" *ENCRYPTED*");
|
||||
}
|
||||
if(isReservedBitSet())
|
||||
{
|
||||
sb.append(" *RESERVED-BIT*");
|
||||
}
|
||||
|
||||
sb.append("FLC MOTOROLA CAPMAX TALKER ALIAS:").append(getTalkerAliasIdentifier());
|
||||
sb.append(" LENGTH:").append(getLength());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Talker alias identifier
|
||||
*
|
||||
* @return identifier
|
||||
*/
|
||||
public DmrTalkerAliasIdentifier getTalkerAliasIdentifier()
|
||||
{
|
||||
if(mTalkerAliasIdentifier == null)
|
||||
{
|
||||
String alias = new String(getMessage().get(ALIAS_START, ALIAS_END).getBytes()).trim();
|
||||
mTalkerAliasIdentifier = DmrTalkerAliasIdentifier.create(alias);
|
||||
}
|
||||
|
||||
return mTalkerAliasIdentifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
if(mIdentifiers == null)
|
||||
{
|
||||
mIdentifiers = new ArrayList<>();
|
||||
mIdentifiers.add(getTalkerAliasIdentifier());
|
||||
}
|
||||
|
||||
return mIdentifiers;
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.dmr.message.data.lc.full.motorola;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.alias.DmrTalkerAliasIdentifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Capacity Max Talker Alias Continuation (FLCO 21 / FID 0x10)
|
||||
*/
|
||||
public class CapacityMaxTalkerAliasContinuation extends CapacityPlusVoiceChannelUser
|
||||
{
|
||||
private static final int ALIAS_START = 16;
|
||||
private static final int ALIAS_END = ALIAS_START + (7 * 8);
|
||||
private DmrTalkerAliasIdentifier mTalkerAliasIdentifier;
|
||||
private List<Identifier> mIdentifiers;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param message for the link control payload
|
||||
* @param timestamp
|
||||
* @param timeslot
|
||||
*/
|
||||
public CapacityMaxTalkerAliasContinuation(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||
{
|
||||
super(message, timestamp, timeslot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append("[CRC-ERROR] ");
|
||||
}
|
||||
if(isEncrypted())
|
||||
{
|
||||
sb.append(" *ENCRYPTED*");
|
||||
}
|
||||
if(isReservedBitSet())
|
||||
{
|
||||
sb.append(" *RESERVED-BIT*");
|
||||
}
|
||||
|
||||
sb.append("FLC MOTOROLA CAPMAX TALKER ALIAS CONTINUED:").append(getTalkerAliasIdentifier());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Talker alias identifier
|
||||
* @return identifier
|
||||
*/
|
||||
public DmrTalkerAliasIdentifier getTalkerAliasIdentifier()
|
||||
{
|
||||
if(mTalkerAliasIdentifier == null)
|
||||
{
|
||||
String alias = new String(getMessage().get(ALIAS_START, ALIAS_END).getBytes()).trim();
|
||||
mTalkerAliasIdentifier = DmrTalkerAliasIdentifier.create(alias);
|
||||
}
|
||||
|
||||
return mTalkerAliasIdentifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
if(mIdentifiers == null)
|
||||
{
|
||||
mIdentifiers = new ArrayList<>();
|
||||
mIdentifiers.add(getTalkerAliasIdentifier());
|
||||
}
|
||||
|
||||
return mIdentifiers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests assembly of talker alias fragments, using Motorola Capacity Max (FID 0x10, FLCO 20-23) link control.
|
||||
*/
|
||||
public class TalkerAliasAssemblerTest
|
||||
{
|
||||
private static final int FLCO_HEADER = 20;
|
||||
private static final int FID_MOTOROLA = 0x10;
|
||||
private static final int FORMAT_UTF8 = 2;
|
||||
|
||||
/**
|
||||
* Creates a 72-bit link control message (minus CRC) carrying the 7 octets that follow the FLCO and FID.
|
||||
*/
|
||||
private static FullLCMessage message(int flco, byte[] payload)
|
||||
{
|
||||
CorrectedBinaryMessage message = new CorrectedBinaryMessage(96);
|
||||
message.load(2, 6, flco);
|
||||
message.load(8, 8, FID_MOTOROLA);
|
||||
|
||||
for(int x = 0; x < 7; x++)
|
||||
{
|
||||
message.load(16 + (x * 8), 8, payload[x] & 0xFF);
|
||||
}
|
||||
|
||||
return switch(flco - FLCO_HEADER)
|
||||
{
|
||||
case 0 -> new TalkerAliasHeader(message, 0, 1);
|
||||
case 1 -> new TalkerAliasBlock1(message, 0, 1);
|
||||
case 2 -> new TalkerAliasBlock2(message, 0, 1);
|
||||
default -> new TalkerAliasBlock3(message, 0, 1);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fragments the alias into the header and three blocks, padded with nulls.
|
||||
*/
|
||||
private static FullLCMessage[] fragment(String alias, int length)
|
||||
{
|
||||
ByteBuffer buffer = ByteBuffer.allocate(28);
|
||||
buffer.put((byte)((FORMAT_UTF8 << 6) | (length << 1)));
|
||||
buffer.put(alias.getBytes(StandardCharsets.UTF_8));
|
||||
byte[] bytes = buffer.array();
|
||||
|
||||
FullLCMessage[] messages = new FullLCMessage[4];
|
||||
|
||||
for(int x = 0; x < 4; x++)
|
||||
{
|
||||
byte[] payload = new byte[7];
|
||||
System.arraycopy(bytes, x * 7, payload, 0, 7);
|
||||
messages[x] = message(FLCO_HEADER + x, payload);
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
private static TalkerAliasComplete assemble(String alias, int length)
|
||||
{
|
||||
TalkerAliasAssembler assembler = new TalkerAliasAssembler();
|
||||
TalkerAliasComplete complete = null;
|
||||
|
||||
for(FullLCMessage message : fragment(alias, length))
|
||||
{
|
||||
assertTrue(message.getOpcode().isTalkerAliasOpcode(), "Talker alias opcode: " + message.getOpcode());
|
||||
TalkerAliasComplete latest = assembler.process(message);
|
||||
complete = latest != null ? latest : complete;
|
||||
}
|
||||
|
||||
return complete;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void utf8LengthInCharacters()
|
||||
{
|
||||
assertEquals("ТЕРНАВА", assemble("ТЕРНАВА", 7).getAliasValue());
|
||||
assertEquals("САЛАМАНДРА", assemble("САЛАМАНДРА", 10).getAliasValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void utf8LengthInBytes()
|
||||
{
|
||||
assertEquals("ТЕРНАВА", assemble("ТЕРНАВА", 14).getAliasValue());
|
||||
assertEquals("САЛАМАНДРА", assemble("САЛАМАНДРА", 20).getAliasValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asciiInUtf8Format()
|
||||
{
|
||||
assertEquals("DISPATCH 1", assemble("DISPATCH 1", 10).getAliasValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void partialCharacterIsNotDisplayed()
|
||||
{
|
||||
TalkerAliasAssembler assembler = new TalkerAliasAssembler();
|
||||
FullLCMessage[] messages = fragment("САЛАМАНДРА", 10);
|
||||
assembler.process(messages[0]);
|
||||
TalkerAliasComplete partial = assembler.process(messages[1]);
|
||||
assertEquals("САЛАМА", partial.getAliasValue());
|
||||
assertFalse(partial.getAliasValue().contains("\uFFFD"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user