package module.decode.p25.message.tdu.lc;
import module.decode.p25.message.P25Message;
import module.decode.p25.message.tsbk.motorola.MotorolaOpcode;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.LinkControlOpcode;
import module.decode.p25.reference.Vendor;
import module.decode.p25.reference.VendorLinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRC;
import edac.Golay24;
import edac.ReedSolomon_63_47_17;
public class TDULinkControlMessage extends P25Message
{
public final static Logger mLog = LoggerFactory.getLogger( TDULinkControlMessage.class );
public static final int[] LC_HEX_0 = { 64,65,66,67,68,69 };
public static final int[] LC_HEX_1 = { 70,71,72,73,74,75 };
public static final int[] LC_HEX_2 = { 88,89,90,91,92,93 };
public static final int[] LC_HEX_3 = { 94,95,96,97,98,99 };
public static final int[] LC_HEX_4 = { 112,113,114,115,116,117 };
public static final int[] LC_HEX_5 = { 118,119,120,121,122,123 };
public static final int[] LC_HEX_6 = { 136,137,138,139,140,141 };
public static final int[] LC_HEX_7 = { 142,143,144,145,146,147 };
public static final int[] LC_HEX_8 = { 160,161,162,163,164,165 };
public static final int[] LC_HEX_9 = { 166,167,168,169,170,171 };
public static final int[] LC_HEX_10 = { 184,185,186,187,188,189 };
public static final int[] LC_HEX_11 = { 190,191,192,193,194,195 };
public static final int[] RS_HEX_0 = { 208,209,210,211,212,213 };
public static final int[] RS_HEX_1 = { 214,215,216,217,218,219 };
public static final int[] RS_HEX_2 = { 232,233,234,235,236,237 };
public static final int[] RS_HEX_3 = { 238,239,240,241,242,243 };
public static final int[] RS_HEX_4 = { 256,257,258,259,260,261 };
public static final int[] RS_HEX_5 = { 262,263,264,265,266,267 };
public static final int[] RS_HEX_6 = { 280,281,282,283,284,285 };
public static final int[] RS_HEX_7 = { 286,287,288,289,290,291 };
public static final int[] RS_HEX_8 = { 304,305,306,307,308,309 };
public static final int[] RS_HEX_9 = { 310,311,312,313,314,315 };
public static final int[] RS_HEX_10 = { 328,329,330,331,332,333 };
public static final int[] RS_HEX_11 = { 334,335,336,337,338,339 };
public static final int ENCRYPTION_FLAG = 64;
public static final int IMPLICIT_VENDOR_FLAG = 65;
public static final int[] OPCODE = { 66,67,68,69,70,71 };
public static final int[] VENDOR = { 72,73,74,75,88,89,90,91 };
/* Reed-Solomon(24,12,13) code protects the link control word. Maximum
* correctable errors are: Hamming Distance(13) / 2 = 6 */
public static final ReedSolomon_63_47_17 mReedSolomonDecoder =
new ReedSolomon_63_47_17( 6 );
public TDULinkControlMessage( BinaryMessage message, DataUnitID duid,
AliasList aliasList )
{
super( message, duid, aliasList );
/* NID CRC is checked in the message framer so a constructed message
* means it passed the CRC */
mCRC = new CRC[ 3 ];
mCRC[ 0 ] = CRC.PASSED;
checkCRC();
}
/**
* Constructs a TDULinkControl message from an existing message and bypasses
* the CRC calculation.
*/
protected TDULinkControlMessage( TDULinkControlMessage message )
{
super( message.getSourceMessage(), DataUnitID.TDULC, message.getAliasList() );
mCRC = message.getCRC();
}
public CRC[] getCRC()
{
return mCRC;
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " " );
sb.append( mMessage.toString() );
return sb.toString();
}
public String getMessageStub()
{
StringBuilder sb = new StringBuilder();
sb.append( "NAC:" );
sb.append( getNAC() );
sb.append( " " );
sb.append( getDUID().getLabel() );
sb.append( " " );
if( isEncrypted() )
{
sb.append( "ENCRYPTED" );
}
else
{
if( isImplicitFormat() || getVendor() == Vendor.STANDARD )
{
sb.append( getOpcode().getLabel() );
}
else
{
sb.append( "VENDOR:" + getVendor().getLabel() + " " );
sb.append( getVendorOpcode().getLabel() );
}
}
return sb.toString();
}
public boolean isEncrypted()
{
return mMessage.get( ENCRYPTION_FLAG );
}
public boolean isImplicitFormat()
{
return mMessage.get( IMPLICIT_VENDOR_FLAG );
}
/**
* Standard opcodes when the Vendor is STANDARD
*/
public LinkControlOpcode getOpcode()
{
return LinkControlOpcode.fromValue( mMessage.getInt( OPCODE ) );
}
/**
* Returns a generic vendor-neutral opcode. Use this when there is a non
* standard vendor code.
*/
public VendorLinkControlOpcode getVendorOpcode()
{
return VendorLinkControlOpcode.fromValue( mMessage.getInt( OPCODE ) );
}
/**
* If the vendor is motorola, returns the Motorola OPCODE for this message
*/
public MotorolaOpcode getMotorolaOpcode()
{
return MotorolaOpcode.fromValue( mMessage.getInt( OPCODE ) );
}
public Vendor getVendor()
{
if( isImplicitFormat() )
{
return Vendor.STANDARD;
}
return Vendor.fromValue( mMessage.getInt( VENDOR ) );
}
/**
* Checks and repairs the 12 x 24-bit golay(24,12,8) codewords and then
* checks and repairs the 24 x 6-bit link control and reed-solomon
* RS(24,12,13) parity codewords
*/
private void checkCRC()
{
/* Check the Golay codewords */
int x = 64;
mCRC[ 1 ] = CRC.PASSED;
while( x < mMessage.size() )
{
mMessage = Golay24.checkAndCorrect( mMessage, x );
if( !( mMessage.getCRC() == CRC.PASSED ) )
{
mCRC[ 1 ] = CRC.FAILED_CRC;
}
x += 24;
}
/* 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_11 );
input[ 1 ] = mMessage.getInt( RS_HEX_10 );
input[ 2 ] = mMessage.getInt( RS_HEX_9 );
input[ 3 ] = mMessage.getInt( RS_HEX_8 );
input[ 4 ] = mMessage.getInt( RS_HEX_7 );
input[ 5 ] = mMessage.getInt( RS_HEX_6 );
input[ 6 ] = mMessage.getInt( RS_HEX_5 );
input[ 7 ] = mMessage.getInt( RS_HEX_4 );
input[ 8 ] = mMessage.getInt( RS_HEX_3 );
input[ 9 ] = mMessage.getInt( RS_HEX_2 );
input[ 10 ] = mMessage.getInt( RS_HEX_1 );
input[ 11 ] = mMessage.getInt( RS_HEX_0 );
input[ 12 ] = mMessage.getInt( LC_HEX_11 );
input[ 13 ] = mMessage.getInt( LC_HEX_10 );
input[ 14 ] = mMessage.getInt( LC_HEX_9 );
input[ 15 ] = mMessage.getInt( LC_HEX_8 );
input[ 16 ] = mMessage.getInt( LC_HEX_7 );
input[ 17 ] = mMessage.getInt( LC_HEX_6 );
input[ 18 ] = mMessage.getInt( LC_HEX_5 );
input[ 19 ] = mMessage.getInt( LC_HEX_4 );
input[ 20 ] = mMessage.getInt( LC_HEX_3 );
input[ 21 ] = mMessage.getInt( LC_HEX_2 );
input[ 22 ] = mMessage.getInt( LC_HEX_1 );
input[ 23 ] = mMessage.getInt( LC_HEX_0 );
/* indexes 24 - 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 link control data hex codewords */
repairHexCodeword( input, output, 12, LC_HEX_11 );
repairHexCodeword( input, output, 13, LC_HEX_10 );
repairHexCodeword( input, output, 14, LC_HEX_9 );
repairHexCodeword( input, output, 15, LC_HEX_8 );
repairHexCodeword( input, output, 16, LC_HEX_7 );
repairHexCodeword( input, output, 17, LC_HEX_6 );
repairHexCodeword( input, output, 18, LC_HEX_5 );
repairHexCodeword( input, output, 19, LC_HEX_4 );
repairHexCodeword( input, output, 20, LC_HEX_3 );
repairHexCodeword( input, output, 21, LC_HEX_2 );
repairHexCodeword( input, output, 22, LC_HEX_1 );
repairHexCodeword( input, output, 23, LC_HEX_0 );
/* If the golay check failed, mark it as corrected too */
if( mCRC[ 1 ] == CRC.FAILED_CRC )
{
mCRC[ 1 ] = CRC.CORRECTED;
}
}
}
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;
}
}
/**
* Indicates if the message passed the golay and reed-solomon parity checks
* or was corrected.
*/
public boolean isValid()
{
// mCRC[ 0 ] - always passes (otherwise message wouldn't be constructed )
// mCRC[ 1 ] - golay - always passes or corrected if mCRC[2] is valid
return mCRC[ 2 ] == CRC.PASSED || mCRC[ 2 ] == CRC.CORRECTED;
}
}