"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