blockmap
Version:
Tizen's block map format
140 lines • 4.99 kB
JavaScript
;
/**
* @license
* Copyright 2019 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.parse = void 0;
const crypto_1 = require("crypto");
const xml_js_1 = require("xml-js");
const blockmap_1 = require("./blockmap");
function firstChild(element, name) {
if (element.elements !== undefined) {
return element.elements.find((e) => e.name === name);
}
}
function firstChildThrow(element, name) {
const result = firstChild(element, name);
if (result === undefined) {
throw new Error(`Missing tag "${name}"`);
}
return result;
}
function getAttribute(element, name) {
if (element.attributes !== undefined) {
return element.attributes[name];
}
}
function getAttributeThrow(element, name) {
const result = getAttribute(element, name);
if (result === undefined) {
throw new Error(`Missing attribute "${name}"`);
}
return result;
}
function getText(element) {
if (Array.isArray(element)) {
return element.map(getText).join('');
}
if (element.type === 'element') {
return getText(element.elements || []);
}
if (element.type === 'text' && element.text !== undefined) {
return '' + element.text;
}
return '';
}
function textContentThrow(element, name) {
return getText(firstChildThrow(element, name)).trim();
}
function textContent(element, name) {
const child = firstChild(element, name);
if (child !== undefined) {
return getText(child).trim();
}
}
function getRanges(element) {
const ranges = [];
for (const node of element.elements || []) {
if (node.type === 'element' &&
node.name !== undefined &&
node.name.toLowerCase() === 'range') {
const [start, end] = getText(node).trim().split('-');
const checksum = getAttribute(node, 'sha1') || getAttribute(node, 'chksum');
if (checksum === undefined) {
throw new Error('No checksum in range');
}
if (typeof checksum !== 'string') {
throw new Error('Checksum is not a string');
}
const range = {
start: parseInt(start, 10),
end: parseInt(end || start, 10),
checksum,
};
ranges.push(range);
}
}
return ranges;
}
/** Zero out the file checksum field for checksum calculation */
function maskChecksum(value) {
const pattern = /BmapFileChecksum/.test(value)
? /(<BmapFileChecksum>\s*)([a-z0-9]+)(\s*<\/BmapFileChecksum>)/i
: /(<BmapFileSHA1>\s*)([a-z0-9]+)(\s*<\/BmapFileSHA1>)/i;
return value.replace(pattern, (_match, start, checksum, end) => {
return start + checksum.replace(/./g, '0') + end;
});
}
/** Parse a .bmap file */
function parse(value, verify = true) {
value = value.toString();
const data = xml_js_1.xml2js(value, { compact: false });
const bmap = firstChildThrow(data, 'bmap');
const version = getAttributeThrow(bmap, 'version');
if (typeof version !== 'string') {
throw new Error(`Version is not a string: ${version}`);
}
if (!blockmap_1.BlockMap.versions.includes(version)) {
throw new Error(`Unsupported block map version "${version}"`);
}
const imageSize = parseInt(textContentThrow(bmap, 'ImageSize'), 10);
const blockSize = parseInt(textContentThrow(bmap, 'BlockSize'), 10);
const blocksCount = parseInt(textContentThrow(bmap, 'BlocksCount'), 10);
const mappedBlocksCount = parseInt(textContentThrow(bmap, 'MappedBlocksCount'), 10);
const checksum = textContent(bmap, 'BmapFileChecksum') || textContent(bmap, 'BmapFileSHA1');
const checksumType = textContent(bmap, 'ChecksumType') || 'sha1';
const map = firstChildThrow(bmap, 'BlockMap');
const ranges = getRanges(map);
if (verify && checksum !== undefined) {
const file = maskChecksum(value);
const digest = crypto_1.createHash(checksumType).update(file).digest('hex');
if (checksum !== digest) {
throw new Error(`File checksum mismatch: ${checksum} != ${digest}`);
}
}
return {
version,
imageSize,
blockSize,
blocksCount,
mappedBlocksCount,
checksum,
checksumType,
ranges,
};
}
exports.parse = parse;
//# sourceMappingURL=parse.js.map