"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EnclaveService = void 0;
const { SgxLowLevel, GramineProtectedFS } = require("../../bindings/sgx-native/build/Release/sgx_native.node");
const crypto_1 = require("crypto");
const fs_1 = require("fs");
const os_1 = require("os");
const path_1 = require("path");
const stream_1 = require("stream");
// @TODO: Read it from nodejs module
const PF_SIZE = 4096;
const chunkedTransformer = (chunkSize = PF_SIZE) => {
    const accumulateBuf = {
        buf: Buffer.alloc(chunkSize),
        bufSize: 0,
    };
    return new stream_1.Transform({
        objectMode: false,
        flush: (callback) => {
            callback(null, accumulateBuf.buf.subarray(0, accumulateBuf.bufSize));
            accumulateBuf.buf = Buffer.alloc(0);
            accumulateBuf.bufSize = 0;
        },
        transform: function (chunk, encoding, callback) {
            if (chunk.length + accumulateBuf.bufSize < chunkSize) {
                const copied = chunk.copy(accumulateBuf.buf, accumulateBuf.bufSize);
                accumulateBuf.bufSize += copied;
            }
            else {
                const mergedBuf = Buffer.concat([accumulateBuf.buf.subarray(0, accumulateBuf.bufSize), chunk]);
                accumulateBuf.bufSize = 0;
                this.push(mergedBuf);
            }
            callback(null, null);
        },
    });
};
class EnclaveService {
    constructor(tmpFolder = (0, os_1.tmpdir)()) {
        this.tmpFolder = tmpFolder;
        this.sgx = new SgxLowLevel();
    }
    /**
     * Requests secret keys from cpu.
     * @param type Value from enum @type KeyType
     * @param policy Value from enum @type KeyPolicy
     * @param previousRequest Request, for example received in a previous call to this method,
     *                        can be undefined - this way a new request will be generated
     * @returns Key and request, generated by method.
     */
    async getSecretKey(type, policy, previousRequest) {
        if (!previousRequest) {
            previousRequest = Buffer.alloc(0);
        }
        return this.sgx.getKey(type, policy, previousRequest);
    }
    /**
     * Determines whether the request to obtain the key is outdated, for example, cpu_svn or isv_svn were updated
     * @param previousRequest - Key Request
     * @returns True if the request is outdated, false - the request is up to date
     */
    async isOutdatedRequest(previousRequest) {
        return this.sgx.isOutdatedRequest(previousRequest);
    }
    /**
     * Returns TEE device information.
     * @returns
     */
    async getTeeDeviceInfo() {
        return this.sgx.getDeviceInfo();
    }
    /**
     * Returns TEE cpu benchmark.
     * @param deviceInfoMemSize
     * @returns
     */
    async getTeeDeviceCpuBenchmark(deviceInfoMemSize) {
        return this.sgx.runCpuBenchmark(deviceInfoMemSize);
    }
    /**
     * Returns TEE memory benchmark.
     * @param deviceInfoTotalPhysicalCores
     * @returns
     */
    async getTeeDeviceMemoryBenchmark(deviceInfoTotalPhysicalCores) {
        return this.sgx.runMemoryBenchmark(deviceInfoTotalPhysicalCores);
    }
    /**
     * Writes input stream to the protected file.
     * @param inputStream
     * @param outputStream
     * @param filepath
     * @param secretKey
     */
    async writeGramineProtectedFile(filepath, inputStream, secretKey) {
        const tmpFileName = (0, path_1.join)(this.tmpFolder, "tee-pf-" + (0, crypto_1.randomBytes)(16).toString("hex") + ".tmp");
        const protectedFsKey = secretKey.length !== 16
            ? (0, crypto_1.createHash)("sha256", secretKey).update(secretKey).digest().slice(0, 16)
            : secretKey;
        let output = null;
        try {
            output = new GramineProtectedFS(tmpFileName, filepath, protectedFsKey, true);
            let totalSize = 0;
            let pfChunkSize = PF_SIZE;
            if (process.env["GRAMINE_PROTECTED_FILES_STREAM_CACHE_SIZE"]) {
                pfChunkSize = parseInt(process.env["GRAMINE_PROTECTED_FILES_STREAM_CACHE_SIZE"]);
            }
            let batchChunkIndex = 0;
            const everyTenFlush = 10;
            for await (const chunk of inputStream.pipe(chunkedTransformer(pfChunkSize))) {
                if (!chunk.length) {
                    // @TODO: It's may be normal, research
                    continue;
                }
                const writtenSize = await output.write(totalSize, chunk);
                if (writtenSize !== chunk.length) {
                    throw new Error(`Fail to write ${chunk.length} to protected file ${filepath}:${tmpFileName}, written only ${writtenSize} bytes`);
                }
                totalSize += writtenSize;
                if (batchChunkIndex && batchChunkIndex++ % everyTenFlush) {
                    await output.flush();
                }
            }
            await output.close();
            return {
                filepath: tmpFileName,
                filesize: (await fs_1.promises.stat(tmpFileName)).size,
                writtenSize: totalSize,
            };
        }
        catch (error) {
            if (output) {
                await output.close();
                await fs_1.promises.rm(tmpFileName, {
                    recursive: true,
                    force: true,
                });
            }
            throw error;
        }
    }
    /**
     * Reads protected file and write to output stream.
     * @param filepath
     * @param inputStream
     * @param outputStream
     * @param secretKey
     */
    async readGramineProtectedFile(filepath, inputStream, secretKey) {
        const tmpFileName = (0, path_1.join)(this.tmpFolder, "tee-pf-" + (0, crypto_1.randomBytes)(16).toString("hex") + ".tmp");
        const protectedFsKey = secretKey.length !== 16
            ? (0, crypto_1.createHash)("sha256", secretKey).update(secretKey).digest().slice(0, 16)
            : secretKey;
        let input = null;
        try {
            const tmpFileStream = (0, fs_1.createWriteStream)(tmpFileName);
            await stream_1.promises.pipeline(inputStream, tmpFileStream);
            input = new GramineProtectedFS(tmpFileName, filepath, protectedFsKey, false);
            const originalFileSize = input.size();
            const readChunkSize = PF_SIZE * 8;
            let isFileEnd = false;
            let cursor = 0;
            let remainedBuffer = Buffer.alloc(0);
            const decryptedStream = new stream_1.Readable({
                read: async function (requestSize) {
                    if (isFileEnd && !remainedBuffer.length) {
                        if (input) {
                            await input.close();
                        }
                        return this.push(null);
                    }
                    let readBuffer = remainedBuffer;
                    let remainedReadSize = requestSize - readBuffer.length;
                    while (!isFileEnd && remainedReadSize > 0) {
                        const chunk = await input.read(cursor, readChunkSize);
                        if (!chunk?.length) {
                            isFileEnd = true;
                            break;
                        }
                        readBuffer = Buffer.concat([readBuffer, chunk]);
                        cursor += chunk.length;
                        remainedReadSize -= chunk.length;
                        if (chunk.length < readChunkSize) {
                            isFileEnd = true;
                            break;
                        }
                    }
                    remainedBuffer = readBuffer.slice(requestSize);
                    this.push(readBuffer.slice(0, requestSize));
                },
            });
            return {
                filepath: tmpFileName,
                fileStream: decryptedStream,
                filesize: originalFileSize,
            };
        }
        catch (error) {
            await fs_1.promises.rm(tmpFileName, {
                recursive: true,
                force: true,
            });
            if (input) {
                await input.close();
            }
            throw error;
        }
    }
}
exports.EnclaveService = EnclaveService;
//# sourceMappingURL=data:application/json;base64, 