Adding CRC32 feature in Secure DFU, not completed.
This commit is contained in:
parent
7c82d575be
commit
09cb5f76eb
|
@ -31,6 +31,7 @@ import android.preference.PreferenceManager;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException;
|
||||
import no.nordicsemi.android.dfu.internal.exception.DfuException;
|
||||
|
@ -255,12 +256,14 @@ import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory;
|
|||
* @throws DeviceDisconnectedException
|
||||
* @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 {
|
||||
byte[] data = new byte[20];
|
||||
int size;
|
||||
while ((size = mInitPacketStream.read(data, 0, data.length)) != -1) {
|
||||
writeInitPacket(characteristic, data, size);
|
||||
if (crc32 != null)
|
||||
crc32.update(data, 0, size);
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
loge("Error while reading Init packet file", e);
|
||||
|
|
|
@ -249,14 +249,13 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
int size;
|
||||
try {
|
||||
size = initPacketStream.available();
|
||||
} catch (final IOException e) {
|
||||
} catch (final Exception e) {
|
||||
size = 0;
|
||||
// not possible
|
||||
}
|
||||
mInitPacketSizeInBytes = size;
|
||||
try {
|
||||
size = firmwareStream.available();
|
||||
} catch (final IOException e) {
|
||||
} catch (final Exception e) {
|
||||
size = 0;
|
||||
// not possible
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
|
|
@ -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. */
|
||||
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;
|
||||
|
||||
/** Performs the DFU process. */
|
||||
|
|
|
@ -521,7 +521,7 @@ import no.nordicsemi.android.error.LegacyDfuError;
|
|||
writeOpCode(mControlPointCharacteristic, OP_CODE_INIT_DFU_PARAMS_START);
|
||||
|
||||
logi("Sending " + mImageSizeInBytes + " bytes of init packet");
|
||||
writeInitData(mPacketCharacteristic);
|
||||
writeInitData(mPacketCharacteristic, null);
|
||||
|
||||
logi("Sending the Initialize DFU Parameters COMPLETE (Op Code = 2, Value = 1)");
|
||||
writeOpCode(mControlPointCharacteristic, OP_CODE_INIT_DFU_PARAMS_COMPLETE);
|
||||
|
|
|
@ -28,8 +28,11 @@ import android.bluetooth.BluetoothGattService;
|
|||
import android.content.Intent;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException;
|
||||
import no.nordicsemi.android.dfu.internal.exception.DfuException;
|
||||
|
@ -139,6 +142,17 @@ import no.nordicsemi.android.error.SecureDfuError;
|
|||
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
|
||||
public boolean hasRequiredCharacteristics(final BluetoothGatt gatt) {
|
||||
final BluetoothGattService dfuService = gatt.getService(DFU_SERVICE_UUID);
|
||||
|
@ -191,6 +205,7 @@ import no.nordicsemi.android.error.SecureDfuError;
|
|||
// End
|
||||
|
||||
try {
|
||||
final CRC32 crc32 = new CRC32(); // Used to calculate CRC32 of the Init packet
|
||||
ObjectChecksum checksum;
|
||||
ObjectInfo info;
|
||||
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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)");
|
||||
setPacketReceiptNotifications(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
|
||||
logi("Creating Init packet object (Op Code = 1, Type = 1, Size = " + mInitPacketSizeInBytes + ")");
|
||||
writeCreateRequest(OBJECT_COMMAND, mInitPacketSizeInBytes);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Command object created");
|
||||
|
||||
}
|
||||
// Write Init data to the Packet Characteristic
|
||||
logi("Sending " + mInitPacketSizeInBytes + " bytes of init packet...");
|
||||
writeInitData(mPacketCharacteristic);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Command object sent");
|
||||
logi("Sending " + (mInitPacketSizeInBytes - info.offset) + " bytes of init packet...");
|
||||
writeInitData(mPacketCharacteristic, crc32);
|
||||
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
|
||||
logi("Sending Calculate Checksum command (Op Code = 3)");
|
||||
checksum = readChecksum();
|
||||
// TODO validate
|
||||
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));
|
||||
|
||||
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
|
||||
logi("Executing init packet (Op Code = 4)");
|
||||
writeExecute();
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Command object executed");
|
||||
}
|
||||
|
||||
// Send the number of packets of firmware before receiving a receipt notification
|
||||
final int numberOfPacketsBeforeNotification = mPacketsBeforeNotification;
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
|
||||
package no.nordicsemi.android.dfu.internal;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
@ -30,6 +32,7 @@ import java.io.InputStream;
|
|||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.ZipEntry;
|
||||
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. */
|
||||
private Map<String, byte[]> entries;
|
||||
private Manifest manifest;
|
||||
private CRC32 crc32;
|
||||
|
||||
private byte[] applicationBytes;
|
||||
private byte[] softDeviceBytes;
|
||||
|
@ -75,6 +79,9 @@ public class ArchiveInputStream extends ZipInputStream {
|
|||
private int applicationSize;
|
||||
private int bytesRead;
|
||||
|
||||
private byte[] markedSource;
|
||||
private int bytesReadFromMarkedSource;
|
||||
|
||||
/**
|
||||
* <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.
|
||||
|
@ -103,6 +110,7 @@ public class ArchiveInputStream extends ZipInputStream {
|
|||
public ArchiveInputStream(final InputStream stream, final int mbrSize, final int types) throws IOException {
|
||||
super(stream);
|
||||
|
||||
this.crc32 = new CRC32();
|
||||
this.entries = new HashMap<>();
|
||||
this.bytesRead = 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.
|
||||
* 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];
|
||||
String manifestData = null;
|
||||
|
||||
|
@ -304,7 +312,7 @@ public class ArchiveInputStream extends ZipInputStream {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int read(final byte[] buffer) throws IOException {
|
||||
public int read(@NonNull final byte[] buffer) throws IOException {
|
||||
int maxSize = currentSource.length - bytesReadFromCurrentSource;
|
||||
int size = buffer.length <= maxSize ? buffer.length : maxSize;
|
||||
System.arraycopy(currentSource, bytesReadFromCurrentSource, buffer, 0, size);
|
||||
|
@ -322,9 +330,42 @@ public class ArchiveInputStream extends ZipInputStream {
|
|||
size += nextSize;
|
||||
}
|
||||
bytesRead += size;
|
||||
crc32.update(buffer, 0, 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.
|
||||
* @return the manifest object
|
||||
|
|
Loading…
Reference in New Issue