+ * 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
+ * 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()
{