/* * ***************************************************************************** * 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; import io.github.dsheirer.bits.CorrectedBinaryMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * DMR CRC check/correction methods */ public class CRCDMR { private final static Logger mLog = LoggerFactory.getLogger(CRCDMR.class); /** * CRC-CCITT 16-bit checksums for a message length of 80 bits plus 16 * additional checksums representing CRC checksum bit errors * * Generated by: * CRCUtil.generate( 80, 16, 0x11021l, 0xFFFFl, true ); */ public static final int[] CCITT_80_CHECKSUMS = new int[] { 0xE434, 0x721A, 0x390D, 0x9496, 0x4A4B, 0xAD35, 0xDE8A, 0x6F45, 0xBFB2, 0x5FD9, 0xA7FC, 0x53FE, 0x29FF, 0x9CEF, 0xC667, 0xEB23, 0xFD81, 0xF6D0, 0x7B68, 0x3DB4, 0x1EDA, 0xF6D, 0x8FA6, 0x47D3, 0xABF9, 0xDDEC, 0x6EF6, 0x377B, 0x93AD, 0xC1C6, 0x60E3, 0xB861, 0xD420, 0x6A10, 0x3508, 0x1A84, 0xD42, 0x6A1, 0x8B40, 0x45A0, 0x22D0, 0x1168, 0x8B4, 0x45A, 0x22D, 0x8906, 0x4483, 0xAA51, 0xDD38, 0x6E9C, 0x374E, 0x1BA7, 0x85C3, 0xCAF1, 0xED68, 0x76B4, 0x3B5A, 0x1DAD, 0x86C6, 0x4363, 0xA9A1, 0xDCC0, 0x6E60, 0x3730, 0x1B98, 0xDCC, 0x6E6, 0x373, 0x89A9, 0xCCC4, 0x6662, 0x3331, 0x9188, 0x48C4, 0x2462, 0x1231, 0x8108, 0x4084, 0x2042, 0x1021, 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000, }; /** * Confirmed Packet Data Unit CRC-9 checksums, generated by: * * long[] table = CRCUtil.generate( 135, 9, 0x259l, 0x1FF, false, Parity.NONE ); */ public static final int[] CRC9_CHECKSUMS = new int[] { 0x1E7, 0x1F3, 0x1F9, 0x1FC, 0x0D2, 0x045, 0x122, 0x0BD, 0x15E, 0x083, 0x141, 0x1A0, 0x0FC, 0x052, 0x005, 0x102, 0x0AD, 0x156, 0x087, 0x143, 0x1A1, 0x1D0, 0x0C4, 0x04E, 0x00B, 0x105, 0x182, 0x0ED, 0x176, 0x097, 0x14B, 0x1A5, 0x1D2, 0x0C5, 0x162, 0x09D, 0x14E, 0x08B, 0x145, 0x1A2, 0x0FD, 0x17E, 0x093, 0x149, 0x1A4, 0x0FE, 0x053, 0x129, 0x194, 0x0E6, 0x05F, 0x12F, 0x197, 0x1CB, 0x1E5, 0x1F2, 0x0D5, 0x16A, 0x099, 0x14C, 0x08A, 0x069, 0x134, 0x0B6, 0x077, 0x13B, 0x19D, 0x1CE, 0x0CB, 0x165, 0x1B2, 0x0F5, 0x17A, 0x091, 0x148, 0x088, 0x068, 0x018, 0x020, 0x03C, 0x032, 0x035, 0x11A, 0x0A1, 0x150, 0x084, 0x06E, 0x01B, 0x10D, 0x186, 0x0EF, 0x177, 0x1BB, 0x1DD, 0x1EE, 0x0DB, 0x16D, 0x1B6, 0x0F7, 0x17B, 0x1BD, 0x1DE, 0x0C3, 0x161, 0x1B0, 0x0F4, 0x056, 0x007, 0x103, 0x181, 0x1C0, 0x0CC, 0x04A, 0x009, 0x104, 0x0AE, 0x07B, 0x13D, 0x19E, 0x0E3, 0x171, 0x1B8, 0x0F0, 0x054, 0x006, 0x02F, 0x117, 0x18B, 0x1C5, 0x1E2, 0x0DD, 0x16E, 0x09B, 0x14D, 0x1A6 }; /** * Error detection and correction of single-bit errors for CCITT 16-bit CRC protected 80-bit messages. */ public static BinaryMessage correctCCITT80(BinaryMessage message, int messageStart, int crcStart) { int calculated = 0; //Starting value /* Iterate the set bits and XOR running checksum with lookup value */ for(int i = message.nextSetBit(messageStart); i >= messageStart && i < crcStart; i = message.nextSetBit(i + 1)) { calculated ^= CCITT_80_CHECKSUMS[i - messageStart]; } int checksum = getIntChecksum(message, crcStart, 16); int residual = calculated ^ checksum; if(residual == 0 || residual == 0xFFFF) { message.setCRC(CRC.PASSED); return message; } else { int errorLocation = getBitError(residual, CCITT_80_CHECKSUMS); if(errorLocation >= 0) { message.flip(errorLocation + messageStart); message.setCRC(CRC.CORRECTED); return message; } } message.setCRC(CRC.FAILED_CRC); return message; } /** * Calculates the CRC checksum for the specified message * @param message to check * @param start of the message * @param length of the message (assumes the CRC checksum follows) * @param checksums to use in calculating the message checksum * @return calculated checksum */ public static int calculate(BinaryMessage message, int start, int length, long[] checksums) { int calculated = 0; /* Iterate the set bits and XOR running checksum with lookup value */ for(int i = message.nextSetBit(start); i >= start && i < length; i = message.nextSetBit(i + 1)) { calculated ^= checksums[i - start]; } return calculated; } /** * Error detection and correction of single-bit errors for CCITT 16-bit CRC protected 80-bit messages. */ public static int correctCCITT80(CorrectedBinaryMessage message, int messageStart, int crcStart, int mask) { int calculated = mask; //Starting value /* Iterate the set bits and XOR running checksum with lookup value */ for(int i = message.nextSetBit(messageStart); i >= messageStart && i < crcStart; i = message.nextSetBit(i + 1)) { calculated ^= CCITT_80_CHECKSUMS[i - messageStart]; } int checksum = getIntChecksum(message, crcStart, 16); int residual = calculated ^ checksum; if(residual == 0 || residual == 0xFFFF) { return 0; } else { int errorLocation = getBitError(residual, CCITT_80_CHECKSUMS); if(errorLocation >= 0) { message.flip(errorLocation + messageStart); message.incrementCorrectedBitCount(1); return 1; } } //Message has at least 2 bit errors - ie uncorrectable message.incrementCorrectedBitCount(2); return 2; } /** * Calculates the residual CRC value using a mask of zero which should identify the mask value when an alternate * mask value is employed. * @param message to be checked * @param messageStart location * @param crcStart location * @return residual CRC check value. */ public static int calculateResidual(CorrectedBinaryMessage message, int messageStart, int crcStart) { int calculated = 0; //Starting value /* Iterate the set bits and XOR running checksum with lookup value */ for(int i = message.nextSetBit(messageStart); i >= messageStart && i < crcStart; i = message.nextSetBit(i + 1)) { calculated ^= CCITT_80_CHECKSUMS[i - messageStart]; } int checksum = getIntChecksum(message, crcStart, 16); return calculated ^ checksum; } /** * Error detection for CRC-9 protected Confirmed Packet Data blocks. These * data blocks have a slightly complicated structure because the checksum * is located between bits 7-15, within a 144 bit block. The checksums * were generated assuming that the message is contiguous from 0 - 134 bits. * No data correction is performed. */ public static CRC checkCRC9(BinaryMessage message, int messageStart) { int calculated = 0x0; //Initial fill of all ones /* Iterate the set bits and XOR running checksum with lookup value */ for(int i = message.nextSetBit(messageStart); i >= messageStart && i < messageStart + 144; i = message.nextSetBit(i + 1)) { /* message bits before the CRC */ if(i < (messageStart + 7)) { calculated ^= CRC9_CHECKSUMS[i - messageStart]; } /* message bits after the CRC */ else if(i > (messageStart + 15)) { calculated ^= CRC9_CHECKSUMS[i - messageStart - 9]; } } int checksum = message.getInt(messageStart + 7, messageStart + 15); int residual = calculated ^ checksum; if(residual == 0 || residual == 0x1FF) { return CRC.PASSED; } return CRC.FAILED_CRC; } /** * Calculates the value of the message checksum as a long */ public static long getLongChecksum(BinaryMessage message, int crcStart, int crcLength) { return message.getLong(crcStart, crcStart + crcLength - 1); } /** * Calculates the value of the message checksum as an integer */ public static int getIntChecksum(BinaryMessage message, int crcStart, int crcLength) { return message.getInt(crcStart, crcStart + crcLength - 1); } /** * Identifies any single bit error position that matches the checksum error. */ public static int getBitError(long checksumError, long[] checksums) { for(int x = 0; x < checksums.length; x++) { if(checksums[x] == checksumError) { return x; } } return -1; } /** * Identifies any single bit error position that matches the checksum error. */ public static int getBitError(int checksumError, int[] checksums) { for(int x = 0; x < checksums.length; x++) { if(checksums[x] == checksumError) { return x; } } return -1; } public static int crc8(BinaryMessage bits, int len) { int crc=0; final int K = 8; final boolean[] poly = new boolean[]{true,false,false,false,false,false,true,true,true}; // crc8 poly boolean[] buf = new boolean[256]; if (len+K > 256) { return 0; } for (int i=0; i