"use strict";
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*
*/
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 });
exports.DaemonClient = void 0;
const path = require("path");
const stream_1 = require("stream");
const imperative_1 = require("@zowe/imperative");
const DaemonUtil_1 = require("./DaemonUtil");
/**
 * Class for handling client connections to our persistent service (e.g. daemon mode)
 * @export
 * @class DaemonClient
 */
class DaemonClient {
    /**
     * Creates an instance of DaemonClient.
     * @param {net.Socket} mClient
     * @param {net.Server} mServer
     * @param {string} mOwner
     * @memberof DaemonClient
     */
    constructor(mClient, mServer, mOwner) {
        this.mClient = mClient;
        this.mServer = mServer;
        this.mOwner = mOwner;
        /**
         * The number of stdin bytes remaining to read from the daemon client.
         */
        this.stdinBytesRemaining = 0;
    }
    /**
     * Run an instance of this client and wait for proper events
     * @memberof DaemonClient
     */
    run() {
        imperative_1.Imperative.api.appLogger.trace('daemon client connected');
        this.mClient.on('end', this.end.bind(this));
        this.mClient.on('close', this.close.bind(this));
        this.mClient.on('data', this.data.bind(this));
    }
    /**
     * End event handler triggered when client disconnects
     * @private
     * @memberof DaemonClient
     */
    end() {
        imperative_1.Imperative.api.appLogger.trace('daemon client disconnected');
    }
    /**
     * Close event handler triggered when client closes connection
     * @private
     * @memberof DaemonClient
     */
    close() {
        imperative_1.Imperative.api.appLogger.trace('client closed');
    }
    /**
     * Shutdown the daemon server cleanly. This is triggered when our EXE
     * sends Control-C in the stdin property of its request object.
     * @private
     * @memberof DaemonClient
     */
    shutdown() {
        imperative_1.Imperative.api.appLogger.debug("shutting down");
        const pidFilePath = path.join(DaemonUtil_1.DaemonUtil.getDaemonDir(), "daemon_pid.json");
        if (imperative_1.IO.existsSync(pidFilePath)) {
            try {
                imperative_1.IO.deleteFile(pidFilePath);
            }
            catch (err) {
                imperative_1.Imperative.api.appLogger.error("Failed to delete file '" + pidFilePath +
                    "'\nDetails = " + err.message);
            }
        }
        this.mClient.end();
        this.mServer.close();
    }
    /**
     * Create readable stream for stdin data received from the daemon client.
     * @param data First chunk of stdin data
     * @param expectedLength Expected byte length of stdin data
     * @private
     * @memberof DaemonClient
     */
    createStdinStream(data, expectedLength) {
        const stream = new stream_1.PassThrough();
        stream.write(data);
        this.stdinBytesRemaining = expectedLength - data.byteLength;
        if (this.stdinBytesRemaining > 0) {
            // Handle really large stdin data that is buffered and received in multiple chunks
            this.mClient.pipe(stream);
            const outer = this; // eslint-disable-line @typescript-eslint/no-this-alias
            this.mClient.on("data", function listener(data) {
                outer.stdinBytesRemaining -= data.byteLength;
                if (outer.stdinBytesRemaining <= 0) {
                    outer.mClient.removeListener("data", listener);
                    outer.mClient.unpipe(stream);
                    stream.end();
                }
            });
        }
        else {
            stream.end();
        }
        return stream;
    }
    /**
     * Data event handler triggered for whenever data comes in on a connection
     * @private
     * @param {Buffer} data
     * @memberof DaemonClient
     */
    data(data) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.stdinBytesRemaining > 0)
                return;
            // Split JSON body and binary data from multipart response
            const jsonEndIdx = data.indexOf("}" + imperative_1.DaemonRequest.EOW_DELIMITER);
            let jsonData;
            let stdinData;
            try {
                jsonData = JSON.parse((jsonEndIdx !== -1 ? data.subarray(0, jsonEndIdx + 1) : data).toString());
                stdinData = jsonEndIdx !== -1 ? data.subarray(jsonEndIdx + 2) : undefined;
            }
            catch (error) {
                imperative_1.Imperative.api.appLogger.logError(new imperative_1.ImperativeError({
                    msg: "Failed to parse data received from daemon client",
                    causeErrors: error
                }));
                // eslint-disable-next-line @typescript-eslint/no-magic-numbers
                imperative_1.Imperative.api.appLogger.trace("First 1024 bytes of daemon request:\n", data.subarray(0, 1024).toString());
                const responsePayload = imperative_1.DaemonRequest.create({
                    stderr: "Failed to parse data received from daemon client:\n" + error.stack,
                    exitCode: 1
                });
                this.mClient.write(responsePayload);
                this.mClient.end();
                return;
            }
            let requestUser = undefined;
            if (jsonData.user != null) {
                try {
                    requestUser = Buffer.from(jsonData.user, 'base64').toString();
                }
                catch (err) {
                    imperative_1.Imperative.api.appLogger.error("The user field on a daemon request was malformed.");
                }
            }
            if (requestUser == null || requestUser === '') {
                // Someone tried connecting but is missing something important.
                imperative_1.Imperative.api.appLogger.warn("A connection was attempted without a valid user.");
                const responsePayload = imperative_1.DaemonRequest.create({
                    stderr: "The daemon client did not supply user information or supplied bad information.\n",
                    exitCode: 1
                });
                this.mClient.write(responsePayload);
                this.mClient.end();
                return;
            }
            else if (requestUser != this.mOwner) {
                // Someone else is trying to use the daemon, and should be stopped.
                imperative_1.Imperative.api.appLogger.warn("The user '" + requestUser + "' attempted to connect.");
                const responsePayload = imperative_1.DaemonRequest.create({
                    stderr: "The user '" + requestUser + "' cannot use this daemon.\n",
                    exitCode: 1
                });
                this.mClient.write(responsePayload);
                this.mClient.end();
                return;
            }
            if (jsonData.stdin != null) {
                if (jsonData.stdin !== DaemonClient.CTRL_C_CHAR) {
                    // This data is related to a prompt reply so we ignore it
                    return;
                }
                else if (this.mServer) {
                    // Ctrl+C signal was sent so we shutdown the server
                    this.shutdown();
                }
            }
            else {
                imperative_1.Imperative.commandLine = jsonData.argv.join(" ");
                imperative_1.Imperative.api.appLogger.trace(`daemon input command: ${imperative_1.Imperative.commandLine}`);
                const context = { stream: this.mClient, response: jsonData };
                if (stdinData != null) {
                    context.stdinStream = this.createStdinStream(stdinData, jsonData.stdinLength);
                }
                imperative_1.Imperative.parse(jsonData.argv, context);
            }
        });
    }
}
exports.DaemonClient = DaemonClient;
/**
 * The character sent when Ctrl+C is pressed to terminate a process.
 * @internal
 */
DaemonClient.CTRL_C_CHAR = "\x03";
//# sourceMappingURL=DaemonClient.js.map