mirror of
https://github.com/thegecko/web-bluetooth-dfu.git
synced 2025-12-13 20:48:15 +08:00
Added secure web example
This commit is contained in:
629
dist/dfu.js
vendored
629
dist/dfu.js
vendored
@@ -1,9 +1,9 @@
|
||||
/* @license
|
||||
*
|
||||
* Secure device firmware update with Web Bluetooth
|
||||
* Device firmware update with Web Bluetooth
|
||||
*
|
||||
* Protocol from:
|
||||
* http://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v13.0.0/lib_dfu_transport_ble.html
|
||||
* http://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v11.0.0/bledfu_transport.html
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
@@ -32,288 +32,439 @@
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['bleat'], factory);
|
||||
define(['es6-promise', 'bleat', 'crc16'], factory);
|
||||
} else if (typeof exports === 'object') {
|
||||
// Node. Does not work with strict CommonJS
|
||||
module.exports = factory(require('bleat').webbluetooth);
|
||||
module.exports = factory(Promise, require('bleat').webbluetooth, require('./crc16'));
|
||||
} else {
|
||||
// Browser globals with support for web workers (root is window)
|
||||
root.SecureDfu = factory(root.navigator.bluetooth);
|
||||
root.dfu = factory(Promise, root.navigator.bluetooth, root.crc16);
|
||||
}
|
||||
}(this, function(bluetooth) {
|
||||
}(this, function(Promise, bluetooth, crc16) {
|
||||
"use strict";
|
||||
|
||||
const SERVICE_UUID = 0xFE59;
|
||||
const CONTROL_UUID = "8ec90001-f315-4f60-9fb8-838830daea50";
|
||||
const PACKET_UUID = "8ec90002-f315-4f60-9fb8-838830daea50";
|
||||
// Make server a global variable (initialized in connect().
|
||||
// This fixes a bug in BlueZ that causes transfers to stall.
|
||||
var currentServer = null;
|
||||
|
||||
const LITTLE_ENDIAN = true;
|
||||
const PACKET_SIZE = 20;
|
||||
var LITTLE_ENDIAN = true;
|
||||
|
||||
const OPERATIONS = {
|
||||
CREATE_COMMAND: [0x01, 0x01],
|
||||
CREATE_DATA: [0x01, 0x02],
|
||||
RECEIPT_NOTIFICATIONS: [0x02],
|
||||
CACULATE_CHECKSUM: [0x03],
|
||||
EXECUTE: [0x04],
|
||||
SELECT_COMMAND: [0x06, 0x01],
|
||||
SELECT_DATA: [0x06, 0x02],
|
||||
RESPONSE: [0x60]
|
||||
var packetSize = 20;
|
||||
var notifySteps = 40;
|
||||
|
||||
var serviceUUID = "00001530-1212-efde-1523-785feabcd123";
|
||||
var controlUUID = "00001531-1212-efde-1523-785feabcd123";
|
||||
var packetUUID = "00001532-1212-efde-1523-785feabcd123";
|
||||
var versionUUID = "00001534-1212-efde-1523-785feabcd123";
|
||||
|
||||
var ImageType = {
|
||||
None: 0,
|
||||
SoftDevice: 1,
|
||||
Bootloader: 2,
|
||||
SoftDevice_Bootloader: 3, // Will not work right now
|
||||
Application: 4
|
||||
};
|
||||
|
||||
const RESPONSE = {
|
||||
0x00: "Invalid code",
|
||||
0x01: "Success",
|
||||
0x02: "Opcode not supported",
|
||||
0x03: "Invalid parameter",
|
||||
0x04: "Insufficient resources",
|
||||
0x05: "Invalid object",
|
||||
0x07: "Unsupported type",
|
||||
0x08: "Operation not permitted",
|
||||
0x0A: "Operation failed",
|
||||
0x0B: "Extended error"
|
||||
// TODO: This should be configurable by the user. For now this will work with any of Nordic's SDK examples.
|
||||
var initPacket = {
|
||||
device_type: 0xFFFF,
|
||||
device_rev: 0xFFFF,
|
||||
app_version: 0xFFFFFFFF,
|
||||
softdevice_len: 0x0001,
|
||||
softdevice: 0xFFFE,
|
||||
crc: 0x0000
|
||||
};
|
||||
|
||||
function secureDfu(crc32) {
|
||||
this.crc32 = crc32;
|
||||
this.events = {};
|
||||
this.notifyFns = {};
|
||||
this.connected = false;
|
||||
this.controlChar = null;
|
||||
this.packetChar = null;
|
||||
this.buffer = null;
|
||||
}
|
||||
|
||||
function createListenerFn(eventTypes) {
|
||||
return function(type, callback, capture) {
|
||||
if (eventTypes.indexOf(type) < 0) return;
|
||||
if (!this.events[type]) this.events[type] = [];
|
||||
this.events[type].push(callback);
|
||||
};
|
||||
}
|
||||
function removeEventListener(type, callback, capture) {
|
||||
if (!this.events[type]) return;
|
||||
let i = this.events[type].indexOf(callback);
|
||||
if (i >= 0) this.events[type].splice(i, 1);
|
||||
if (this.events[type].length === 0) delete this.events[type];
|
||||
}
|
||||
function dispatchEvent(event) {
|
||||
if (!this.events[event.type]) return;
|
||||
event.target = this;
|
||||
this.events[event.type].forEach(callback => {
|
||||
if (typeof callback === "function") callback(event);
|
||||
});
|
||||
}
|
||||
|
||||
secureDfu.prototype.log = function(message) {
|
||||
this.dispatchEvent({
|
||||
type: "log",
|
||||
message: message
|
||||
});
|
||||
var OPCODE = {
|
||||
RESERVED: 0,
|
||||
START_DFU: 1,
|
||||
INITIALIZE_DFU_PARAMETERS: 2,
|
||||
RECEIVE_FIRMWARE_IMAGE: 3,
|
||||
VALIDATE_FIRMWARE: 4,
|
||||
ACTIVATE_IMAGE_AND_RESET: 5,
|
||||
RESET_SYSTEM: 6,
|
||||
REPORT_RECEIVED_IMAGE_SIZE: 7,
|
||||
PACKET_RECEIPT_NOTIFICATION_REQUEST: 8,
|
||||
RESPONSE_CODE: 16,
|
||||
PACKET_RECEIPT_NOTIFICATION: 17
|
||||
};
|
||||
|
||||
secureDfu.prototype.requestDevice = function(filters) {
|
||||
if (!filters) {
|
||||
filters = [{
|
||||
services: [SERVICE_UUID]
|
||||
}];
|
||||
var loggers = [];
|
||||
function addLogger(loggerFn) {
|
||||
if (typeof loggerFn === "function") {
|
||||
loggers.push(loggerFn);
|
||||
}
|
||||
}
|
||||
function log(message) {
|
||||
loggers.forEach(function(logger) {
|
||||
logger(message);
|
||||
});
|
||||
}
|
||||
|
||||
function findDevice(filters) {
|
||||
return bluetooth.requestDevice({
|
||||
filters: filters,
|
||||
optionalServices: [SERVICE_UUID]
|
||||
});
|
||||
};
|
||||
|
||||
secureDfu.prototype.connect = function(device) {
|
||||
let service = null;
|
||||
|
||||
device.addEventListener("gattserverdisconnected", event => {
|
||||
this.connected = false;
|
||||
this.controlChar = null;
|
||||
this.packetChar = null;
|
||||
this.buffer = null;
|
||||
this.log("disconnected");
|
||||
});
|
||||
|
||||
return device.gatt.connect()
|
||||
.then(gattServer => {
|
||||
this.log("connected to gatt server");
|
||||
return gattServer.getPrimaryService(SERVICE_UUID);
|
||||
})
|
||||
.then(primaryService => {
|
||||
this.log("found DFU service");
|
||||
service = primaryService;
|
||||
return service.getCharacteristic(CONTROL_UUID);
|
||||
})
|
||||
.then(characteristic => {
|
||||
this.log("found control characteristic");
|
||||
if (!characteristic.properties.notify) {
|
||||
throw new Error("control characterisitc does not allow notifications");
|
||||
}
|
||||
this.controlChar = characteristic;
|
||||
return characteristic.startNotifications();
|
||||
})
|
||||
.then(() => {
|
||||
this.log("enabled control notifications");
|
||||
this.controlChar.addEventListener("characteristicvaluechanged", this.handleNotification.bind(this));
|
||||
return service.getCharacteristic(PACKET_UUID);
|
||||
})
|
||||
.then(characteristic => {
|
||||
this.log("found packet characteristic");
|
||||
this.packetChar = characteristic;
|
||||
this.connected = true;
|
||||
});
|
||||
};
|
||||
|
||||
secureDfu.prototype.handleNotification = function(event) {
|
||||
let view = event.target.value;
|
||||
|
||||
if (view.getUint8(0) !== OPERATIONS.RESPONSE[0]) {
|
||||
throw new Error("unrecognised control characteristic response notification");
|
||||
}
|
||||
|
||||
let operation = view.getUint8(1);
|
||||
if (this.notifyFns[operation]) {
|
||||
let result = view.getUint8(2);
|
||||
let response = RESPONSE[result];
|
||||
|
||||
if (result === 0x01) {
|
||||
let data = new DataView(view.buffer, 3);
|
||||
this.notifyFns[operation].resolve(data);
|
||||
} else {
|
||||
this.log(`notify: ${response}`);
|
||||
this.notifyFns[operation].reject(response);
|
||||
}
|
||||
|
||||
delete this.notifyFns[operation];
|
||||
}
|
||||
};
|
||||
|
||||
secureDfu.prototype.sendOperation = function(operation, buffer) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.connected) throw new Error("device not connected");
|
||||
if (!this.controlChar) throw new Error("control characteristic not found");
|
||||
if (!this.packetChar) throw new Error("packet characteristic not found");
|
||||
|
||||
let size = operation.length;
|
||||
if (buffer) size += buffer.byteLength;
|
||||
|
||||
let value = new Uint8Array(size);
|
||||
value.set(operation);
|
||||
if (buffer) {
|
||||
let data = new Uint8Array(buffer);
|
||||
value.set(data, operation.length);
|
||||
}
|
||||
|
||||
this.notifyFns[operation[0]] = {
|
||||
resolve: resolve,
|
||||
reject: reject
|
||||
};
|
||||
|
||||
this.controlChar.writeValue(value);
|
||||
filters: [ filters ],
|
||||
optionalServices: [serviceUUID]
|
||||
});
|
||||
}
|
||||
|
||||
secureDfu.prototype.transferInit = function(buffer) {
|
||||
return this.sendOperation(OPERATIONS.SELECT_COMMAND)
|
||||
.then(response => {
|
||||
/**
|
||||
* Switch to bootloader/DFU mode by writing to the control point of the DFU Service.
|
||||
* The DFU Controller is not responsible for disconnecting from the application (DFU Target) after the write.
|
||||
* The application (DFU Target) will issue a GAP Disconnect and reset into bootloader/DFU mode.
|
||||
*
|
||||
* https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v11.0.0/bledfu_appswitching.html?cp=4_0_0_4_1_3_2_2
|
||||
*/
|
||||
function writeMode(device) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
|
||||
let maxSize = response.getUint32(0, LITTLE_ENDIAN);
|
||||
let offset = response.getUint32(4, LITTLE_ENDIAN);
|
||||
let crc = response.getInt32(8, LITTLE_ENDIAN);
|
||||
|
||||
if (offset === buffer.byteLength && this.checkCrc(buffer, crc)) {
|
||||
this.log("init packet already available, skipping transfer");
|
||||
return;
|
||||
var resolved = false;
|
||||
function disconnectHandler() {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
log("DFU target issued GAP disconnect and reset into bootloader/DFU mode");
|
||||
resolve(device);
|
||||
}
|
||||
}
|
||||
device.addEventListener("gattserverdisconnected", disconnectHandler);
|
||||
|
||||
this.buffer = buffer;
|
||||
return this.transferObject(OPERATIONS.CREATE_COMMAND, maxSize, offset);
|
||||
var characteristics = null;
|
||||
|
||||
connect(device)
|
||||
.then(function(chars) {
|
||||
log("enabling notifications");
|
||||
characteristics = chars;
|
||||
return characteristics.controlChar.startNotifications();
|
||||
})
|
||||
.then(function() {
|
||||
log("writing modeData");
|
||||
return characteristics.controlChar.writeValue(new Uint8Array([OPCODE.START_DFU, ImageType.Application]));
|
||||
})
|
||||
.then(function() {
|
||||
log("modeData written");
|
||||
// TODO: Remove this when gattserverdisconnected event is implemented and possibly put a timeout in that event handler before resolving
|
||||
setTimeout(function() {
|
||||
if (currentServer && currentServer.connected === true) {
|
||||
currentServer.disconnect();
|
||||
}
|
||||
disconnectHandler();
|
||||
}, 5000);
|
||||
})
|
||||
.catch(function(error) {
|
||||
error = "writeMode error: " + error;
|
||||
log(error);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
secureDfu.prototype.transferFirmware = function(buffer) {
|
||||
return this.sendOperation(OPERATIONS.SELECT_DATA)
|
||||
.then(response => {
|
||||
/**
|
||||
* Contains basic functionality for performing safety checks on software updates for nRF5 based devices.
|
||||
* Init packet used for pre-checking to ensure the following image is compatible with the device.
|
||||
* Contains information on device type, revision, and supported SoftDevices along with a CRC or hash of firmware image.
|
||||
*
|
||||
* Not used in mbed bootloader (init packet was optional in SDK v6.x).
|
||||
*/
|
||||
function generateInitPacket() {
|
||||
var buffer = new ArrayBuffer(14);
|
||||
var view = new DataView(buffer);
|
||||
view.setUint16(0, initPacket.device_type, LITTLE_ENDIAN);
|
||||
view.setUint16(2, initPacket.device_rev, LITTLE_ENDIAN);
|
||||
view.setUint32(4, initPacket.app_version, LITTLE_ENDIAN); // Application version for the image software. This field allows for additional checking, for example ensuring that a downgrade is not allowed.
|
||||
view.setUint16(8, initPacket.softdevice_len, LITTLE_ENDIAN); // Number of different SoftDevice revisions compatible with this application.
|
||||
view.setUint16(10, initPacket.softdevice, LITTLE_ENDIAN); // Variable length array of SoftDevices compatible with this application. The length of the array is specified in the length (softdevice_len) field. 0xFFFE indicates any SoftDevice.
|
||||
view.setUint16(12, initPacket.crc, LITTLE_ENDIAN);
|
||||
return view;
|
||||
}
|
||||
|
||||
let maxSize = response.getUint32(0, LITTLE_ENDIAN);
|
||||
let offset = response.getUint32(4, LITTLE_ENDIAN);
|
||||
let crc = response.getInt32(8, LITTLE_ENDIAN);
|
||||
function provision(device, arrayBuffer, imageType, crc) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
imageType = imageType || ImageType.Application;
|
||||
initPacket.crc = crc || crc16(arrayBuffer);
|
||||
var versionChar = null;
|
||||
|
||||
this.buffer = buffer;
|
||||
return this.transferObject(OPERATIONS.CREATE_DATA, maxSize, offset);
|
||||
connect(device)
|
||||
.then(function(chars) {
|
||||
versionChar = chars.versionChar;
|
||||
// Older DFU implementations (from older Nordic SDKs < 7.0) have no DFU Version characteristic.
|
||||
if (versionChar) {
|
||||
return versionChar.readValue()
|
||||
.then(function(data) {
|
||||
log('read versionChar');
|
||||
var major = data.getUint8(0);
|
||||
var minor = data.getUint8(1);
|
||||
return transfer(chars, arrayBuffer, imageType, major, minor);
|
||||
});
|
||||
} else {
|
||||
// Default to version 6.0 (mbed).
|
||||
return transfer(chars, arrayBuffer, imageType, 6, 0);
|
||||
}
|
||||
})
|
||||
.then(function() {
|
||||
resolve();
|
||||
})
|
||||
.catch(function(error) {
|
||||
log(error);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
secureDfu.prototype.transferObject = function(createType, maxSize, offset) {
|
||||
let start = offset - offset % maxSize;
|
||||
let end = Math.min(start + maxSize, this.buffer.byteLength);
|
||||
function connect(device) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var service = null;
|
||||
var controlChar = null;
|
||||
var packetChar = null;
|
||||
var versionChar = null;
|
||||
|
||||
let view = new DataView(new ArrayBuffer(4));
|
||||
view.setUint32(0, end - start, LITTLE_ENDIAN);
|
||||
|
||||
return this.sendOperation(createType, view.buffer)
|
||||
.then(response => {
|
||||
let data = this.buffer.slice(start, end);
|
||||
return this.transferData(data, start);
|
||||
})
|
||||
.then(() => {
|
||||
return this.sendOperation(OPERATIONS.CACULATE_CHECKSUM);
|
||||
})
|
||||
.then(response => {
|
||||
let crc = response.getInt32(4, LITTLE_ENDIAN);
|
||||
let transferred = response.getUint32(0, LITTLE_ENDIAN);
|
||||
let data = this.buffer.slice(0, transferred);
|
||||
|
||||
if (this.checkCrc(data, crc)) {
|
||||
this.log(`written ${transferred} bytes`);
|
||||
offset = transferred;
|
||||
return this.sendOperation(OPERATIONS.EXECUTE);
|
||||
} else {
|
||||
this.log("object failed to validate");
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
if (end < this.buffer.byteLength) {
|
||||
return this.transferObject(createType, maxSize, offset);
|
||||
} else {
|
||||
this.log("transfer complete");
|
||||
function complete() {
|
||||
resolve({
|
||||
controlChar: controlChar,
|
||||
packetChar: packetChar,
|
||||
versionChar: versionChar
|
||||
});
|
||||
}
|
||||
|
||||
device.gatt.connect()
|
||||
.then(function(gattServer) {
|
||||
log("connected to device");
|
||||
currentServer = gattServer;
|
||||
// This delay is needed because BlueZ needs time to update it's cache.
|
||||
return new Promise(function(resolve, reject) {
|
||||
setTimeout(resolve, 2000);
|
||||
});
|
||||
})
|
||||
.then(function() {
|
||||
return currentServer.getPrimaryService(serviceUUID);
|
||||
})
|
||||
.then(function(primaryService) {
|
||||
log("found DFU service");
|
||||
service = primaryService;
|
||||
return service.getCharacteristic(controlUUID);
|
||||
})
|
||||
.then(function(characteristic) {
|
||||
log("found control characteristic");
|
||||
controlChar = characteristic;
|
||||
return service.getCharacteristic(packetUUID);
|
||||
})
|
||||
.then(function(characteristic) {
|
||||
log("found packet characteristic");
|
||||
packetChar = characteristic;
|
||||
service.getCharacteristic(versionUUID)
|
||||
// Older DFU implementations (from older Nordic SDKs) have no DFU Version characteristic. So this may fail.
|
||||
.then(function(characteristic) {
|
||||
log("found version characteristic");
|
||||
versionChar = characteristic;
|
||||
complete();
|
||||
})
|
||||
.catch(function(error) {
|
||||
log("info: no version characteristic found");
|
||||
complete();
|
||||
});
|
||||
})
|
||||
.catch(function(error) {
|
||||
error = "connect error: " + error;
|
||||
log(error);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
secureDfu.prototype.transferData = function(data, offset, start) {
|
||||
start = start || 0;
|
||||
let end = start + PACKET_SIZE;
|
||||
let packet = data.slice(start, end);
|
||||
var interval;
|
||||
var offset;
|
||||
function transfer(chars, arrayBuffer, imageType, majorVersion, minorVersion) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var controlChar = chars.controlChar;
|
||||
var packetChar = chars.packetChar;
|
||||
log('using dfu version ' + majorVersion + "." + minorVersion);
|
||||
|
||||
return this.packetChar.writeValue(packet)
|
||||
.then(() => {
|
||||
this.dispatchEvent({
|
||||
type: "progress",
|
||||
currentBytes: offset + end,
|
||||
totalBytes: this.buffer.byteLength
|
||||
var resolved = false;
|
||||
function disconnectHandler() {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
log('disconnected and completed the DFU transfer');
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
currentServer.device.addEventListener("gattserverdisconnected", disconnectHandler);
|
||||
|
||||
// Set up receipts
|
||||
interval = Math.floor(arrayBuffer.byteLength / (packetSize * notifySteps));
|
||||
offset = 0;
|
||||
|
||||
if (!controlChar.properties.notify) {
|
||||
var err = "controlChar missing notify property";
|
||||
log(err);
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
log("enabling notifications");
|
||||
controlChar.startNotifications()
|
||||
.then(function() {
|
||||
controlChar.addEventListener('characteristicvaluechanged', handleControl);
|
||||
log("sending imagetype: " + imageType);
|
||||
return controlChar.writeValue(new Uint8Array([OPCODE.START_DFU, imageType]));
|
||||
})
|
||||
.then(function() {
|
||||
log("sent start");
|
||||
|
||||
var softLength = (imageType === ImageType.SoftDevice) ? arrayBuffer.byteLength : 0;
|
||||
var bootLength = (imageType === ImageType.Bootloader) ? arrayBuffer.byteLength : 0;
|
||||
var appLength = (imageType === ImageType.Application) ? arrayBuffer.byteLength : 0;
|
||||
|
||||
var buffer = new ArrayBuffer(12);
|
||||
var view = new DataView(buffer);
|
||||
view.setUint32(0, softLength, LITTLE_ENDIAN);
|
||||
view.setUint32(4, bootLength, LITTLE_ENDIAN);
|
||||
view.setUint32(8, appLength, LITTLE_ENDIAN);
|
||||
|
||||
return packetChar.writeValue(view);
|
||||
})
|
||||
.then(function() {
|
||||
log("sent image size: " + arrayBuffer.byteLength);
|
||||
})
|
||||
.catch(function(error) {
|
||||
error = "start error: " + error;
|
||||
log(error);
|
||||
reject(error);
|
||||
});
|
||||
|
||||
if (end < data.byteLength) {
|
||||
return this.transferData(data, offset, end);
|
||||
function handleControl(event) {
|
||||
var view = event.target.value;
|
||||
|
||||
var opCode = view.getUint8(0);
|
||||
var req_opcode = view.getUint8(1);
|
||||
var resp_code = view.getUint8(2);
|
||||
|
||||
if (opCode === OPCODE.RESPONSE_CODE) {
|
||||
if (resp_code !== 1) {
|
||||
var err = "error from control point notification, resp_code: " + resp_code;
|
||||
log(err);
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
switch(req_opcode) {
|
||||
case OPCODE.START_DFU:
|
||||
case OPCODE.INITIALIZE_DFU_PARAMETERS:
|
||||
if(req_opcode === OPCODE.START_DFU && majorVersion > 6) { // init packet is not used in SDK v6 (so not used in mbed).
|
||||
log('write init packet');
|
||||
controlChar.writeValue(new Uint8Array([OPCODE.INITIALIZE_DFU_PARAMETERS, 0]))
|
||||
.then(function() {
|
||||
return packetChar.writeValue(generateInitPacket());
|
||||
})
|
||||
.then(function() {
|
||||
return controlChar.writeValue(new Uint8Array([OPCODE.INITIALIZE_DFU_PARAMETERS, 1]));
|
||||
})
|
||||
.catch(function(error) {
|
||||
error = "error writing dfu init parameters: " + error;
|
||||
log(error);
|
||||
reject(error);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
log('send packet count');
|
||||
|
||||
var buffer = new ArrayBuffer(3);
|
||||
view = new DataView(buffer);
|
||||
view.setUint8(0, OPCODE.PACKET_RECEIPT_NOTIFICATION_REQUEST);
|
||||
view.setUint16(1, interval, LITTLE_ENDIAN);
|
||||
|
||||
controlChar.writeValue(view)
|
||||
.then(function() {
|
||||
log("sent packet count: " + interval);
|
||||
return controlChar.writeValue(new Uint8Array([OPCODE.RECEIVE_FIRMWARE_IMAGE]));
|
||||
})
|
||||
.then(function() {
|
||||
log("sent receive");
|
||||
return writePacket(packetChar, arrayBuffer, 0);
|
||||
})
|
||||
.catch(function(error) {
|
||||
error = "error sending packet count: " + error;
|
||||
log(error);
|
||||
reject(error);
|
||||
});
|
||||
break;
|
||||
case OPCODE.RECEIVE_FIRMWARE_IMAGE:
|
||||
log('check length');
|
||||
|
||||
controlChar.writeValue(new Uint8Array([OPCODE.REPORT_RECEIVED_IMAGE_SIZE]))
|
||||
.catch(function(error) {
|
||||
error = "error checking length: " + error;
|
||||
log(error);
|
||||
reject(error);
|
||||
});
|
||||
break;
|
||||
case OPCODE.REPORT_RECEIVED_IMAGE_SIZE:
|
||||
var bytesReceived = view.getUint32(3, LITTLE_ENDIAN);
|
||||
log('length: ' + bytesReceived);
|
||||
log('validate...');
|
||||
|
||||
controlChar.writeValue(new Uint8Array([OPCODE.VALIDATE_FIRMWARE]))
|
||||
.catch(function(error) {
|
||||
error = "error validating: " + error;
|
||||
log(error);
|
||||
reject(error);
|
||||
});
|
||||
break;
|
||||
case OPCODE.VALIDATE_FIRMWARE:
|
||||
log('complete, reset...');
|
||||
|
||||
controlChar.writeValue(new Uint8Array([OPCODE.ACTIVATE_IMAGE_AND_RESET]))
|
||||
.then(function() {
|
||||
log('image activated and dfu target reset');
|
||||
// TODO: Remove this when gattserverdisconnected event is implemented and possibly put a timeout in that event handler before resolving
|
||||
setTimeout(function() {
|
||||
if (currentServer && currentServer.connected === true) {
|
||||
currentServer.disconnect();
|
||||
}
|
||||
disconnectHandler();
|
||||
}, 5000);
|
||||
})
|
||||
.catch(function(error) {
|
||||
error = "error resetting: " + error;
|
||||
log(error);
|
||||
reject(error);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
log('unexpected req opCode - ERROR');
|
||||
break;
|
||||
}
|
||||
|
||||
} else if (opCode === OPCODE.PACKET_RECEIPT_NOTIFICATION) {
|
||||
var bytes = view.getUint32(1, LITTLE_ENDIAN);
|
||||
log('transferred: ' + bytes);
|
||||
writePacket(packetChar, arrayBuffer, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
secureDfu.prototype.checkCrc = function(buffer, crc) {
|
||||
if (!this.crc32) {
|
||||
this.log("crc32 not found, skipping CRC check");
|
||||
return true;
|
||||
}
|
||||
function writePacket(packetChar, arrayBuffer, count) {
|
||||
var size = (offset + packetSize > arrayBuffer.byteLength) ? arrayBuffer.byteLength - offset : packetSize;
|
||||
var packet = arrayBuffer.slice(offset, offset + size);
|
||||
var view = new Uint8Array(packet);
|
||||
|
||||
return crc === this.crc32(new Uint8Array(buffer));
|
||||
packetChar.writeValue(view)
|
||||
.then(function() {
|
||||
count ++;
|
||||
offset += packetSize;
|
||||
if (count < interval && offset < arrayBuffer.byteLength) {
|
||||
writePacket(packetChar, arrayBuffer, count);
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
error = "writePacket error: " + error;
|
||||
log(error);
|
||||
});
|
||||
}
|
||||
|
||||
secureDfu.prototype.addEventListener = createListenerFn([ "log", "progress" ]);
|
||||
secureDfu.prototype.removeEventListener = removeEventListener;
|
||||
secureDfu.prototype.dispatchEvent = dispatchEvent;
|
||||
|
||||
return secureDfu;
|
||||
return {
|
||||
addLogger: addLogger,
|
||||
ImageType: ImageType,
|
||||
findDevice: findDevice,
|
||||
writeMode: writeMode,
|
||||
provision: provision
|
||||
};
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user