Skip to content

Commit

Permalink
Add instance images, encapsulate and add docs
Browse files Browse the repository at this point in the history
  • Loading branch information
strazzere committed Sep 17, 2021
1 parent edfea16 commit fdc5dac
Show file tree
Hide file tree
Showing 3 changed files with 268 additions and 132 deletions.
127 changes: 127 additions & 0 deletions src/images.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
"use strict";

const Resumable = require("../resumable");
const util = require("util");
const fs = require("fs");
const path = require("path");
const os = require("os");
const JSZip = require("jszip");

/**
* @typedef {object} KernelImage
* @property {string} id
* @property {string} name
*/

/**
* @typedef {object} FirmwareImage
* @property {string} id
* @property {string} name
*/

/**
* @typedef {object} Image
* @property {string} status - "active"
* @property {string} id - uuid needed to pass to a createInstance call if this is a kernel
* @property {string} name - "Image"
* @property {string} type - "kernel"
* @property {string} self - uri
* @property {string} file - file uri
* @property {number} size
* @property {string} checksum
* @property {string} encoding - "encrypted"
* @property {string} project - Project uuid
* @property {string} createdAt - ISO datetime string
* @property {string} updatedAt - ISO datetime string
*/

class File {
constructor({ filePath, type, size }) {
this.path = filePath;
this.name = path.basename(filePath);
this.type = type;
this.size = size;
}

slice(start, end, _contentType) {
return fs.createReadStream(this.path, { start, end });
}
}

function isCompressed(data) {
return Buffer.compare(data.slice(0, 4), Buffer.from([0x50, 0x4b, 0x03, 0x04])) === 0;
}

async function compress(data, name) {
var zip = new JSZip();
var tmpfile = path.join(os.tmpdir(), name);
zip.file(name, data);

const streamZip = new Promise((resolve, reject) => {
zip.generateNodeStream({
type: "nodebuffer",
streamFile: true,
})
.pipe(fs.createWriteStream(tmpfile))
.on("finish", function () {
resolve();
})
.on("error", function (err) {
reject(err);
});
});

await streamZip;

return tmpfile;
}

async function uploadFile(token, url, filePath, progress) {
return new Promise((resolve, reject) => {
const r = new Resumable({
target: url,
headers: {
authorization: token,
"x-corellium-image-encoding": "plain",
},
uploadMethod: "PUT",
chunkSize: 5 * 1024 * 1024,
prioritizeFirstAndLastChunk: true,
method: "octet",
});

r.on("fileAdded", (_file) => {
r.upload();
});

r.on("progress", () => {
if (progress) progress(r.progress());
});

r.on("fileError", (_file, message) => {
reject(message);
});

r.on("fileSuccess", (_file, message) => {
resolve(JSON.parse(message));
});

return util
.promisify(fs.stat)(filePath)
.then((stat) => {
const file = new File({
filePath: filePath,
type: "application/octet-stream",
size: stat.size,
});

r.addFile(file);
});
});
}

module.exports = {
isCompressed,
compress,
uploadFile,
};
100 changes: 100 additions & 0 deletions src/instance.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ const Agent = require("./agent");
const pTimeout = require("p-timeout");
const NetworkMonitor = require("./netmon");
const { sleep } = require("./util/sleep");
const util = require("util");
const fs = require("fs");
const { isCompressed, compress, uploadFile } = require("./images");
const uuidv4 = require("uuid/v4");

/**
* @typedef {object} ThreadInfo
Expand Down Expand Up @@ -768,6 +772,102 @@ class Instance extends EventEmitter {
async _fetch(endpoint = "", options = {}) {
return await fetchApi(this.project, `/instances/${this.id}${endpoint}`, options);
}

/**
* Delete Iot Firmware that is attached to an instance
* @param {FirmwareImage} firmwareImage
*/
async deleteIotFirmware(firmwareImage) {
return await this.deleteImage(firmwareImage);
}

/**
* Delete kernel that is attached to an instance
* @param {KernelImage} kernelImage
*/
async deleteKernel(kernelImage) {
return await this.deleteImage(kernelImage);
}

/**
* Delete an image that is attached to an instance
* @param {Image | KernelImage | FirmwareImage} kernelImage
*/
async deleteImage(image) {
return await fetchApi(this, `/images/${image.id}`, {
method: "DELETE",
});
}

/**
* Add a custom IoT firmware image to a project for use in creating new instances.
*
* @param {string} filePath - The path on the local file system to get the firmware file.
* @param {string} name - The name of the file to identify the file on the server. Usually the basename of the path.
* @param {Project~progressCallback} [progress] - The callback for file upload progress information.
*
* @returns {Promise<FirmwareImage>}
*/
async uploadIotFirmware(filePath, name, progress) {
return await this.uploadKernel(filePath, name, progress);
}

/**
* Add a kernel image to a project for use in creating new instances.
*
* @param {string} filePath - The path on the local file system to get the kernel file.
* @param {string} name - The name of the file to identify the file on the server. Usually the basename of the path.
* @param {Project~progressCallback} [progress] - The callback for file upload progress information.
*
* @returns {Promise<KernelImage>}
*/
async uploadKernel(filePath, name, progress) {
let tmpfile = null;
const data = await util.promisify(fs.readFile)(filePath);

if (!isCompressed(data)) {
tmpfile = await compress(data, name);
}

let uploadedImage = await this.uploadImage(
"kernel",
tmpfile ? tmpfile : filePath,
name,
progress,
);

if (tmpfile) {
fs.unlinkSync(tmpfile);
}

return { id: uploadedImage.id, name: uploadedImage.name };
}

/**
* Add an image to the project. These images may be removed at any time and are meant to facilitate creating a new Instance with images.
*
* @param {string} type - E.g. fw for the main firmware image.
* @param {string} filePath - The path on the local file system to get the file.
* @param {string} name - The name of the file to identify the file on the server. Usually the basename of the path.
* @param {Project~progressCallback} [progress] - The callback for file upload progress information.
*
* @returns {Promise<Image>}
*/
async uploadImage(type, filePath, name, progress) {
const token = await this.project.getToken();
const url =
this.project.api +
"/instances/" +
encodeURIComponent(this.id) +
"/image-upload/" +
encodeURIComponent(type) +
"/" +
encodeURIComponent(uuidv4()) +
"/" +
encodeURIComponent(name);

return await uploadFile(token, url, filePath, progress);
}
}

module.exports = Instance;
Loading

0 comments on commit fdc5dac

Please sign in to comment.