Adding CRC32 feature in Secure DFU, not completed.

This commit is contained in:
Aleksander Nowakowski 2016-06-09 14:02:28 +02:00
parent 7c82d575be
commit 09cb5f76eb
7 changed files with 170 additions and 32 deletions

View File

@ -31,6 +31,7 @@ import android.preference.PreferenceManager;
import java.io.IOException; import java.io.IOException;
import java.util.UUID; import java.util.UUID;
import java.util.zip.CRC32;
import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException; import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException;
import no.nordicsemi.android.dfu.internal.exception.DfuException; import no.nordicsemi.android.dfu.internal.exception.DfuException;
@ -255,12 +256,14 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory;
* @throws DeviceDisconnectedException * @throws DeviceDisconnectedException
* @throws UploadAbortedException * @throws UploadAbortedException
*/ */
protected void writeInitData(final BluetoothGattCharacteristic characteristic) throws DfuException, DeviceDisconnectedException, UploadAbortedException { protected void writeInitData(final BluetoothGattCharacteristic characteristic, final CRC32 crc32) throws DfuException, DeviceDisconnectedException, UploadAbortedException {
try { try {
byte[] data = new byte[20]; byte[] data = new byte[20];
int size; int size;
while ((size = mInitPacketStream.read(data, 0, data.length)) != -1) { while ((size = mInitPacketStream.read(data, 0, data.length)) != -1) {
writeInitPacket(characteristic, data, size); writeInitPacket(characteristic, data, size);
if (crc32 != null)
crc32.update(data, 0, size);
} }
} catch (final IOException e) { } catch (final IOException e) {
loge("Error while reading Init packet file", e); loge("Error while reading Init packet file", e);

View File

@ -249,14 +249,13 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
int size; int size;
try { try {
size = initPacketStream.available(); size = initPacketStream.available();
} catch (final IOException e) { } catch (final Exception e) {
size = 0; size = 0;
// not possible
} }
mInitPacketSizeInBytes = size; mInitPacketSizeInBytes = size;
try { try {
size = firmwareStream.available(); size = firmwareStream.available();
} catch (final IOException e) { } catch (final Exception e) {
size = 0; size = 0;
// not possible // not possible
} }

View File

@ -416,6 +416,10 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres
* Thrown when the firmware file is not word-aligned. The firmware size must be dividable by 4 bytes. * Thrown when the firmware file is not word-aligned. The firmware size must be dividable by 4 bytes.
*/ */
public static final int ERROR_FILE_SIZE_INVALID = ERROR_MASK | 0x0C; public static final int ERROR_FILE_SIZE_INVALID = ERROR_MASK | 0x0C;
/**
* Thrown when the received CRC does not match with the calculated one. The service will try 3 times to send the data, and if the CRC fails each time this error will be thrown.
*/
public static final int ERROR_CRC_ERROR = ERROR_MASK | 0x0D;
/** /**
* Flag set when the DFU target returned a DFU error. Look for DFU specification to get error codes. * Flag set when the DFU target returned a DFU error. Look for DFU specification to get error codes.
*/ */

View File

@ -47,7 +47,10 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
/** This method must return true if the device is compatible with this DFU implementation, false otherwise. */ /** This method must return true if the device is compatible with this DFU implementation, false otherwise. */
boolean hasRequiredCharacteristics(final BluetoothGatt gatt); boolean hasRequiredCharacteristics(final BluetoothGatt gatt);
/** Initializes the DFU implementation and does some initial setting up. */ /**
* Initializes the DFU implementation and does some initial setting up.
* @return true if initialization was successful and the DFU process may begin, false to finish teh DFU service
*/
boolean initialize(final Intent intent, final BluetoothGatt gatt, final int fileType, final InputStream firmwareStream, final InputStream initPacketStream) throws DfuException, DeviceDisconnectedException, UploadAbortedException; boolean initialize(final Intent intent, final BluetoothGatt gatt, final int fileType, final InputStream firmwareStream, final InputStream initPacketStream) throws DfuException, DeviceDisconnectedException, UploadAbortedException;
/** Performs the DFU process. */ /** Performs the DFU process. */

View File

@ -521,7 +521,7 @@ import no.nordicsemi.android.error.LegacyDfuError;
writeOpCode(mControlPointCharacteristic, OP_CODE_INIT_DFU_PARAMS_START); writeOpCode(mControlPointCharacteristic, OP_CODE_INIT_DFU_PARAMS_START);
logi("Sending " + mImageSizeInBytes + " bytes of init packet"); logi("Sending " + mImageSizeInBytes + " bytes of init packet");
writeInitData(mPacketCharacteristic); writeInitData(mPacketCharacteristic, null);
logi("Sending the Initialize DFU Parameters COMPLETE (Op Code = 2, Value = 1)"); logi("Sending the Initialize DFU Parameters COMPLETE (Op Code = 2, Value = 1)");
writeOpCode(mControlPointCharacteristic, OP_CODE_INIT_DFU_PARAMS_COMPLETE); writeOpCode(mControlPointCharacteristic, OP_CODE_INIT_DFU_PARAMS_COMPLETE);

View File

@ -28,8 +28,11 @@ import android.bluetooth.BluetoothGattService;
import android.content.Intent; import android.content.Intent;
import android.os.SystemClock; import android.os.SystemClock;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale; import java.util.Locale;
import java.util.UUID; import java.util.UUID;
import java.util.zip.CRC32;
import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException; import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException;
import no.nordicsemi.android.dfu.internal.exception.DfuException; import no.nordicsemi.android.dfu.internal.exception.DfuException;
@ -139,6 +142,17 @@ import no.nordicsemi.android.error.SecureDfuError;
return dfuService != null; return dfuService != null;
} }
@Override
public boolean initialize(final Intent intent, final BluetoothGatt gatt, final int fileType, final InputStream firmwareStream, final InputStream initPacketStream) throws DfuException, DeviceDisconnectedException, UploadAbortedException {
if (initPacketStream == null) {
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_ERROR, "The Init packet is required by this version DFU Bootloader");
mService.terminateConnection(gatt, DfuBaseService.ERROR_INIT_PACKET_REQUIRED);
return false;
}
return super.initialize(intent, gatt, fileType, firmwareStream, initPacketStream);
}
@Override @Override
public boolean hasRequiredCharacteristics(final BluetoothGatt gatt) { public boolean hasRequiredCharacteristics(final BluetoothGatt gatt) {
final BluetoothGattService dfuService = gatt.getService(DFU_SERVICE_UUID); final BluetoothGattService dfuService = gatt.getService(DFU_SERVICE_UUID);
@ -191,6 +205,7 @@ import no.nordicsemi.android.error.SecureDfuError;
// End // End
try { try {
final CRC32 crc32 = new CRC32(); // Used to calculate CRC32 of the Init packet
ObjectChecksum checksum; ObjectChecksum checksum;
ObjectInfo info; ObjectInfo info;
byte[] response; byte[] response;
@ -206,35 +221,108 @@ import no.nordicsemi.android.error.SecureDfuError;
// Ignore this. DFU target will send an error if init packet is too large after sending the 'Create object' command // Ignore this. DFU target will send an error if init packet is too large after sending the 'Create object' command
} }
// Can we resume? If the offset obtained from the device is greater then zero we can compare it with the local init packet CRC
// and resume sending the init packet, or even skip sending it if the whole file was sent before.
boolean skipSendingInitPacket = false;
boolean resumeSendingInitPacket = false;
if (info.offset > 0) {
try {
// Read the same number of bytes from the current init packet to calculate local CRC32
final byte[] buffer = new byte[info.offset];
mInitPacketStream.read(buffer);
// Calculate the CRC32
crc32.update(buffer);
final int crc = (int) (crc32.getValue() & 0xFFFFFFFL);
if (info.CRC32 == crc) {
logi("Init packet CRC is the same");
if (info.offset == mInitPacketSizeInBytes) {
// The whole init packet was sent and it is equal to one we try to send now.
// There is no need to send it again. We may try to resume sending data.
logi("-> Whole Init packet was sent before");
skipSendingInitPacket = true;
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Command object match Init packet");
} else {
logi("-> " + info.offset + " bytes of Init packet were sent before");
resumeSendingInitPacket = true;
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Resuming sending Init packet...");
}
} else {
// A different Init packet was sent before, or the error occurred while sending.
// We have to send the while Init packet again.
mInitPacketStream.reset();
crc32.reset();
}
} catch (final IOException e) {
loge("Error while reading " + info.offset + " bytes from the init packet stream", e);
try {
// Go back to the beginning of the stream, we will send the whole init packet
mInitPacketStream.reset();
crc32.reset();
} catch (final IOException e1) {
loge("Error while resetting the init packet stream", e1);
mService.terminateConnection(gatt, DfuBaseService.ERROR_FILE_IO_EXCEPTION);
return;
}
}
}
if (!skipSendingInitPacket) {
// The Init packet is sent different way in this implementation than the firmware, and receiving PRNs is not implemented. // The Init packet is sent different way in this implementation than the firmware, and receiving PRNs is not implemented.
// This value might have been stored on the device, so we have to explicitly disable PRNs. // This value might have been stored on the device, so we have to explicitly disable PRNs.
logi("Disabling Packet Receipt Notifications (Op Code = 2, Value = 0)"); logi("Disabling Packet Receipt Notifications (Op Code = 2, Value = 0)");
setPacketReceiptNotifications(0); setPacketReceiptNotifications(0);
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Packet Receipt Notif disabled (Op Code = 2, Value = 0)"); mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Packet Receipt Notif disabled (Op Code = 2, Value = 0)");
// TODO resume uploading for (int i = 0; i <= 2; i++) {
if (!resumeSendingInitPacket) {
// Create the Init object // Create the Init object
logi("Creating Init packet object (Op Code = 1, Type = 1, Size = " + mInitPacketSizeInBytes + ")"); logi("Creating Init packet object (Op Code = 1, Type = 1, Size = " + mInitPacketSizeInBytes + ")");
writeCreateRequest(OBJECT_COMMAND, mInitPacketSizeInBytes); writeCreateRequest(OBJECT_COMMAND, mInitPacketSizeInBytes);
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Command object created"); mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Command object created");
}
// Write Init data to the Packet Characteristic // Write Init data to the Packet Characteristic
logi("Sending " + mInitPacketSizeInBytes + " bytes of init packet..."); logi("Sending " + (mInitPacketSizeInBytes - info.offset) + " bytes of init packet...");
writeInitData(mPacketCharacteristic); writeInitData(mPacketCharacteristic, crc32);
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Command object sent"); final int crc = (int) (crc32.getValue() & 0xFFFFFFFL);
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, String.format(Locale.US, "Command object sent (CRC = %08X)", crc));
// Calculate Checksum // Calculate Checksum
logi("Sending Calculate Checksum command (Op Code = 3)"); logi("Sending Calculate Checksum command (Op Code = 3)");
checksum = readChecksum(); checksum = readChecksum();
// TODO validate
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, String.format(Locale.US, "Checksum received (Offset = %d, CRC = %08X)", checksum.offset, checksum.CRC32)); mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, String.format(Locale.US, "Checksum received (Offset = %d, CRC = %08X)", checksum.offset, checksum.CRC32));
logi(String.format(Locale.US, "Checksum received (Offset = %d, CRC = %08X)", checksum.offset, checksum.CRC32)); logi(String.format(Locale.US, "Checksum received (Offset = %d, CRC = %08X)", checksum.offset, checksum.CRC32));
if (crc != checksum.CRC32) {
if (i < 2) {
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING, "CRC32 does not match! Retrying...(" + (i + 2) + "/3)");
logi("CRC32 does not match! Retrying...(" + (i + 2) + "/3)");
try {
// Go back to the beginning, we will send the whole Init packet again
resumeSendingInitPacket = false;
mInitPacketStream.reset();
crc32.reset();
} catch (final IOException e) {
loge("Error while resetting the init packet stream", e);
mService.terminateConnection(gatt, DfuBaseService.ERROR_FILE_IO_EXCEPTION);
return;
}
} else {
loge("CRC32 does not match!");
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_ERROR, "CRC32 does not match!");
mService.terminateConnection(gatt, DfuBaseService.ERROR_CRC_ERROR);
return;
}
} else {
break;
}
}
// Execute Init packet // Execute Init packet
logi("Executing init packet (Op Code = 4)"); logi("Executing init packet (Op Code = 4)");
writeExecute(); writeExecute();
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Command object executed"); mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Command object executed");
}
// Send the number of packets of firmware before receiving a receipt notification // Send the number of packets of firmware before receiving a receipt notification
final int numberOfPacketsBeforeNotification = mPacketsBeforeNotification; final int numberOfPacketsBeforeNotification = mPacketsBeforeNotification;

View File

@ -22,6 +22,8 @@
package no.nordicsemi.android.dfu.internal; package no.nordicsemi.android.dfu.internal;
import android.support.annotation.NonNull;
import com.google.gson.Gson; import com.google.gson.Gson;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -30,6 +32,7 @@ import java.io.InputStream;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
@ -61,6 +64,7 @@ public class ArchiveInputStream extends ZipInputStream {
/** Contains bytes arrays with BIN files. HEX files are converted to BIN before being added to this map. */ /** Contains bytes arrays with BIN files. HEX files are converted to BIN before being added to this map. */
private Map<String, byte[]> entries; private Map<String, byte[]> entries;
private Manifest manifest; private Manifest manifest;
private CRC32 crc32;
private byte[] applicationBytes; private byte[] applicationBytes;
private byte[] softDeviceBytes; private byte[] softDeviceBytes;
@ -75,6 +79,9 @@ public class ArchiveInputStream extends ZipInputStream {
private int applicationSize; private int applicationSize;
private int bytesRead; private int bytesRead;
private byte[] markedSource;
private int bytesReadFromMarkedSource;
/** /**
* <p> * <p>
* The ArchiveInputStream read HEX or BIN files from the Zip stream. It may skip some of them, depending on the value of the types parameter. * The ArchiveInputStream read HEX or BIN files from the Zip stream. It may skip some of them, depending on the value of the types parameter.
@ -103,6 +110,7 @@ public class ArchiveInputStream extends ZipInputStream {
public ArchiveInputStream(final InputStream stream, final int mbrSize, final int types) throws IOException { public ArchiveInputStream(final InputStream stream, final int mbrSize, final int types) throws IOException {
super(stream); super(stream);
this.crc32 = new CRC32();
this.entries = new HashMap<>(); this.entries = new HashMap<>();
this.bytesRead = 0; this.bytesRead = 0;
this.bytesReadFromCurrentSource = 0; this.bytesReadFromCurrentSource = 0;
@ -254,7 +262,7 @@ public class ArchiveInputStream extends ZipInputStream {
* but than it MUST include at least one of the following files: softdevice.bin/hex, bootloader.bin/hex, application.bin/hex. * but than it MUST include at least one of the following files: softdevice.bin/hex, bootloader.bin/hex, application.bin/hex.
* To support the init packet such ZIP file should contain also application.dat and/or system.dat (with the CRC16 of a SD, BL or SD+BL together). * To support the init packet such ZIP file should contain also application.dat and/or system.dat (with the CRC16 of a SD, BL or SD+BL together).
*/ */
private void parseZip(int mbrSize) throws IOException { private void parseZip(final int mbrSize) throws IOException {
final byte[] buffer = new byte[1024]; final byte[] buffer = new byte[1024];
String manifestData = null; String manifestData = null;
@ -304,7 +312,7 @@ public class ArchiveInputStream extends ZipInputStream {
} }
@Override @Override
public int read(final byte[] buffer) throws IOException { public int read(@NonNull final byte[] buffer) throws IOException {
int maxSize = currentSource.length - bytesReadFromCurrentSource; int maxSize = currentSource.length - bytesReadFromCurrentSource;
int size = buffer.length <= maxSize ? buffer.length : maxSize; int size = buffer.length <= maxSize ? buffer.length : maxSize;
System.arraycopy(currentSource, bytesReadFromCurrentSource, buffer, 0, size); System.arraycopy(currentSource, bytesReadFromCurrentSource, buffer, 0, size);
@ -322,9 +330,42 @@ public class ArchiveInputStream extends ZipInputStream {
size += nextSize; size += nextSize;
} }
bytesRead += size; bytesRead += size;
crc32.update(buffer, 0, size);
return size; return size;
} }
@Override
public boolean markSupported() {
return true;
}
/**
* Marks the current position in the stream. The parameter is ignored.
* @param readlimit this parameter is ignored, can be anything
*/
@Override
public void mark(final int readlimit) {
markedSource = currentSource;
bytesReadFromMarkedSource = bytesReadFromCurrentSource;
}
@Override
public void reset() throws IOException {
currentSource = markedSource;
bytesReadFromCurrentSource = bytesReadFromMarkedSource;
// Restore the CRC to the value is was on mark.
crc32.reset();
if (currentSource == bootloaderBytes && softDeviceBytes != null) {
crc32.update(softDeviceBytes);
}
crc32.update(currentSource, 0, bytesReadFromCurrentSource);
}
public int getCrc32() {
return (int) (crc32.getValue() & 0xFFFFFFFFL);
}
/** /**
* Returns the manifest object if it was specified in the ZIP file. * Returns the manifest object if it was specified in the ZIP file.
* @return the manifest object * @return the manifest object