package module.decode.p25.message.hdu; import module.decode.p25.message.P25Message; import module.decode.p25.reference.DataUnitID; import module.decode.p25.reference.Encryption; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import alias.AliasList; import bits.BinaryMessage; import edac.CRC; import edac.Golay18; import edac.ReedSolomon_63_47_17; public class HDUMessage extends P25Message { private final static Logger mLog = LoggerFactory.getLogger( HDUMessage.class ); public static final int[] MESSAGE_INDICATOR_A = { 64,65,66,67,68,69,82,83, 84,85,86,87,100,101,102,103,104,105,118,119,120,121,122,123,136,137,138, 139,140,141,154,155,156,157,158,159 }; public static final int[] MESSAGE_INDICATOR_B = { 172,173,174,175,176,177, 190,191,192,193,194,195,208,209,210,211,212,213,226,227,228,229,230,231, 244,245,246,247,248,249,262,263,264,265,266,267 }; public static final int[] VENDOR_ID = { 280,281,282,283,284,285,298,299 }; public static final int[] ALGORITHM_ID = { 300,301,302,303,316,317,318,319 }; public static final int[] KEY_ID = { 320,321,334,335,336,337,338,339,352, 353,354,355,356,357,370,371 }; public static final int[] TALKGROUP_ID = { 372,373,374,375,388,389,390,391, 392,393,406,407,408,409,410,411 }; public static final int[] GOLAY_WORD_STARTS = { 64,82,100,118,136,154,172, 190,208,226,244,262,280,298,316,334,352,370,388,406,424,442,460,478, 496,514,532,550,568,586,604,622,640,658,676,694 }; public static final int[] CW_HEX_0 = { 64,65,66,67,68,69 }; public static final int[] CW_HEX_1 = { 82,83,84,85,86,87 }; public static final int[] CW_HEX_2 = { 100,101,102,103,104,105 }; public static final int[] CW_HEX_3 = { 118,119,120,121,122,123 }; public static final int[] CW_HEX_4 = { 136,137,138,139,140,141 }; public static final int[] CW_HEX_5 = { 154,155,156,157,158,159 }; public static final int[] CW_HEX_6 = { 172,173,174,175,176,177 }; public static final int[] CW_HEX_7 = { 190,191,192,193,194,195 }; public static final int[] CW_HEX_8 = { 208,209,210,211,212,213 }; public static final int[] CW_HEX_9 = { 226,227,228,229,230,231 }; public static final int[] CW_HEX_10 = { 244,245,246,247,248,249 }; public static final int[] CW_HEX_11 = { 262,263,264,265,266,267 }; public static final int[] CW_HEX_12 = { 280,281,282,283,284,285 }; public static final int[] CW_HEX_13 = { 298,299,300,301,302,303 }; public static final int[] CW_HEX_14 = { 316,317,318,319,320,321 }; public static final int[] CW_HEX_15 = { 334,335,336,337,338,339 }; public static final int[] CW_HEX_16 = { 352,353,354,355,356,357 }; public static final int[] CW_HEX_17 = { 370,371,372,373,374,375 }; public static final int[] CW_HEX_18 = { 388,389,390,391,392,393 }; public static final int[] CW_HEX_19 = { 406,407,408,409,410,411 }; public static final int[] RS_HEX_0 = { 424,425,426,427,428,429 }; public static final int[] RS_HEX_1 = { 442,443,444,445,446,447 }; public static final int[] RS_HEX_2 = { 460,461,462,463,464,465 }; public static final int[] RS_HEX_3 = { 478,479,480,481,482,483 }; public static final int[] RS_HEX_4 = { 496,497,498,499,500,501 }; public static final int[] RS_HEX_5 = { 514,515,516,517,518,519 }; public static final int[] RS_HEX_6 = { 532,533,534,535,536,537 }; public static final int[] RS_HEX_7 = { 550,551,552,553,554,555 }; public static final int[] RS_HEX_8 = { 568,569,570,571,572,573 }; public static final int[] RS_HEX_9 = { 586,587,588,589,590,591 }; public static final int[] RS_HEX_10 = { 604,605,606,607,608,609 }; public static final int[] RS_HEX_11 = { 622,623,624,625,626,627 }; public static final int[] RS_HEX_12 = { 640,641,642,643,644,645 }; public static final int[] RS_HEX_13 = { 658,659,660,661,662,663 }; public static final int[] RS_HEX_14 = { 676,677,678,679,680,681 }; public static final int[] RS_HEX_15 = { 694,695,696,697,698,699 }; /* Reed-Solomon(36,20,17) code protects the header word. Maximum * correctable errors are: Hamming Distance(17) / 2 = 8 */ public static final ReedSolomon_63_47_17 mReedSolomonDecoder = new ReedSolomon_63_47_17( 8 ); public HDUMessage( BinaryMessage message, DataUnitID duid, AliasList aliasList ) { super( message, duid, aliasList ); /* NID CRC is checked in the message framer, thus a constructed message * means it passed the CRC */ mCRC = new CRC[ 3 ]; mCRC[ 0 ] = CRC.PASSED; checkCRC(); } private void checkCRC() { /* Golay( 18,6,18 ) error detection and correction */ for( int index: GOLAY_WORD_STARTS ) { Golay18.checkAndCorrect( mMessage, index ); } mCRC[ 1 ] = CRC.PASSED; /* Reed-Solomon( 36,20,17 ) error detection and correction * Check the Reed-Solomon parity bits. The RS decoder expects the link * control data and reed solomon parity hex codewords in reverse order. * The RS(24,12,13) code used by P25 removes the left-hand 47 data hex * words, so we replace them with zeros. */ int[] input = new int [63]; int[] output = new int [63]; input[ 0 ] = mMessage.getInt( RS_HEX_15 ); input[ 1 ] = mMessage.getInt( RS_HEX_14 ); input[ 2 ] = mMessage.getInt( RS_HEX_13 ); input[ 3 ] = mMessage.getInt( RS_HEX_12 ); input[ 4 ] = mMessage.getInt( RS_HEX_11 ); input[ 5 ] = mMessage.getInt( RS_HEX_10 ); input[ 6 ] = mMessage.getInt( RS_HEX_9 ); input[ 7 ] = mMessage.getInt( RS_HEX_8 ); input[ 8 ] = mMessage.getInt( RS_HEX_7 ); input[ 9 ] = mMessage.getInt( RS_HEX_6 ); input[ 10 ] = mMessage.getInt( RS_HEX_5 ); input[ 11 ] = mMessage.getInt( RS_HEX_4 ); input[ 12 ] = mMessage.getInt( RS_HEX_3 ); input[ 13 ] = mMessage.getInt( RS_HEX_2 ); input[ 14 ] = mMessage.getInt( RS_HEX_1 ); input[ 15 ] = mMessage.getInt( RS_HEX_0 ); input[ 16 ] = mMessage.getInt( CW_HEX_19 ); input[ 17 ] = mMessage.getInt( CW_HEX_18 ); input[ 18 ] = mMessage.getInt( CW_HEX_17 ); input[ 19 ] = mMessage.getInt( CW_HEX_16 ); input[ 20 ] = mMessage.getInt( CW_HEX_15 ); input[ 21 ] = mMessage.getInt( CW_HEX_14 ); input[ 22 ] = mMessage.getInt( CW_HEX_13 ); input[ 23 ] = mMessage.getInt( CW_HEX_12 ); input[ 24 ] = mMessage.getInt( CW_HEX_11 ); input[ 25 ] = mMessage.getInt( CW_HEX_10 ); input[ 26 ] = mMessage.getInt( CW_HEX_9 ); input[ 27 ] = mMessage.getInt( CW_HEX_8 ); input[ 28 ] = mMessage.getInt( CW_HEX_7 ); input[ 29 ] = mMessage.getInt( CW_HEX_6 ); input[ 30 ] = mMessage.getInt( CW_HEX_5 ); input[ 31 ] = mMessage.getInt( CW_HEX_4 ); input[ 32 ] = mMessage.getInt( CW_HEX_3 ); input[ 33 ] = mMessage.getInt( CW_HEX_2 ); input[ 34 ] = mMessage.getInt( CW_HEX_1 ); input[ 35 ] = mMessage.getInt( CW_HEX_0 ); /* indexes 36 - 62 are defaulted to zero */ boolean irrecoverableErrors = mReedSolomonDecoder.decode( input, output ); if( irrecoverableErrors ) { mCRC[ 2 ] = CRC.FAILED_CRC; } else { mCRC[ 2 ] = CRC.PASSED; /* Only fix the codeword data hex codewords */ repairHexCodeword( input, output, 16, CW_HEX_19 ); repairHexCodeword( input, output, 17, CW_HEX_18 ); repairHexCodeword( input, output, 18, CW_HEX_17 ); repairHexCodeword( input, output, 19, CW_HEX_16 ); repairHexCodeword( input, output, 20, CW_HEX_15 ); repairHexCodeword( input, output, 21, CW_HEX_14 ); repairHexCodeword( input, output, 22, CW_HEX_13 ); repairHexCodeword( input, output, 23, CW_HEX_12 ); repairHexCodeword( input, output, 24, CW_HEX_11 ); repairHexCodeword( input, output, 25, CW_HEX_10 ); repairHexCodeword( input, output, 26, CW_HEX_9 ); repairHexCodeword( input, output, 27, CW_HEX_8 ); repairHexCodeword( input, output, 28, CW_HEX_7 ); repairHexCodeword( input, output, 29, CW_HEX_6 ); repairHexCodeword( input, output, 30, CW_HEX_5 ); repairHexCodeword( input, output, 31, CW_HEX_4 ); repairHexCodeword( input, output, 32, CW_HEX_3 ); repairHexCodeword( input, output, 33, CW_HEX_2 ); repairHexCodeword( input, output, 34, CW_HEX_1 ); repairHexCodeword( input, output, 35, CW_HEX_0 ); } } private void repairHexCodeword( int[] input, int[] output, int index, int[] indexSet ) { if( input[ index ] != output[ index ] ) { mMessage.load( indexSet[ 0 ], 6, output[ index ] ); mCRC[ 2 ] = CRC.CORRECTED; } } @Override public String getMessage() { StringBuilder sb = new StringBuilder(); sb.append( "NAC:" ); sb.append( getNAC() ); sb.append( " " ); sb.append( getDUID().getLabel() ); sb.append( " TALKGROUP:" + getTalkgroupID() ); Encryption encryption = getEncryption(); if( encryption != Encryption.UNENCRYPTED ) { sb.append( " ENCRYPTION:" ); if( encryption == Encryption.UNKNOWN ) { sb.append( "UNK ALGO ID:" ); sb.append( mMessage.getInt( ALGORITHM_ID ) ); } else { sb.append( encryption.name() ); } sb.append( " KEY ID:" + getKeyID() ); sb.append( " MSG INDICATOR:" + getMessageIndicator() ); } else { sb.append( " UNENCRYPTED" ); } sb.append( " " ); sb.append( mMessage.toString() ); return sb.toString(); } public String getMessageIndicator() { StringBuilder sb = new StringBuilder(); sb.append( mMessage.getHex( MESSAGE_INDICATOR_A, 9 ) ); sb.append( mMessage.getHex( MESSAGE_INDICATOR_B, 9 ) ); return sb.toString(); } public Encryption getEncryption() { return Encryption.fromValue( mMessage.getInt( ALGORITHM_ID ) ); } public boolean isEncrypted() { return getEncryption() != Encryption.UNENCRYPTED; } public int getKeyID() { return mMessage.getInt( KEY_ID ); } public String getTalkgroupID() { return mMessage.getHex( TALKGROUP_ID, 4 ); } @Override public String getToID() { return getTalkgroupID(); } public boolean isValid() { return mCRC[ 2 ] != null && mCRC[ 2 ] != CRC.FAILED_CRC; } }