mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-09 15:17:24 -05:00
#2166 DMR adds RS(12,9) error detection and correction on link control. Enhances DMR CRC mask manager to process CSBK and link control messages to improve handling of RAS enabled systems when the user has not opted to ignore CRC checksums. Fixes issue with CapMax voice channels not resolving to frequency. Fixes issue in playlist editor where DMR LCN description values were not updating correctly. (#2200)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
6
.idea/dictionaries/denny.xml
generated
6
.idea/dictionaries/denny.xml
generated
@@ -2,18 +2,24 @@
|
||||
<dictionary name="denny">
|
||||
<words>
|
||||
<w>bahroo</w>
|
||||
<w>baseband</w>
|
||||
<w>basebanded</w>
|
||||
<w>broadcastify</w>
|
||||
<w>cach</w>
|
||||
<w>correlator</w>
|
||||
<w>deinterleave</w>
|
||||
<w>descrambled</w>
|
||||
<w>dibit</w>
|
||||
<w>dinf</w>
|
||||
<w>dqpsk</w>
|
||||
<w>esnid</w>
|
||||
<w>fleetsync</w>
|
||||
<w>heterdyned</w>
|
||||
<w>heterodynes</w>
|
||||
<w>hytera</w>
|
||||
<w>icecast</w>
|
||||
<w>ident</w>
|
||||
<w>lcns</w>
|
||||
<w>nutall</w>
|
||||
<w>overflowable</w>
|
||||
<w>polyphase</w>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -44,9 +44,9 @@ public class Checksum_5_DMR
|
||||
/**
|
||||
* Indicates if the 77-bit Full Link Control message is valid.
|
||||
* @param message to check
|
||||
* @return true if the checksum matches.
|
||||
* @return residual value leftover from xor of the calculated and transmitted checksums.
|
||||
*/
|
||||
public static boolean isValid(CorrectedBinaryMessage message)
|
||||
public static int isValid(CorrectedBinaryMessage message)
|
||||
{
|
||||
int accumulator = message.getInt(LC_0);
|
||||
accumulator += message.getInt(LC_1);
|
||||
@@ -57,10 +57,8 @@ public class Checksum_5_DMR
|
||||
accumulator += message.getInt(LC_6);
|
||||
accumulator += message.getInt(LC_7);
|
||||
accumulator += message.getInt(LC_8);
|
||||
|
||||
accumulator = accumulator % 31;
|
||||
int checksum = message.getInt(CHECKSUM);
|
||||
|
||||
return accumulator == checksum;
|
||||
return accumulator ^ checksum;
|
||||
}
|
||||
}
|
||||
|
||||
535
src/main/java/io/github/dsheirer/edac/RS_12_9_DMR.java
Normal file
535
src/main/java/io/github/dsheirer/edac/RS_12_9_DMR.java
Normal file
@@ -0,0 +1,535 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implements Reed Solomon RS(12,9,4) error detection and correction. The RS code is based on a shortened GF(8)
|
||||
* RS(255,252,4) code which can detect and correct up to 1x symbol error. Since each symbol is 8-bits long, we can
|
||||
* correct up to 8-bit errors, as long as the bit errors are contained within the same 8-bit symbol.
|
||||
*
|
||||
* The DMR full link control (FLC) message is composed of 96 bits representing 12x 8-bit symbols where the first
|
||||
* 9x symbols are message codewords and the 3x trailing are parity symbols.
|
||||
*
|
||||
* This class was ported from the original implementation at:
|
||||
* https://github.com/nonoo/dmrshark/blob/master/libs/coding/rs-12-9.c
|
||||
* License: dmrshark library uses the same GPL3 license used by sdrtrunk.
|
||||
*
|
||||
* Usage: all methods are static. Use the correct(CorrectedBinaryMessage cbm) method to detect and correct errors and
|
||||
* inspect the cbm.getCorrectedBitCount(). A non-negative value indicates success and the count of bits corrected.
|
||||
*/
|
||||
public class RS_12_9_DMR
|
||||
{
|
||||
/**
|
||||
* Primitive polynomial used to generate the EXPONENTS_TABLE and LOG_TABLE values. See p.142, B.14
|
||||
*/
|
||||
private static final int PRIMITIVE_POLYNOMIAL = 0x11D;
|
||||
|
||||
// See DMR AI. spec. page 142, Table B.19
|
||||
private static final int[] EXPONENTS_TABLE = {
|
||||
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1D, 0x3A, 0x74, 0xE8, 0xCD, 0x87, 0x13, 0x26,
|
||||
0x4C, 0x98, 0x2D, 0x5A, 0xB4, 0x75, 0xEA, 0xC9, 0x8F, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0,
|
||||
0x9D, 0x27, 0x4E, 0x9C, 0x25, 0x4A, 0x94, 0x35, 0x6A, 0xD4, 0xB5, 0x77, 0xEE, 0xC1, 0x9F, 0x23,
|
||||
0x46, 0x8C, 0x05, 0x0A, 0x14, 0x28, 0x50, 0xA0, 0x5D, 0xBA, 0x69, 0xD2, 0xB9, 0x6F, 0xDE, 0xA1,
|
||||
0x5F, 0xBE, 0x61, 0xC2, 0x99, 0x2F, 0x5E, 0xBC, 0x65, 0xCA, 0x89, 0x0F, 0x1E, 0x3C, 0x78, 0xF0,
|
||||
0xFD, 0xE7, 0xD3, 0xBB, 0x6B, 0xD6, 0xB1, 0x7F, 0xFE, 0xE1, 0xDF, 0xA3, 0x5B, 0xB6, 0x71, 0xE2,
|
||||
0xD9, 0xAF, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0D, 0x1A, 0x34, 0x68, 0xD0, 0xBD, 0x67, 0xCE,
|
||||
0x81, 0x1F, 0x3E, 0x7C, 0xF8, 0xED, 0xC7, 0x93, 0x3B, 0x76, 0xEC, 0xC5, 0x97, 0x33, 0x66, 0xCC,
|
||||
0x85, 0x17, 0x2E, 0x5C, 0xB8, 0x6D, 0xDA, 0xA9, 0x4F, 0x9E, 0x21, 0x42, 0x84, 0x15, 0x2A, 0x54,
|
||||
0xA8, 0x4D, 0x9A, 0x29, 0x52, 0xA4, 0x55, 0xAA, 0x49, 0x92, 0x39, 0x72, 0xE4, 0xD5, 0xB7, 0x73,
|
||||
0xE6, 0xD1, 0xBF, 0x63, 0xC6, 0x91, 0x3F, 0x7E, 0xFC, 0xE5, 0xD7, 0xB3, 0x7B, 0xF6, 0xF1, 0xFF,
|
||||
0xE3, 0xDB, 0xAB, 0x4B, 0x96, 0x31, 0x62, 0xC4, 0x95, 0x37, 0x6E, 0xDC, 0xA5, 0x57, 0xAE, 0x41,
|
||||
0x82, 0x19, 0x32, 0x64, 0xC8, 0x8D, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xDD, 0xA7, 0x53, 0xA6,
|
||||
0x51, 0xA2, 0x59, 0xB2, 0x79, 0xF2, 0xF9, 0xEF, 0xC3, 0x9B, 0x2B, 0x56, 0xAC, 0x45, 0x8A, 0x09,
|
||||
0x12, 0x24, 0x48, 0x90, 0x3D, 0x7A, 0xF4, 0xF5, 0xF7, 0xF3, 0xFB, 0xEB, 0xCB, 0x8B, 0x0B, 0x16,
|
||||
0x2C, 0x58, 0xB0, 0x7D, 0xFA, 0xE9, 0xCF, 0x83, 0x1B, 0x36, 0x6C, 0xD8, 0xAD, 0x47, 0x8E, 0x01
|
||||
};
|
||||
|
||||
// See DMR AI. spec. page 143, Table B.20.
|
||||
private static final int[] LOG_TABLE = {0, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224,
|
||||
14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218,
|
||||
240, 18, 130, 69, 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 191, 139, 98,
|
||||
102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92,
|
||||
131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10,
|
||||
21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49,
|
||||
197, 254, 24, 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 63, 209, 91, 149,
|
||||
188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71,
|
||||
109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 108, 161, 59, 82, 41,
|
||||
157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122,
|
||||
117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175
|
||||
};
|
||||
|
||||
// See DMR AI. spec. page 141, B.10 for these coefficients.
|
||||
private static final int[] GENERATOR_POLYNOMIAL = {0x40, 0x38, 0x0e, 0x01};
|
||||
private static final int CODEWORD_SIZE = 12;
|
||||
private static final int CHECKSUM_SIZE = 3; //L
|
||||
private static final int POLYNOMIAL_MAXIMUM_DEGREE = (CHECKSUM_SIZE * 2);
|
||||
public static final int ERRORS_CANT_BE_CORRECTED = -1;
|
||||
|
||||
/**
|
||||
* Performs error detection and correction on the Full Link Control (FLC) message. The message should be 96-bits
|
||||
* long containing 9x 8-bit message symbols and 3x 8-bit parity symbols. The RS(12,9,4) algorithm can detect and
|
||||
* correct up to 1x 8-bit symbol, or up to 8 bit errors if they are all contained in the same symbol.
|
||||
*
|
||||
* Error detection and correction results will be stored in the message.getCorrectedBitCount() and the message will
|
||||
* be corrected in place, if able to correct. A corrected bit count of ERRORS_CANT_BE_CORRECTED (-1) indicates that
|
||||
* the message could not be corrected.
|
||||
*
|
||||
* @param cbm to correct.
|
||||
* @param mask to apply/remove before error detection and correction.
|
||||
*/
|
||||
public static int correct(CorrectedBinaryMessage cbm, int mask)
|
||||
{
|
||||
int[] codeword = new int[12];
|
||||
|
||||
for(int index = 0; index < 12; index++)
|
||||
{
|
||||
codeword[index] = (0xFF & cbm.getByte(index * 8));
|
||||
|
||||
//Apply the mask to parity symbols 9-11
|
||||
if(index >= 9)
|
||||
{
|
||||
codeword[index] ^= (mask & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
int[] syndrome = calculateSyndrome(codeword);
|
||||
|
||||
if(hasErrors(syndrome))
|
||||
{
|
||||
int correctedSymbolIndex = correctErrors(codeword, syndrome);
|
||||
|
||||
if(correctedSymbolIndex >= 0)
|
||||
{
|
||||
//Get the previous symbol value and xor to calculate the bit error correction pattern
|
||||
int previousValue = (0xFF & cbm.getByte(correctedSymbolIndex * 8));
|
||||
int correctionPattern = previousValue ^ codeword[correctedSymbolIndex];
|
||||
|
||||
//Apply the correction to the original message
|
||||
cbm.setByte(8 * correctedSymbolIndex, (byte)(0xFF & codeword[correctedSymbolIndex]));
|
||||
|
||||
//Set the corrected bit count to the number of ones in the correction pattern from the corrected symbol.
|
||||
cbm.setCorrectedBitCount(Integer.bitCount(correctionPattern));
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Message can't be corrected - too many errors.
|
||||
cbm.setCorrectedBitCount(correctedSymbolIndex);
|
||||
|
||||
//Calculate the checksum residual and return that as the observed alternate mask value. Note: the
|
||||
//DMR ICD checksum has already been applied to the codeword and this calculated residual is the
|
||||
//'other' masking value that is being used on both Headers and Terminators.
|
||||
int[] checksum = calculateChecksum(codeword);
|
||||
|
||||
for(int index = 0; index < 3; index++)
|
||||
{
|
||||
checksum[index] ^= codeword[index + 9];
|
||||
}
|
||||
|
||||
return checksum[0] << 16 | checksum[1] << 8 | checksum[2];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cbm.setCorrectedBitCount(0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs GF(8) multiplication (a + b % 255)
|
||||
* @param factorA factor
|
||||
* @param factorB factor
|
||||
* @return product
|
||||
*/
|
||||
private static int galoisMultiplication(int factorA, int factorB)
|
||||
{
|
||||
if (factorA == 0 || factorB == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return EXPONENTS_TABLE[((LOG_TABLE[factorA & 0xFF] + LOG_TABLE[factorB & 0xFF]) % 255)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the inverse.
|
||||
* @param elt value
|
||||
* @return inverse
|
||||
*/
|
||||
private static int galoisInverse(int elt)
|
||||
{
|
||||
return EXPONENTS_TABLE[255 - LOG_TABLE[elt]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies the polynomial by z, resulting in a shift right by 1 operation.
|
||||
* @param poly to multiply.
|
||||
*/
|
||||
private static void multiplyPolynomialByZ(int[] poly)
|
||||
{
|
||||
for(int i = POLYNOMIAL_MAXIMUM_DEGREE - 1; i > 0; i--)
|
||||
{
|
||||
poly[i] = poly[i - 1];
|
||||
}
|
||||
|
||||
poly[0] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies the polynomials and returns the result.
|
||||
* @param poly1 argument
|
||||
* @param poly2 argument
|
||||
* @return result
|
||||
*/
|
||||
private static int[] multiplyPolynomials(int[] poly1, int[] poly2)
|
||||
{
|
||||
int[] product = new int[POLYNOMIAL_MAXIMUM_DEGREE * 2];
|
||||
|
||||
int i;
|
||||
int j;
|
||||
int[] tmp1 = new int[POLYNOMIAL_MAXIMUM_DEGREE * 2];
|
||||
|
||||
for (i = 0; i < POLYNOMIAL_MAXIMUM_DEGREE * 2; i++)
|
||||
{
|
||||
product[i] = 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < POLYNOMIAL_MAXIMUM_DEGREE; i++)
|
||||
{
|
||||
for(j = POLYNOMIAL_MAXIMUM_DEGREE; j < (POLYNOMIAL_MAXIMUM_DEGREE *2); j++)
|
||||
{
|
||||
tmp1[j] = 0;
|
||||
}
|
||||
|
||||
// Scale tmp1 by p1[i]
|
||||
for(j = 0; j < POLYNOMIAL_MAXIMUM_DEGREE; j++)
|
||||
{
|
||||
tmp1[j] = galoisMultiplication(poly2[j], poly1[i]);
|
||||
}
|
||||
|
||||
// Shift (multiply) tmp1 right by i
|
||||
for (j = (POLYNOMIAL_MAXIMUM_DEGREE * 2) - 1; j >= i; j--)
|
||||
{
|
||||
tmp1[j] = tmp1[j-i];
|
||||
}
|
||||
|
||||
for (j = 0; j < i; j++)
|
||||
{
|
||||
tmp1[j] = 0;
|
||||
}
|
||||
|
||||
// Add into partial product.
|
||||
for (j = 0; j < (POLYNOMIAL_MAXIMUM_DEGREE *2); j++)
|
||||
{
|
||||
product[j] ^= tmp1[j];
|
||||
}
|
||||
}
|
||||
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the error evaluator polynomial (EEP). Computes the combined erasure/error evaluator polynomial
|
||||
* (error_locator_poly*syndrome mod z^4
|
||||
* @param elp error locator polynomial
|
||||
* @param syndrome calculated from the codeword
|
||||
* @return error evaluator polynomial (EEP)
|
||||
*/
|
||||
private static int[] calculateEEP(int[] elp, int[] syndrome)
|
||||
{
|
||||
int[] eep = new int[POLYNOMIAL_MAXIMUM_DEGREE];
|
||||
int[] product = multiplyPolynomials(elp, syndrome);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CHECKSUM_SIZE; i++)
|
||||
{
|
||||
eep[i] = product[i];
|
||||
}
|
||||
|
||||
for (; i < POLYNOMIAL_MAXIMUM_DEGREE; i++)
|
||||
{
|
||||
eep[i] = 0;
|
||||
}
|
||||
|
||||
return eep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the discrepancy.
|
||||
* @param elp error locator polynomial
|
||||
* @param syndrome from the codeword
|
||||
* @param L maximum capacity to correct.
|
||||
* @param n argument
|
||||
* @return discrepancy
|
||||
*/
|
||||
private static int computeDiscrepancy(int[] elp, int[] syndrome, int L, int n)
|
||||
{
|
||||
int sum = 0;
|
||||
|
||||
for (int i = 0; i <= L; i++)
|
||||
{
|
||||
sum ^= galoisMultiplication(elp[i], syndrome[n - i]);
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the coefficients of the error locator polynomial (ELP) and then calculates the error evaluator
|
||||
* (EEP) using the Berlekamp-Massey algorithm. From Cain, Clark, "Error-Correction Coding For Digital
|
||||
* Communications", pp. 216.
|
||||
* @param syndrome calculated from the codeword
|
||||
* @param elp calculated.
|
||||
* @return EEP
|
||||
*/
|
||||
private static int[] calculateELP(int[] syndrome)
|
||||
{
|
||||
int[] elp = new int[POLYNOMIAL_MAXIMUM_DEGREE];
|
||||
int[] psi2 = new int[POLYNOMIAL_MAXIMUM_DEGREE];
|
||||
int[] bigD = new int[POLYNOMIAL_MAXIMUM_DEGREE];
|
||||
|
||||
int L = 0;
|
||||
int L2;
|
||||
int k = -1;
|
||||
int d;
|
||||
int i;
|
||||
|
||||
bigD[1] = 1;
|
||||
elp[0] = 1;
|
||||
|
||||
for(int n = 0; n < CHECKSUM_SIZE; n++)
|
||||
{
|
||||
d = computeDiscrepancy(elp, syndrome, L, n);
|
||||
|
||||
if (d != 0)
|
||||
{
|
||||
// psi2 = error_locator_poly - d*D
|
||||
for (i = 0; i < POLYNOMIAL_MAXIMUM_DEGREE; i++)
|
||||
{
|
||||
psi2[i] = (elp[i] ^ galoisMultiplication(d, bigD[i]));
|
||||
}
|
||||
|
||||
if (L < (n - k))
|
||||
{
|
||||
L2 = (n - k);
|
||||
k = (n - L);
|
||||
|
||||
for (i = 0; i < POLYNOMIAL_MAXIMUM_DEGREE; i++)
|
||||
{
|
||||
bigD[i] = galoisMultiplication(elp[i], galoisInverse(d));
|
||||
}
|
||||
|
||||
L = L2;
|
||||
}
|
||||
|
||||
// error_locator_poly = psi2
|
||||
for (i = 0; i < POLYNOMIAL_MAXIMUM_DEGREE; i++)
|
||||
{
|
||||
elp[i] = psi2[i];
|
||||
}
|
||||
}
|
||||
|
||||
multiplyPolynomialByZ(bigD);
|
||||
}
|
||||
|
||||
return elp;
|
||||
}
|
||||
|
||||
/**
|
||||
* The error-locator polynomial's roots are found by looking for the values of a^n where evaluating the polynomial
|
||||
* yields zero (evaluating rs_12_9_error_locator_poly at successive values of alpha (Chien's search)).
|
||||
* @param elp error locator polynomial.
|
||||
* @return error location roots.
|
||||
*/
|
||||
private static List<Integer> findRoots(int[] elp)
|
||||
{
|
||||
List<Integer> roots = new ArrayList<>();
|
||||
int sum;
|
||||
int k;
|
||||
|
||||
for(int r = 1; r < 256; r++)
|
||||
{
|
||||
sum = 0;
|
||||
|
||||
// Evaluate ELP at r
|
||||
for(k = 0; k < CHECKSUM_SIZE + 1; k++)
|
||||
{
|
||||
sum ^= galoisMultiplication(EXPONENTS_TABLE[(k * r) % 255], elp[k]);
|
||||
}
|
||||
|
||||
if(sum == 0)
|
||||
{
|
||||
roots.add(255 - r);
|
||||
}
|
||||
}
|
||||
|
||||
return roots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the syndrome for a codeword.
|
||||
* @param codeword to check.
|
||||
* @return syndrome.
|
||||
*/
|
||||
private static int[] calculateSyndrome(int[] codeword)
|
||||
{
|
||||
if(codeword.length != CODEWORD_SIZE)
|
||||
{
|
||||
throw new IllegalArgumentException("Codeword length [" + codeword.length + "] should be " + CODEWORD_SIZE +
|
||||
" symbols");
|
||||
}
|
||||
|
||||
int[] syndrome = new int[POLYNOMIAL_MAXIMUM_DEGREE];
|
||||
|
||||
int i, temp;
|
||||
|
||||
for(int j = 0; j < 3; j++)
|
||||
{
|
||||
for (i = 0; i < codeword.length; i++)
|
||||
{
|
||||
temp = galoisMultiplication(EXPONENTS_TABLE[j + 1], syndrome[j]);
|
||||
temp ^= codeword[i];
|
||||
syndrome[j] = (0xFF & temp);
|
||||
}
|
||||
}
|
||||
|
||||
return syndrome;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the syndrome array for non-zero values indicating an error.
|
||||
* @param syndrome to check
|
||||
* @return true if the syndrome array has any non-zero values.
|
||||
*/
|
||||
private static boolean hasErrors(int[] syndrome)
|
||||
{
|
||||
for(int symbol: syndrome)
|
||||
{
|
||||
if(symbol != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Corrects errors in the codeword using the calculated syndrome.
|
||||
* @param codeword to correct. The codeword will be modified with corrections, if able.
|
||||
* @param syndrome calculated from the codeword.
|
||||
* @return -1 if the codeword can't be corrected or the index of the symbol that was corrected.
|
||||
*/
|
||||
private static int correctErrors(int[] codeword, int[] syndrome)
|
||||
{
|
||||
int r;
|
||||
int i;
|
||||
int j;
|
||||
int errorMask;
|
||||
int num, denom;
|
||||
|
||||
//Error Locator Polynomial (ELP)
|
||||
int[] elp = calculateELP(syndrome);
|
||||
|
||||
//Error Evaluator Polynomial (EEP)
|
||||
int[] eep = calculateEEP(elp, syndrome);
|
||||
|
||||
List<Integer> roots = findRoots(elp);
|
||||
|
||||
if (roots.isEmpty())
|
||||
{
|
||||
return ERRORS_CANT_BE_CORRECTED;
|
||||
}
|
||||
|
||||
// Error correction is done using the error-evaluator equation on pp 207.
|
||||
if (roots.size() <= CHECKSUM_SIZE)
|
||||
{
|
||||
// First check for illegal error locations.
|
||||
for (r = 0; r < roots.size(); r++)
|
||||
{
|
||||
if (roots.get(r) >= CODEWORD_SIZE)
|
||||
{
|
||||
return ERRORS_CANT_BE_CORRECTED;
|
||||
}
|
||||
}
|
||||
|
||||
int errorsCorrected = 0;
|
||||
|
||||
// Evaluates rs_12_9_error_evaluator_poly/rs_12_9_error_locator_poly' at the roots
|
||||
// alpha^(-i) for error locs i.
|
||||
for (r = 0; r < roots.size(); r++)
|
||||
{
|
||||
i = roots.get(r);
|
||||
|
||||
// Evaluate rs_12_9_error_evaluator_poly at alpha^(-i)
|
||||
num = 0;
|
||||
for (j = 0; j < POLYNOMIAL_MAXIMUM_DEGREE; j++)
|
||||
{
|
||||
num ^= galoisMultiplication(eep[j], EXPONENTS_TABLE[((255 - i) * j) % 255]);
|
||||
}
|
||||
|
||||
// Evaluate rs_12_9_error_evaluator_poly' (derivative) at alpha^(-i). All odd powers disappear.
|
||||
denom = 0;
|
||||
|
||||
for (j = 1; j < POLYNOMIAL_MAXIMUM_DEGREE; j += 2)
|
||||
{
|
||||
denom ^= galoisMultiplication(elp[j], EXPONENTS_TABLE[((255-i)*(j-1)) % 255]);
|
||||
}
|
||||
|
||||
errorMask = galoisMultiplication(num, galoisInverse(denom));
|
||||
int index = CODEWORD_SIZE - i - 1;
|
||||
codeword[index] ^= errorMask;
|
||||
return index;
|
||||
}
|
||||
|
||||
return errorsCorrected;
|
||||
}
|
||||
|
||||
return ERRORS_CANT_BE_CORRECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the checksum parity symbols for the codeword using the DMR generator polynomial.
|
||||
* @param codeword to calculate.
|
||||
* @return checksum bytes (3)
|
||||
*/
|
||||
private static int[] calculateChecksum(int[] codeword)
|
||||
{
|
||||
int[] checksum = new int[CHECKSUM_SIZE];
|
||||
|
||||
int i;
|
||||
int feedback;
|
||||
|
||||
for (i = 0; i < 9; i++)
|
||||
{
|
||||
feedback = (codeword[i] ^ checksum[0]);
|
||||
checksum[0] = (checksum[1] ^ galoisMultiplication(GENERATOR_POLYNOMIAL[2], feedback));
|
||||
checksum[1] = (checksum[2] ^ galoisMultiplication(GENERATOR_POLYNOMIAL[1], feedback));
|
||||
checksum[2] = galoisMultiplication(GENERATOR_POLYNOMIAL[0], feedback);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Berlecamp-Massey decoder for Reed Solomon RS(12,9,4) protected messages using DMR generator polynomial
|
||||
*/
|
||||
public class ReedSolomon_12_9_4_DMR extends ReedSolomon_255_DMR
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ReedSolomon_12_9_4_DMR.class);
|
||||
|
||||
private static final int[] FLC_CODEWORD_0 = {0, 1, 2, 3, 4, 5, 6, 7};
|
||||
private static final int[] FLC_CODEWORD_1 = {8, 9, 10, 11, 12, 13, 14, 15};
|
||||
private static final int[] FLC_CODEWORD_2 = {16, 17, 18, 19, 20, 21, 22, 23};
|
||||
private static final int[] FLC_CODEWORD_3 = {24, 25, 26, 27, 28, 29, 30, 31};
|
||||
private static final int[] FLC_CODEWORD_4 = {32, 33, 34, 35, 36, 37, 38, 39};
|
||||
private static final int[] FLC_CODEWORD_5 = {40, 41, 42, 43, 44, 45, 46, 47};
|
||||
private static final int[] FLC_CODEWORD_6 = {48, 49, 50, 51, 52, 53, 54, 55};
|
||||
private static final int[] FLC_CODEWORD_7 = {56, 57, 58, 59, 60, 61, 62, 63};
|
||||
private static final int[] FLC_CODEWORD_8 = {64, 65, 66, 67, 68, 69, 70, 71};
|
||||
private static final int[] FLC_PARITY_0 = {72, 73, 74, 75, 76, 77, 78, 79};
|
||||
private static final int[] FLC_PARITY_1 = {80, 81, 82, 83, 84, 85, 86, 87};
|
||||
private static final int[] FLC_PARITY_2 = {88, 89, 90, 91, 92, 93, 94, 95};
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public ReedSolomon_12_9_4_DMR()
|
||||
{
|
||||
//Note: the RS(12,9,4) is a shortened form of RS(255,252,4) over GF(8)
|
||||
super(255, 252);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to correct the Full Link Control message returning true if there were zero or one correctable errors
|
||||
* and returning false if there were two or more errors detected.
|
||||
*
|
||||
* Note: if one bit error was detected and corrected, the corrected bit count is set to 1 in the message
|
||||
*
|
||||
* @param message to be corrected.
|
||||
* @param crcMask as defined in the TS 102-361-1 Table B.21
|
||||
* @return true if the message is correct or if the message was corrected.
|
||||
*/
|
||||
public boolean correctFullLinkControl(CorrectedBinaryMessage message, int crcMask)
|
||||
{
|
||||
int[] input = new int[255];
|
||||
int[] output = new int[255];
|
||||
|
||||
input[0] = (message.getInt(FLC_PARITY_2) ^ crcMask);
|
||||
input[1] = (message.getInt(FLC_PARITY_1) ^ crcMask);
|
||||
input[2] = (message.getInt(FLC_PARITY_0) ^ crcMask);
|
||||
input[3] = message.getInt(FLC_CODEWORD_8);
|
||||
input[4] = message.getInt(FLC_CODEWORD_7);
|
||||
input[5] = message.getInt(FLC_CODEWORD_6);
|
||||
input[6] = message.getInt(FLC_CODEWORD_5);
|
||||
input[7] = message.getInt(FLC_CODEWORD_4);
|
||||
input[8] = message.getInt(FLC_CODEWORD_3);
|
||||
input[9] = message.getInt(FLC_CODEWORD_2);
|
||||
input[10] = message.getInt(FLC_CODEWORD_1);
|
||||
input[11] = message.getInt(FLC_CODEWORD_0);
|
||||
|
||||
try
|
||||
{
|
||||
boolean irrecoverableErrors = decode(input, output);
|
||||
|
||||
if(irrecoverableErrors)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int pointer = 0;
|
||||
|
||||
for(int x = 11; x >= 0; x--)
|
||||
{
|
||||
if(output[x] != -1 && (output[x] != input[x]))
|
||||
{
|
||||
message.load(pointer, 8, output[x]);
|
||||
message.setCorrectedBitCount(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
pointer += 8;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
int[] output = new int[255];
|
||||
int[] input = new int[255];
|
||||
input[0] = 247;
|
||||
input[1] = 236;
|
||||
input[2] = 171;
|
||||
input[3] = 24;
|
||||
input[4] = 46;
|
||||
input[5] = 177;
|
||||
input[6] = 246;
|
||||
input[7] = 205;
|
||||
input[8] = 232;
|
||||
input[9] = 32;
|
||||
input[10] = 124;
|
||||
input[11] = 0;
|
||||
|
||||
// input[254] = 247;
|
||||
// input[253] = 236;
|
||||
// input[252] = 171;
|
||||
// input[251] = 24;
|
||||
// input[250] = 46;
|
||||
// input[249] = 177;
|
||||
// input[248] = 246;
|
||||
// input[247] = 205;
|
||||
// input[246] = 232;
|
||||
// input[245] = 32;
|
||||
// input[244] = 124;
|
||||
// input[243] = 0;
|
||||
|
||||
ReedSolomon_12_9_4_DMR rs = new ReedSolomon_12_9_4_DMR();
|
||||
boolean irrecoverable = rs.decode(input, output);
|
||||
|
||||
mLog.info("Irrecoverable: " + irrecoverable);
|
||||
}
|
||||
}
|
||||
@@ -78,8 +78,8 @@ public abstract class BCH
|
||||
/**
|
||||
* Lookup tables. Note: naming convention is from the original C++ implementation.
|
||||
*/
|
||||
private int[] a_pow_tab;
|
||||
private int[] a_log_tab;
|
||||
public int[] a_pow_tab;
|
||||
public int[] a_log_tab;
|
||||
private int[] xi_tab;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -17,28 +17,30 @@
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.edac;
|
||||
package io.github.dsheirer.edac.bch;
|
||||
|
||||
/**
|
||||
* Berlekemp Massey decoder for GF(8) using DMR Generator Polynomial
|
||||
*/
|
||||
public class ReedSolomon_255_DMR extends BerlekempMassey
|
||||
import java.util.Arrays;
|
||||
|
||||
public class BCH_255 extends BCH
|
||||
{
|
||||
/**
|
||||
* DMR Reed-Solomon code is generated from a Galois Field(2^8) by the polynomial: a8 + a4 + a3 + a2 + a0.
|
||||
* In binary, this is expressed as: 100011101 which is reversed to big-endian format for this algorithm
|
||||
* See: TS 102-361-1 paragraph B.3.6 Reed-Solomon(12,9)
|
||||
*/
|
||||
public static final int[] DMR_GENERATOR_POLYNOMIAL = {1, 0, 1, 1, 1, 0, 0, 0, 1};
|
||||
public static final int M = 8;
|
||||
|
||||
/**
|
||||
* Constructs an instance of RS(total symbols, message symbols)
|
||||
*
|
||||
* @param n total symbols
|
||||
* @param k message symbols
|
||||
* Constructs an instance
|
||||
* @param k data bits size
|
||||
* @param t error detection and correction capacity of the generator polynomial used to create the codewords.
|
||||
*/
|
||||
public ReedSolomon_255_DMR(int n, int k)
|
||||
public BCH_255(int k, int t)
|
||||
{
|
||||
super(8, n, k, DMR_GENERATOR_POLYNOMIAL);
|
||||
// super(M, k, t, PRIMITIVE_POLYNOMIAL_GF_255);
|
||||
super(M, k, t, 0x12D);
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
BCH_255 b = new BCH_255(M, 1);
|
||||
|
||||
System.out.println("ALOG: " + Arrays.toString(b.a_log_tab));
|
||||
System.out.println("APOW: " + Arrays.toString(b.a_pow_tab));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -296,7 +296,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
||||
|
||||
TableColumn<TimeslotFrequency,String> lsnColumn = new TableColumn("IDs (TS1/TS2)");
|
||||
lsnColumn.setPrefWidth(225);
|
||||
lsnColumn.setCellValueFactory(new PropertyValueFactory<>("logicalSlotNumbers"));
|
||||
lsnColumn.setCellValueFactory(new PropertyValueFactory<>("description"));
|
||||
mTimeslotFrequencyTable.getColumns().addAll(lsnColumn);
|
||||
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ package io.github.dsheirer.gui.viewer;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import io.github.dsheirer.channel.state.DecoderStateEvent;
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRCrcMaskManager;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRDecoderState;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRHardSymbolProcessor;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRMessageFramer;
|
||||
@@ -80,12 +81,12 @@ public class DmrViewer extends VBox
|
||||
private static final Logger mLog = LoggerFactory.getLogger(DmrViewer.class);
|
||||
private static final KeyCodeCombination KEY_CODE_COPY = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY);
|
||||
private static final String LAST_SELECTED_DIRECTORY = "last.selected.directory.dmr";
|
||||
private Preferences mPreferences = Preferences.userNodeForPackage(DmrViewer.class);
|
||||
private final Preferences mPreferences = Preferences.userNodeForPackage(DmrViewer.class);
|
||||
private Button mSelectFileButton;
|
||||
private Label mSelectedFileLabel;
|
||||
private TableView<MessagePackage> mMessagePackageTableView;
|
||||
private ObservableList<MessagePackage> mMessagePackages = FXCollections.observableArrayList();
|
||||
private FilteredList<MessagePackage> mFilteredMessagePackages = new FilteredList<>(mMessagePackages);
|
||||
private final ObservableList<MessagePackage> mMessagePackages = FXCollections.observableArrayList();
|
||||
private final FilteredList<MessagePackage> mFilteredMessagePackages = new FilteredList<>(mMessagePackages);
|
||||
private CheckBox mShowTS0;
|
||||
private CheckBox mShowTS1;
|
||||
private CheckBox mShowTS2;
|
||||
@@ -178,12 +179,13 @@ public class DmrViewer extends VBox
|
||||
public void run()
|
||||
{
|
||||
List<MessagePackage> messagePackages = new ArrayList<>();
|
||||
DMRMessageFramer messageFramer = new DMRMessageFramer();
|
||||
DMRCrcMaskManager crcMaskManager = new DMRCrcMaskManager(false);
|
||||
DMRMessageFramer messageFramer = new DMRMessageFramer(crcMaskManager);
|
||||
messageFramer.start();
|
||||
DMRHardSymbolProcessor symbolProcessor = new DMRHardSymbolProcessor(messageFramer);
|
||||
DecodeConfigDMR config = new DecodeConfigDMR();
|
||||
config.setUseCompressedTalkgroups(useCompressed);
|
||||
DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config);
|
||||
DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config, crcMaskManager);
|
||||
messageFramer.setListener(messageProcessor);
|
||||
MessagePackager messagePackager = new MessagePackager();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -47,6 +47,12 @@ public class DmrTalkerAliasIdentifier extends TalkerAliasIdentifier
|
||||
return Protocol.DMR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "A-" + super.toString();
|
||||
}
|
||||
|
||||
public static DmrTalkerAliasIdentifier create(String value)
|
||||
{
|
||||
return new DmrTalkerAliasIdentifier(value);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -19,148 +19,179 @@
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr;
|
||||
|
||||
import io.github.dsheirer.edac.CRCDMR;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Manages alternate CRC masks that may be employed in a system. Some vendors employ non-standard CRC checksum masks
|
||||
* or initial fill values. This utility tracks these non-standard values by observation count and time and accepts
|
||||
* these alternate mask values to automatically correct or validate messages when those messages employ these
|
||||
* alternate mask values. Alternate mask values must be observed at least 3x times in a 2-minute window against the
|
||||
* same message content, in order for the alternate mask value to be used. Tracked values that do not get another
|
||||
* observation count within a 2-minute window will be ejected from the cache and excluded as a valid CRC mask candidate.
|
||||
*
|
||||
* Notes: different alternate CRC mask values can be used independently in each timeslot. Further, I've seen multiple
|
||||
* CRC mask values being employed within the same timeslot. This indicates that systems can use multiple CRC mask
|
||||
* values against CSBK messages and some other function is involved in determining which CRC mask value to use when
|
||||
* checking the CRC of the transmitted CSBK ... fun and games!
|
||||
* Tracks alternate CRC masks that may be employed in a system. Some vendors employ non-standard error detection and
|
||||
* correction checksum masks or initial fill values. This utility tracks these non-standard values by observation count
|
||||
* and time and accepts these alternate mask values to automatically correct or validate messages when those messages
|
||||
* employ these alternate mask values. Alternate mask values must be observed at least 2x times in a staleness
|
||||
* threshold window against the same message type and opcode, in order for the alternate mask value to be accepted as
|
||||
* valid. Tracked values that do not get another observation count within the staleness time threshold will be
|
||||
* ejected from the cache and excluded as a valid CRC mask candidate.
|
||||
*/
|
||||
public class DMRCrcMaskManager
|
||||
{
|
||||
private Logger LOGGER = LoggerFactory.getLogger(DMRCrcMaskManager.class);
|
||||
private Map<Integer, MaskTracker> mCsbkTrackerMapTS1 = new TreeMap<>();
|
||||
private Map<Integer, MaskTracker> mCsbkTrackerMapTS2 = new TreeMap<>();
|
||||
private Set<MaskTracker> mSortedTrackersTS1 = new TreeSet<>();
|
||||
private Set<MaskTracker> mSortedTrackersTS2 = new TreeSet<>();
|
||||
/**
|
||||
* Maximum time age of a tracked (alternate) mask value before it's ejected from the cache.
|
||||
*/
|
||||
private static final long CACHE_EJECTION_POLICY_TIME = TimeUnit.MINUTES.toMillis(2);
|
||||
|
||||
/**
|
||||
* Maximum staleness count before a mask is ejected from the cache. Staleness count is only incremented when the
|
||||
* specific type and opcode is encountered.
|
||||
*/
|
||||
private static final int CACHE_EJECTION_POLICY_COUNT = 20;
|
||||
|
||||
private final Map<Integer, OpcodeMaskTracker> mCsbkTrackerMap = new TreeMap<>();
|
||||
private final Map<Integer, OpcodeMaskTracker> mCrc5TrackerMap = new TreeMap<>();
|
||||
private final Map<Integer, OpcodeMaskTracker> mRs12_9TrackerMap = new TreeMap<>();
|
||||
private final boolean mIgnoreCRCChecksums;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param ignoreCRCChecksums value used to flag all error detection as valid.
|
||||
*/
|
||||
public DMRCrcMaskManager()
|
||||
public DMRCrcMaskManager(boolean ignoreCRCChecksums)
|
||||
{
|
||||
mIgnoreCRCChecksums = ignoreCRCChecksums;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the current tracked CRC values and counts.
|
||||
* Indicates if the decoder is configured to ignore invalid error detection and correction checksums.
|
||||
*/
|
||||
public void log()
|
||||
public boolean isIgnoreCRCChecksums()
|
||||
{
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("DMR CRC Mask Manager - TS1 Current Tracked Values\n");
|
||||
for(MaskTracker tracker: mSortedTrackersTS1)
|
||||
{
|
||||
sb.append("\tTracked Value: ").append(String.format("0x%04X", tracker.getTrackedValue()));
|
||||
sb.append(" Count:").append(tracker.getObservationCount());
|
||||
sb.append(" Last Observed: " + new Date(tracker.getLastUpdated())).append("\n");
|
||||
}
|
||||
|
||||
sb.append("DMR CRC Mask Manager - TS2 Current Tracked Values\n");
|
||||
for(MaskTracker tracker: mSortedTrackersTS2)
|
||||
{
|
||||
sb.append("\tTracked Value: ").append(String.format("0x%04X", tracker.getTrackedValue()));
|
||||
sb.append(" Count:").append(tracker.getObservationCount());
|
||||
sb.append(" Last Observed: " + new Date(tracker.getLastUpdated())).append("\n");
|
||||
}
|
||||
|
||||
LOGGER.info(sb.toString());
|
||||
return mIgnoreCRCChecksums;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the CSBK message that has a failed CRC checksum to detect if an alternate CRC mask value is employed and
|
||||
* if so, attempts to correct the message.
|
||||
* @param csbk to re-check using the currently tracked mask value.
|
||||
* Indicates if the residual value is commonly seen for this opcode.
|
||||
* @param opcode of the link control message
|
||||
* @param residual from the RS(12,9) check
|
||||
* @param timestamp of the message
|
||||
* @return true if this value is repeatedly seen for this opcode.
|
||||
*/
|
||||
public void check(CSBKMessage csbk)
|
||||
public boolean isValidRS12_9(int opcode, int residual, long timestamp)
|
||||
{
|
||||
if(!csbk.isValid())
|
||||
return mIgnoreCRCChecksums || isValid(opcode, residual, timestamp, mRs12_9TrackerMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the residual value is commonly seen for this opcode.
|
||||
* @param opcode of the link control message
|
||||
* @param residual from the CRC-5 check
|
||||
* @param timestamp of the message
|
||||
* @return true if this value is repeatedly seen for this opcode.
|
||||
*/
|
||||
public boolean isValidCRC5(int opcode, int residual, long timestamp)
|
||||
{
|
||||
return mIgnoreCRCChecksums || isValid(opcode, residual, timestamp, mCrc5TrackerMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the residual value is commonly seen for this opcode.
|
||||
* @param opcode of the link control message
|
||||
* @param residual from the CRC-5 check
|
||||
* @param timestamp of the message
|
||||
* @return true if this value is repeatedly seen for this opcode.
|
||||
*/
|
||||
public boolean isValidCSBK(int opcode, int residual, long timestamp)
|
||||
{
|
||||
return mIgnoreCRCChecksums || isValid(opcode, residual, timestamp, mCsbkTrackerMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the opcode and residual mask value have been seen recently, multiple times.
|
||||
* @param opcode to check
|
||||
* @param residual from the CRC or RS checksum.
|
||||
* @param timestamp of the message
|
||||
* @param trackerMap to check
|
||||
* @return true if the opcode and residual have been seen recently, multiple times.
|
||||
*/
|
||||
private boolean isValid(int opcode, int residual, long timestamp, Map<Integer, OpcodeMaskTracker> trackerMap)
|
||||
{
|
||||
if(opcode >= 0)
|
||||
{
|
||||
int alternateMask = CRCDMR.calculateResidual(csbk.getMessage(), 0, 80);
|
||||
OpcodeMaskTracker tracker = trackerMap.get(opcode);
|
||||
|
||||
Map<Integer,MaskTracker> map;
|
||||
Set<MaskTracker> sortedSet;
|
||||
|
||||
if(csbk.getTimeslot() == 1)
|
||||
if(tracker != null)
|
||||
{
|
||||
map = mCsbkTrackerMapTS1;
|
||||
sortedSet = mSortedTrackersTS1;
|
||||
return tracker.isValid(residual, timestamp);
|
||||
}
|
||||
else
|
||||
{
|
||||
map = mCsbkTrackerMapTS2;
|
||||
sortedSet = mSortedTrackersTS2;
|
||||
trackerMap.put(opcode, new OpcodeMaskTracker(residual, timestamp));
|
||||
}
|
||||
}
|
||||
|
||||
if(map.containsKey(alternateMask))
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks a set of mask values for an opcode value.
|
||||
*/
|
||||
public static class OpcodeMaskTracker
|
||||
{
|
||||
private final Map<Integer, MaskTracker> mTrackerMap = new TreeMap<>();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param mask to track for the opcode
|
||||
* @param timestamp when the mask was detected, for tracking staleness
|
||||
*/
|
||||
public OpcodeMaskTracker(int mask, long timestamp)
|
||||
{
|
||||
mTrackerMap.put(mask, new MaskTracker(timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the mask is valid by checking for multiple recent usage.
|
||||
* @param mask to check
|
||||
* @param timestamp when the mask was used
|
||||
* @return true if the mask has been seen multiple times recently
|
||||
*/
|
||||
public boolean isValid(int mask, long timestamp)
|
||||
{
|
||||
boolean valid = false;
|
||||
|
||||
if(mTrackerMap.containsKey(mask))
|
||||
{
|
||||
MaskTracker matchingTracker = map.get(alternateMask);
|
||||
matchingTracker.increment(csbk.getTimestamp());
|
||||
|
||||
if(matchingTracker.isValid())
|
||||
{
|
||||
csbk.checkCRC(matchingTracker.getTrackedValue());
|
||||
}
|
||||
MaskTracker matchingTracker = mTrackerMap.get(mask);
|
||||
matchingTracker.increment(timestamp);
|
||||
valid = matchingTracker.isValid();
|
||||
}
|
||||
else
|
||||
{
|
||||
//Attempt to use other tracked mask values in order of observed count and also remove stale trackers
|
||||
boolean found = false;
|
||||
MaskTracker next;
|
||||
Iterator<MaskTracker> it = sortedSet.iterator();
|
||||
mTrackerMap.put(mask, new MaskTracker(timestamp));
|
||||
}
|
||||
|
||||
Iterator<Map.Entry<Integer,MaskTracker>> it = mTrackerMap.entrySet().iterator();
|
||||
{
|
||||
while(it.hasNext())
|
||||
{
|
||||
next = it.next();
|
||||
|
||||
//If the tracker is stale, remove it
|
||||
if(next.isStale(csbk.getTimestamp()))
|
||||
if(it.next().getValue().isStale(timestamp))
|
||||
{
|
||||
it.remove();
|
||||
map.remove(next.getTrackedValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
//If we haven't found a match yet, attempt to correct the message using this tracked mask value
|
||||
if(!found)
|
||||
{
|
||||
csbk.checkCRC(next.getTrackedValue());
|
||||
|
||||
if(csbk.isValid())
|
||||
{
|
||||
found = true;
|
||||
next.increment(csbk.getTimestamp());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//If we didn't find a perfect match or even a close match (with 1 bit error), create a new tracked value.
|
||||
if(!found)
|
||||
{
|
||||
MaskTracker tracker = new MaskTracker(alternateMask, csbk.getTimestamp());
|
||||
map.put(alternateMask, tracker);
|
||||
sortedSet.add(tracker);
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for(Map.Entry<Integer,MaskTracker> entry: mTrackerMap.entrySet())
|
||||
{
|
||||
sb.append("\t").append("Mask [").append(entry.getKey()).append("] ").append(entry.getValue()).append("\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,11 +199,8 @@ public class DMRCrcMaskManager
|
||||
* Alternate CRC mask value tracker. Tracks the number of observances of the mask value to detect when an alternate
|
||||
* CRC mask pattern is employed and can then be used to automatically correct CRC values for messages.
|
||||
*/
|
||||
public class MaskTracker implements Comparable<MaskTracker>
|
||||
public static class MaskTracker implements Comparable<MaskTracker>
|
||||
{
|
||||
private static final long STALENESS_TIME_THRESHOLD = TimeUnit.MINUTES.toMillis(2);
|
||||
private static final int STALENESS_COUNT_THRESHOLD = 100;
|
||||
private int mTrackedValue;
|
||||
private int mObservationCount;
|
||||
private int mStalenessCount;
|
||||
private long mLastUpdated;
|
||||
@@ -180,16 +208,20 @@ public class DMRCrcMaskManager
|
||||
/**
|
||||
* Constructs an instance for the specified mask, sets the observation count to one and sets the last update to
|
||||
* the supplied timestamp.
|
||||
* @param residual value to track.
|
||||
* @param timestamp for the observation
|
||||
*/
|
||||
public MaskTracker(int residual, long timestamp)
|
||||
public MaskTracker(long timestamp)
|
||||
{
|
||||
mTrackedValue = residual;
|
||||
mObservationCount = 1;
|
||||
mLastUpdated = timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "Observed:" + mObservationCount + " Staleness:" + mStalenessCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of observations for this mask value.
|
||||
* @return count
|
||||
@@ -200,13 +232,13 @@ public class DMRCrcMaskManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that the residual value has been observed at least 3x times within the preceding 2 minute window,
|
||||
* Indicates that the residual value has been observed at least 3x times within the preceding 2-minute window,
|
||||
* indicating that it is (likely) valid.
|
||||
* @return true if the tracked mask value is valid.
|
||||
*/
|
||||
public boolean isValid()
|
||||
{
|
||||
return mObservationCount >= 3;
|
||||
return mObservationCount >= 2;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -219,8 +251,8 @@ public class DMRCrcMaskManager
|
||||
public boolean isStale(long timestamp)
|
||||
{
|
||||
mStalenessCount++;
|
||||
return mStalenessCount > STALENESS_COUNT_THRESHOLD ||
|
||||
Math.abs(timestamp - mLastUpdated) > STALENESS_TIME_THRESHOLD;
|
||||
return mStalenessCount > CACHE_EJECTION_POLICY_COUNT ||
|
||||
Math.abs(timestamp - mLastUpdated) > CACHE_EJECTION_POLICY_TIME;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,15 +263,6 @@ public class DMRCrcMaskManager
|
||||
return mLastUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracked alternate mask value.
|
||||
* @return tracked mask value
|
||||
*/
|
||||
public int getTrackedValue()
|
||||
{
|
||||
return mTrackedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the observation count and use the supplied timestamp as the last updated timestamp.
|
||||
* @param timestamp for the observed message.
|
||||
@@ -248,7 +271,7 @@ public class DMRCrcMaskManager
|
||||
{
|
||||
mStalenessCount = 0;
|
||||
mObservationCount++;
|
||||
//Detect integer rollover and apply max integer value.
|
||||
//Detect integer rollover and adjust to max integer value.
|
||||
if(mObservationCount < 0)
|
||||
{
|
||||
mObservationCount = Integer.MAX_VALUE;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -86,12 +86,12 @@ public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplex
|
||||
private static final int SYMBOL_RATE = 4800;
|
||||
private static final Map<Double,float[]> BASEBAND_FILTERS = new HashMap<>();
|
||||
private DQPSKDemodulator mDemodulator;
|
||||
private DMRMessageFramer mMessageFramer = new DMRMessageFramer();
|
||||
private DMRSoftSymbolProcessor mSymbolProcessor = new DMRSoftSymbolProcessor(mMessageFramer);
|
||||
private DMRMessageProcessor mMessageProcessor;
|
||||
private final DMRMessageFramer mMessageFramer;
|
||||
private final DMRSoftSymbolProcessor mSymbolProcessor;
|
||||
private final DMRMessageProcessor mMessageProcessor;
|
||||
private IRealFilter mIBasebandFilter;
|
||||
private IRealFilter mQBasebandFilter;
|
||||
private PowerMonitor mPowerMonitor = new PowerMonitor();
|
||||
private final PowerMonitor mPowerMonitor = new PowerMonitor();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -99,7 +99,10 @@ public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplex
|
||||
*/
|
||||
public DMRDecoder(DecodeConfigDMR config, boolean isTrafficChannel)
|
||||
{
|
||||
mMessageProcessor = new DMRMessageProcessor(config);
|
||||
DMRCrcMaskManager crcMaskManager = new DMRCrcMaskManager(config.getIgnoreCRCChecksums());
|
||||
mMessageFramer = new DMRMessageFramer(crcMaskManager);
|
||||
mSymbolProcessor = new DMRSoftSymbolProcessor(mMessageFramer);
|
||||
mMessageProcessor = new DMRMessageProcessor(config, crcMaskManager);
|
||||
mMessageProcessor.setMessageListener(getMessageListener());
|
||||
setSampleRate(25000.0);
|
||||
|
||||
@@ -259,11 +262,9 @@ public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplex
|
||||
*/
|
||||
private void process(SourceEvent sourceEvent)
|
||||
{
|
||||
switch(sourceEvent.getEvent())
|
||||
if(sourceEvent != null && sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE)
|
||||
{
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
setSampleRate(sourceEvent.getValue().doubleValue());
|
||||
break;
|
||||
setSampleRate(sourceEvent.getValue().doubleValue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,10 +330,6 @@ public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplex
|
||||
errors = burst.getMessage().getCorrectedBitCount();
|
||||
mTotalMessageCounter++;
|
||||
|
||||
if(mTotalMessageCounter == 492)
|
||||
{
|
||||
int a = 0;
|
||||
}
|
||||
if(burst.isValid())
|
||||
{
|
||||
mBitErrorCounter += Math.max(errors, 0);
|
||||
|
||||
@@ -150,9 +150,9 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
|
||||
//For RAS protected systems, allows user to ignore CRC checksums and still decode the system
|
||||
if(channel.getDecodeConfiguration() instanceof DecodeConfigDMR)
|
||||
if(channel.getDecodeConfiguration() instanceof DecodeConfigDMR config)
|
||||
{
|
||||
mIgnoreCRCChecksums = ((DecodeConfigDMR)channel.getDecodeConfiguration()).getIgnoreCRCChecksums();
|
||||
mIgnoreCRCChecksums = config.getIgnoreCRCChecksums();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -857,7 +857,10 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
//Channel rotation monitor normally uses only CONTROL state, so when we detect that we're a
|
||||
//Capacity plus system, add ACTIVE as an active state to the monitor. This can be requested repeatedly.
|
||||
getInterModuleEventBus().post(CAPACITY_PLUS_ACTIVE_STATE_REQUEST);
|
||||
if(getInterModuleEventBus() != null)
|
||||
{
|
||||
getInterModuleEventBus().post(CAPACITY_PLUS_ACTIVE_STATE_REQUEST);
|
||||
}
|
||||
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot()));
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -35,7 +35,7 @@ import java.util.Arrays;
|
||||
* DMR message framer. Processes a DMR symbol stream to produce DMR messages/bursts. Employs two symbol buffers to
|
||||
* track and frame both DMR timeslots. Responds to external sync detections to alternately activate one of the two
|
||||
* buffers and capture a DMR burst, convert it to the correct message format and send it to a message listener.
|
||||
*
|
||||
* <p>
|
||||
* This framer is able to detect DMR Direct mode and track one active and one inactive timeslot across each voice
|
||||
* super frame in addition to framing base and mobile station data and voice bursts.
|
||||
*/
|
||||
@@ -44,8 +44,8 @@ public class DMRMessageFramer implements Listener<Dibit>
|
||||
private static final int DIBIT_CACH_START = 0;
|
||||
private static final int DIBIT_BURST_END = 144;
|
||||
private Listener<IMessage> mMessageListener;
|
||||
private Dibit[] mBufferA = new Dibit[144];
|
||||
private Dibit[] mBufferB = new Dibit[144];
|
||||
private final Dibit[] mBufferA = new Dibit[144];
|
||||
private final Dibit[] mBufferB = new Dibit[144];
|
||||
private int mBufferAPointer = 0;
|
||||
private int mBufferBPointer = 0;
|
||||
private DMRSyncPattern mBufferAPattern = DMRSyncPattern.UNKNOWN;
|
||||
@@ -58,14 +58,16 @@ public class DMRMessageFramer implements Listener<Dibit>
|
||||
private int mDibitSinceTimestampCounter = 0;
|
||||
private long mReferenceTimestamp = 0;
|
||||
private boolean mRunning = false;
|
||||
private final DMRMessageFactory mMessageFactory;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public DMRMessageFramer()
|
||||
public DMRMessageFramer(DMRCrcMaskManager crcMaskManager)
|
||||
{
|
||||
Arrays.fill(mBufferA, Dibit.D00_PLUS_1);
|
||||
Arrays.fill(mBufferB, Dibit.D00_PLUS_1);
|
||||
mMessageFactory = new DMRMessageFactory(crcMaskManager);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,7 +140,7 @@ public class DMRMessageFramer implements Listener<Dibit>
|
||||
|
||||
mDibitCounter = 0;
|
||||
|
||||
CorrectedBinaryMessage message = getMessage(mBufferA, DIBIT_CACH_START, DIBIT_BURST_END);
|
||||
CorrectedBinaryMessage message = getMessage(mBufferA);
|
||||
CACH cach = CACH.getCACH(message);
|
||||
|
||||
if(mBufferAPattern.hasCACH() && cach.isValid() && mBufferATimeslot != cach.getTimeslot())
|
||||
@@ -156,7 +158,7 @@ public class DMRMessageFramer implements Listener<Dibit>
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(DMRMessageFactory.create(mBufferAPattern, message, cach, getTimestamp(), mBufferATimeslot));
|
||||
dispatch(mMessageFactory.create(mBufferAPattern, message, cach, getTimestamp(), mBufferATimeslot));
|
||||
|
||||
//Since voice frames only have sync on the first burst, we use pseudo-patterns to track the rest of the bursts
|
||||
//across the voice super-frame. If the current pattern is a voice frame, set it to the next pseudo voice sync.
|
||||
@@ -206,7 +208,7 @@ public class DMRMessageFramer implements Listener<Dibit>
|
||||
|
||||
mDibitCounter = 0;
|
||||
|
||||
CorrectedBinaryMessage burst = getMessage(mBufferB, DIBIT_CACH_START, DIBIT_BURST_END);
|
||||
CorrectedBinaryMessage burst = getMessage(mBufferB);
|
||||
CACH cach = CACH.getCACH(burst);
|
||||
|
||||
if(mBufferBPattern.hasCACH() && cach.isValid() && mBufferBTimeslot != cach.getTimeslot())
|
||||
@@ -225,7 +227,7 @@ public class DMRMessageFramer implements Listener<Dibit>
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(DMRMessageFactory.create(mBufferBPattern, burst, cach, getTimestamp(), mBufferBTimeslot));
|
||||
dispatch(mMessageFactory.create(mBufferBPattern, burst, cach, getTimestamp(), mBufferBTimeslot));
|
||||
|
||||
//Since voice frames only have sync on the first burst, we use pseudo-patterns to track the rest of the bursts
|
||||
//across the voice super-frame. If the current pattern is a voice frame, set it to the next pseudo voice sync.
|
||||
@@ -249,7 +251,7 @@ public class DMRMessageFramer implements Listener<Dibit>
|
||||
|
||||
/**
|
||||
* Dispatches the message to the registered message listener.
|
||||
* @param message
|
||||
* @param message to dispatch
|
||||
*/
|
||||
private void dispatch(IMessage message)
|
||||
{
|
||||
@@ -286,17 +288,16 @@ public class DMRMessageFramer implements Listener<Dibit>
|
||||
|
||||
/**
|
||||
* Creates a binary message from the dibit buffer.
|
||||
*
|
||||
* @param buffer containing dibits
|
||||
* @param start index inclusive
|
||||
* @param end index exclusive.
|
||||
* @return binary message
|
||||
*/
|
||||
private CorrectedBinaryMessage getMessage(Dibit[] buffer, int start, int end)
|
||||
private CorrectedBinaryMessage getMessage(Dibit[] buffer)
|
||||
{
|
||||
CorrectedBinaryMessage message = new CorrectedBinaryMessage(2 * (end - start));
|
||||
CorrectedBinaryMessage message = new CorrectedBinaryMessage(2 * (DMRMessageFramer.DIBIT_BURST_END - DMRMessageFramer.DIBIT_CACH_START));
|
||||
|
||||
Dibit dibit = null;
|
||||
for(int i = start; i < end; i++)
|
||||
Dibit dibit;
|
||||
for(int i = DMRMessageFramer.DIBIT_CACH_START; i < DMRMessageFramer.DIBIT_BURST_END; i++)
|
||||
{
|
||||
dibit = buffer[i];
|
||||
message.add(dibit.getBit1(), dibit.getBit2());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.dmr;
|
||||
|
||||
import io.github.dsheirer.edac.CRCDMR;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.message.SyncLossMessage;
|
||||
@@ -63,28 +64,31 @@ import org.slf4j.LoggerFactory;
|
||||
public class DMRMessageProcessor implements Listener<IMessage>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageProcessor.class);
|
||||
private DecodeConfigDMR mConfigDMR;
|
||||
private VoiceSuperFrameProcessor mSuperFrameProcessor1 = new VoiceSuperFrameProcessor();
|
||||
private VoiceSuperFrameProcessor mSuperFrameProcessor2 = new VoiceSuperFrameProcessor();
|
||||
private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1);
|
||||
private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2);
|
||||
private MBCAssembler mMBCAssembler;
|
||||
private PacketSequenceAssembler mPacketSequenceAssembler;
|
||||
private SLCAssembler mSLCAssembler = new SLCAssembler();
|
||||
private TalkerAliasAssembler mTalkerAliasAssembler = new TalkerAliasAssembler();
|
||||
private final DecodeConfigDMR mConfigDMR;
|
||||
private final VoiceSuperFrameProcessor mSuperFrameProcessor1 = new VoiceSuperFrameProcessor();
|
||||
private final VoiceSuperFrameProcessor mSuperFrameProcessor2 = new VoiceSuperFrameProcessor();
|
||||
private final FLCAssembler mFLCAssemblerTimeslot1;
|
||||
private final FLCAssembler mFLCAssemblerTimeslot2;
|
||||
private final MBCAssembler mMBCAssembler;
|
||||
private final PacketSequenceAssembler mPacketSequenceAssembler;
|
||||
private final SLCAssembler mSLCAssembler = new SLCAssembler();
|
||||
private final TalkerAliasAssembler mTalkerAliasAssembler = new TalkerAliasAssembler();
|
||||
private Listener<IMessage> mMessageListener;
|
||||
private Map<Integer,TimeslotFrequency> mTimeslotFrequencyMap = new TreeMap<>();
|
||||
private DMRCrcMaskManager mCrcMaskManager = new DMRCrcMaskManager();
|
||||
private boolean mIgnoreCrcChecksums;
|
||||
private final Map<Integer,TimeslotFrequency> mTimeslotFrequencyMap = new TreeMap<>();
|
||||
private final DMRCrcMaskManager mCrcMaskManager;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param config for the DMR decoder
|
||||
* @param crcMaskManager for message error detection and correction.
|
||||
*/
|
||||
public DMRMessageProcessor(DecodeConfigDMR config)
|
||||
public DMRMessageProcessor(DecodeConfigDMR config, DMRCrcMaskManager crcMaskManager)
|
||||
{
|
||||
mConfigDMR = config;
|
||||
mIgnoreCrcChecksums = config.getIgnoreCRCChecksums();
|
||||
mMBCAssembler = new MBCAssembler(mIgnoreCrcChecksums);
|
||||
mCrcMaskManager = crcMaskManager;
|
||||
mMBCAssembler = new MBCAssembler(crcMaskManager.isIgnoreCRCChecksums());
|
||||
mFLCAssemblerTimeslot1 = new FLCAssembler(1, crcMaskManager);
|
||||
mFLCAssemblerTimeslot2 = new FLCAssembler(2, crcMaskManager);
|
||||
|
||||
for(TimeslotFrequency timeslotFrequency: config.getTimeslotMap())
|
||||
{
|
||||
@@ -102,7 +106,7 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
*/
|
||||
private boolean isValid(IMessage message)
|
||||
{
|
||||
return mIgnoreCrcChecksums || message.isValid();
|
||||
return mCrcMaskManager.isIgnoreCRCChecksums() || message.isValid();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,10 +127,12 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
mFLCAssemblerTimeslot2.reset();
|
||||
}
|
||||
|
||||
//Detect and correct messages employing an alternate CRC mask pattern.
|
||||
if(!message.isValid() && message instanceof CSBKMessage csbk)
|
||||
//Detect and correct messages employing an alternate CRC mask pattern (ie RAS) when ignore CRC is disabled
|
||||
if(message instanceof CSBKMessage csbk && !message.isValid())
|
||||
{
|
||||
mCrcMaskManager.check(csbk);
|
||||
int opcode = csbk.getOpcode().getValue();
|
||||
int residual = CRCDMR.calculateResidual(csbk.getMessage(), 0, 80);
|
||||
csbk.setValid(mCrcMaskManager.isValidCSBK(opcode, residual, csbk.getTimestamp()));
|
||||
}
|
||||
|
||||
if(message instanceof FullLCMessage flc)
|
||||
@@ -199,10 +205,8 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
}
|
||||
}
|
||||
//Extract the Short Link Control message fragment from the DMR burst message when it has one
|
||||
else if(message instanceof DMRBurst)
|
||||
else if(message instanceof DMRBurst dmrBurst)
|
||||
{
|
||||
DMRBurst dmrBurst = (DMRBurst)message;
|
||||
|
||||
if(dmrBurst.hasCACH())
|
||||
{
|
||||
CACH cach = dmrBurst.getCACH();
|
||||
@@ -272,9 +276,8 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
*/
|
||||
private void enrich(IMessage message)
|
||||
{
|
||||
if(message instanceof ITimeslotFrequencyReceiver)
|
||||
if(message instanceof ITimeslotFrequencyReceiver receiver)
|
||||
{
|
||||
ITimeslotFrequencyReceiver receiver = (ITimeslotFrequencyReceiver)message;
|
||||
int[] lcns = receiver.getLogicalChannelNumbers();
|
||||
|
||||
List<TimeslotFrequency> timeslotFrequencies = new ArrayList<>();
|
||||
@@ -316,8 +319,8 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error applying compressed talkgroup setting to identifier in message: " + message +
|
||||
" [" + message.getClass() + "]");
|
||||
mLog.error("Error applying compressed talkgroup setting to identifier in message: {} [{}]",
|
||||
message, message.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -28,6 +28,8 @@ import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.util.Callback;
|
||||
|
||||
/**
|
||||
@@ -40,6 +42,7 @@ public class TimeslotFrequency
|
||||
private LongProperty mUplinkFrequencyProperty = new SimpleLongProperty();
|
||||
private DoubleProperty mDownlinkMhzProperty = new SimpleDoubleProperty();
|
||||
private DoubleProperty mUplinkMhzProperty = new SimpleDoubleProperty();
|
||||
private StringProperty mDescriptionProperty = new SimpleStringProperty();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -60,6 +63,12 @@ public class TimeslotFrequency
|
||||
return copy;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public StringProperty descriptionProperty()
|
||||
{
|
||||
return mDescriptionProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logical Slot Number property
|
||||
*/
|
||||
@@ -122,7 +131,7 @@ public class TimeslotFrequency
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String getLogicalSlotNumbers()
|
||||
public void updateDescription()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
@@ -148,7 +157,25 @@ public class TimeslotFrequency
|
||||
sb.append("(empty)");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
setDescription(sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Description of the logical slot numbers for the channel number.
|
||||
*/
|
||||
@JsonIgnore
|
||||
public String getDescription()
|
||||
{
|
||||
return mDescriptionProperty.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description of the timeslots for the channel number.
|
||||
* @param description of the logical timeslots for the channel number.
|
||||
*/
|
||||
public void setDescription(String description)
|
||||
{
|
||||
mDescriptionProperty.set(description);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,6 +185,7 @@ public class TimeslotFrequency
|
||||
public void setNumber(int lcn)
|
||||
{
|
||||
mNumberProperty.set(lcn);
|
||||
updateDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,6 +240,6 @@ public class TimeslotFrequency
|
||||
public static Callback<TimeslotFrequency,Observable[]> extractor()
|
||||
{
|
||||
return (TimeslotFrequency tf) -> new Observable[] {tf.getNumberProperty(), tf.downlinkFrequencyProperty(),
|
||||
tf.uplinkFrequencyProperty(), tf.getDownlinkMHz(), tf.getUplinkMHz()};
|
||||
tf.uplinkFrequencyProperty(), tf.getDownlinkMHz(), tf.getUplinkMHz(), tf.descriptionProperty()};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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,6 +22,7 @@ package io.github.dsheirer.module.decode.dmr.message;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.message.EmptyTimeslotPlaceholderMessage;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRCrcMaskManager;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.DMRDataMessageFactory;
|
||||
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceAMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
|
||||
@@ -31,9 +32,22 @@ import io.github.dsheirer.protocol.Protocol;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* DMR Message Factory class for creating messages from DMR bursts.
|
||||
*/
|
||||
public class DMRMessageFactory
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageFactory.class);
|
||||
private DMRDataMessageFactory mDataMessageFactory;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param crcMaskManager for error detection and correction.
|
||||
*/
|
||||
public DMRMessageFactory(DMRCrcMaskManager crcMaskManager)
|
||||
{
|
||||
mDataMessageFactory = new DMRDataMessageFactory(crcMaskManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DMR message wrapper for the binary message. DMR message type is specified by the sync pattern.
|
||||
@@ -43,8 +57,8 @@ public class DMRMessageFactory
|
||||
* @param timeslot for the burst
|
||||
* @return DMRMessage instance or null for UNKNOWN sync pattern
|
||||
*/
|
||||
public static IMessage create(DMRSyncPattern syncPattern, CorrectedBinaryMessage binaryMessage, CACH cach,
|
||||
long timestamp, int timeslot)
|
||||
public IMessage create(DMRSyncPattern syncPattern, CorrectedBinaryMessage binaryMessage, CACH cach, long timestamp,
|
||||
int timeslot)
|
||||
{
|
||||
switch(syncPattern)
|
||||
{
|
||||
@@ -60,11 +74,11 @@ public class DMRMessageFactory
|
||||
return createVoiceMessage(syncPattern, binaryMessage, cach, timestamp, 2);
|
||||
case BASE_STATION_DATA:
|
||||
case MOBILE_STATION_DATA:
|
||||
return DMRDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, timeslot);
|
||||
return mDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, timeslot);
|
||||
case DIRECT_DATA_TIMESLOT_1:
|
||||
return DMRDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, 1);
|
||||
return mDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, 1);
|
||||
case DIRECT_DATA_TIMESLOT_2:
|
||||
return DMRDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, 2);
|
||||
return mDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, 2);
|
||||
case DIRECT_EMPTY_TIMESLOT:
|
||||
return new EmptyTimeslotPlaceholderMessage(timestamp, 288, Protocol.DMR, timeslot);
|
||||
case UNKNOWN:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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.BitSetFullException;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCDMR;
|
||||
import io.github.dsheirer.edac.trellis.ViterbiDecoder_3_4_DMR;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRCrcMaskManager;
|
||||
import io.github.dsheirer.module.decode.dmr.bptc.BPTC_196_96;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock1Rate;
|
||||
@@ -45,6 +46,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.header.VoiceHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraDataEncryptionHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MNISProprietaryDataHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaDataEncryptionHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessageFactory;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.usb.USBData;
|
||||
@@ -62,6 +65,16 @@ public class DMRDataMessageFactory
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DMRDataMessageFactory.class);
|
||||
private static final ViterbiDecoder_3_4_DMR VITERBI_DECODER = new ViterbiDecoder_3_4_DMR();
|
||||
private final LCMessageFactory mLinkControlMessageFactory;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param maskManager for managing CSBK and link control error detection.
|
||||
*/
|
||||
public DMRDataMessageFactory(DMRCrcMaskManager maskManager)
|
||||
{
|
||||
mLinkControlMessageFactory = new LCMessageFactory(maskManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a data message class
|
||||
@@ -72,8 +85,8 @@ public class DMRDataMessageFactory
|
||||
* @param timeslot for the message
|
||||
* @return data message instance
|
||||
*/
|
||||
public static DataMessage create(DMRSyncPattern pattern, CorrectedBinaryMessage message, CACH cach, long timestamp,
|
||||
int timeslot)
|
||||
public DataMessage create(DMRSyncPattern pattern, CorrectedBinaryMessage message, CACH cach, long timestamp,
|
||||
int timeslot)
|
||||
{
|
||||
SlotType slotType = SlotType.getSlotType(message);
|
||||
|
||||
@@ -107,18 +120,12 @@ public class DMRDataMessageFactory
|
||||
}
|
||||
return csbk;
|
||||
case USB_DATA:
|
||||
CorrectedBinaryMessage usbdPayload = payload;
|
||||
switch(USBData.getServiceType(usbdPayload))
|
||||
DataMessage usb = new USBData(pattern, payload, cach, slotType, timestamp, timeslot);
|
||||
if(payload.getCorrectedBitCount() < 0)
|
||||
{
|
||||
case LIP_SHORT_LOCATION_REQUEST:
|
||||
default:
|
||||
DataMessage usb =new USBData(pattern, payload, cach, slotType, timestamp, timeslot);
|
||||
if(payload.getCorrectedBitCount() < 0)
|
||||
{
|
||||
usb.setValid(false);
|
||||
}
|
||||
return usb;
|
||||
usb.setValid(false);
|
||||
}
|
||||
return usb;
|
||||
case MBC_ENC_HEADER:
|
||||
case MBC_HEADER:
|
||||
DataMessage mbc = new MBCHeader(pattern, payload, cach, slotType, timestamp, timeslot);
|
||||
@@ -129,14 +136,18 @@ public class DMRDataMessageFactory
|
||||
return mbc;
|
||||
case CHANNEL_CONTROL_ENC_HEADER:
|
||||
case PI_HEADER:
|
||||
DataMessage pi = new PiHeader(pattern, payload, cach, slotType, timestamp, timeslot);
|
||||
LCMessage piLinkControl = mLinkControlMessageFactory.createFullEncryption(payload, timestamp,
|
||||
timeslot);
|
||||
DataMessage pi = new PiHeader(pattern, payload, cach, slotType, timestamp, timeslot, piLinkControl);
|
||||
if(payload.getCorrectedBitCount() < 0)
|
||||
{
|
||||
pi.setValid(false);
|
||||
}
|
||||
return pi;
|
||||
case VOICE_HEADER:
|
||||
DataMessage voice = new VoiceHeader(pattern, payload, cach, slotType, timestamp, timeslot);
|
||||
LCMessage vhLinkControl = mLinkControlMessageFactory.createFull(payload, timestamp, timeslot, false);
|
||||
DataMessage voice = new VoiceHeader(pattern, payload, cach, slotType, timestamp, timeslot,
|
||||
vhLinkControl);
|
||||
if(payload.getCorrectedBitCount() < 0)
|
||||
{
|
||||
voice.setValid(false);
|
||||
@@ -151,7 +162,8 @@ public class DMRDataMessageFactory
|
||||
switch(dpf)
|
||||
{
|
||||
case CONFIRMED_DATA_PACKET:
|
||||
ConfirmedDataHeader cdh = new ConfirmedDataHeader(pattern, payload, cach, slotType, timestamp, timeslot);
|
||||
ConfirmedDataHeader cdh = new ConfirmedDataHeader(pattern, payload, cach, slotType,
|
||||
timestamp, timeslot);
|
||||
cdh.setValid(valid);
|
||||
return cdh;
|
||||
case PROPRIETARY_DATA_PACKET:
|
||||
@@ -231,7 +243,8 @@ public class DMRDataMessageFactory
|
||||
return dh;
|
||||
}
|
||||
case TLC:
|
||||
DataMessage tlc = new Terminator(pattern, payload, cach, slotType, timestamp, timeslot);
|
||||
LCMessage tlcLinkControl = mLinkControlMessageFactory.createFull(payload, timestamp, timeslot, true);
|
||||
DataMessage tlc = new Terminator(pattern, payload, cach, slotType, timestamp, timeslot, tlcLinkControl);
|
||||
if(payload.getCorrectedBitCount() < 0)
|
||||
{
|
||||
tlc.setValid(false);
|
||||
@@ -285,8 +298,7 @@ public class DMRDataMessageFactory
|
||||
/**
|
||||
* De-scramble, decode and error check a BPTC protected raw message and return a 96-bit error corrected
|
||||
* payload or null.
|
||||
* @param message
|
||||
* @return
|
||||
* @param message containing a payload
|
||||
*/
|
||||
private static CorrectedBinaryMessage getPayload(CorrectedBinaryMessage message)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -69,6 +69,14 @@ public abstract class DataMessage extends DMRBurst
|
||||
return getMessage().getInt(BPTC_RESERVED_BITS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Value of the reserved bits. Normally this is 2 for RAS enabled systems.
|
||||
*/
|
||||
public int getRAS()
|
||||
{
|
||||
return getBPTCReservedBits();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the BPTC reserved bits value is anything other than 0.
|
||||
* @return true if non-zero.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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,8 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessageFactory;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
|
||||
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -45,11 +43,13 @@ public abstract class DataMessageWithLinkControl extends DataMessage
|
||||
* @param slotType for this data message
|
||||
* @param timestamp message was received
|
||||
* @param timeslot for the DMR burst
|
||||
* @param linkControl message
|
||||
*/
|
||||
public DataMessageWithLinkControl(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach,
|
||||
SlotType slotType, long timestamp, int timeslot)
|
||||
SlotType slotType, long timestamp, int timeslot, LCMessage linkControl)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
mLCMessage = linkControl;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,12 +57,6 @@ public abstract class DataMessageWithLinkControl extends DataMessage
|
||||
*/
|
||||
public LCMessage getLCMessage()
|
||||
{
|
||||
if(mLCMessage == null)
|
||||
{
|
||||
boolean isTerminator = getSlotType().getDataType() == DataType.TLC;
|
||||
mLCMessage = LCMessageFactory.createFull(getMessage(), getTimestamp(), getTimeslot(), isTerminator);
|
||||
}
|
||||
|
||||
return mLCMessage;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -258,42 +258,19 @@ public class CapacityMaxAdvantageModeVoiceChannelUpdate extends CSBKMessage impl
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
if(hasChannel1Timeslot1())
|
||||
{
|
||||
getChannel1TS1().apply(timeslotFrequencies);
|
||||
}
|
||||
if(hasChannel1Timeslot2())
|
||||
{
|
||||
getChannel1TS2().apply(timeslotFrequencies);
|
||||
}
|
||||
if(hasChannel2Timeslot1())
|
||||
{
|
||||
getChannel2TS1().apply(timeslotFrequencies);
|
||||
}
|
||||
if(hasChannel2Timeslot2())
|
||||
{
|
||||
getChannel2TS2().apply(timeslotFrequencies);
|
||||
}
|
||||
getChannel1TS1().apply(timeslotFrequencies);
|
||||
getChannel1TS2().apply(timeslotFrequencies);
|
||||
getChannel2TS1().apply(timeslotFrequencies);
|
||||
getChannel2TS2().apply(timeslotFrequencies);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getLogicalChannelNumbers()
|
||||
{
|
||||
//Since both timeslots share the same LCN, we use just TS1's channel for both channel 1 and channel 2
|
||||
if((hasChannel1Timeslot1() || hasChannel1Timeslot2()) && (hasChannel2Timeslot1() || hasChannel2Timeslot2()))
|
||||
{
|
||||
return new int[]{getChannel1TS1().getChannelId(), getChannel2TS1().getChannelId()};
|
||||
}
|
||||
else if((hasChannel1Timeslot1() || hasChannel1Timeslot2()))
|
||||
{
|
||||
return new int[]{getChannel1TS1().getChannelId()};
|
||||
}
|
||||
else if((hasChannel2Timeslot1() || hasChannel2Timeslot2()))
|
||||
{
|
||||
return new int[]{getChannel2TS1().getChannelId()};
|
||||
}
|
||||
|
||||
return getChannel1TS1().getLogicalChannelNumbers();
|
||||
return new int[]{getChannel1TS1().getChannelNumber(),
|
||||
getChannel1TS2().getChannelNumber(),
|
||||
getChannel2TS1().getChannelNumber(),
|
||||
getChannel2TS2().getChannelNumber()};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -176,36 +176,14 @@ public class CapacityMaxOpenModeVoiceChannelUpdate extends CSBKMessage implement
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
if(hasTimeslot1())
|
||||
{
|
||||
getChannelTS1().apply(timeslotFrequencies);
|
||||
}
|
||||
if(hasTimeslot2())
|
||||
{
|
||||
getChannelTS2().apply(timeslotFrequencies);
|
||||
}
|
||||
getChannelTS1().apply(timeslotFrequencies);
|
||||
getChannelTS2().apply(timeslotFrequencies);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getLogicalChannelNumbers()
|
||||
{
|
||||
boolean ts1 = hasTimeslot1();
|
||||
boolean ts2 = hasTimeslot2();
|
||||
|
||||
if(ts1 && ts2)
|
||||
{
|
||||
return new int[]{getChannelTS1().getChannelNumber(), getChannelTS2().getChannelNumber()};
|
||||
}
|
||||
else if(ts1)
|
||||
{
|
||||
return new int[]{getChannelTS1().getChannelNumber()};
|
||||
}
|
||||
else if(ts2)
|
||||
{
|
||||
return new int[]{getChannelTS2().getChannelNumber()};
|
||||
}
|
||||
|
||||
return new int[0];
|
||||
return new int[]{getChannelTS1().getChannelNumber(), getChannelTS2().getChannelNumber()};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -42,11 +42,12 @@ public class DataHeader extends HeaderMessage
|
||||
* @param slotType for this data message
|
||||
* @param timestamp message was received
|
||||
* @param timeslot for the DMR burst
|
||||
* @param linkControl message from the payload
|
||||
*/
|
||||
public DataHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
|
||||
long timestamp, int timeslot)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.DataMessageWithLinkControl;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
|
||||
|
||||
/**
|
||||
@@ -39,10 +40,11 @@ public class HeaderMessage extends DataMessageWithLinkControl
|
||||
* @param slotType for this data message
|
||||
* @param timestamp message was received
|
||||
* @param timeslot for the DMR burst
|
||||
* @param linkControl message from the payload
|
||||
*/
|
||||
public HeaderMessage(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
|
||||
long timestamp, int timeslot)
|
||||
long timestamp, int timeslot, LCMessage linkControl)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot, linkControl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
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.lc.LCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessageFactory;
|
||||
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
|
||||
|
||||
/**
|
||||
@@ -40,24 +39,12 @@ public class PiHeader extends HeaderMessage
|
||||
* @param slotType for this data message
|
||||
* @param timestamp message was received
|
||||
* @param timeslot for the DMR burst
|
||||
* @param linkControl message from payload
|
||||
*/
|
||||
public PiHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||
public PiHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
|
||||
long timestamp, int timeslot, LCMessage linkControl)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the embedded link control message
|
||||
*/
|
||||
public LCMessage getLCMessage()
|
||||
{
|
||||
if(mLCMessage == null)
|
||||
{
|
||||
//Overrides default to ignore opcode and construct an encryption parameters message.
|
||||
mLCMessage = LCMessageFactory.createFullEncryption(getMessage(), getTimestamp(), getTimeslot());
|
||||
}
|
||||
|
||||
return mLCMessage;
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot, linkControl);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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,9 +20,10 @@
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.header;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
|
||||
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.lc.LCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
|
||||
|
||||
/**
|
||||
* Voice Header data message.
|
||||
@@ -38,17 +39,22 @@ public class VoiceHeader extends HeaderMessage
|
||||
* @param slotType for this data message
|
||||
* @param timestamp message was received
|
||||
* @param timeslot for the DMR burst
|
||||
* @param linkControl payload message
|
||||
*/
|
||||
public VoiceHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
|
||||
long timestamp, int timeslot)
|
||||
long timestamp, int timeslot, LCMessage linkControl)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot, linkControl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if(hasRAS())
|
||||
{
|
||||
sb.append("RAS:").append(getBPTCReservedBits()).append(" ");
|
||||
}
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" VOICE HEADER");
|
||||
sb.append(" ").append(getLCMessage());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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,8 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCDMR;
|
||||
import io.github.dsheirer.edac.Checksum_5_DMR;
|
||||
import io.github.dsheirer.edac.ReedSolomon_12_9_4_DMR;
|
||||
import io.github.dsheirer.edac.RS_12_9_DMR;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRCrcMaskManager;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.EncryptionParameters;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
|
||||
@@ -63,9 +64,18 @@ import org.slf4j.LoggerFactory;
|
||||
public class LCMessageFactory
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(LCMessageFactory.class);
|
||||
private static final ReedSolomon_12_9_4_DMR REED_SOLOMON_12_9_4_DMR = new ReedSolomon_12_9_4_DMR();
|
||||
private static final int TERMINATOR_LINK_CONTROL_CRC_MASK = 0x99;
|
||||
private static final int VOICE_LINK_CONTROL_CRC_MASK = 0x96;
|
||||
private final DMRCrcMaskManager mMaskManager;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param maskManager to check and/or override error detection
|
||||
*/
|
||||
public LCMessageFactory(DMRCrcMaskManager maskManager)
|
||||
{
|
||||
mMaskManager = maskManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a full link control message specifically for the PI_HEADER message type.
|
||||
@@ -74,7 +84,7 @@ public class LCMessageFactory
|
||||
* @param timeslot of the original message
|
||||
* @return encryption parameters link control.
|
||||
*/
|
||||
public static FullLCMessage createFullEncryption(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||
public FullLCMessage createFullEncryption(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||
{
|
||||
return new EncryptionParameters(message, timestamp, timeslot);
|
||||
}
|
||||
@@ -87,28 +97,27 @@ public class LCMessageFactory
|
||||
* @param isTerminator set to true if this is LC for a terminator data burst.
|
||||
* @return message class
|
||||
*/
|
||||
public static FullLCMessage createFull(CorrectedBinaryMessage message, long timestamp, int timeslot, boolean isTerminator)
|
||||
public FullLCMessage createFull(CorrectedBinaryMessage message, long timestamp, int timeslot, boolean isTerminator)
|
||||
{
|
||||
if(message == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Message cannot be null");
|
||||
}
|
||||
|
||||
boolean valid = true;
|
||||
int residual = 0;
|
||||
|
||||
if(message.size() == 77)
|
||||
{
|
||||
valid = Checksum_5_DMR.isValid(message);
|
||||
residual = Checksum_5_DMR.isValid(message);
|
||||
}
|
||||
else if(message.size() == 96)
|
||||
{
|
||||
//RS(12,9,4) can correct up to floor(4/2) = 1 bit error.
|
||||
valid = REED_SOLOMON_12_9_4_DMR.correctFullLinkControl(message,
|
||||
isTerminator ? TERMINATOR_LINK_CONTROL_CRC_MASK : VOICE_LINK_CONTROL_CRC_MASK);
|
||||
//RS(12,9,4) can correct up to floor((12-9)/2) = 1 symbol (or up to 8 bits if they are in the same symbol).
|
||||
residual = RS_12_9_DMR.correct(message, isTerminator ? TERMINATOR_LINK_CONTROL_CRC_MASK : VOICE_LINK_CONTROL_CRC_MASK);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.warn("Unrecognized Link Control Message Size: " + message.size());
|
||||
mLog.warn("Unrecognized Link Control Message Size: {}", message.size());
|
||||
}
|
||||
|
||||
LCOpcode opcode = FullLCMessage.getOpcode(message);
|
||||
@@ -116,13 +125,29 @@ public class LCMessageFactory
|
||||
//Some Hytera Tier-3 systems use a zero-valued mask when the FLC is carried in the voice header or terminator
|
||||
//and in the same transmission it uses the standard mask when carried across the voice frames. It doesn't make
|
||||
//sense, but maybe it's a bug in their software implementation?
|
||||
if(!valid && message.size() == 96 && opcode == LCOpcode.FULL_STANDARD_GROUP_VOICE_CHANNEL_USER)
|
||||
if(residual != 0 && message.size() == 96 && opcode == LCOpcode.FULL_STANDARD_GROUP_VOICE_CHANNEL_USER)
|
||||
{
|
||||
//Retry the RS(12,9,4) with a mask value of zero
|
||||
valid = REED_SOLOMON_12_9_4_DMR.correctFullLinkControl(message, 0);
|
||||
residual = RS_12_9_DMR.correct(message, 0);
|
||||
}
|
||||
|
||||
FullLCMessage flc = null;
|
||||
boolean valid = (residual != 0);
|
||||
|
||||
if(!valid)
|
||||
{
|
||||
if(message.size() == 96)
|
||||
{
|
||||
//Check if the residual CRC check value is commonly seen for this opcode (ie RAS).
|
||||
valid = mMaskManager.isValidRS12_9(opcode.getValue(), residual, timestamp);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Check if the residual CRC check value is commonly seen for this opcode (ie RAS).
|
||||
valid = mMaskManager.isValidCRC5(opcode.getValue(), residual, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
FullLCMessage flc;
|
||||
|
||||
switch(opcode)
|
||||
{
|
||||
@@ -208,47 +233,24 @@ public class LCMessageFactory
|
||||
{
|
||||
LCOpcode opcode = ShortLCMessage.getOpcode(message);
|
||||
|
||||
ShortLCMessage slc = null;
|
||||
|
||||
switch(opcode)
|
||||
ShortLCMessage slc = switch(opcode)
|
||||
{
|
||||
case SHORT_STANDARD_NULL_MESSAGE:
|
||||
slc = new NullMessage(message, timestamp, timeslot);
|
||||
break;
|
||||
case SHORT_STANDARD_ACTIVITY_UPDATE:;
|
||||
slc = new ActivityUpdateMessage(message, timestamp, timeslot);
|
||||
break;
|
||||
case SHORT_CAPACITY_PLUS_REST_CHANNEL_NOTIFICATION:
|
||||
slc = new CapacityPlusRestChannel(message, timestamp, timeslot);
|
||||
break;
|
||||
case SHORT_CONNECT_PLUS_CONTROL_CHANNEL:
|
||||
slc = new ConnectPlusControlChannel(message, timestamp, timeslot);
|
||||
break;
|
||||
case SHORT_CONNECT_PLUS_TRAFFIC_CHANNEL:
|
||||
slc = new ConnectPlusTrafficChannel(message, timestamp, timeslot);
|
||||
break;
|
||||
case SHORT_STANDARD_CONTROL_CHANNEL_SYSTEM_PARAMETERS:
|
||||
slc = new ControlChannelSystemParameters(message, timestamp, timeslot);
|
||||
break;
|
||||
case SHORT_STANDARD_TRAFFIC_CHANNEL_SYSTEM_PARAMETERS:
|
||||
slc = new TrafficChannelSystemParameters(message, timestamp, timeslot);
|
||||
break;
|
||||
case SHORT_HYTERA_XPT_CHANNEL:
|
||||
case SHORT_STANDARD_XPT_CHANNEL:
|
||||
slc = new HyteraXPTChannel(message, timestamp, timeslot);
|
||||
break;
|
||||
case SHORT_STANDARD_UNKNOWN:
|
||||
default:
|
||||
slc = new UnknownShortLCMessage(message, timestamp, timeslot);
|
||||
break;
|
||||
}
|
||||
|
||||
boolean valid = CRCDMR.crc8(message, 36) == 0;
|
||||
|
||||
// System.out.println("SLC MSG:" + message.toHexString() + " CRC-8 RESIDUAL: " + CRCDMR.crc8(message, 36));
|
||||
//
|
||||
slc.setValid(valid);
|
||||
case SHORT_STANDARD_NULL_MESSAGE -> new NullMessage(message, timestamp, timeslot);
|
||||
case SHORT_STANDARD_ACTIVITY_UPDATE -> new ActivityUpdateMessage(message, timestamp, timeslot);
|
||||
case SHORT_CAPACITY_PLUS_REST_CHANNEL_NOTIFICATION ->
|
||||
new CapacityPlusRestChannel(message, timestamp, timeslot);
|
||||
case SHORT_CONNECT_PLUS_CONTROL_CHANNEL -> new ConnectPlusControlChannel(message, timestamp, timeslot);
|
||||
case SHORT_CONNECT_PLUS_TRAFFIC_CHANNEL -> new ConnectPlusTrafficChannel(message, timestamp, timeslot);
|
||||
case SHORT_STANDARD_CONTROL_CHANNEL_SYSTEM_PARAMETERS ->
|
||||
new ControlChannelSystemParameters(message, timestamp, timeslot);
|
||||
case SHORT_STANDARD_TRAFFIC_CHANNEL_SYSTEM_PARAMETERS ->
|
||||
new TrafficChannelSystemParameters(message, timestamp, timeslot);
|
||||
case SHORT_HYTERA_XPT_CHANNEL, SHORT_STANDARD_XPT_CHANNEL ->
|
||||
new HyteraXPTChannel(message, timestamp, timeslot);
|
||||
default -> new UnknownShortLCMessage(message, timestamp, timeslot);
|
||||
};
|
||||
|
||||
slc.setValid(CRCDMR.crc8(message, 36) == 0);
|
||||
return slc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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,30 +22,30 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.bits.BitSetFullException;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRCrcMaskManager;
|
||||
import io.github.dsheirer.module.decode.dmr.bptc.BPTC_128_77;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessageFactory;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.LCSS;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Reassembles embedded signalling fragments into Full Link Control Messages
|
||||
*/
|
||||
public class FLCAssembler
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(FLCAssembler.class);
|
||||
private CorrectedBinaryMessage mAssemblingMessage;
|
||||
private int mFragmentCount = 0;
|
||||
private long mTimestamp;
|
||||
private int mTimeslot;
|
||||
private BPTC_128_77 mBPTC = new BPTC_128_77();
|
||||
private final int mTimeslot;
|
||||
private final BPTC_128_77 mBPTC = new BPTC_128_77();
|
||||
private final LCMessageFactory mMessageFactory;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public FLCAssembler(int timeslot)
|
||||
public FLCAssembler(int timeslot, DMRCrcMaskManager maskManager)
|
||||
{
|
||||
mTimeslot = timeslot;
|
||||
mMessageFactory = new LCMessageFactory(maskManager);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,8 +111,7 @@ public class FLCAssembler
|
||||
|
||||
if(mFragmentCount == 4)
|
||||
{
|
||||
// message = decode(mAssemblingMessage, mTimestamp, mTimeslot);
|
||||
message = decode2(mAssemblingMessage, mTimestamp, mTimeslot);
|
||||
message = decode(mAssemblingMessage, mTimestamp, mTimeslot);
|
||||
}
|
||||
|
||||
mAssemblingMessage = null;
|
||||
@@ -123,12 +122,12 @@ public class FLCAssembler
|
||||
/**
|
||||
* Decodes an assembled FLC message, performs error detection and correction and returns a constructed FLC message
|
||||
*/
|
||||
private FullLCMessage decode2(BinaryMessage message, long timestamp, int timeslot)
|
||||
private FullLCMessage decode(BinaryMessage message, long timestamp, int timeslot)
|
||||
{
|
||||
if(message != null)
|
||||
{
|
||||
CorrectedBinaryMessage extractedMessage = mBPTC.extract(message);
|
||||
FullLCMessage flco = LCMessageFactory.createFull(extractedMessage, timestamp, timeslot, false);
|
||||
FullLCMessage flco = mMessageFactory.createFull(extractedMessage, timestamp, timeslot, false);
|
||||
|
||||
if(extractedMessage.getCorrectedBitCount() < 0)
|
||||
{
|
||||
@@ -141,111 +140,6 @@ public class FLCAssembler
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// /**
|
||||
// * Decodes an assembled FLC message, performs error detection and correction and returns a constructed FLC message
|
||||
// */
|
||||
// private static FullLCMessage decode(BinaryMessage message, long timestamp, int timeslot)
|
||||
// {
|
||||
// if(message != null)
|
||||
// {
|
||||
// System.out.println("FLC Raw:" + message);
|
||||
// boolean valid = true;
|
||||
//
|
||||
// CorrectedBinaryMessage descrambled = new CorrectedBinaryMessage(128);
|
||||
// int src;
|
||||
// descrambled.set(127, message.get(127));
|
||||
// for(int i = 0; i < 127; i++)
|
||||
// {
|
||||
// src = (i * 8) % 127;
|
||||
// boolean x = message.get(src);
|
||||
// descrambled.set(i, message.get(src));
|
||||
// }
|
||||
//
|
||||
// System.out.println("FLC Des:" + descrambled);
|
||||
// int errorCount = 0;
|
||||
//
|
||||
// for(int row = 0; row < 8; row++)
|
||||
// {
|
||||
// int errorIndex = Hamming16.getErrorIndex(descrambled, row * 16);
|
||||
// BinaryMessage rowErrors = BPTC_128_77.getRowErrors(descrambled);
|
||||
// BinaryMessage columnErrors = BPTC_128_77.getColumnErrors(descrambled);
|
||||
//
|
||||
// int rowErrorCount = Hamming16.checkAndCorrect(descrambled, row * 16);
|
||||
//
|
||||
// if(rowErrorCount == 0)
|
||||
// {
|
||||
// System.out.println("Row [" + row + "]");
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// System.out.println("Row [" + row + "] Error Count: " + rowErrorCount + " Index:" + errorIndex +
|
||||
// " Value: " + message.get(errorIndex) +
|
||||
// " Row Errors: " + rowErrors + " Column Errors: " + columnErrors );
|
||||
// }
|
||||
//
|
||||
// errorCount += rowErrorCount;
|
||||
//
|
||||
// if(errorCount > 1)
|
||||
// {
|
||||
// valid = false;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// //If valid, check the column parity values
|
||||
// if(valid)
|
||||
// {
|
||||
// //Check the column parity values
|
||||
// for(int column = 0; column < 16; column++)
|
||||
// {
|
||||
// boolean parity = descrambled.get(column);
|
||||
//
|
||||
// for(int y = 1; y < 8; y++)
|
||||
// {
|
||||
// parity ^= descrambled.get(column + (y * 16));
|
||||
// }
|
||||
//
|
||||
// if(parity)
|
||||
// {
|
||||
// valid = false;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// //Extract the message. Row 1 and 2 are 11 bits. Rows 3-7 are 10 bits.
|
||||
// CorrectedBinaryMessage extractedMessage = new CorrectedBinaryMessage(77);
|
||||
// int pointer = 0;
|
||||
//
|
||||
// for(int row = 0; row < 2; row++)
|
||||
// {
|
||||
// for(int column = 0; column < 11; column++)
|
||||
// {
|
||||
// extractedMessage.set(pointer++, descrambled.get(row * 16 + column));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// for(int row = 2; row < 7; row++)
|
||||
// {
|
||||
// for(int column = 0; column < 10; column++)
|
||||
// {
|
||||
// extractedMessage.set(pointer++, descrambled.get(row * 16 + column));
|
||||
// }
|
||||
//
|
||||
// //Transfer the checksum parity bit from this row
|
||||
// extractedMessage.set(70 + row, descrambled.get(row * 16 + 10));
|
||||
// }
|
||||
//
|
||||
// extractedMessage.setCorrectedBitCount(errorCount);
|
||||
//
|
||||
//
|
||||
// FullLCMessage flco = LCMessageFactory.createFull(extractedMessage, timestamp, timeslot, false);
|
||||
// flco.setValid(valid);
|
||||
// return flco;
|
||||
// }
|
||||
//
|
||||
// return null;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Adds the cach payload to the currently assembling message
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.DataMessageWithLinkControl;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
|
||||
|
||||
/**
|
||||
@@ -39,10 +40,11 @@ public class Terminator extends DataMessageWithLinkControl
|
||||
* @param slotType for this data message
|
||||
* @param timestamp message was received
|
||||
* @param timeslot for the DMR burst
|
||||
* @param linkControl message from the payload
|
||||
*/
|
||||
public Terminator(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
|
||||
long timestamp, int timeslot)
|
||||
long timestamp, int timeslot, LCMessage linkControl)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot, linkControl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -62,4 +62,16 @@ public class ByteUtil
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String toHexString(int[] intBytes)
|
||||
{
|
||||
byte[] converted = new byte[intBytes.length];
|
||||
|
||||
for(int x = 0; x < intBytes.length; x++)
|
||||
{
|
||||
converted[x] = (byte)(intBytes[x] & 0xFF);
|
||||
}
|
||||
|
||||
return toHexString(converted);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user