"use strict";
//
//  Copyright (c) Microsoft Corporation. All rights reserved.
//
Object.defineProperty(exports, "__esModule", { value: true });
exports.MultiChannelStream = void 0;
const vscode_jsonrpc_1 = require("vscode-jsonrpc");
const sshChannel_1 = require("./sshChannel");
const sshStream_1 = require("./sshStream");
const sshSession_1 = require("./sshSession");
const sshSessionConfiguration_1 = require("./sshSessionConfiguration");
const transportMessages_1 = require("./messages/transportMessages");
const trace_1 = require("./trace");
const connectionMessages_1 = require("./messages/connectionMessages");
const sshReportProgressEventArgs_1 = require("./events/sshReportProgressEventArgs");
/**
 * Multiplexes multiple virtual streams (channels) over a single transport stream, using the
 * SSH protocol while providing a simplified interface without any encryption or authentication.
 *
 * This class is a complement to `SecureStream`, which provides only the encryption and
 * authentication functions of SSH.
 *
 * To communicate over multiple channels, two sides first establish a transport stream
 * over a pipe, socket, or anything else. Then one side accepts a channel while the
 * other side opens a channel. Either side can both open and accept channels over the
 * same transport stream, as long as the other side does the complementary action.
 */
class MultiChannelStream {
    /**
     * Creates a new multi-channel stream over an underlying transport stream.
     * @param transportStream Stream that is used to multiplex all the channels.
     */
    constructor(transportStream) {
        this.transportStream = transportStream;
        this.disposed = false;
        this.disposables = [];
        this.reportProgressEmitter = new vscode_jsonrpc_1.Emitter();
        /**
         * Event that is raised to report connection progress.
         *
         * See `Progress` for a description of the different progress events that can be reported.
         */
        this.onReportProgress = this.reportProgressEmitter.event;
        /**
         * Gets or sets the maximum window size for channels within the multi-channel stream.
         * @see `SshChannel.maxWindowSize`
         */
        this.channelMaxWindowSize = sshChannel_1.SshChannel.defaultMaxWindowSize;
        this.closedEmitter = new vscode_jsonrpc_1.Emitter();
        this.onClosed = this.closedEmitter.event;
        this.channelOpeningEmitter = new vscode_jsonrpc_1.Emitter();
        this.onChannelOpening = this.channelOpeningEmitter.event;
        if (!transportStream)
            throw new TypeError('transportStream is required.');
        const noSecurityConfig = new sshSessionConfiguration_1.SshSessionConfiguration(false);
        this.session = new sshSession_1.SshSession(noSecurityConfig);
        this.session.onReportProgress((args) => this.raiseReportProgress(args.progress, args.sessionNumber), this, this.disposables);
        this.session.onClosed(this.onSessionClosed, this, this.disposables);
        this.session.onChannelOpening(this.onSessionChannelOpening, this, this.disposables);
    }
    get trace() {
        return this.session.trace;
    }
    set trace(trace) {
        this.session.trace = trace;
    }
    raiseReportProgress(progress, sessionNumber) {
        const args = new sshReportProgressEventArgs_1.SshReportProgressEventArgs(progress, sessionNumber);
        this.reportProgressEmitter.fire(args);
    }
    get isClosed() {
        return this.disposed || this.session.isClosed;
    }
    /**
     * Initiates the SSH session over the transport stream by exchanging initial messages with the
     * remote peer. Waits for the protocol version exchange and key exchange. Additional message
     * processing is kicked off as a background promise chain.
     * @param cancellation optional cancellation token.
     */
    async connect(cancellation) {
        await this.session.connect(this.transportStream, cancellation);
    }
    /**
     * Asynchronously waits for the other side to open a channel.
     * @param channelType optional channel type
     * @param cancellation optional cancellation token.
     */
    async acceptChannel(channelType, cancellation) {
        await this.session.connect(this.transportStream, cancellation);
        const channel = await this.session.acceptChannel(channelType, cancellation);
        return channel;
    }
    /**
     * Asynchronously waits for the other side to open a channel.
     * @param channelType optional channel type
     * @param cancellation optional cancellation token.
     */
    async acceptStream(channelType, cancellation) {
        return this.createStream(await this.acceptChannel(channelType, cancellation));
    }
    /**
     * Opens a channel and asynchronously waits for the other side to accept it.
     * @param channelType optional channel type
     * @param cancellation optional cancellation token.
     */
    async openChannel(channelType, cancellation) {
        await this.session.connect(this.transportStream, cancellation);
        const openMessage = new connectionMessages_1.ChannelOpenMessage();
        openMessage.channelType = channelType !== null && channelType !== void 0 ? channelType : sshChannel_1.SshChannel.sessionChannelType;
        openMessage.maxWindowSize = this.channelMaxWindowSize;
        const channel = await this.session.openChannel(openMessage, null, cancellation);
        return channel;
    }
    /**
     * Opens a channel and asynchronously waits for the other side to accept it.
     * @param channelType optional channel type
     * @param cancellation optional cancellation token.
     */
    async openStream(channelType, cancellation) {
        return this.createStream(await this.openChannel(channelType, cancellation));
    }
    /**
     * Creates a stream instance for a channel. May be overridden to create a `SshStream` subclass.
     */
    createStream(channel) {
        return new sshStream_1.SshStream(channel);
    }
    /**
     * Connects, waits until the session closes or `cancellation` is cancelled, and then disposes the
     * session and the transport stream.
     * @param cancellation optional cancellation token.
     */
    async connectAndRunUntilClosed(cancellation) {
        const disposables = [];
        const sessionClosedPromise = new Promise((resolve, reject) => {
            cancellation === null || cancellation === void 0 ? void 0 : cancellation.onCancellationRequested(reject, null, disposables);
            this.session.onClosed(resolve, null, disposables);
        });
        try {
            await this.connect(cancellation);
            await sessionClosedPromise;
        }
        finally {
            disposables.forEach((d) => d.dispose());
            await this.close();
        }
    }
    dispose() {
        if (!this.disposed) {
            this.disposed = true;
            this.session.dispose();
            this.unsubscribe();
            try {
                if (this.transportStream)
                    this.transportStream.close().catch((e) => {
                        this.trace(trace_1.TraceLevel.Error, trace_1.SshTraceEventIds.streamCloseError, `Error closing transport stream: ${e.message}`, e);
                    });
            }
            catch (e) {
                if (!(e instanceof Error))
                    throw e;
                this.trace(trace_1.TraceLevel.Error, trace_1.SshTraceEventIds.streamCloseError, `Error closing transport stream: ${e.message}`, e);
            }
        }
    }
    async close() {
        if (!this.disposed) {
            this.disposed = true;
            await this.session.close(transportMessages_1.SshDisconnectReason.none, 'SshSession disposed');
            this.session.dispose();
            this.unsubscribe();
            await this.transportStream.close();
        }
    }
    onSessionClosed(e) {
        this.unsubscribe();
        this.closedEmitter.fire(e);
    }
    onSessionChannelOpening(e) {
        if (e.isRemoteRequest) {
            e.channel.maxWindowSize = this.channelMaxWindowSize;
        }
        this.channelOpeningEmitter.fire(e);
    }
    unsubscribe() {
        this.disposables.forEach((d) => d.dispose());
        this.disposables = [];
    }
}
exports.MultiChannelStream = MultiChannelStream;
//# sourceMappingURL=https://github.com/VSCodium/sourcemaps/releases/download/stable-4c0b0c6cc561d2d3636d1ec250935431876ce4dc/node_modules-@microsoft-dev-tunnels-ssh-multiChannelStream.js.map