hackpro-sdk
Version:
399 lines • 15 kB
JavaScript
"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