package uk.org.smithfamily.mslogger.comms; import java.util.Arrays; import java.util.zip.CRC32; import uk.org.smithfamily.mslogger.log.DebugLogManager; import android.util.Log; /** * Helper class used for communication with MS3 ECU that support CRC32 validated protocol. * * See http://www.msextra.com/doc/ms3/files/ms3_serial_protocol_0.05.pdf */ public class CRC32ProtocolHandler { private final static int PAYLOAD_LENGTH = 2; private final static int TYPE_LENGTH = 1; private final static int CRC32_LENGTH = 4; /** * Wrap an array of bytes into a CRC32 validation * * @param naked * @return */ public static byte[] wrap(byte[] naked) { byte[] wrapped = new byte[naked.length + PAYLOAD_LENGTH + CRC32_LENGTH]; // Add 2 bytes for payload size and 4 bytes for CRC32 wrapped[0] = 0; wrapped[1] = (byte) naked.length; System.arraycopy(naked, 0, wrapped, 2, naked.length); // Copy wrapped into naked CRC32 check = new CRC32(); check.update(naked); long crc32value = check.getValue(); int crcIndex = wrapped.length - CRC32_LENGTH; wrapped[crcIndex] = (byte) ((crc32value >> 24) & 0xff); wrapped[crcIndex + 1] = (byte) ((crc32value >> 16) & 0xff); wrapped[crcIndex + 2] = (byte) ((crc32value >> 8) & 0xff); wrapped[crcIndex + 3] = (byte) ((crc32value >> 0) & 0xff); return wrapped; } /** * Check if the wrapped array of bytes is valid * * @param wrapped * @return */ public static boolean check(byte[] wrapped) { // The type is included in the CRC calculation int notDataLength = PAYLOAD_LENGTH + CRC32_LENGTH; // Not enough data to do a check if (wrapped.length < notDataLength) { return true; } // Extract crc32 byte[] crc32 = new byte[CRC32_LENGTH]; System.arraycopy(wrapped, wrapped.length - CRC32_LENGTH, crc32, 0, CRC32_LENGTH); // Copy crc32 from wrapped into crc32 bytes array // Remove payload size, only keep the data byte[] data = new byte[wrapped.length - notDataLength]; System.arraycopy(wrapped, 2, data, 0, wrapped.length - notDataLength); // Generate CRC32 on data CRC32 check = new CRC32(); check.update(data); long crc32value = check.getValue(); // Copy the value into a byte buffer as sign changes can cause weirdness, just dodge that bullet. byte[] crcBytes = new byte[4]; crcBytes[0] = (byte) ((crc32value >> 24) & 0xff); crcBytes[1] = (byte) ((crc32value >> 16) & 0xff); crcBytes[2] = (byte) ((crc32value >> 8) & 0xff); crcBytes[3] = (byte) ((crc32value >> 0) & 0xff); // Compare crc32 we generated from the data with what we got to see if it match if (crc32[0] == crcBytes[0] && crc32[1] == crcBytes[1] && crc32[2] == crcBytes[2] && crc32[3] == crcBytes[3]) { return true; } DebugLogManager.INSTANCE.log("CRC32 mismatch from MS3!: " + Arrays.toString(wrapped), Log.DEBUG); DebugLogManager.INSTANCE.log("CRC32 mismatch crc32: " + crc32[0] + " ==? " + crcBytes[0], Log.DEBUG); DebugLogManager.INSTANCE.log("CRC32 mismatch crc32: " + crc32[1] + " ==? " + crcBytes[1], Log.DEBUG); DebugLogManager.INSTANCE.log("CRC32 mismatch crc32: " + crc32[2] + " ==? " + crcBytes[2], Log.DEBUG); DebugLogManager.INSTANCE.log("CRC32 mismatch crc32: " + crc32[3] + " ==? " + crcBytes[3], Log.DEBUG); return false; } /** * Take a wrapped array of bytes and unwrap it * * @param wrapped * @return */ public static byte[] unwrap(byte[] wrapped) { int notDataLength = getValidationLength(); if (wrapped.length < notDataLength)// Bail out { return wrapped; } byte[] naked = new byte[wrapped.length - notDataLength]; System.arraycopy(wrapped, 3, naked, 0, wrapped.length - notDataLength); return naked; } /** * @return The total length of the payload, type and CRC32 */ public static int getValidationLength() { return PAYLOAD_LENGTH + TYPE_LENGTH + CRC32_LENGTH; } }