diff --git a/src/main/java/io/github/dsheirer/edac/CRCUtil.java b/src/main/java/io/github/dsheirer/edac/CRCUtil.java index 4b0b08a2a..be7382df4 100644 --- a/src/main/java/io/github/dsheirer/edac/CRCUtil.java +++ b/src/main/java/io/github/dsheirer/edac/CRCUtil.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -226,17 +226,19 @@ public static void main(String[] args) { mLog.debug("Starting"); - //DMR message -// String raw = "100110010001000000001001000000001111011011010000011001010000000000000000000000001100001000001011"; - String raw = "101010000000000000110110100000101101011011010000011001111100000000000011100101001001111010001110"; - mLog.debug(raw); - raw = "101010000000000000110110100000101101011011010000011001111100000000000011100101000000000000000000"; - BinaryMessage message = BinaryMessage.load(raw); - mLog.debug(message.toString()); - - long polynomial = 0x11021l; - decode(message, 0, 80, polynomial, 16); - mLog.debug(message.toString()); - mLog.debug("Finished"); + long poly = 0x13l; + long[] checksums = generate(32, 4, poly, 0, true); + + StringBuilder sb = new StringBuilder(); + sb.append("private static int[] CHECKSUMS = new int[]{"); + for(long checksum: checksums) + { + sb.append("0x").append(Long.toHexString(checksum).toUpperCase()); + sb.append(","); + } + + sb.append("};"); + + System.out.println("Checksums:\n" + sb); } } diff --git a/src/main/java/io/github/dsheirer/edac/Golay24.java b/src/main/java/io/github/dsheirer/edac/Golay24.java index 44ad8a504..b6735d961 100644 --- a/src/main/java/io/github/dsheirer/edac/Golay24.java +++ b/src/main/java/io/github/dsheirer/edac/Golay24.java @@ -1,3 +1,22 @@ +/* + * ***************************************************************************** + * 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.edac; import io.github.dsheirer.bits.BinaryMessage; @@ -5,27 +24,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014 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 - * ----------------------------------------------------------------------- - * Galois24 decoder based on Hank Wallace's tutorial/algorithm located at: - * http://www.aqdi.com/golay.htm - ******************************************************************************/ - /** * Galois 24/12/7 decoder */ @@ -160,4 +158,17 @@ private static int getSyndrome(BinaryMessage message, int startIndex) return (checksum ^ calculated); } + + public static void main(String[] args) + { +// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F3BB20")); +// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F0C5C0")); + CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("AFAC00")); + + System.out.println("M:" + bm.toHexString()); + int a = Golay24.checkAndCorrect(bm, 0); + System.out.println("M:" + bm.toHexString()); + + System.out.println("A:" + a); + } } 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 0ac7d2271..fcd0bd33e 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 @@ -34,6 +34,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader; import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader; import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader; +import io.github.dsheirer.module.decode.dmr.message.data.header.VoiceHeader; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FLCAssembler; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasAssembler; @@ -43,7 +44,9 @@ import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock; import io.github.dsheirer.module.decode.dmr.message.data.packet.PacketSequenceAssembler; import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator; +import io.github.dsheirer.module.decode.dmr.message.voice.CallEncryptionParametersCollector; import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage; +import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage; import io.github.dsheirer.sample.Listener; import java.util.ArrayList; import java.util.List; @@ -59,6 +62,8 @@ public class DMRMessageProcessor implements Listener { private final static Logger mLog = LoggerFactory.getLogger(DMRMessageProcessor.class); private DecodeConfigDMR mConfigDMR; + private CallEncryptionParametersCollector mEncryptionCollector1 = new CallEncryptionParametersCollector(); + private CallEncryptionParametersCollector mEncryptionCollector2 = new CallEncryptionParametersCollector(); private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1); private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2); private MBCAssembler mMBCAssembler = new MBCAssembler(); @@ -89,11 +94,47 @@ public DMRMessageProcessor(DecodeConfigDMR config) @Override public void receive(IMessage message) { + //Process voice header and voice frame messages to extract encryption parameters when call is encrypted, + //otherwise reset the encryption collectors when the message is valid but not a voice header or + if(message instanceof VoiceHeader voiceHeader) + { + if(voiceHeader.getTimeslot() == 1) + { + mEncryptionCollector1.process(voiceHeader); + } + else + { + mEncryptionCollector2.process(voiceHeader); + } + } + else if(message instanceof VoiceMessage voiceMessage) + { + if(voiceMessage.getTimeslot() == 1) + { + mEncryptionCollector1.process(voiceMessage); + } + else + { + mEncryptionCollector2.process(voiceMessage); + } + } + else if(message instanceof DMRBurst dmrBurst && dmrBurst.isValid()) + { + if(dmrBurst.getTimeslot() == 1) + { +// mEncryptionCollector1.reset(); + } + else + { +// mEncryptionCollector2.reset(); + } + } + //Enrich messages that carry DMR Logical Slot Number channels with LCN to frequency mappings if(message instanceof ITimeslotFrequencyReceiver) { ITimeslotFrequencyReceiver receiver = (ITimeslotFrequencyReceiver)message; - int[] lsns = receiver.getLogicalTimeslotNumbers(); + int[] lsns = receiver.getLogicalSlotNumbers(); List timeslotFrequencies = new ArrayList<>(); diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRAbsoluteChannel.java b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRAbsoluteChannel.java index 671273c19..bf773bf1b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRAbsoluteChannel.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRAbsoluteChannel.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2023 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 - * * ***************************************************************************** + * 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.channel; @@ -39,7 +36,7 @@ public class DMRAbsoluteChannel extends DMRChannel */ public DMRAbsoluteChannel(int lcn, int timeslot, long downlinkFrequency, long uplinkFrequency) { - super(lcn * 2 + timeslot); + super(lcn, timeslot); mDownlinkFrequency = downlinkFrequency; mUplinkFrequency = uplinkFrequency; } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRChannel.java b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRChannel.java index 1ecbabbb3..e4653a2ca 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRChannel.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRChannel.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * 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 @@ -26,21 +26,27 @@ import io.github.dsheirer.identifier.integer.IntegerIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand; import io.github.dsheirer.protocol.Protocol; +import org.apache.commons.lang3.Validate; /** * Base DMR Channel + * + * Note: timeslots are tracked as 1 and 2 */ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDescriptor { + private int mTimeslot; + /** * Constructs an instance - * - * @param lsn number that is a combination of the repeater number and timeslot where the channel number or repeater - * number is left shifted by two and the timeslot is added as either a zero or one value. + * @param channel number or repeater number, one-based repeater number. + * @param timeslot in range: 1 or 2 */ - public DMRChannel(int lsn) + public DMRChannel(int channel, int timeslot) { - super(lsn, IdentifierClass.NETWORK, Form.CHANNEL, Role.BROADCAST); + super(channel, IdentifierClass.NETWORK, Form.CHANNEL, Role.BROADCAST); + Validate.inclusiveBetween(0, 2, timeslot, "Timeslot must be between 1 and 2"); + mTimeslot = timeslot; } @Override @@ -50,24 +56,20 @@ public Protocol getProtocol() } /** - * Timeslot for the channel. Note: timeslot values are normally zero or one, but for this application we use - * timeslot values of 1 and 2 so that we can reserve timeslot 0 as the slow link control (SLC) channel. - * - * @return timeslot as 1-based index with values in range: 1-2. + * Repeater number or channel number for the channel. */ - public int getTimeslot() + public int getChannel() { - return getValue() % 2 + 1; + return getValue(); } /** - * Returns an array of length 1 containing this channel's logical slot number + * Timeslot for the channel. + * @return timeslot as zero-based index with values in range: 0 or 1. */ - public int[] getLSNArray() + public int getTimeslot() { - int[] logicalSlotNumbers = new int[1]; - logicalSlotNumbers[0] = getValue(); - return logicalSlotNumbers; + return mTimeslot; } /** @@ -89,6 +91,13 @@ public boolean isTDMAChannel() return true; } + + @Override + public String toString() + { + return "CHAN:" + getChannel() + ":" + getTimeslot(); + } + /** * Not implemented */ diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRLogicalChannel.java b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRLsn.java similarity index 50% rename from src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRLogicalChannel.java rename to src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRLsn.java index 541fdbb9c..1cb9c5a83 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRLogicalChannel.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRLsn.java @@ -19,26 +19,60 @@ package io.github.dsheirer.module.decode.dmr.channel; +import java.util.List; + /** - * DMR logical channel. This channel uses a logical channel number and a timeslot. + * DMR Logical Slot Number (LSN) channel. + * + * Note: LSN 1-16 are represented in memory using the following channel and timeslot values: + * + * LSN: CHANNEL/TIMESLOT + * 1: 1/1 + * 2: 1/2 + * 3: 2/1 + * 4: 2/2 + * 5: 3/1 + * 6: 3/2 + * 7: 4/1 + * 8: 4/2 + * 9: 5/1 + * 10: 5/2 + * 11: 6/1 + * 12: 6/2 + * 13: 7/1 + * 14: 7/2 + * 15: 8/1 + * 16: 8/2 */ -public class DMRLogicalChannel extends DMRChannel +public class DMRLsn extends DMRChannel implements ITimeslotFrequencyReceiver { private TimeslotFrequency mTimeslotFrequency; /** - * Constructs an instance with a logical slot number. - * @param lsn in range 0-15 + * Constructs an instance + * + * @param lsn in range 1 - 16 */ - public DMRLogicalChannel(int lsn) + public DMRLsn(int lsn) + { + super(((lsn - 1) / 2) + 1, ((lsn - 1) % 2) + 1); + } + + @Override + public String toString() { - super(lsn); + return "LSN:" + getLsn(); } /** - * Downlink frequency - * @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping + * Logical Slot Number + * @return lsn */ + public int getLsn() + { + return ((getChannel() - 1) * 2) + getTimeslot(); + } + @Override public long getDownlinkFrequency() { @@ -50,10 +84,6 @@ public long getDownlinkFrequency() return 0; } - /** - * Uplink frequency - * @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping - */ @Override public long getUplinkFrequency() { @@ -65,12 +95,40 @@ public long getUplinkFrequency() return 0; } + @Override + public int[] getLogicalSlotNumbers() + { + return new int[]{getLsn()}; + } + /** - * Sets the timeslot frequency mapping - * @param timeslotFrequency + * Sets the lsn to frequency mapper value. + * @param timeslotFrequency to set */ public void setTimeslotFrequency(TimeslotFrequency timeslotFrequency) { mTimeslotFrequency = timeslotFrequency; } + + @Override + public void apply(List timeslotFrequencies) + { + for(TimeslotFrequency timeslotFrequency: timeslotFrequencies) + { + if(timeslotFrequency.getNumber() == getLsn()) + { + setTimeslotFrequency(timeslotFrequency); + return; + } + } + } + + public static void main(String[] args) + { + for(int x = 1; x <= 16; x++) + { + DMRLsn lsn = new DmrRestLsn(x); + System.out.println(lsn); + } + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRTier3Channel.java b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRTier3Channel.java index 857ef63e8..3955bc46b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRTier3Channel.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRTier3Channel.java @@ -1,19 +1,89 @@ +/* + * ***************************************************************************** + * 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.channel; /** - * DMR Tier III Trunking Channel + * DMR Tier III Trunking channel. This channel uses a logical channel number and a timeslot. */ -public class DMRTier3Channel extends DMRLogicalChannel +public class DMRTier3Channel extends DMRChannel { + private TimeslotFrequency mTimeslotFrequency; + /** * Constructs an instance. Note: radio reference uses a one based index, so we add a value of one to the * calculated logical slot value for visual compatibility for users. * * @param channel number or repeater number - * @param timeslot as zero or one + * @param logicalSlotNumber - zero based index. */ public DMRTier3Channel(int channel, int timeslot) { - super(channel << 1 + timeslot); + super(channel, timeslot); + } + + /** + * Downlink frequency + * @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping + */ + @Override + public long getDownlinkFrequency() + { + if(mTimeslotFrequency != null) + { + return mTimeslotFrequency.getDownlinkFrequency(); + } + + return 0; + } + + /** + * Uplink frequency + * @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping + */ + @Override + public long getUplinkFrequency() + { + if(mTimeslotFrequency != null) + { + return mTimeslotFrequency.getUplinkFrequency(); + } + + return 0; + } + + /** + * Sets the timeslot frequency mapping + * @param timeslotFrequency + */ + public void setTimeslotFrequency(TimeslotFrequency timeslotFrequency) + { + mTimeslotFrequency = timeslotFrequency; + } + + /** + * Formatted channel number + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(" LCN:").append(getValue()); + return sb.toString(); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DmrRestLsn.java b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DmrRestLsn.java new file mode 100644 index 000000000..c29135a20 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DmrRestLsn.java @@ -0,0 +1,42 @@ +/* + * ***************************************************************************** + * 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.channel; + +/** + * DMR Rest Channel logical slot number (LSN). + */ +public class DmrRestLsn extends DMRLsn +{ + /** + * Constructs an instance + * + * @param lsn in range 1 - 16 + */ + public DmrRestLsn(int lsn) + { + super(lsn); + } + + @Override + public String toString() + { + return "REST:" + getLsn(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/ITimeslotFrequencyReceiver.java b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/ITimeslotFrequencyReceiver.java index a1f5b245d..fffd15418 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/ITimeslotFrequencyReceiver.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/ITimeslotFrequencyReceiver.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * 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 @@ -26,7 +26,7 @@ public interface ITimeslotFrequencyReceiver /** * Provides the logical slot number(s) that require a matching timeslot frequency mapping */ - public int[] getLogicalTimeslotNumbers(); + public int[] getLogicalSlotNumbers(); /** * Applies the list of timeslot frequency mappings to the implementer diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/TimeslotFrequency.java b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/TimeslotFrequency.java index 08adf174d..1a5a4e810 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/TimeslotFrequency.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/TimeslotFrequency.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * 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 @@ -31,7 +31,7 @@ import javafx.util.Callback; /** - * Maps a timeslot number to a pair of channel frequency values + * Maps a logical slot number (LSN) to a pair of channel frequency values */ public class TimeslotFrequency { @@ -73,7 +73,7 @@ public IntegerProperty getNumberProperty() * Downlink Frequency property */ @JsonIgnore - public LongProperty downlinkFrequencyPropertyProperty() + public LongProperty downlinkFrequencyProperty() { return mDownlinkFrequencyProperty; } @@ -168,7 +168,7 @@ public String toString() */ public static Callback extractor() { - return (TimeslotFrequency tf) -> new Observable[] {tf.getNumberProperty(), tf.downlinkFrequencyPropertyProperty(), + return (TimeslotFrequency tf) -> new Observable[] {tf.getNumberProperty(), tf.downlinkFrequencyProperty(), tf.uplinkFrequencyProperty(), tf.getDownlinkMHz(), tf.getUplinkMHz()}; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRBurst.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRBurst.java index 5a8c8f907..06db716dd 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRBurst.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRBurst.java @@ -1,5 +1,25 @@ +/* + * ***************************************************************************** + * 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; +import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; /** @@ -53,4 +73,13 @@ public DMRSyncPattern getSyncPattern() { return mSyncPattern; } + + /** + * Extracts the sync payload from between the two payload fragments. + * @return binary message containing just the sync payload bits (64). + */ + public BinaryMessage getSyncPayload() + { + return getMessage().get(SYNC_START, PAYLOAD_2_START); + } } \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/IServiceOptionsProvider.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/IServiceOptionsProvider.java new file mode 100644 index 000000000..ba2ff3d4a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/IServiceOptionsProvider.java @@ -0,0 +1,34 @@ +/* + * ***************************************************************************** + * 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; + +import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions; + +/** + * DMR message that exposes a service options configuration + */ +public interface IServiceOptionsProvider +{ + /** + * Service Options + * @return service options + */ + ServiceOptions getServiceOptions(); +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusNeighbors.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusNeighbors.java index 7dde6f54b..409d10bad 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusNeighbors.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusNeighbors.java @@ -22,7 +22,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; -import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel; +import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn; 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.DMRSite; @@ -57,7 +57,7 @@ public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequ private static final int[] NEIGHBOR_6_REST = new int[]{76, 77, 78, 79}; - private DMRLogicalChannel mRestChannel; + private DmrRestLsn mRestChannel; private DMRSite mSite; private List mIdentifiers; @@ -288,20 +288,20 @@ public int getRestLSN() /** * DMR Channel */ - public DMRLogicalChannel getRestChannel() + public DmrRestLsn getRestChannel() { if(mRestChannel == null) { - mRestChannel = new DMRLogicalChannel(getRestLSN()); + mRestChannel = new DmrRestLsn(getRestLSN()); } return mRestChannel; } @Override - public int[] getLogicalTimeslotNumbers() + public int[] getLogicalSlotNumbers() { - return getRestChannel().getLSNArray(); + return getRestChannel().getLogicalSlotNumbers(); } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java index a406f03e5..d42a49543 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java @@ -22,7 +22,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; -import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel; +import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn; 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.message.CACH; @@ -43,7 +43,7 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq private static final int[] LSN_VOICE_BITMAP = new int[]{24, 25, 26, 27, 28, 29, 30, 31}; private static final int LSN_1_8_BITMAP_START = 24; - private DMRLogicalChannel mRestChannel; + private DmrRestLsn mRestChannel; private List mIdentifiers; /** @@ -78,7 +78,7 @@ public String toString() sb.append(" RAS:").append(getBPTCReservedBits()); } - sb.append(" CSBK CAP+ SITE STATUS REST:").append(getRestChannel()); + sb.append(" CSBK CAP+ SITE STATUS ").append(getRestChannel()); sb.append(" ").append(getSegmentIndicator()); sb.append(" "); @@ -275,11 +275,11 @@ public SegmentIndicator getSegmentIndicator() /** * Current rest channel for this site. */ - public DMRLogicalChannel getRestChannel() + public DmrRestLsn getRestChannel() { if(mRestChannel == null) { - mRestChannel = new DMRLogicalChannel(getRestLSN()); + mRestChannel = new DmrRestLsn(getRestLSN()); } return mRestChannel; @@ -299,9 +299,9 @@ public int getRestLSN() * Logical slot numbers that require slot to frequency mappings. */ @Override - public int[] getLogicalTimeslotNumbers() + public int[] getLogicalSlotNumbers() { - return getRestChannel().getLSNArray(); + return getRestChannel().getLogicalSlotNumbers(); } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataChannelGrant.java index dcb120d0f..b2e56521d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataChannelGrant.java @@ -23,7 +23,7 @@ import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; -import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel; +import io.github.dsheirer.module.decode.dmr.channel.DMRLsn; 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.DMRRadio; @@ -46,7 +46,7 @@ public class ConnectPlusDataChannelGrant extends CSBKMessage implements ITimeslo private static final int[] UNKNOWN_FIELD = new int[]{48, 49, 50, 51, 52, 53, 54, 55}; private RadioIdentifier mTargetRadio; - private DMRLogicalChannel mDMRLogicalChannel; + private DMRLsn mDmrLsn; private List mIdentifiers; /** @@ -119,20 +119,20 @@ public int getRepeater() /** * DMR Channel */ - public DMRLogicalChannel getChannel() + public DMRLsn getChannel() { - if(mDMRLogicalChannel == null) + if(mDmrLsn == null) { - mDMRLogicalChannel = new DMRLogicalChannel(getRepeater()); + mDmrLsn = new DMRLsn(getRepeater()); } - return mDMRLogicalChannel; + return mDmrLsn; } @Override - public int[] getLogicalTimeslotNumbers() + public int[] getLogicalSlotNumbers() { - return getChannel().getLSNArray(); + return getChannel().getLogicalSlotNumbers(); } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusOTAAnnouncement.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusOTAAnnouncement.java index 5e8049e87..a886be8e0 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusOTAAnnouncement.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusOTAAnnouncement.java @@ -22,7 +22,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; -import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel; +import io.github.dsheirer.module.decode.dmr.channel.DMRLsn; 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.message.CACH; @@ -47,7 +47,7 @@ public class ConnectPlusOTAAnnouncement extends CSBKMessage implements ITimeslot 57, 58, 59, 60, 61, 62}; private static final int[] DATA_REPEATER = new int[]{63, 64, 65, 66, 67}; - private DMRLogicalChannel mDataChannel; + private DMRLsn mDataChannel; private List mIdentifiers; /** @@ -109,20 +109,20 @@ public int getDataRepeater() /** * DMR Channel where the data is available */ - public DMRLogicalChannel getDataChannel() + public DMRLsn getDataChannel() { if(mDataChannel == null) { - mDataChannel = new DMRLogicalChannel(getDataRepeater()); + mDataChannel = new DMRLsn(getDataRepeater()); } return mDataChannel; } @Override - public int[] getLogicalTimeslotNumbers() + public int[] getLogicalSlotNumbers() { - return getDataChannel().getLSNArray(); + return getDataChannel().getLogicalSlotNumbers(); } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusVoiceChannelUser.java index 2823ef222..ea6741639 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusVoiceChannelUser.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusVoiceChannelUser.java @@ -24,7 +24,7 @@ import io.github.dsheirer.identifier.radio.RadioIdentifier; import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; -import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel; +import io.github.dsheirer.module.decode.dmr.channel.DMRLsn; 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.DMRRadio; @@ -49,7 +49,7 @@ public class ConnectPlusVoiceChannelUser extends CSBKMessage implements ITimeslo private RadioIdentifier mRadio; private TalkgroupIdentifier mTalkgroup; - private DMRLogicalChannel mDMRLogicalChannel; + private DMRLsn mDmrLsn; private List mIdentifiers; /** @@ -134,20 +134,20 @@ public int getTrafficChannelRepeater() /** * DMR Channel */ - public DMRLogicalChannel getChannel() + public DMRLsn getChannel() { - if(mDMRLogicalChannel == null) + if(mDmrLsn == null) { - mDMRLogicalChannel = new DMRLogicalChannel(getTrafficChannelRepeater()); + mDmrLsn = new DMRLsn(getTrafficChannelRepeater()); } - return mDMRLogicalChannel; + return mDmrLsn; } @Override - public int[] getLogicalTimeslotNumbers() + public int[] getLogicalSlotNumbers() { - return getChannel().getLSNArray(); + return getChannel().getLogicalSlotNumbers(); } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Clear.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Clear.java index f4c05d06e..d99632c14 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Clear.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Clear.java @@ -25,7 +25,7 @@ import io.github.dsheirer.identifier.radio.RadioIdentifier; import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; -import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel; +import io.github.dsheirer.module.decode.dmr.channel.DMRLsn; 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; @@ -224,11 +224,11 @@ public DMRChannel getMoveToChannel() * Logical Slot Number(s) for channels contained in this message */ @Override - public int[] getLogicalTimeslotNumbers() + public int[] getLogicalSlotNumbers() { - if(getMoveToChannel() instanceof DMRLogicalChannel) + if(getMoveToChannel() instanceof DMRLsn dmrLsn) { - return ((DMRLogicalChannel)getMoveToChannel()).getLSNArray(); + return dmrLsn.getLogicalSlotNumbers(); } return new int[0]; @@ -241,9 +241,9 @@ public int[] getLogicalTimeslotNumbers() @Override public void apply(List timeslotFrequencies) { - if(getMoveToChannel() instanceof DMRLogicalChannel) + if(getMoveToChannel() instanceof DMRTier3Channel) { - DMRLogicalChannel channel = (DMRLogicalChannel)getMoveToChannel(); + DMRTier3Channel channel = (DMRTier3Channel)getMoveToChannel(); for(TimeslotFrequency timeslotFrequency: timeslotFrequencies) { diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AdjacentSiteInformation.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AdjacentSiteInformation.java index ae9733f0c..73121bdec 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AdjacentSiteInformation.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AdjacentSiteInformation.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * 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 @@ -23,24 +23,20 @@ import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; -import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel; 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.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock; import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters; import io.github.dsheirer.module.decode.dmr.message.type.DataType; import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode; - import java.util.ArrayList; import java.util.List; /** * DMR Tier III - Announcement Message - Adjacent/Neighbor Site Information */ -public class AdjacentSiteInformation extends Announcement implements ITimeslotFrequencyReceiver +public class AdjacentSiteInformation extends Announcement { private static final int NEIGHBOR_SYSTEM_IDENTITY_CODE_OFFSET = 21; private static final int NETWORK_CONNECTION_STATUS_AVAILABLE_FLAG = 56; @@ -235,39 +231,4 @@ public List getIdentifiers() return mIdentifiers; } - - /** - * Logical Slot Number(s) for channels contained in this message - */ - @Override - public int[] getLogicalTimeslotNumbers() - { - if(getNeighborChannel() instanceof DMRLogicalChannel) - { - return ((DMRLogicalChannel)getNeighborChannel()).getLSNArray(); - } - - return new int[0]; - } - - /** - * Applies the timeslot frequency lookup information to channels contained in this message - * @param timeslotFrequencies that match the logical timeslots - */ - @Override - public void apply(List timeslotFrequencies) - { - if(getNeighborChannel() instanceof DMRLogicalChannel) - { - DMRLogicalChannel channel = (DMRLogicalChannel)getNeighborChannel(); - - for(TimeslotFrequency timeslotFrequency: timeslotFrequencies) - { - if(channel.getValue() == timeslotFrequency.getNumber()) - { - channel.setTimeslotFrequency(timeslotFrequency); - } - } - } - } } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceWithdrawTSCC.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceWithdrawTSCC.java index 4dd2ca297..b1674fde3 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceWithdrawTSCC.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceWithdrawTSCC.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * 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 @@ -29,7 +29,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock; import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters; import io.github.dsheirer.module.decode.dmr.message.type.DataType; - import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/VoteNowAdvice.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/VoteNowAdvice.java index a8b541297..c0c5c6dab 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/VoteNowAdvice.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/VoteNowAdvice.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * 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 @@ -23,7 +23,7 @@ import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; -import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel; +import io.github.dsheirer.module.decode.dmr.channel.DMRLsn; 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; @@ -33,7 +33,6 @@ import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters; import io.github.dsheirer.module.decode.dmr.message.type.DataType; import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode; - import java.util.ArrayList; import java.util.List; @@ -240,11 +239,11 @@ public List getIdentifiers() * Logical Slot Number(s) for channels contained in this message */ @Override - public int[] getLogicalTimeslotNumbers() + public int[] getLogicalSlotNumbers() { - if(getChannel() instanceof DMRLogicalChannel) + if(getChannel() instanceof DMRLsn dmrLsn) { - return ((DMRLogicalChannel)getChannel()).getLSNArray(); + return dmrLsn.getLogicalSlotNumbers(); } return new int[0]; @@ -257,9 +256,9 @@ public int[] getLogicalTimeslotNumbers() @Override public void apply(List timeslotFrequencies) { - if(getChannel() instanceof DMRLogicalChannel) + if(getChannel() instanceof DMRTier3Channel) { - DMRLogicalChannel channel = (DMRLogicalChannel)getChannel(); + DMRTier3Channel channel = (DMRTier3Channel)getChannel(); for(TimeslotFrequency timeslotFrequency: timeslotFrequencies) { diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/ChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/ChannelGrant.java index 8ec61ee64..6229430e4 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/ChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/ChannelGrant.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * 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 @@ -22,7 +22,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; -import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel; +import io.github.dsheirer.module.decode.dmr.channel.DMRLsn; 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; @@ -32,7 +32,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock; import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters; import io.github.dsheirer.module.decode.dmr.message.type.DataType; - import java.util.List; /** @@ -159,11 +158,11 @@ public DMRChannel getChannel() * Logical Slot Number(s) for channels contained in this message */ @Override - public int[] getLogicalTimeslotNumbers() + public int[] getLogicalSlotNumbers() { - if(getChannel() instanceof DMRLogicalChannel) + if(getChannel() instanceof DMRLsn dmrLsn) { - return ((DMRLogicalChannel)getChannel()).getLSNArray(); + return dmrLsn.getLogicalSlotNumbers(); } return new int[0]; @@ -176,9 +175,9 @@ public int[] getLogicalTimeslotNumbers() @Override public void apply(List timeslotFrequencies) { - if(getChannel() instanceof DMRLogicalChannel) + if(getChannel() instanceof DMRTier3Channel) { - DMRLogicalChannel channel = (DMRLogicalChannel)getChannel(); + DMRTier3Channel channel = (DMRTier3Channel)getChannel(); for(TimeslotFrequency timeslotFrequency: timeslotFrequencies) { diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/AbstractVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/AbstractVoiceChannelUser.java index 635bd5310..b4d78c962 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/AbstractVoiceChannelUser.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/AbstractVoiceChannelUser.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * 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 @@ -20,6 +20,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.full; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.module.decode.dmr.message.IServiceOptionsProvider; import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions; /** @@ -27,7 +28,7 @@ * * ETSI TS 102 361-2 7.1.1.2 */ -public abstract class AbstractVoiceChannelUser extends FullLCMessage +public abstract class AbstractVoiceChannelUser extends FullLCMessage implements IServiceOptionsProvider { private static final int[] SERVICE_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23}; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraFullLC.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraFullLC.java index 8d40334d3..2c7936543 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraFullLC.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraFullLC.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * 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 @@ -22,13 +22,14 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.radio.RadioIdentifier; import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; +import io.github.dsheirer.module.decode.dmr.message.IServiceOptionsProvider; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage; import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions; /** * Hytera Full Link Control */ -public abstract class HyteraFullLC extends FullLCMessage +public abstract class HyteraFullLC extends FullLCMessage implements IServiceOptionsProvider { private static final int[] SERVICE_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23}; private static final int[] FREE_REPEATER = new int[]{24, 25, 26, 27}; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusVoiceChannelUser.java index 9c80a9e89..d49ba208b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusVoiceChannelUser.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusVoiceChannelUser.java @@ -20,13 +20,14 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.module.decode.dmr.message.IServiceOptionsProvider; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage; import io.github.dsheirer.module.decode.dmr.message.type.CapacityPlusServiceOptions; /** * Any Capacity Plus Voice Channel User link control message that contains vendor-specific service options. */ -public abstract class CapacityPlusVoiceChannelUser extends FullLCMessage +public abstract class CapacityPlusVoiceChannelUser extends FullLCMessage implements IServiceOptionsProvider { private static final int[] SERVICE_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23}; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusWideAreaVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusWideAreaVoiceChannelUser.java index fdc7dbd7b..3e86157ed 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusWideAreaVoiceChannelUser.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusWideAreaVoiceChannelUser.java @@ -23,7 +23,7 @@ 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.channel.DMRLogicalChannel; +import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn; 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.DMRRadio; @@ -41,15 +41,15 @@ public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann //Bits 16-23: Service Options private static final int[] UNKNOWN_1 = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; private static final int[] GROUP_ID = new int[]{40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] UNUSED = new int[]{48, 49, 50, 51}; - private static final int[] REST_LSN = new int[]{52, 53, 54, 55}; + private static final int[] UNUSED = new int[]{48, 49, 50}; + private static final int[] REST_LSN = new int[]{51, 52, 53, 54, 55}; private static final int[] RADIO_ID = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75}; private static final int[] UNKNOWN_2 = new int[]{76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95}; private RadioIdentifier mRadio; private TalkgroupIdentifier mTalkgroup; - private DMRLogicalChannel mRestChannel; + private DmrRestLsn mRestChannel; private List mIdentifiers; /** @@ -85,14 +85,9 @@ public String toString() sb.append("FLC MOTOROLA CAP+ WIDE-AREA VOICE CHANNEL USER FM:"); sb.append(getRadio()); sb.append(" TO:").append(getTalkgroup()); - sb.append(" REST:"); if(hasRestChannel()) { - sb.append(getRestChannel()); - } - else - { - sb.append("--"); + sb.append(" ").append(getRestChannel()); } sb.append(" ").append(getServiceOptions()); sb.append(" UNK1:").append(getUnknown1()); @@ -120,11 +115,11 @@ public String getUnknown2() /** * Logical channel number (ie repeater number). */ - public DMRLogicalChannel getRestChannel() + public DmrRestLsn getRestChannel() { if(mRestChannel == null) { - mRestChannel = new DMRLogicalChannel(getRestLSN()); + mRestChannel = new DmrRestLsn(getRestLSN()); } return mRestChannel; @@ -191,9 +186,9 @@ public List getIdentifiers() * Exposes the rest channel logical slot number so that a LSN to frequency map can be applied to this message. */ @Override - public int[] getLogicalTimeslotNumbers() + public int[] getLogicalSlotNumbers() { - return getRestChannel().getLSNArray(); + return getRestChannel().getLogicalSlotNumbers(); } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/CapacityPlusRestChannel.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/CapacityPlusRestChannel.java index 6fb055de3..b3e3e893b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/CapacityPlusRestChannel.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/CapacityPlusRestChannel.java @@ -21,7 +21,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel; +import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn; 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.DMRSite; @@ -38,7 +38,7 @@ public class CapacityPlusRestChannel extends ShortLCMessage implements ITimeslot private static final int[] SITE = new int[]{20, 21, 22, 23, 24}; private static final int[] UNKNOWN_2 = new int[]{25, 26, 27}; - private DMRLogicalChannel mRestChannel; + private DmrRestLsn mRestChannel; private DMRSite mSite; private List mIdentifiers; @@ -61,7 +61,7 @@ public String toString() sb.append("[CRC ERROR] "); } sb.append("SLC MOTOROLA CAP+ SITE:").append(getSite()); - sb.append(" REST:").append(getRestChannel()); + sb.append(" ").append(getRestChannel()); sb.append(" MSG:").append(getMessage().toHexString()); return sb.toString(); } @@ -82,11 +82,11 @@ public DMRSite getSite() /** * Rest Channel Number */ - public DMRLogicalChannel getRestChannel() + public DmrRestLsn getRestChannel() { if(mRestChannel == null) { - mRestChannel = new DMRLogicalChannel(getRestLSN()); + mRestChannel = new DmrRestLsn(getRestLSN()); } return mRestChannel; @@ -105,9 +105,9 @@ public int getRestLSN() * Exposes the rest channel logical slot number so that a LSN to frequency map can be applied to this message. */ @Override - public int[] getLogicalTimeslotNumbers() + public int[] getLogicalSlotNumbers() { - return getRestChannel().getLSNArray(); + return getRestChannel().getLogicalSlotNumbers(); } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/CallEncryptionParametersCollector.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/CallEncryptionParametersCollector.java new file mode 100644 index 000000000..86ac4f6f9 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/CallEncryptionParametersCollector.java @@ -0,0 +1,252 @@ +/* + * ***************************************************************************** + * 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.voice; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.Golay24; +import io.github.dsheirer.module.decode.dmr.message.IServiceOptionsProvider; +import io.github.dsheirer.module.decode.dmr.message.data.header.VoiceHeader; + +/** + * Monitors audio call voice frame messaging to detect encrypted audio calls and extract the encryption parameters + * that are embedded into the six voice frames that comprise a voice super-frame. + *

+ * See patent: https://patents.google.com/patent/EP2347540B1/en + */ +public class CallEncryptionParametersCollector +{ + private static final int[] IV = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] CRC4 = new int[]{56, 57, 58, 59}; + private boolean mEncrypted = false; + private int mAlgorithm; + private int mKey; + private byte[] mFragmentA; + private byte[] mFragmentB; + private byte[] mFragmentC; + private byte[] mFragmentD; + private byte[] mFragmentE; + + /** + * Constructor. + */ + public CallEncryptionParametersCollector() + { + } + + /** + * Indicates if this collector is in collecting mode. + * + * @return true if collecting. + */ + public boolean isCollecting() + { + return mEncrypted; + } + + /** + * Fully resets this collector + */ + public void reset() + { + mEncrypted = false; + softReset(); + } + + private void softReset() + { + mAlgorithm = 0; + mKey = 0; + mFragmentA = null; + mFragmentB = null; + mFragmentC = null; + mFragmentD = null; + mFragmentE = null; + } + + /** + * Indicates if the IV fragments from voice frames A-E have been collected. + * + * @return true if the fragments are non-null. + */ + private boolean isComplete() + { + return mFragmentA != null && mFragmentB != null && mFragmentC != null && mFragmentD != null && mFragmentE != null; + } + + /** + * Processes a voice header to determine if this is an encrypted call. + * + * @param voiceHeader containing an FLC message with service options. + */ + public void process(VoiceHeader voiceHeader) + { + reset(); + + if(voiceHeader.getLCMessage() instanceof IServiceOptionsProvider provider && provider.getServiceOptions().isEncrypted()) + { + mEncrypted = true; + } + } + + /** + * Processes the voice frame to extract the IV fragments and the encryption parameters. + * + * @param voiceMessage to process. + */ + public void process(VoiceMessage voiceMessage) + { + if(mEncrypted) + { + switch(voiceMessage.getSyncPattern()) + { + case BASE_STATION_VOICE: + mFragmentA = voiceMessage.getIvFragments(); + break; + case BS_VOICE_FRAME_B: + mFragmentB = voiceMessage.getIvFragments(); + break; + case BS_VOICE_FRAME_C: + mFragmentC = voiceMessage.getIvFragments(); + break; + case BS_VOICE_FRAME_D: + mFragmentD = voiceMessage.getIvFragments(); + break; + case BS_VOICE_FRAME_E: + mFragmentE = voiceMessage.getIvFragments(); + break; + case BS_VOICE_FRAME_F: + if(isComplete() && voiceMessage instanceof VoiceEMBMessage voiceFrame6) + { + String iv = extractIV(voiceMessage.getIvFragments()); + + if(iv != null) + { + BinaryMessage parameters = voiceMessage.getSyncPayload(); + voiceFrame6.setEncryptionParameters(extractParameters(parameters, iv)); + } + } + softReset(); + break; + } + } + } + + /** + * Extracts the algorithm and key ID from the parameters bitset and combines them to return a parameters object. + * + * @param parameters to extract + * @param iv that was previously extracted + * @return encryption parameters instance or null if the error correction/detection fails. + */ + private EncryptionParameters extractParameters(BinaryMessage parameters, String iv) + { + //TODO: extract algorithm and key ID from parameters + return new EncryptionParameters(0, 0, iv); + } + + /** + * Extracts the initialization vector from the voice frame fragments, performs error detection and correction and + * if the error correction passes, returns the hex string version of the 32-bit IV. + * + * @param mFragmentF voice frame 6 IV fragments. + * @return 32-bit IV as hex string or null if the extraction process couldn't extract/correct the IV. + */ + private String extractIV(byte[] mFragmentF) + { + CorrectedBinaryMessage iv = new CorrectedBinaryMessage(72); + iv.setByte(0, combine(mFragmentA[0], mFragmentB[0])); + iv.setByte(8, combine(mFragmentC[0], mFragmentD[0])); + iv.setByte(16, combine(mFragmentE[0], mFragmentF[0])); + iv.setByte(24, combine(mFragmentA[1], mFragmentB[1])); + iv.setByte(32, combine(mFragmentC[1], mFragmentD[1])); + iv.setByte(40, combine(mFragmentE[1], mFragmentF[1])); + iv.setByte(48, combine(mFragmentA[2], mFragmentB[2])); + iv.setByte(56, combine(mFragmentC[2], mFragmentD[2])); + iv.setByte(64, combine(mFragmentE[2], mFragmentF[2])); + + int check1 = Golay24.checkAndCorrect(iv, 0); + int check2 = Golay24.checkAndCorrect(iv, 24); + int check3 = Golay24.checkAndCorrect(iv, 48); + +// if(Golay24.checkAndCorrect(iv, 0) == 2) +// { +// return null; +// } +// +// if(Golay24.checkAndCorrect(iv, 24) == 2) +// { +// return null; +// } +// +// if(Golay24.checkAndCorrect(iv, 48) == 2) +// { +// return null; +// } + + int value = iv.getInt(IV); + int crc = iv.getInt(CRC4); + + boolean passes = crc4(value, crc); +// if(crc4(value, crc)) +// { + return String.format("%04X", value) + " (" + check1 + "/" + check2 + "/" + check3 + "/" + iv.getCorrectedBitCount() + "/" + passes + ")"; +// } +// return null; + } + + /** + * Combines the low nibble from the hi byte with the low nibble from the lo byte + * + * @param hi byte containing a low order nibble + * @param lo byte containing a low order nibble + * @return low order nibbles from hi and lo bytes combined. + */ + private byte combine(byte hi, byte lo) + { + return (byte) (((hi & 0xF) << 4) | (lo & 0xF)); + } + + /** + * Calculates a CRC value from the polynomial: x4 + x1 + 1 (0x13) + * @param value to calculate CRC from + * @param crc to compare + * @return true of the calculated CRC from the value matches the crc argument value. + */ + private static boolean crc4(int value, int crc) + { + long checksum = (value & 0x0FFFFFFFFl) << 4; + long polynomial = 0x013l << 31; + long checkBit = 0x1l << 35; + + for(int x = 31; x >= 0; x--) + { + if((checksum & checkBit) == checkBit) + { + checksum ^= polynomial; + } + polynomial >>= 1; + checkBit >>= 1; + } + + return (int)checksum == crc; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/EncryptionParameters.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/EncryptionParameters.java new file mode 100644 index 000000000..698165394 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/EncryptionParameters.java @@ -0,0 +1,35 @@ +/* + * ***************************************************************************** + * 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.voice; + +/** + * Encryption parameters. + * @param algorithm identifier + * @param key identifier + * @param iv initialization vector + */ +public record EncryptionParameters(int algorithm, int key, String iv) +{ + @Override + public String toString() + { + return "ALGORITHM:" + algorithm() + " KEY:" + key() + " IV:" + iv(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceAMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceAMessage.java index 9a6fbdb6a..03f09c1eb 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceAMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceAMessage.java @@ -23,6 +23,7 @@ import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; +import io.github.dsheirer.util.ByteUtil; import java.util.Collections; import java.util.List; @@ -57,6 +58,8 @@ public String toString() sb.append("CC:- ").append(getSyncPattern()); } + sb.append(" FRAGS:").append(ByteUtil.toHexString(getIvFragments())); + return sb.toString(); } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java index aebe3c07b..cdec0d90d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -23,6 +23,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; +import io.github.dsheirer.util.ByteUtil; /** * DMR Voice Frames B - F @@ -35,6 +36,7 @@ public class VoiceEMBMessage extends VoiceMessage private static final int PAYLOAD_END = 172; private EMB mEMB; + private EncryptionParameters mEncryptionParameters; /** * DMR message frame. This message is comprised of a 24-bit prefix and a 264-bit message frame. Outbound base @@ -61,11 +63,22 @@ public String toString() sb.append(getSyncPattern().toString()); - if(getEMB().isValid() && getEMB().isEncrypted()) + sb.append(" FRAGS:").append(ByteUtil.toHexString(getIvFragments())); + + if(hasEncryptionParameters()) + { + sb.append(" ENCRYPTION ").append(getEncryptionParameters()); + } + else if(getEMB().isValid() && getEMB().isEncrypted()) { sb.append(" ENCRYPTED"); } + if(getSyncPattern() == DMRSyncPattern.BS_VOICE_FRAME_F) + { + sb.append(" MSG:").append(getMessage().toHexString()); + } + return sb.toString(); } @@ -97,4 +110,34 @@ public BinaryMessage getFLCFragment() { return getMessage().getSubMessage(PAYLOAD_START, PAYLOAD_END); } + + /** + * Optional encryption parameters for this voice super-frame. + * @return encryption parameters or null. + */ + public EncryptionParameters getEncryptionParameters() + { + return mEncryptionParameters; + } + + /** + * Sets the encryption parameters for this voice message that apply to the entire voice super-frame. + * + * These parameters are normally extracted by an external process and applied to voice frame F and the parameters + * apply to the entire voice super-frame. + * @param encryptionParameters to set + */ + public void setEncryptionParameters(EncryptionParameters encryptionParameters) + { + mEncryptionParameters = encryptionParameters; + } + + /** + * Indicates if this voice message contains encryption parameters. + * @return true if it contains. + */ + public boolean hasEncryptionParameters() + { + return mEncryptionParameters != null; + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceMessage.java index 1107ff73b..fee98ca19 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceMessage.java @@ -1,3 +1,22 @@ +/* + * ***************************************************************************** + * 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.voice; import io.github.dsheirer.bits.CorrectedBinaryMessage; @@ -5,7 +24,6 @@ import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.DMRBurst; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -15,6 +33,14 @@ */ public abstract class VoiceMessage extends DMRBurst { + /** + * Initialization vector (IV) fragments can be embedded into the AMBE voice frame for late entry to encrypted calls + * using bits 71, 67, 63, and 59 from the interleaved (ie transmitted order) voice frame. + */ + public static int[] FRAME_1_IV_FRAGMENT = new int[]{95, 91, 87, 83}; //Offset 24 + public static int[] FRAME_2_IV_FRAGMENT = new int[]{215, 211, 207, 203}; //Offset 184 - 40 = 144 + public static int[] FRAME_3_IV_FRAGMENT = new int[]{287, 283, 279, 275}; //Offset 216 + /** * DMR message frame. This message is comprised of a 24-bit prefix and a 264-bit message frame. Outbound base * station frames transmit a Common Announcement Channel (CACH) in the 24-bit prefix, whereas Mobile inbound frames @@ -65,6 +91,24 @@ public List getAMBEFrames() return frames; } + /** + * Extracts the four low-order bits from the deinterleaved C3 vector of each of the three AMBE frames that carry + * fragments of the initialization vector (IV) for encrypted calls. Since we don't have access to the deinterleaved + * voice frame here, we perform delinterleaving of the 4-bit nibble using the FRAME_x_IV_FRAGMENT constants. + *

+ * See patent: https://patents.google.com/patent/EP2347540B1/en + * + * @return a three-byte array with the low-order four bits from each frame's C3 vector stored in the low nibble. + */ + public byte[] getIvFragments() + { + byte[] fragments = new byte[3]; + fragments[0] = (byte)getMessage().getInt(FRAME_1_IV_FRAGMENT); + fragments[1] = (byte)getMessage().getInt(FRAME_2_IV_FRAGMENT); + fragments[2] = (byte)getMessage().getInt(FRAME_3_IV_FRAGMENT); + return fragments; + } + @Override public boolean isValid() {