Skip to content

Commit c3e6593

Browse files
authored
Merge pull request #774 from particle-iot/feature/strip-assets-from-binary
Remove assets from application binary
2 parents 492a2e1 + 375096a commit c3e6593

File tree

8 files changed

+267
-57
lines changed

8 files changed

+267
-57
lines changed

npm-shrinkwrap.json

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
],
5454
"dependencies": {
5555
"@particle/device-constants": "^3.5.0",
56-
"binary-version-reader": "^2.4.0",
56+
"binary-version-reader": "^2.5.1",
5757
"chalk": "^2.4.2",
5858
"cli-progress": "^3.12.0",
5959
"cli-spinner": "^0.2.10",

src/cli/binary.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,27 @@ module.exports = ({ commandProcessor, root }) => {
2828
}
2929
});
3030

31+
commandProcessor.createCommand(binary, 'list-assets', 'Lists assets present in an application binary', {
32+
params: '<file>',
33+
handler: (args) => {
34+
const BinaryCommand = require('../cmd/binary');
35+
return new BinaryCommand().listAssetsFromApplication(args.params.file);
36+
},
37+
examples: {
38+
'$0 $command app-with-assets.bin': 'Show the list of assets in the application binary'
39+
}
40+
});
41+
42+
commandProcessor.createCommand(binary, 'strip-assets', 'Remove assets from application binary', {
43+
params: '<file>',
44+
handler: (args) => {
45+
const BinaryCommand = require('../cmd/binary');
46+
return new BinaryCommand().stripAssetsFromApplication(args.params.file);
47+
},
48+
examples: {
49+
'$0 $command app-with-assets.bin': 'Remove assets from the application binary'
50+
}
51+
});
52+
3153
return binary;
3254
};

src/cmd/binary.js

Lines changed: 76 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,22 @@ License along with this program; if not, see <http://www.gnu.org/licenses/>.
2626
*/
2727

2828
const fs = require('fs-extra');
29+
const os = require('os');
2930
const path = require('path');
3031
const VError = require('verror');
3132
const chalk = require('chalk');
32-
const { HalModuleParser: Parser, unpackApplicationAndAssetBundle, isAssetValid, createProtectedModule, ModuleInfo } = require('binary-version-reader');
33+
const {
34+
HalModuleParser: Parser,
35+
unpackApplicationAndAssetBundle,
36+
isAssetValid,
37+
createProtectedModule,
38+
ModuleInfo,
39+
listModuleExtensions,
40+
removeModuleExtensions
41+
} = require('binary-version-reader');
3342
const utilities = require('../lib/utilities');
3443
const ensureError = utilities.ensureError;
44+
const filenameNoExt = utilities.filenameNoExt;
3545

3646
const INVALID_SUFFIX_SIZE = 65535;
3747
const DEFAULT_PRODUCT_ID = 65535;
@@ -43,7 +53,8 @@ class BinaryCommand {
4353
async inspectBinary(file) {
4454
await this._checkFile(file);
4555
const extractedFiles = await this._extractApplicationFiles(file);
46-
const parsedAppInfo = await this._parseApplicationBinary(extractedFiles.application);
56+
const parsedAppInfo = await this._parseBinary(extractedFiles.application);
57+
await this._showInspectOutput(parsedAppInfo);
4758
const assets = extractedFiles.assets;
4859
await this._verifyBundle(parsedAppInfo, assets);
4960
}
@@ -81,6 +92,61 @@ class BinaryCommand {
8192
}
8293
}
8394

95+
async listAssetsFromApplication(file) {
96+
await this._checkFile(file);
97+
const extractedFile = await this._extractApplicationFiles(file);
98+
const parsedAppInfo = await this._parseBinary(extractedFile.application);
99+
100+
const assets = await listModuleExtensions({
101+
module: parsedAppInfo.fileBuffer,
102+
exts: [ModuleInfo.ModuleInfoExtension.ASSET_DEPENDENCY]
103+
});
104+
105+
//if no assets, print no assets
106+
if (assets.length === 0) {
107+
throw new Error('No assets found');
108+
}
109+
110+
console.log('Assets found in ' + path.basename(file) + ':');
111+
for (const asset of assets) {
112+
console.log(' ' + chalk.bold(asset.name) + ' (' + asset.hash + ')');
113+
}
114+
console.log(os.EOL);
115+
116+
return assets;
117+
118+
}
119+
120+
async stripAssetsFromApplication(file) {
121+
// Verify that the file exists and that it has assets
122+
this._checkFile(file);
123+
const extractedFile = await this._extractApplicationFiles(file);
124+
const parsedAppInfo = await this._parseBinary(extractedFile.application);
125+
126+
const assets = await listModuleExtensions({
127+
module: parsedAppInfo.fileBuffer,
128+
exts: [ModuleInfo.ModuleInfoExtension.ASSET_DEPENDENCY]
129+
});
130+
131+
//if no assets, print no assets
132+
if (assets.length === 0) {
133+
throw new Error('No assets found');
134+
}
135+
136+
// Remove assets
137+
const appWithAssetsRemoved = await removeModuleExtensions({
138+
module: parsedAppInfo.fileBuffer,
139+
exts: [ModuleInfo.ModuleInfoExtension.ASSET_DEPENDENCY]
140+
});
141+
142+
// Provide the path of the new application binary file with assets removed
143+
const outputFile = filenameNoExt(file) + '-no-assets.bin';
144+
await fs.writeFile(outputFile, appWithAssetsRemoved);
145+
console.log('Application binary without assets saved to ' + outputFile);
146+
console.log(os.EOL);
147+
return outputFile;
148+
}
149+
84150
async _checkFile(file) {
85151
try {
86152
await fs.access(file);
@@ -110,24 +176,15 @@ class BinaryCommand {
110176
}
111177
}
112178

113-
async _parseApplicationBinary(applicationBinary) {
114-
const parser = new Parser();
115-
let fileInfo;
116-
try {
117-
fileInfo = await parser.parseBuffer({ filename: applicationBinary.name, fileBuffer: applicationBinary.data });
118-
} catch (err) {
119-
throw new VError(ensureError(err), `Could not parse ${applicationBinary.name}`);
120-
}
121-
122-
const filename = path.basename(fileInfo.filename);
123-
if (fileInfo.suffixInfo.suffixSize === INVALID_SUFFIX_SIZE){
179+
async _showInspectOutput(appInfo) {
180+
const filename = path.basename(appInfo.filename);
181+
if (appInfo.suffixInfo.suffixSize === INVALID_SUFFIX_SIZE){
124182
throw new VError(`${filename} does not contain inspection information`);
125183
}
126184
console.log(chalk.bold(filename));
127-
this._showCrc(fileInfo);
128-
this._showPlatform(fileInfo);
129-
this._showModuleInfo(fileInfo);
130-
return fileInfo;
185+
this._showCrc(appInfo);
186+
this._showPlatform(appInfo);
187+
this._showModuleInfo(appInfo);
131188
}
132189

133190
async _parseBinary(binary) {
@@ -241,6 +298,8 @@ class BinaryCommand {
241298
+ chalk.bold(fileInfo.prefixInfo.dep2ModuleVersion.toString()));
242299
}
243300
}
301+
302+
244303
}
245304

246305
module.exports = BinaryCommand;

src/cmd/binary.test.js

Lines changed: 71 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -104,35 +104,6 @@ describe('Binary Inspect', () => {
104104
});
105105
});
106106

107-
describe('_parseApplicationBinary', () => {
108-
it('parses a .bin file', async () => {
109-
const name = 'argon_stroby.bin';
110-
const data = await fs.readFile(path.join(PATH_FIXTURES_BINARIES_DIR, name));
111-
const applicationBinary = { name, data };
112-
113-
const res = await binaryCommand._parseApplicationBinary(applicationBinary);
114-
115-
expect(path.basename(res.filename)).to.equal('argon_stroby.bin');
116-
expect(res.crc.ok).to.equal(true);
117-
expect(res).to.have.property('prefixInfo');
118-
expect(res).to.have.property('suffixInfo');
119-
});
120-
121-
it('errors if the binary is not valid', async () => {
122-
const applicationBinary = { name: 'junk', data: Buffer.from('junk') };
123-
124-
let error;
125-
try {
126-
await binaryCommand._parseApplicationBinary(applicationBinary);
127-
} catch (_error) {
128-
error = _error;
129-
}
130-
131-
expect(error).to.be.an.instanceof(Error);
132-
expect(error.message).to.match(/Could not parse junk/);
133-
});
134-
});
135-
136107
describe('_parseBinary', () => {
137108
it('parses a .bin file', async () => {
138109
const name = 'argon_stroby.bin';
@@ -166,7 +137,7 @@ describe('Binary Inspect', () => {
166137
it('verifies bundle with asset info', async () => {
167138
const zipPath = path.join(PATH_FIXTURES_THIRDPARTY_OTA_DIR, 'bundle.zip');
168139
const res = await binaryCommand._extractApplicationFiles(zipPath);
169-
const parsedBinaryInfo = await binaryCommand._parseApplicationBinary(res.application);
140+
const parsedBinaryInfo = await binaryCommand._parseBinary(res.application);
170141

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

@@ -251,5 +222,75 @@ describe('Binary Inspect', () => {
251222
expect(error.message).to.equal('Device protection feature is not supported for this binary.');
252223
});
253224
});
225+
226+
describe('listAssetsFromApplication', () => {
227+
it('lists assets from a bundle', async () => {
228+
const zipPath = path.join(PATH_FIXTURES_THIRDPARTY_OTA_DIR, 'bundle.zip');
229+
230+
const assets = await binaryCommand.listAssetsFromApplication(zipPath);
231+
232+
expect(assets).to.have.lengthOf(3);
233+
expect(assets.map(a => a.name)).to.eql(['cat.txt', 'house.txt', 'water.txt']);
234+
});
235+
236+
it('lists assets from an application binary', async () => {
237+
const binPath = path.join(PATH_FIXTURES_THIRDPARTY_OTA_DIR, 'app-with-assets.bin');
238+
239+
const assets = await binaryCommand.listAssetsFromApplication(binPath);
240+
241+
expect(assets).to.have.lengthOf(3);
242+
expect(assets.map(a => a.name)).to.eql(['cat.txt', 'house.txt', 'water.txt']);
243+
});
244+
245+
it('lists assets from a binary which does not have assets', async () => {
246+
const binPath = path.join(PATH_FIXTURES_BINARIES_DIR, 'argon_stroby.bin');
247+
248+
let error;
249+
try {
250+
await binaryCommand.listAssetsFromApplication(binPath);
251+
} catch (e) {
252+
error = e;
253+
}
254+
255+
expect(error).to.be.an.instanceof(Error);
256+
expect(error.message).to.equal('No assets found');
257+
});
258+
});
259+
260+
describe('stripAssetsFromApplication', () => {
261+
it('strips assets from a binary', async () => {
262+
const binPath = path.join(PATH_FIXTURES_THIRDPARTY_OTA_DIR, 'app-with-assets.bin');
263+
264+
const res = await binaryCommand.stripAssetsFromApplication(binPath);
265+
266+
expect(res).to.equal(path.join(PATH_FIXTURES_THIRDPARTY_OTA_DIR, 'app-with-assets-no-assets.bin'));
267+
268+
await fs.remove(res);
269+
});
270+
271+
it('strips assets from a bundle', async () => {
272+
const zipPath = path.join(PATH_FIXTURES_THIRDPARTY_OTA_DIR, 'bundle.zip');
273+
274+
const res = await binaryCommand.stripAssetsFromApplication(zipPath);
275+
276+
expect(res).to.equal(path.join(PATH_FIXTURES_THIRDPARTY_OTA_DIR, 'bundle-no-assets.bin'));
277+
278+
await fs.remove(res);
279+
});
280+
281+
it('errors if binary has no assets', async () => {
282+
const binPath = path.join(PATH_FIXTURES_BINARIES_DIR, 'argon_stroby.bin');
283+
284+
let error;
285+
try {
286+
await binaryCommand.stripAssetsFromApplication(binPath);
287+
} catch (e) {
288+
error = e;
289+
}
290+
291+
expect(error).to.be.an.instanceof(Error);
292+
expect(error.message).to.equal('No assets found');
293+
});
294+
});
254295
});
255296

Binary file not shown.

0 commit comments

Comments
 (0)