Skip to content

Commit

Permalink
Add commands to list and strip assets from a binary
Browse files Browse the repository at this point in the history
  • Loading branch information
keeramis committed Oct 15, 2024
1 parent 492a2e1 commit ad11f3a
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 58 deletions.
16 changes: 8 additions & 8 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
],
"dependencies": {
"@particle/device-constants": "^3.5.0",
"binary-version-reader": "^2.4.0",
"binary-version-reader": "^2.5.0",
"chalk": "^2.4.2",
"cli-progress": "^3.12.0",
"cli-spinner": "^0.2.10",
Expand Down
22 changes: 22 additions & 0 deletions src/cli/binary.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,27 @@ module.exports = ({ commandProcessor, root }) => {
}
});

commandProcessor.createCommand(binary, 'list-assets', 'Lists assets present in an application binary', {
params: '<file>',
handler: (args) => {
const BinaryCommand = require('../cmd/binary');
return new BinaryCommand().listAssetsFromApplication(args.params.file);
},
examples: {
'$0 $command app-with-assets.bin': 'Show the list of assets in the application binary'
}
});

commandProcessor.createCommand(binary, 'strip-assets', 'Remove assets from application binary', {
params: '<file>',
handler: (args) => {
const BinaryCommand = require('../cmd/binary');
return new BinaryCommand().stripAssetsFromApplication(args.params.file);
},
examples: {
'$0 $command app-with-assets.bin': 'Remove assets from the application binary'
}
});

return binary;
};
93 changes: 76 additions & 17 deletions src/cmd/binary.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,22 @@ License along with this program; if not, see <http://www.gnu.org/licenses/>.
*/

const fs = require('fs-extra');
const os = require('os');
const path = require('path');
const VError = require('verror');
const chalk = require('chalk');
const { HalModuleParser: Parser, unpackApplicationAndAssetBundle, isAssetValid, createProtectedModule, ModuleInfo } = require('binary-version-reader');
const {
HalModuleParser: Parser,
unpackApplicationAndAssetBundle,
isAssetValid,
createProtectedModule,
ModuleInfo,
listModuleExtensions,
removeModuleExtensions
} = require('binary-version-reader');
const utilities = require('../lib/utilities');
const ensureError = utilities.ensureError;
const filenameNoExt = utilities.filenameNoExt;

const INVALID_SUFFIX_SIZE = 65535;
const DEFAULT_PRODUCT_ID = 65535;
Expand All @@ -43,7 +53,8 @@ class BinaryCommand {
async inspectBinary(file) {
await this._checkFile(file);
const extractedFiles = await this._extractApplicationFiles(file);
const parsedAppInfo = await this._parseApplicationBinary(extractedFiles.application);
const parsedAppInfo = await this._parseBinary(extractedFiles.application);
await this._showInspectOutput(parsedAppInfo);
const assets = extractedFiles.assets;
await this._verifyBundle(parsedAppInfo, assets);
}
Expand Down Expand Up @@ -81,6 +92,61 @@ class BinaryCommand {
}
}

async listAssetsFromApplication(file) {
await this._checkFile(file);
const extractedFile = await this._extractApplicationFiles(file);
const parsedAppInfo = await this._parseBinary(extractedFile.application);

const assets = await listModuleExtensions({
module: parsedAppInfo.fileBuffer,
exts: [ModuleInfo.ModuleInfoExtension.ASSET_DEPENDENCY]
});

//if no assets, print no assets
if (assets.length === 0) {
throw new Error('No assets found');
}

console.log('Assets found in ' + path.basename(file) + ':');
for (const asset of assets) {
console.log(' ' + chalk.bold(asset.name) + ' (' + asset.hash + ')');
}
console.log(os.EOL);

return assets;

}

async stripAssetsFromApplication(file) {
// Verify that the file exists and that it has assets
this._checkFile(file);
const extractedFile = await this._extractApplicationFiles(file);
const parsedAppInfo = await this._parseBinary(extractedFile.application);

const assets = await listModuleExtensions({
module: parsedAppInfo.fileBuffer,
exts: [ModuleInfo.ModuleInfoExtension.ASSET_DEPENDENCY]
});

//if no assets, print no assets
if (assets.length === 0) {
throw new Error('No assets found');
}

// Remove assets
const appWithAssetsRemoved = await removeModuleExtensions({
module: parsedAppInfo.fileBuffer,
exts: [ModuleInfo.ModuleInfoExtension.ASSET_DEPENDENCY]
});

// Provide the path of the new application binary file with assets removed
const outputFile = filenameNoExt(file) + '-no-assets.bin';
await fs.writeFile(outputFile, appWithAssetsRemoved);
console.log('Application binary without assets saved to ' + outputFile);
console.log(os.EOL);
return outputFile;
}

async _checkFile(file) {
try {
await fs.access(file);
Expand Down Expand Up @@ -110,24 +176,15 @@ class BinaryCommand {
}
}

async _parseApplicationBinary(applicationBinary) {
const parser = new Parser();
let fileInfo;
try {
fileInfo = await parser.parseBuffer({ filename: applicationBinary.name, fileBuffer: applicationBinary.data });
} catch (err) {
throw new VError(ensureError(err), `Could not parse ${applicationBinary.name}`);
}

const filename = path.basename(fileInfo.filename);
if (fileInfo.suffixInfo.suffixSize === INVALID_SUFFIX_SIZE){
async _showInspectOutput(appInfo) {
const filename = path.basename(appInfo.filename);
if (appInfo.suffixInfo.suffixSize === INVALID_SUFFIX_SIZE){
throw new VError(`${filename} does not contain inspection information`);
}
console.log(chalk.bold(filename));
this._showCrc(fileInfo);
this._showPlatform(fileInfo);
this._showModuleInfo(fileInfo);
return fileInfo;
this._showCrc(appInfo);
this._showPlatform(appInfo);
this._showModuleInfo(appInfo);
}

async _parseBinary(binary) {
Expand Down Expand Up @@ -241,6 +298,8 @@ class BinaryCommand {
+ chalk.bold(fileInfo.prefixInfo.dep2ModuleVersion.toString()));
}
}


}

module.exports = BinaryCommand;
103 changes: 72 additions & 31 deletions src/cmd/binary.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const { expect } = require('../../test/setup');
const path = require('path');
const fs = require('fs-extra');
const { PATH_FIXTURES_THIRDPARTY_OTA_DIR, PATH_FIXTURES_BINARIES_DIR } = require('../../test/lib/env');
describe('Binary Inspect', () => {
describe.only('Binary Inspect', () => {
let binaryCommand;

beforeEach(async () => {
Expand Down Expand Up @@ -104,35 +104,6 @@ describe('Binary Inspect', () => {
});
});

describe('_parseApplicationBinary', () => {
it('parses a .bin file', async () => {
const name = 'argon_stroby.bin';
const data = await fs.readFile(path.join(PATH_FIXTURES_BINARIES_DIR, name));
const applicationBinary = { name, data };

const res = await binaryCommand._parseApplicationBinary(applicationBinary);

expect(path.basename(res.filename)).to.equal('argon_stroby.bin');
expect(res.crc.ok).to.equal(true);
expect(res).to.have.property('prefixInfo');
expect(res).to.have.property('suffixInfo');
});

it('errors if the binary is not valid', async () => {
const applicationBinary = { name: 'junk', data: Buffer.from('junk') };

let error;
try {
await binaryCommand._parseApplicationBinary(applicationBinary);
} catch (_error) {
error = _error;
}

expect(error).to.be.an.instanceof(Error);
expect(error.message).to.match(/Could not parse junk/);
});
});

describe('_parseBinary', () => {
it('parses a .bin file', async () => {
const name = 'argon_stroby.bin';
Expand Down Expand Up @@ -166,7 +137,7 @@ describe('Binary Inspect', () => {
it('verifies bundle with asset info', async () => {
const zipPath = path.join(PATH_FIXTURES_THIRDPARTY_OTA_DIR, 'bundle.zip');
const res = await binaryCommand._extractApplicationFiles(zipPath);
const parsedBinaryInfo = await binaryCommand._parseApplicationBinary(res.application);
const parsedBinaryInfo = await binaryCommand._parseBinary(res.application);

const verify = await binaryCommand._verifyBundle(parsedBinaryInfo, res.assets);

Expand Down Expand Up @@ -251,5 +222,75 @@ describe('Binary Inspect', () => {
expect(error.message).to.equal('Device protection feature is not supported for this binary.');
});
});

describe('listAssetsFromApplication', () => {
it('lists assets from a bundle', async () => {
const zipPath = path.join(PATH_FIXTURES_THIRDPARTY_OTA_DIR, 'bundle.zip');

const assets = await binaryCommand.listAssetsFromApplication(zipPath);

expect(assets).to.have.lengthOf(3);
expect(assets.map(a => a.name)).to.eql(['cat.txt', 'house.txt', 'water.txt']);
});

it('lists assets from an application binary', async () => {
const binPath = path.join(PATH_FIXTURES_THIRDPARTY_OTA_DIR, 'app-with-assets.bin');

const assets = await binaryCommand.listAssetsFromApplication(binPath);

expect(assets).to.have.lengthOf(3);
expect(assets.map(a => a.name)).to.eql(['cat.txt', 'house.txt', 'water.txt']);
});

it('lists assets from a binary which does not have assets', async () => {
const binPath = path.join(PATH_FIXTURES_BINARIES_DIR, 'argon_stroby.bin');

let error;
try {
await binaryCommand.listAssetsFromApplication(binPath);
} catch (e) {
error = e;
}

expect(error).to.be.an.instanceof(Error);
expect(error.message).to.equal('No assets found');
});
});

describe('stripAssetsFromApplication', () => {
it('strips assets from a binary', async () => {
const binPath = path.join(PATH_FIXTURES_THIRDPARTY_OTA_DIR, 'app-with-assets.bin');

const res = await binaryCommand.stripAssetsFromApplication(binPath);

expect(res).to.equal(PATH_FIXTURES_THIRDPARTY_OTA_DIR + '/app-with-assets-no-assets.bin');

await fs.remove(res);
});

it('strips assets from a bundle', async () => {
const zipPath = path.join(PATH_FIXTURES_THIRDPARTY_OTA_DIR, 'bundle.zip');

const res = await binaryCommand.stripAssetsFromApplication(zipPath);

expect(res).to.equal(PATH_FIXTURES_THIRDPARTY_OTA_DIR + '/bundle-no-assets.bin');

await fs.remove(res);
});

it('errors if binary has no assets', async () => {
const binPath = path.join(PATH_FIXTURES_BINARIES_DIR, 'argon_stroby.bin');

let error;
try {
await binaryCommand.stripAssetsFromApplication(binPath);
} catch (e) {
error = e;
}

expect(error).to.be.an.instanceof(Error);
expect(error.message).to.equal('No assets found');
});
});
});

Binary file not shown.
Loading

0 comments on commit ad11f3a

Please sign in to comment.