UNPKG

blockmap

Version:
140 lines 4.99 kB
"use strict"; /** * @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