From 10ed02fd0d2885c30a3c8db8b37626ccaf62431b Mon Sep 17 00:00:00 2001 From: sheirerd Date: Sat, 13 Jan 2024 09:46:46 -0500 Subject: [PATCH] #1787 DMR Decoder enhancements for RAS enabled systems. Adds support for CSBKO:33 and FLCO:16 --- .../module/decode/dmr/DMRDecoderState.java | 71 +++++--- .../decode/dmr/DMRMessageProcessor.java | 22 ++- .../message/data/csbk/CSBKMessageFactory.java | 5 +- .../decode/dmr/message/data/csbk/Opcode.java | 2 + .../CapacityMaxGroupVoiceChannelUpdate.java | 153 ++++++++++++++++++ .../grant/TalkgroupVoiceChannelGrant.java | 1 + .../dmr/message/data/lc/LCMessageFactory.java | 4 + .../decode/dmr/message/data/lc/LCOpcode.java | 1 + .../motorola/CapacityMaxVoiceChannelUser.java | 102 ++++++++++++ .../dmr/message/data/mbc/MBCAssembler.java | 19 ++- 10 files changed, 351 insertions(+), 29 deletions(-) create mode 100644 src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxGroupVoiceChannelUpdate.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityMaxVoiceChannelUser.java diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java index aa0298636..a73e74463 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java @@ -66,6 +66,7 @@ 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.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; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser; @@ -142,6 +143,16 @@ public DMRDecoderState(Channel channel, int timeslot, DMRTrafficChannelManager t } } + /** + * Indicates if the message is valid or if the Ignore CRC Checksums feature is enabled. + * @param message to check + * @return true if ignore CRC checksums or if the message is valid, meaning the message has passed CRC check. + */ + private boolean isValid(IMessage message) + { + return mIgnoreCRCChecksums || message.isValid(); + } + /** * Indicates if this decoder state has an (optional) traffic channel manager. */ @@ -206,21 +217,21 @@ public void receive(IMessage message) { if(message.getTimeslot() == getTimeslot()) { - if(message instanceof VoiceMessage) + if(message instanceof VoiceMessage voice) { - processVoice((VoiceMessage)message); + processVoice(voice); } - else if(message instanceof DataMessage) + else if(message instanceof DataMessage data) { - processData((DataMessage)message); + processData(data); } - else if((message.isValid() || mIgnoreCRCChecksums) && message instanceof LCMessage) + else if(isValid(message) && message instanceof LCMessage lcMessage) { - processLinkControl((LCMessage)message, false); + processLinkControl(lcMessage, false); } - else if(message.isValid() && message instanceof DMRPacketMessage) + else if(isValid(message) && message instanceof DMRPacketMessage packet) { - processPacket((DMRPacketMessage)message); + processPacket(packet); } else if(message instanceof UDTShortMessageService sms) { @@ -232,16 +243,15 @@ else if(message instanceof DMRMessage) } } //SLCO messages on timeslot 0 to catch capacity plus rest channel events - else if((message.isValid() || mIgnoreCRCChecksums) && message.getTimeslot() == 0 && message instanceof LCMessage) + else if(isValid(message) && message.getTimeslot() == 0 && message instanceof LCMessage lcMessage) { - processLinkControl((LCMessage)message, false); + processLinkControl(lcMessage, false); } //Pass the message to the network configuration monitor, if this decoder state has a non-null instance - if(mNetworkConfigurationMonitor != null && (message.isValid() || mIgnoreCRCChecksums) && - message instanceof DMRMessage) + if(mNetworkConfigurationMonitor != null && isValid(message) && message instanceof DMRMessage dmrMessage) { - mNetworkConfigurationMonitor.process((DMRMessage)message); + mNetworkConfigurationMonitor.process(dmrMessage); } } @@ -503,7 +513,7 @@ private void processHeader(HeaderMessage header) //Process the link control message to get the identifiers LCMessage lc = header.getLCMessage(); - if(lc.isValid()) + if(isValid(lc)) { processLinkControl(lc, false); } @@ -520,15 +530,15 @@ private void processData(DataMessage message) switch(message.getSlotType().getDataType()) { case CSBK: - if((message.isValid() || mIgnoreCRCChecksums) && message instanceof CSBKMessage) + if(isValid(message) && message instanceof CSBKMessage csbk) { - processCSBK((CSBKMessage)message); + processCSBK(csbk); } break; case VOICE_HEADER: - if(message instanceof HeaderMessage) + if(message instanceof HeaderMessage header) { - processVoiceHeader((HeaderMessage)message); + processVoiceHeader(header); } break; case USB_DATA: @@ -539,9 +549,9 @@ private void processData(DataMessage message) case MBC_ENC_HEADER: case DATA_ENC_HEADER: case CHANNEL_CONTROL_ENC_HEADER: - if(message instanceof HeaderMessage) + if(message instanceof HeaderMessage header) { - processHeader((HeaderMessage)message); + processHeader(header); } break; case SLOT_IDLE: @@ -579,7 +589,7 @@ private void processTerminator(Terminator terminator) LCMessage lcMessage = terminator.getLCMessage(); - if(lcMessage.isValid()) + if(isValid(lcMessage)) { processLinkControl(lcMessage, true); } @@ -592,7 +602,7 @@ private void processVoiceHeader(HeaderMessage voiceHeader) { LCMessage lcMessage = voiceHeader.getLCMessage(); - if(lcMessage.isValid()) + if(isValid(lcMessage)) { processLinkControl(lcMessage, false); } @@ -1133,6 +1143,23 @@ private void processLinkControl(LCMessage message, boolean isTerminator) } } break; + case FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER: + if(message instanceof CapacityMaxVoiceChannelUser cmvcu) + { + if(isTerminator) + { + getIdentifierCollection().remove(Role.FROM); + getIdentifierCollection().update(cmvcu.getTalkgroup()); + } + else + { + getIdentifierCollection().update(message.getIdentifiers()); + ServiceOptions serviceOptions = cmvcu.getServiceOptions(); + updateCurrentCall(serviceOptions.isEncrypted() ? DecodeEventType.CALL_GROUP_ENCRYPTED : + DecodeEventType.CALL_GROUP, serviceOptions.toString(), message.getTimestamp()); + } + } + break; case FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER: if(message instanceof CapacityPlusWideAreaVoiceChannelUser) { diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java index 9daaa8707..ee19b0687 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java @@ -66,13 +66,14 @@ public class DMRMessageProcessor implements Listener private VoiceSuperFrameProcessor mSuperFrameProcessor2 = new VoiceSuperFrameProcessor(); private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1); private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2); - private MBCAssembler mMBCAssembler = new MBCAssembler(); + private MBCAssembler mMBCAssembler; private PacketSequenceAssembler mPacketSequenceAssembler; private SLCAssembler mSLCAssembler = new SLCAssembler(); private TalkerAliasAssembler mTalkerAliasAssembler = new TalkerAliasAssembler(); private Listener mMessageListener; private Map mTimeslotFrequencyMap = new TreeMap<>(); private DmrCrcMaskManager mCrcMaskManager = new DmrCrcMaskManager(); + private boolean mIgnoreCrcChecksums; /** * Constructs an instance @@ -80,6 +81,8 @@ public class DMRMessageProcessor implements Listener public DMRMessageProcessor(DecodeConfigDMR config) { mConfigDMR = config; + mIgnoreCrcChecksums = config.getIgnoreCRCChecksums(); + mMBCAssembler = new MBCAssembler(mIgnoreCrcChecksums); for(TimeslotFrequency timeslotFrequency: config.getTimeslotMap()) { @@ -89,6 +92,17 @@ public DMRMessageProcessor(DecodeConfigDMR config) mPacketSequenceAssembler = new PacketSequenceAssembler(); } + /** + * Indicates if the message is valid or if the Ignore CRC Checksums feature is enabled. + * + * @param message to check + * @return true if ignore CRC checksums or if the message is valid. + */ + private boolean isValid(IMessage message) + { + return mIgnoreCrcChecksums || message.isValid(); + } + /** * Primary message processing */ @@ -128,7 +142,7 @@ else if(message instanceof VoiceMessage voiceMessage) mSuperFrameProcessor2.process(voiceMessage); } } - else if(message instanceof DMRBurst dmrBurst && dmrBurst.isValid()) + else if(message instanceof DMRBurst dmrBurst && isValid(dmrBurst)) { if(dmrBurst.getTimeslot() == 1) { @@ -243,14 +257,14 @@ else if((message instanceof IDLEMessage || message instanceof Aloha || message i } //Reset talker alias assembler on Idle or Terminator - if(message.isValid() && (message instanceof IDLEMessage || message instanceof Terminator)) + if(isValid(message) && (message instanceof IDLEMessage || message instanceof Terminator)) { mTalkerAliasAssembler.reset(message.getTimeslot()); } } //Assemble Talker Alias from FLC message fragments (header & blocks 1-3) - if(message instanceof FullLCMessage flc && flc.getOpcode().isTalkerAliasOpcode() && message.isValid()) + if(message instanceof FullLCMessage flc && flc.getOpcode().isTalkerAliasOpcode() && isValid(message)) { dispatch(mTalkerAliasAssembler.process(flc)); } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessageFactory.java index 134860f7a..66e6bfc45 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessageFactory.java @@ -34,6 +34,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTPreamble; import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTSiteState; import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAloha; +import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxGroupVoiceChannelUpdate; import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusCSBKO_60; import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusDataRevertWindowAnnouncement; import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusDataRevertWindowGrant; @@ -277,7 +278,9 @@ public static CSBKMessage create(DMRSyncPattern pattern, CorrectedBinaryMessage case MOTOROLA_CAPPLUS_DATA_WINDOW_GRANT: csbk = new CapacityPlusDataRevertWindowGrant(pattern, message, cach, slotType, timestamp, timeslot); break; - + case MOTOROLA_CAPMAX_GROUP_VOICE_CHANNEL_UPDATE: + csbk = new CapacityMaxGroupVoiceChannelUpdate(pattern, message, cach, slotType, timestamp, timeslot); + break; case MOTOROLA_CONPLUS_CSBKO_10: csbk = new ConnectPlusOTAAnnouncement(pattern, message, cach, slotType, timestamp, timeslot); break; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/Opcode.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/Opcode.java index 5bc57c5fc..bb4f86066 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/Opcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/Opcode.java @@ -77,7 +77,9 @@ public enum Opcode MOTOROLA_CONPLUS_DATA_WINDOW_ANNOUNCEMENT(Vendor.MOTOROLA_CONNECT_PLUS, 28, "ENHANCED DATA REVERT WINDOW ANNOUNCEMENT"), MOTOROLA_CONPLUS_DATA_WINDOW_GRANT(Vendor.MOTOROLA_CONNECT_PLUS, 29, "ENHANCED DATA REVERT WINDOW GRANT"), + MOTOROLA_CAPMAX_GROUP_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, 16, "GROUP CHANNEL USER"), MOTOROLA_CAPMAX_ALOHA(Vendor.MOTOROLA_CAPACITY_PLUS, 25, "ALOHA"), + MOTOROLA_CAPMAX_GROUP_VOICE_CHANNEL_UPDATE(Vendor.MOTOROLA_CAPACITY_PLUS, 33, "TALKGROUP ACTIVE"), MOTOROLA_CAPPLUS_CALL_ALERT(Vendor.MOTOROLA_CAPACITY_PLUS, 31, "CALL ALERT"), MOTOROLA_CAPPLUS_CALL_ALERT_ACK(Vendor.MOTOROLA_CAPACITY_PLUS, 32, "CALL ALERT ACK"), diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxGroupVoiceChannelUpdate.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxGroupVoiceChannelUpdate.java new file mode 100644 index 000000000..a79f9562e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxGroupVoiceChannelUpdate.java @@ -0,0 +1,153 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; +import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel; +import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver; +import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency; +import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; +import io.github.dsheirer.module.decode.dmr.message.CACH; +import io.github.dsheirer.module.decode.dmr.message.data.SlotType; +import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; + +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola Capacity Max - Group Voice Channel Update (Opcode 33 / 0x21) + * + * Likely incomplete implementation. + */ +public class CapacityMaxGroupVoiceChannelUpdate extends CSBKMessage implements ITimeslotFrequencyReceiver +{ + private static final int[] CHANNEL_NUMBER = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}; + private static final int[] TIMESLOT = new int[]{28}; //Logical guess - not seeing it set in the example Tier3 recording + + private static final int[] TALKGROUP = new int[]{64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private TalkgroupIdentifier mTalkgroup; + private DMRTier3Channel mChannel; + private List mIdentifiers; + + /** + * Constructs an instance + * + * @param syncPattern for the CSBK + * @param message bits + * @param cach for the DMR burst + * @param slotType for this message + * @param timestamp + * @param timeslot + */ + public CapacityMaxGroupVoiceChannelUpdate(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot) + { + super(syncPattern, message, cach, slotType, timestamp, timeslot); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(!isValid()) + { + sb.append("[CRC-ERROR] "); + } + + sb.append("CC:").append(getSlotType().getColorCode()); + if(hasRAS()) + { + sb.append(" RAS:").append(getBPTCReservedBits()); + } + + sb.append(" CSBK CAP-MAX GROUP VOICE CHANNEL UPDATE TALKGROUP:").append(getTalkgroup()); + sb.append(" CHANNEL:").append(getChannel()); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } + + /** + * Talkgroup that is active on the channel. + */ + public TalkgroupIdentifier getTalkgroup() + { + if(mTalkgroup == null) + { + mTalkgroup = DMRTalkgroup.create(getMessage().getInt(TALKGROUP)); + } + + return mTalkgroup; + } + + /** + * Channel the talkgroup is using. + * @return channel. + */ + public DMRTier3Channel getChannel() + { + if(mChannel == null) + { + mChannel = new DMRTier3Channel(getMessage().getInt(CHANNEL_NUMBER), getChannelTimeslot()); + } + + return mChannel; + } + + /** + * Timeslot + */ + private int getChannelTimeslot() + { + return getMessage().getInt(TIMESLOT) + 1; + } + + /** + * Assigns a timeslot frequency map for the DMR channel + * + * @param timeslotFrequencies that match the logical timeslots + */ + @Override + public void apply(List timeslotFrequencies) + { + getChannel().apply(timeslotFrequencies); + } + + @Override + public int[] getLogicalChannelNumbers() + { + return getChannel().getLogicalChannelNumbers(); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTalkgroup()); + mIdentifiers.add(getChannel()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupVoiceChannelGrant.java index a52526ba7..a8220da15 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupVoiceChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupVoiceChannelGrant.java @@ -105,6 +105,7 @@ public String toString() sb.append(" TALKGROUP VOICE CHANNEL GRANT FM:").append(getSourceRadio()); sb.append(" TO:").append(getDestinationTalkgroup()); sb.append(" ").append(getChannel()); + sb.append(" MSG:").append(getMessage().toHexString()); return sb.toString(); } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java index 0d885f664..0e98ea91d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java @@ -37,6 +37,7 @@ 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.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.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; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser; @@ -137,6 +138,9 @@ else if(message.size() == 96) case FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER: flc = new MotorolaGroupVoiceChannelUser(message, timestamp, timeslot); break; + case FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER: + flc = new CapacityMaxVoiceChannelUser(message, timestamp, timeslot); + break; case FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER: flc = new CapacityPlusEncryptedVoiceChannelUser(message, timestamp, timeslot); break; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java index 457d6183f..798a3cebd 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java @@ -45,6 +45,7 @@ public enum LCOpcode FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 0, "GROUP VOICE CHANNEL USER"), 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"), //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 diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityMaxVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityMaxVoiceChannelUser.java new file mode 100644 index 000000000..bef25982f --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityMaxVoiceChannelUser.java @@ -0,0 +1,102 @@ +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.radio.RadioIdentifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; +import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; + +import java.util.ArrayList; +import java.util.List; + +/** + * Capacity Max Voice Channel User (FLCO 16 / 0x10) + */ +public class CapacityMaxVoiceChannelUser extends CapacityPlusVoiceChannelUser +{ + private static final int[] TALKGROUP = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] RADIO = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 70, 71}; + + private TalkgroupIdentifier mTalkgroup; + private RadioIdentifier mRadio; + private List mIdentifiers; + + /** + * Constructs an instance. + * + * @param message for the link control payload + * @param timestamp + * @param timeslot + */ + public CapacityMaxVoiceChannelUser(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 GROUP VOICE CHANNEL USER FM:"); + sb.append(getRadio()); + sb.append(" TO:").append(getTalkgroup()); + sb.append(" ").append(getServiceOptions()); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } + + /** + * Talkgroup TO ID + */ + public TalkgroupIdentifier getTalkgroup() + { + if(mTalkgroup == null) + { + mTalkgroup = DMRTalkgroup.create(getMessage().getInt(TALKGROUP)); + } + + return mTalkgroup; + } + + /** + * Radio FROM ID + */ + public RadioIdentifier getRadio() + { + if(mRadio == null) + { + mRadio = DMRRadio.createFrom(getMessage().getInt(RADIO)); + } + + return mRadio; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTalkgroup()); + mIdentifiers.add(getRadio()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/mbc/MBCAssembler.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/mbc/MBCAssembler.java index 4eb1391b7..249b75538 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/mbc/MBCAssembler.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/mbc/MBCAssembler.java @@ -19,6 +19,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.mbc; +import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessageFactory; import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader; @@ -34,21 +35,35 @@ public class MBCAssembler private MBCHeader mTS2Header; private List mTS1ContinuationBlocks = new ArrayList<>(); private List mTS2ContinuationBlocks = new ArrayList<>(); + private boolean mIgnoreCrcChecksums; /** * Constructs an instance */ - public MBCAssembler() + public MBCAssembler(boolean ignoreCrcChecksums) { + mIgnoreCrcChecksums = ignoreCrcChecksums; } + /** + * Indicates if the message is valid or if the Ignore CRC Checksums feature is enabled. + * @param message to check + * @return true if ignore CRC checksums or if the message is valid. + */ + private boolean isValid(IMessage message) + { + return mIgnoreCrcChecksums || message.isValid(); + } + + + /** * Processes the MBC header * @param header to process */ public void process(MBCHeader header) { - if(header != null && header.isValid()) + if(header != null && isValid(header)) { reset(header.getTimeslot());