"use strict";
//
//  Copyright (c) Microsoft Corporation. All rights reserved.
//
Object.defineProperty(exports, "__esModule", { value: true });
exports.PipeExtensions = void 0;
const buffer_1 = require("buffer");
const vscode_jsonrpc_1 = require("vscode-jsonrpc");
const transportMessages_1 = require("./messages/transportMessages");
const connectionMessages_1 = require("./messages/connectionMessages");
const promiseCompletionSource_1 = require("./util/promiseCompletionSource");
const errors_1 = require("./errors");
const trace_1 = require("./trace");
/**
 * Extension methods for piping sessions and channels.
 *
 * Note this class is not exported from the package. Instead, the piping APIs are exposed via
 * public methods on the `SshSession` and `SshChannel` classes. See those respective methods
 * for API documentation.
 */
class PipeExtensions {
    static async pipeSession(session, toSession) {
        if (!session)
            throw new TypeError('Session is required.');
        if (!toSession)
            throw new TypeError('Target session is required');
        const endCompletion = new promiseCompletionSource_1.PromiseCompletionSource();
        session.onRequest((e) => {
            e.responsePromise = PipeExtensions.forwardSessionRequest(e, toSession, e.cancellation);
        });
        toSession.onRequest((e) => {
            e.responsePromise = PipeExtensions.forwardSessionRequest(e, session, e.cancellation);
        });
        session.onChannelOpening((e) => {
            if (e.isRemoteRequest) {
                e.openingPromise = PipeExtensions.forwardChannel(e, toSession, e.cancellation);
            }
        });
        toSession.onChannelOpening((e) => {
            if (e.isRemoteRequest) {
                e.openingPromise = PipeExtensions.forwardChannel(e, session, e.cancellation);
            }
        });
        session.onClosed((e) => {
            endCompletion.resolve(PipeExtensions.forwardSessionClose(toSession, e));
        });
        toSession.onClosed((e) => {
            endCompletion.resolve(PipeExtensions.forwardSessionClose(session, e));
        });
        const endPromise = await endCompletion.promise;
        await endPromise;
    }
    static async pipeChannel(channel, toChannel) {
        if (!channel)
            throw new TypeError('Channel is required.');
        if (!toChannel)
            throw new TypeError('Target channel is required');
        const endCompletion = new promiseCompletionSource_1.PromiseCompletionSource();
        let closed = false;
        channel.onRequest((e) => {
            e.responsePromise = PipeExtensions.forwardChannelRequest(e, toChannel, e.cancellation);
        });
        toChannel.onRequest((e) => {
            e.responsePromise = PipeExtensions.forwardChannelRequest(e, channel, e.cancellation);
        });
        channel.onDataReceived((data) => {
            void PipeExtensions.forwardData(channel, toChannel, data).catch();
        });
        toChannel.onDataReceived((data) => {
            void PipeExtensions.forwardData(toChannel, channel, data).catch();
        });
        channel.onEof(() => {
            void PipeExtensions.forwardData(channel, toChannel, buffer_1.Buffer.alloc(0)).catch();
        });
        toChannel.onEof(() => {
            void PipeExtensions.forwardData(toChannel, channel, buffer_1.Buffer.alloc(0)).catch();
        });
        channel.onExtendedDataReceived((data) => {
            void PipeExtensions.forwardExtendedData(channel, toChannel, data.dataTypeCode, data.data).catch();
        });
        toChannel.onExtendedDataReceived((data) => {
            void PipeExtensions.forwardExtendedData(toChannel, channel, data.dataTypeCode, data.data).catch();
        });
        channel.onClosed((e) => {
            if (!closed) {
                closed = true;
                endCompletion.resolve(PipeExtensions.forwardChannelClose(channel, toChannel, e));
            }
        });
        toChannel.onClosed((e) => {
            if (!closed) {
                closed = true;
                endCompletion.resolve(PipeExtensions.forwardChannelClose(toChannel, channel, e));
            }
        });
        const endTask = await endCompletion.promise;
        await endTask;
    }
    static async forwardSessionRequest(e, toSession, cancellation) {
        // `SshSession.requestResponse()` always set `wantReply` to `true` internally and waits for a
        // response, but since the message buffer is cached the updated `wantReply` value is not sent.
        // Anyway, it's better to forward a no-reply message as another no-reply message, using
        // `SshSession.request()` instead.
        if (!e.request.wantReply) {
            return toSession
                .request(e.request, cancellation)
                .then(() => new transportMessages_1.SessionRequestSuccessMessage());
        }
        return toSession.requestResponse(e.request, transportMessages_1.SessionRequestSuccessMessage, transportMessages_1.SessionRequestFailureMessage, cancellation);
    }
    static async forwardChannel(e, toSession, cancellation) {
        var _a;
        try {
            const toChannel = await toSession.openChannel(e.request, null, cancellation);
            void PipeExtensions.pipeChannel(e.channel, toChannel).catch();
            return new connectionMessages_1.ChannelOpenConfirmationMessage();
        }
        catch (err) {
            if (!(err instanceof Error))
                throw err;
            const failureMessage = new connectionMessages_1.ChannelOpenFailureMessage();
            if (err instanceof errors_1.SshChannelError) {
                failureMessage.reasonCode = (_a = err.reason) !== null && _a !== void 0 ? _a : connectionMessages_1.SshChannelOpenFailureReason.connectFailed;
            }
            else {
                failureMessage.reasonCode = connectionMessages_1.SshChannelOpenFailureReason.connectFailed;
            }
            failureMessage.description = err.message;
            return failureMessage;
        }
    }
    static async forwardChannelRequest(e, toChannel, cancellation) {
        e.request.recipientChannel = toChannel.remoteChannelId;
        const result = await toChannel.request(e.request, cancellation);
        return result ? new connectionMessages_1.ChannelSuccessMessage() : new connectionMessages_1.ChannelFailureMessage();
    }
    static async forwardSessionClose(session, e) {
        var _a;
        return session.close(e.reason, e.message, (_a = e.error) !== null && _a !== void 0 ? _a : undefined);
    }
    static async forwardData(channel, toChannel, data) {
        // Make a copy of the buffer before sending because SshChannel.send() is an async operation
        // (it may need to wait for the window to open), while the buffer will be re-used for the
        // next message as sson as this task yields.
        const buffer = buffer_1.Buffer.alloc(data.length);
        data.copy(buffer);
        const promise = toChannel.send(buffer, vscode_jsonrpc_1.CancellationToken.None);
        channel.adjustWindow(buffer.length);
        return promise;
    }
    static async forwardExtendedData(channel, toChannel, dataTypeCode, data) {
        // Make a copy of the buffer before sending because SshChannel.send() is an async operation
        // (it may need to wait for the window to open), while the buffer will be re-used for the
        // next message as sson as this task yields.
        const buffer = buffer_1.Buffer.alloc(data.length);
        data.copy(buffer);
        const promise = toChannel.sendExtendedData(dataTypeCode, buffer, vscode_jsonrpc_1.CancellationToken.None);
        channel.adjustWindow(buffer.length);
        return promise;
    }
    static async forwardChannelClose(fromChannel, toChannel, e) {
        const message = `Piping channel closure.\n` +
            `Source: ${fromChannel.session} ${fromChannel}\n` +
            `Destination: ${toChannel.session} ${toChannel}\n`;
        toChannel.session.trace(trace_1.TraceLevel.Verbose, trace_1.SshTraceEventIds.channelClosed, message);
        if (e.error) {
            toChannel.close(e.error);
            return Promise.resolve();
        }
        else if (e.exitSignal) {
            return toChannel.close(e.exitSignal, e.errorMessage);
        }
        else if (typeof e.exitStatus === 'number') {
            return toChannel.close(e.exitStatus);
        }
        else {
            return toChannel.close();
        }
    }
}
exports.PipeExtensions = PipeExtensions;
//# sourceMappingURL=https://github.com/VSCodium/sourcemaps/releases/download/stable-4c0b0c6cc561d2d3636d1ec250935431876ce4dc/node_modules-@microsoft-dev-tunnels-ssh-pipeExtensions.js.map