Skip to content

Commit

Permalink
Improve the logic that checks for Device Protection when obtaining th…
Browse files Browse the repository at this point in the history
…e device
  • Loading branch information
keeramis committed Jul 31, 2024
1 parent 948e8e2 commit 7fd3de3
Showing 1 changed file with 82 additions and 21 deletions.
103 changes: 82 additions & 21 deletions src/cmd/usb-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ const {
DeviceProtectionError
} = require('particle-usb');
const deviceProtectionHelper = require('../lib/device-protection-helper');
const spinnerMixin = require('../lib/spinner-mixin');

// Timeout when reopening a USB device after an update via control requests. This timeout should be
// long enough to allow the bootloader apply the update
const REOPEN_TIMEOUT = 60000;
// When reopening a device that was about to reset, give it some time to boot into the firmware
const REOPEN_DELAY = 500;
// Attempt to disable Service Mode to turn the device back to a Protected Device
const MAX_SERVICE_MODE_DISABLE_ATTEMPTS = 3;

async function _getDeviceInfo(device) {
let id = null;
Expand Down Expand Up @@ -95,37 +98,47 @@ class UsbPermissionsError extends Error {
* });
*/
async function executeWithUsbDevice({ args, func, dfuMode = false } = {}) {
const spinnerObj = {};
spinnerMixin(spinnerObj);
let device = await getOneUsbDevice(args, { dfuMode });
let deviceIsProtected = false;
try {
const s = await deviceProtectionHelper.getProtectionStatus(device);
deviceIsProtected = s.protected && !s.overrridden;
} catch (err) {
if (err.message === 'Not supported') {
// Device Protection is not supported on certain platforms and versions.
// It means that the device is not protected.
}
throw err;
}
if (deviceIsProtected) {
const deviceWasInDfuMode = device.isInDfuMode;
if (deviceWasInDfuMode) {
device = await _putDeviceInSafeMode(device);

const platform = platformForId(device.platformId);
if (platform.generation > 2) { // Skipping device protection check for Gen2 platforms
try {
spinnerObj.newSpin('Checking Device Protection...').start();
const s = await deviceProtectionHelper.getProtectionStatus(device);
deviceIsProtected = s.protected && !s.overrridden;
} catch (err) {
if (err.message === 'Not supported') {
// Device Protection is not supported on certain platforms and versions.
// It means that the device is not protected.
} else {
throw err;
}
}
await deviceProtectionHelper.disableDeviceProtection(device);
if (deviceWasInDfuMode) {
device = await reopenInDfuMode(device);
if (deviceIsProtected) {
const deviceWasInDfuMode = device.isInDfuMode;
if (deviceWasInDfuMode) {
device = await _putDeviceInSafeMode(device);
}
await deviceProtectionHelper.disableDeviceProtection(device);
if (deviceWasInDfuMode) {
device = await reopenInDfuMode(device);
}
}
spinnerObj.stopSpin();
}

let res;
try {
res = await func(device);
} finally {
if (deviceIsProtected) {
device = await reopenDevice(device);
// FIXME: ignore errors
await deviceProtectionHelper.turnOffServiceMode(device);
// Brief spinner: Specially useful for DFU->normal mode transition (~1-5s)
spinnerObj.newSpin('Re-enabling Device Protection...').start();
device = await disableServiceMode(device);
spinnerObj.stopSpin();
}
if (device && device.isOpen) {
await device.close();
Expand All @@ -134,6 +147,53 @@ async function executeWithUsbDevice({ args, func, dfuMode = false } = {}) {
return res;
}

/**
* Disables Service Mode on a device.
* @param {*} device
* @returns {Promise} returns device if successful, false otherwise
*/
async function disableServiceMode(device) {
for (let tries = 0; tries < MAX_SERVICE_MODE_DISABLE_ATTEMPTS; tries++) {
try {
device = await reopenDevice(device);
if (!device.isInDfuMode) {
device = await waitForDeviceToRespond(device);
}
await deviceProtectionHelper.turnOffServiceMode(device);
} catch (error) {
// Silently ignore the error. At most, the Protected Device will be left in Service Mode
}
}
return device;
}

/**
* Reboots device and waits for it to enter normal mode.
* Useful for enabling Device Protection on a device in after its current operation completes.
* @param {*} deviceId
* @returns
*/
async function waitForDeviceToRespond(device) {
const deviceId = device.id;
const REBOOT_TIME_MSEC = 20000;
const REBOOT_INTERVAL_MSEC = 1000;
const start = Date.now();
if (device.isOpen) {
await device.close();
}
while (Date.now() - start < REBOOT_TIME_MSEC) {
try {
await delay(REBOOT_INTERVAL_MSEC);
const device = await reopenDevice({ id: deviceId });
// Check device readiness
await device.getDeviceId();
return device;
} catch (error) {
// ignore error
}
}
}

/**
* Attempts to enter Safe Mode to enable operations on Protected Devices in DFU mode.
*
Expand Down Expand Up @@ -515,5 +575,6 @@ module.exports = {
DeviceProtectionError,
forEachUsbDevice,
openUsbDevices,
executeWithUsbDevice
executeWithUsbDevice,
waitForDeviceToRespond
};

0 comments on commit 7fd3de3

Please sign in to comment.