UNPKG

hackpro-sdk

Version:
399 lines 15 kB
"use strict"; /* * Copyright 2018 balena.io * * 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. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const events_1 = require("events"); const fileType = require("file-type"); const partitioninfo_1 = require("partitioninfo"); const path_1 = require("path"); const process_1 = require("process"); const xxhash_1 = require("xxhash"); const constants_1 = require("../constants"); const errors_1 = require("../errors"); const sparse_filter_stream_1 = require("../sparse-stream/sparse-filter-stream"); const sparse_read_stream_1 = require("../sparse-stream/sparse-read-stream"); const utils_1 = require("../utils"); const progress_1 = require("./progress"); const BITS = process_1.arch === "x64" || process_1.arch === "aarch64" ? 64 : 32; class CountingHashStream extends xxhash_1.Stream { constructor() { super(...arguments); this.bytesWritten = 0; } _transform(chunk, encoding, callback) { super._transform(chunk, encoding, () => { callback(); this.bytesWritten += chunk.length; }); } } exports.CountingHashStream = CountingHashStream; exports.ProgressHashStream = progress_1.makeClassEmitProgressEvents(CountingHashStream, "bytesWritten", "bytesWritten", constants_1.PROGRESS_EMISSION_INTERVAL); function createHasher() { const hasher = new exports.ProgressHashStream(constants_1.XXHASH_SEED, BITS, "buffer"); hasher.on("finish", () => __awaiter(this, void 0, void 0, function* () { const checksum = (yield utils_1.streamToBuffer(hasher)).toString("hex"); hasher.emit("checksum", checksum); })); return hasher; } exports.createHasher = createHasher; class SourceDestinationFs { // Adapts a SourceDestination to an fs like interface (so it can be used in udif for example) constructor(source) { this.source = source; } open(_path, _options, callback) { callback(null, 1); } close(_fd, callback) { callback(null); } fstat(_fd, callback) { this.source .getMetadata() .then(metadata => { if (metadata.size === undefined) { callback(new Error("No size")); return; } callback(null, { size: metadata.size }); }) .catch(callback); } read(_fd, buffer, bufferOffset, length, sourceOffset, callback) { this.source .read(buffer, bufferOffset, length, sourceOffset) .then((res) => { callback(null, res.bytesRead, res.buffer); }) .catch(callback); } } exports.SourceDestinationFs = SourceDestinationFs; class Verifier extends events_1.EventEmitter { constructor() { super(...arguments); this.progress = { bytes: 0, position: 0, speed: 0 }; } handleEventsAndPipe(stream, meter) { meter.on("progress", (progress) => { this.progress = progress; this.emit("progress", progress); }); stream.on("end", this.emit.bind(this, "end")); meter.on("finish", this.emit.bind(this, "finish")); stream.once("error", () => { stream.unpipe(meter); meter.end(); }); stream.pipe(meter); } } exports.Verifier = Verifier; class StreamVerifier extends Verifier { constructor(source, checksum, size) { super(); this.source = source; this.checksum = checksum; this.size = size; } run() { return __awaiter(this, void 0, void 0, function* () { const stream = yield this.source.createReadStream(false, 0, this.size - 1); stream.on("error", this.emit.bind(this, "error")); const hasher = createHasher(); hasher.on("error", this.emit.bind(this, "error")); hasher.on("checksum", (streamChecksum) => { if (streamChecksum !== this.checksum) { this.emit("error", new errors_1.ChecksumVerificationError(`Source and destination checksums do not match: ${this.checksum} !== ${streamChecksum}`, streamChecksum, this.checksum)); } }); this.handleEventsAndPipe(stream, hasher); }); } } exports.StreamVerifier = StreamVerifier; class SparseStreamVerifier extends Verifier { constructor(source, blocks) { super(); this.source = source; this.blocks = blocks; } run() { return __awaiter(this, void 0, void 0, function* () { let stream; if (yield this.source.canRead()) { stream = new sparse_read_stream_1.SparseReadStream(this.source, this.blocks, constants_1.CHUNK_SIZE, true, // verify false // generateChecksums ); stream.on("error", this.emit.bind(this, "error")); } else if (yield this.source.canCreateReadStream()) { const originalStream = yield this.source.createReadStream(); originalStream.once("error", (error) => { originalStream.unpipe(transform); this.emit("error", error); }); const transform = new sparse_filter_stream_1.SparseFilterStream(this.blocks, true, // verify false // generateChecksums ); transform.once("error", (error) => { originalStream.unpipe(transform); // @ts-ignore if (typeof originalStream.destroy === "function") { // @ts-ignore originalStream.destroy(); } this.emit("error", error); }); originalStream.pipe(transform); stream = transform; } else { throw new errors_1.NotCapable(); } const meter = new progress_1.ProgressWritable({ objectMode: true }); this.handleEventsAndPipe(stream, meter); }); } } exports.SparseStreamVerifier = SparseStreamVerifier; class SourceDestination extends events_1.EventEmitter { constructor() { super(...arguments); this.isOpen = false; } static register(Cls) { if (Cls.mimetype !== undefined) { SourceDestination.mimetypes.set(Cls.mimetype, Cls); } } canRead() { return __awaiter(this, void 0, void 0, function* () { return false; }); } canWrite() { return __awaiter(this, void 0, void 0, function* () { return false; }); } canCreateReadStream() { return __awaiter(this, void 0, void 0, function* () { return false; }); } canCreateSparseReadStream() { return __awaiter(this, void 0, void 0, function* () { return false; }); } canCreateWriteStream() { return __awaiter(this, void 0, void 0, function* () { return false; }); } canCreateSparseWriteStream() { return __awaiter(this, void 0, void 0, function* () { return false; }); } getMetadata() { return __awaiter(this, void 0, void 0, function* () { if (this.metadata === undefined) { this.metadata = yield this._getMetadata(); } return this.metadata; }); } _getMetadata() { return __awaiter(this, void 0, void 0, function* () { return {}; }); } read(_buffer, _bufferOffset, _length, _sourceOffset) { return __awaiter(this, void 0, void 0, function* () { throw new errors_1.NotCapable(); }); } write(_buffer, _bufferOffset, _length, _fileOffset) { return __awaiter(this, void 0, void 0, function* () { throw new errors_1.NotCapable(); }); } createReadStream(_emitProgress = false, _start = 0, _end) { return __awaiter(this, void 0, void 0, function* () { throw new errors_1.NotCapable(); }); } createSparseReadStream(_generateChecksums = false) { return __awaiter(this, void 0, void 0, function* () { throw new errors_1.NotCapable(); }); } getBlocks() { return __awaiter(this, void 0, void 0, function* () { throw new errors_1.NotCapable(); }); } createWriteStream() { return __awaiter(this, void 0, void 0, function* () { throw new errors_1.NotCapable(); }); } createSparseWriteStream() { return __awaiter(this, void 0, void 0, function* () { throw new errors_1.NotCapable(); }); } open() { return __awaiter(this, void 0, void 0, function* () { if (!this.isOpen) { yield this._open(); this.isOpen = true; } }); } close() { return __awaiter(this, void 0, void 0, function* () { if (this.isOpen) { yield this._close(); this.isOpen = false; } }); } _open() { return __awaiter(this, void 0, void 0, function* () { // noop }); } _close() { return __awaiter(this, void 0, void 0, function* () { // noop }); } createVerifier(checksumOrBlocks, size) { if (Array.isArray(checksumOrBlocks)) { for (const block of checksumOrBlocks) { if (block.checksumType === undefined || block.checksum === undefined) { throw new Error("Block is missing checksum or checksumType attributes, can not create verifier"); } } return new SparseStreamVerifier(this, checksumOrBlocks); } else { if (size === undefined) { throw new Error("A size argument is required for creating a stream checksum verifier"); } return new StreamVerifier(this, checksumOrBlocks, size); } } getMimeTypeFromName() { return __awaiter(this, void 0, void 0, function* () { const metadata = yield this.getMetadata(); if (metadata.name === undefined) { return; } const extension = path_1.extname(metadata.name).toLowerCase(); if (extension === ".dmg" || ".hpro") { return "application/x-apple-diskimage"; } }); } getMimeTypeFromContent() { return __awaiter(this, void 0, void 0, function* () { let stream; try { stream = yield this.createReadStream(false, 0, 263); // TODO: constant } catch (error) { if (error instanceof errors_1.NotCapable) { return; } throw error; } const ft = fileType(yield utils_1.streamToBuffer(stream)); if (ft !== null) { return ft.mime; } }); } getInnerSourceHelper(mimetype) { return __awaiter(this, void 0, void 0, function* () { if (mimetype === undefined) { return this; } const Cls = SourceDestination.mimetypes.get(mimetype); if (Cls === undefined) { return this; } if (Cls.requiresRandomReadableSource && !(yield this.canRead())) { throw new errors_1.NotCapable(`Can not read a ${Cls.name} from a ${this.constructor.name}.`); } const innerSource = new Cls(this); return yield innerSource.getInnerSource(); }); } getInnerSource() { return __awaiter(this, void 0, void 0, function* () { yield this.open(); const metadata = yield this.getMetadata(); if (metadata.isEtch === true) { return this; } let mimetype = yield this.getMimeTypeFromName(); if (mimetype !== undefined) { try { return yield this.getInnerSourceHelper(mimetype); } catch (error) { if (error instanceof errors_1.NotCapable) { throw error; } // File extension may be wrong, try content. } } mimetype = yield this.getMimeTypeFromContent(); return yield this.getInnerSourceHelper(mimetype); }); } getPartitionTable() { return __awaiter(this, void 0, void 0, function* () { const stream = yield this.createReadStream(false, 0, 65535); // TODO: constant const buffer = yield utils_1.streamToBuffer(stream); try { return yield partitioninfo_1.getPartitions(buffer, { getLogical: false }); } catch (_a) { // no partitions } }); } } exports.SourceDestination = SourceDestination; SourceDestination.imageExtensions = ["img", "bin", "raw", "dmg", "hpro"]; SourceDestination.mimetypes = new Map(); //# sourceMappingURL=source-destination.js.map