Skip to content

Commit

Permalink
Improved formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
keeramis committed Jun 17, 2024
1 parent e5b9ad1 commit 8e16c09
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 55 deletions.
17 changes: 11 additions & 6 deletions src/cli/device-protection.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const unindent = require('../lib/unindent');

module.exports = ({ commandProcessor, root }) => {
const deviceProtection = commandProcessor.createCategory(root, 'protection', 'Commands for managing device protection');
const deviceProtection = commandProcessor.createCategory(root, 'device-protection', 'Commands for managing device protection');

commandProcessor.createCommand(deviceProtection, 'status', 'Gets the current device protection status', {
handler: () => {
Expand All @@ -14,14 +14,19 @@ module.exports = ({ commandProcessor, root }) => {
});

commandProcessor.createCommand(deviceProtection, 'disable', 'Disables device protection (temporary or permanent)', {
params: '[permanent]',
options: {
'open': {
boolean: true,
description: 'Unlocks a protected device and makes it an Open device'
}
},
handler: (args) => {
const DeviceProtectionCommands = require('../cmd/device-protection');
return new DeviceProtectionCommands().disableProtection(args.params);
return new DeviceProtectionCommands(args).disableProtection(args);
},
examples: {
'$0 $command': 'Disables device protection temporarily',
'$0 $command permanent': 'Disables device protection permanently'
'$0 $command': 'Device is temporarily unprotected',
'$0 $command --open': '[TBD] Device becomes an Open device'
}
});

Expand All @@ -39,7 +44,7 @@ module.exports = ({ commandProcessor, root }) => {
params: '<file>',
handler: (args) => {
const DeviceProtectionCommands = require('../cmd/device-protection');
return new DeviceProtectionCommands().protectBinary(args.params.file);
return new DeviceProtectionCommands().protectBinary({ file: args.params.file, verbose: true });
},
examples: {
'$0 $command myBootloader.bin': 'Adds device-protection to your bootloader binary'
Expand Down
9 changes: 9 additions & 0 deletions src/cmd/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,15 @@ module.exports = class ParticleApi {
}));
}

getProduct({ product, auth, headers, context }) {
return this._wrap(this.api.getProduct({
product,
auth,
headers,
context
}));
}

_wrap(promise){
return Promise.resolve(promise)
.then(result => result.body || result)
Expand Down
116 changes: 74 additions & 42 deletions src/cmd/device-protection.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ const fs = require('fs-extra');
const { downloadDeviceOsVersionBinaries } = require('../lib/device-os-version-util');
const FlashCommand = require('./flash');
const { platformForId } = require('../lib/platform');
const chalk = require('chalk');

module.exports = class DeviceProtectionCommands extends CLICommandBase {
constructor({ ui } = {}) {
Expand All @@ -36,34 +35,38 @@ module.exports = class DeviceProtectionCommands extends CLICommandBase {
try {
s = await this.device.getProtectionState();
} catch (error) {
if (error.message === 'Not implemented') {
throw new Error(`Device protection status is not supported on this device${os.EOL}${os.EOL}`);
if (error.message === 'Not supported') {
throw new Error(`Device protection feature is not supported on this device${os.EOL}`);
}
throw new Error('Failed to get device protection status');
}

let res;
if (!s.protected && !s.overridden) {
res = 'not protected';
res = 'Open (not protected)';
} else if (s.protected && !s.overridden) {
res = 'protected';
res = 'Protected';
} else if (s.overridden) {
res = 'temporarily not protected. A reset is required to re-enable protection.';
res = `Protected (service mode)${os.EOL}Device is put into service mode for a total of 20 reboots or 24 hours.`;
}

this.ui.stdout.write(`Device (${this.deviceId}) is ${chalk.bold(res)}${os.EOL}${os.EOL}`);
this.ui.stdout.write(`Device protection: ${res}${os.EOL}`);

return s;
});
}

async disableProtection({ permanent }) {
// TODO : Remove logs with sensitive information

return this._withDevice(async () => {
let s = await this.device.getProtectionState();
async disableProtection({ open } = {}) {
return this._withDevice(async () => {
let s;
try {
s = await this.device.getProtectionState();
} catch (error) {
if (error.message === 'Not supported') {
throw new Error(`Device protection feature is not supported on this device${os.EOL}`);
}
}
if (!s.protected && !s.overridden) {
this.ui.stdout.write(`Device is not protected${os.EOL}${os.EOL}`);
this.ui.stdout.write(`Device is not protected${os.EOL}`);
return;
}

Expand Down Expand Up @@ -102,19 +105,20 @@ module.exports = class DeviceProtectionCommands extends CLICommandBase {
await this.device.unprotectDevice({ action: 'confirm', serverSignature, serverPublicKeyFingerprint });

s = await this.device.getProtectionState();
if (!permanent) {
this.ui.stdout.write(`Device protection ${chalk.bold('temporarily disabled')}${os.EOL}${os.EOL}`);
if (!open) {
this.ui.stdout.write(`Device protection temporarily disabled.${os.EOL}Device is put into service mode for 20 reboots or 24 hours.${os.EOL}`);
return;
}

if (permanent) {
if (open) {

const localBootloaderPath = await this._downloadBootloader();

await this._flashBootloader(localBootloaderPath);
await this._flashBootloader(localBootloaderPath, 'disable');

this.ui.stdout.write(os.EOL);

this.ui.stdout.write(`Device is permanently unlocked${os.EOL}${os.EOL}`);
this.ui.stdout.write(`Device protection disabled.${os.EOL}Device is open${os.EOL}`);

const success = await this._markAsDevelopmentDevice(true);

Expand All @@ -126,7 +130,6 @@ module.exports = class DeviceProtectionCommands extends CLICommandBase {
}

async _downloadBootloader() {

let version;
const modules = await this.device.getFirmwareModuleInfo();
modules.forEach((m) => {
Expand All @@ -153,56 +156,83 @@ module.exports = class DeviceProtectionCommands extends CLICommandBase {
// TODO: error if device protection is not supported on this firmware version

return this._withDevice(async () => {
let s = await this.device.getProtectionState();
let s;
try {
s = await this.device.getProtectionState();
} catch (error) {
if (error.message === 'Not supported') {
throw new Error(`Device protection feature is not supported on this device${os.EOL}`);
}
}

const attrs = await this.api.getDeviceAttributes(this.deviceId);
let deviceProtectionActiveInProduct = false;
if (attrs.platform_id !== attrs.product_id) {
// it's in a product
const res = await this.api.getProduct({ product: attrs.product_id, auth: settings.access_token });
deviceProtectionActiveInProduct = res.product.device_protection;
}

if (s.protected && !s.overridden) {
this.ui.stdout.write(`Device is protected${os.EOL}${os.EOL}`);
this.ui.stdout.write(`Device is protected${os.EOL}`);
return;
}

if (s.overridden) {
// terminate unlock
await this.device.unprotectDevice({ action: 'reset' });
this.ui.stdout.write(`Device is ${chalk.bold('protected')}${os.EOL}`);
this.ui.stdout.write(`Device is protected${os.EOL}`);
const success = await this._markAsDevelopmentDevice(false);
if (success) {
this.ui.stdout.write(`Device was removed from development mode to keep in protection mode.${os.EOL}`);
} else {
this.ui.stdout.write(`Failed to mark device as development device${os.EOL}`);
if (!success) {
this.ui.stdout.write(`Failed to remove device from development mode. Ensure it is not in development mode for protection to work properly.${os.EOL}`);
}

return;
}

if (!s.protected && !s.overridden) {
if (!s.protected && !s.overridden && deviceProtectionActiveInProduct) {
// Protect device (permanently)

const localBootloaderPath = await this._downloadBootloader();

const resPath = await this.protectBinary(localBootloaderPath);

await this._flashBootloader(resPath);
const resPath = await this.protectBinary({ file: localBootloaderPath, verbose: false });

this.ui.stdout.write(`Remove device as development device...${os.EOL}${os.EOL}`);
await this._flashBootloader(resPath, 'enable');

const success = await this._markAsDevelopmentDevice(false);

if (success) {
this.ui.stdout.write(`Device was removed from development mode to enable protection.${os.EOL}`);
} else {
this.ui.stdout.write(`Failed to remove device from development mode.${os.EOL}`);

if (!success) {
this.ui.stdout.write(`Failed to remove device from development mode. Ensure it is not in development mode for protection to work properly.${os.EOL}`);
}
}

this.ui.write(`${os.EOL}Device is protected${os.EOL}`);
});
}

async _flashBootloader(path) {
async _flashBootloader(path, action) {
let msg;
switch (action) {
case 'enable':
msg = 'Enabling protection on the device. Please wait...';
break;
case 'disable':
msg = 'Disabling protection on the device. Please wait...';
break;
default:
throw new Error('Invalid action');
}

const flashCmdInstance = new FlashCommand();
await flashCmdInstance.flashLocal({ files: [path], applicationOnly: true });

const flashPromise = flashCmdInstance.flashLocal({ files: [path], applicationOnly: true, verbose: false });

await this.ui.showBusySpinnerUntilResolved(msg, flashPromise);
}

async _markAsDevelopmentDevice(state) {
let attrs;
try {
// TODO: Refactor
attrs = await this.api.getDeviceAttributes(this.deviceId);
if (attrs.platform_id !== attrs.product_id) {
// it's in a product
Expand Down Expand Up @@ -252,7 +282,7 @@ module.exports = class DeviceProtectionCommands extends CLICommandBase {
await new Promise(resolve => setTimeout(resolve, 3000));
}

async protectBinary(file) {
async protectBinary({ file, verbose=true }) {
if (!file) {
throw new Error('Please provide a file to add device protection');
}
Expand All @@ -266,7 +296,9 @@ module.exports = class DeviceProtectionCommands extends CLICommandBase {
const protectedBinary = await createProtectedModule(binary);
await fs.writeFile(resBinaryPath, protectedBinary);

this.ui.stdout.write(`Protected binary saved at ${resBinaryPath}${os.EOL}${os.EOL}`);
if (verbose) {
this.ui.stdout.write(`Protected binary saved at ${resBinaryPath}${os.EOL}`);
}

return resBinaryPath;
}
Expand Down
8 changes: 5 additions & 3 deletions src/cmd/flash.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ module.exports = class FlashCommand extends CLICommandBase {
return new SerialCommands().flashDevice(binary, { port, yes });
}

async flashLocal({ files, applicationOnly, target }) {
async flashLocal({ files, applicationOnly, target, verbose=true }) {
const { files: parsedFiles, deviceIdOrName, knownApp } = await this._analyzeFiles(files);
const { api, auth } = this._particleApi();
const device = await usbUtils.getOneUsbDevice({ idOrName: deviceIdOrName, api, auth, ui: this.ui });
Expand All @@ -124,7 +124,9 @@ module.exports = class FlashCommand extends CLICommandBase {
const platformName = platformForId(platformId).name;
const currentDeviceOsVersion = device.firmwareVersion;

this.ui.write(`Flashing ${platformName} ${deviceIdOrName || device.id}`);
if (verbose) {
this.ui.write(`Flashing ${platformName} ${deviceIdOrName || device.id}`);
}

validateDFUSupport({ device, ui: this.ui });

Expand Down Expand Up @@ -160,7 +162,7 @@ module.exports = class FlashCommand extends CLICommandBase {
platformId
});

await flashFiles({ device, flashSteps, ui: this.ui });
await flashFiles({ device, flashSteps, ui: this.ui, verbose });
}

async _analyzeFiles(files) {
Expand Down
10 changes: 7 additions & 3 deletions src/lib/flash-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ const ensureError = utilities.ensureError;
// Flashing an NCP firmware can take a few minutes
const FLASH_TIMEOUT = 4 * 60000;

async function flashFiles({ device, flashSteps, resetAfterFlash = true, ui }) {
const progress = _createFlashProgress({ flashSteps, ui });
async function flashFiles({ device, flashSteps, resetAfterFlash = true, ui, verbose=true }) {
let progress = null;
progress = verbose ? _createFlashProgress({ flashSteps, ui, verbose }) : null;
let success = false;
let lastStepDfu = false;
try {
Expand All @@ -35,7 +36,9 @@ async function flashFiles({ device, flashSteps, resetAfterFlash = true, ui }) {
}
success = true;
} finally {
progress({ event: 'finish', success });
if (progress) {
progress({ event: 'finish', success });
}
if (device.isOpen) {
// only reset the device if the last step was in DFU mode
if (resetAfterFlash && lastStepDfu) {
Expand Down Expand Up @@ -135,6 +138,7 @@ async function _flashDeviceInDfuMode(device, data, { name, altSetting, startAddr
}

function _createFlashProgress({ flashSteps, ui }) {
console.log('verbose: ' , verbose);
const NORMAL_MULTIPLIER = 10; // flashing in normal mode is slower so count each byte more
const { isInteractive } = ui;
let progressBar;
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/help.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ describe('Help & Unknown Command / Argument Handling', () => {
'variable list', 'variable get', 'variable monitor', 'variable',
'webhook create', 'webhook list', 'webhook delete', 'webhook POST',
'webhook GET', 'webhook', 'whoami', 'wifi add', 'wifi join', 'wifi clear', 'wifi list', 'wifi remove', 'wifi current', 'wifi',
'protection', 'protection status', 'protection disable', 'protection enable'
'device-protection', 'device-protection status', 'device-protection disable', 'device-protection enable'
];

const mainCmds = dedupe(allCmds.map(c => c.split(' ')[0]));
Expand Down

0 comments on commit 8e16c09

Please sign in to comment.