"use strict";
//
//  Copyright (c) Microsoft Corporation. All rights reserved.
//
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var PortForwardingService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PortForwardingService = void 0;
const dev_tunnels_ssh_1 = require("@microsoft/dev-tunnels-ssh");
const vscode_jsonrpc_1 = require("vscode-jsonrpc");
const forwardedPort_1 = require("../events/forwardedPort");
const forwardedPortsCollection_1 = require("../events/forwardedPortsCollection");
const ipAddressConversions_1 = require("../ipAddressConversions");
const portForwardChannelOpenMessage_1 = require("../messages/portForwardChannelOpenMessage");
const portForwardRequestMessage_1 = require("../messages/portForwardRequestMessage");
const portForwardSuccessMessage_1 = require("../messages/portForwardSuccessMessage");
const tcpListenerFactory_1 = require("../tcpListenerFactory");
const portForwardMessageFactory_1 = require("../portForwardMessageFactory");
const localPortForwarder_1 = require("./localPortForwarder");
const remotePortForwarder_1 = require("./remotePortForwarder");
const remotePortStreamer_1 = require("./remotePortStreamer");
const forwardedPortEventArgs_1 = require("../events/forwardedPortEventArgs");
/**
 * Implements the standard SSH port-forwarding protocol.
 * @example
 * Use `SshSessionConfiguration.addService()` on both client and server side configurations
 * to add the `PortForwardingService` type before attempting to call methods on the service.
 * Then use `SshSession.activateService()` to get the service instance:
 *
 *     const config = new SshSessionConfiguration();
 *     config.addService(PortForwardingService);
 *     const client = new SshClient(config);
 *     const session = await client.openSession(host, port);
 *     await session.authenticate(clientCredentials);
 *     const pfs = session.activateService(PortForwardingService);
 *     const forwarder = pfs.forwardToRemotePort('::', 3000);
 */
let PortForwardingService = PortForwardingService_1 = class PortForwardingService extends dev_tunnels_ssh_1.SshService {
    /* @internal */
    constructor(session) {
        super(session);
        /**
         * Maps from FORWARDED port number to the object that manages listening for incoming
         * connections for that port and forwarding them through the session.
         *
         * Note the actual local source port number used may be different from the forwarded port
         * number if the local TCP listener factory chose a different port. The forwarded port number
         * is used to identify the port in any messages exchanged between client and server.
         */
        this.localForwarders = new Map();
        /**
         * Maps from FORWARDED port numbers to the object that manages relaying forwarded connections
         * from the session to a local port.
         *
         * Note the actual local destination port number used may be different from the forwarded port
         * number. The forwarded port number is used to identify the port in any messages exchanged
         * between client and server.
         */
        this.remoteConnectors = new Map();
        /* @internal */
        this.streamForwarders = [];
        /**
         * Gets or sets a value that controls whether the port-forwarding service listens on
         * local TCP sockets to accept connections for ports that are forwarded from the remote side.
         *
         * The default is true.
         *
         * This property is typically initialized before connecting a session (if not keeping the
         * default). It may be changed at any time while the session is connected, and the new value
         * will affect any newly forwarded ports after that, but not previously-forwarded ports.
         *
         * Regardless of whether this is enabled, connections to forwarded ports can be made using
         * `connectToForwardedPort()`.
         */
        this.acceptLocalConnectionsForForwardedPorts = true;
        /**
         * Gets or sets a value that controls whether the port-forwarding service forwards connections
         * to local TCP sockets.
         *
         * The default is true.
         *
         * This property is typically initialized before connecting a session (if not keeping the
         * default). It may be changed at any time while the session is connected, and the new value
         * will affect any newly forwarded ports after that, but not previously-forwarded ports.
         */
        this.forwardConnectionsToLocalPorts = true;
        /**
         * Gets or sets a value that controls whether the port-forwarding service accepts
         * 'direct-tcpip' channel open requests and forwards the channel connections to the local port.
         *
         * The default is true.
         *
         * This property is typically initialized before connecting a session (if not keeping the
         * default). It may be changed at any time while the session is connected, and the new value
         * will affect any newly forwarded ports after that, but not previously-forwarded ports.
         *
         * Regardless of whether this is enabled, the remote side can open 'forwarded-tcpip' channels
         * to connect to ports that were explicitly forwarded by this side.
         */
        this.acceptRemoteConnectionsForNonForwardedPorts = true;
        /**
         * Gets the collection of ports that are currently being forwarded from the remote side
         * to the local side.
         *
         * Ports are added to this collection when `forwardFromRemotePort()` or
         * `streamFromRemotePort()` is called (and the other side accepts the
         * 'tcpip-forward' request), and then are removed when the `RemotePortForwarder`
         * is disposed (which also sends a 'cancel-tcpip-forward' message).
         *
         * Each forwarded port may have 0 or more active connections (channels).
         *
         * The collection does not include direct connections initiated via
         * `forwardToRemotePort()` or `streamToRemotePort()`.
         *
         * Local forwarded ports may or may not have local TCP listeners automatically set up,
         * depending on the value of `acceptLocalConnectionsForForwardedPorts`.
         */
        this.localForwardedPorts = new forwardedPortsCollection_1.ForwardedPortsCollection();
        /**
         * Gets the collection of ports that are currently being forwarded from the local side
         * to the remote side.
         *
         * Ports are added to this collection when the port-forwarding service handles a
         * 'tcpip-forward' request message, and removed when it receives a 'cancel-tcpip-forward'
         * request message.
         *
         * Each forwarded port may have 0 or more active connections (channels).
         *
         * The collection does not include direct connections initiated via
         * `forwardToRemotePort()` or `streamToRemotePort()`.
         */
        this.remoteForwardedPorts = new forwardedPortsCollection_1.ForwardedPortsCollection();
        /**
         * Gets or sets a factory for creating TCP listeners.
         *
         * Applications may override this factory to provide custom logic for selecting
         * local port numbers to listen on for port-forwarding.
         *
         * This factory is not used when `acceptLocalConnectionsForForwardedPorts` is
         * set to false.
         */
        this.tcpListenerFactory = new tcpListenerFactory_1.DefaultTcpListenerFactory();
        /**
         * Gets or sets a factory for creating port-forwarding messages.
         *
         * A message factory enables applications to extend port-forwarding by providing custom
         * message subclasses that may include additional properties.
         */
        this.messageFactory = new portForwardMessageFactory_1.DefaultPortForwardMessageFactory();
        this.forwardedPortConnectingEmitter = new vscode_jsonrpc_1.Emitter();
        /**
         * Event raised when an incoming or outgoing connection to a forwarded port is
         * about to be established.
         */
        this.onForwardedPortConnecting = this.forwardedPortConnectingEmitter.event;
    }
    /* @internal */
    async forwardedPortConnecting(port, isIncoming, stream, cancellation) {
        try {
            const args = new forwardedPortEventArgs_1.ForwardedPortConnectingEventArgs(port, isIncoming, stream, cancellation);
            this.forwardedPortConnectingEmitter.fire(args);
            if (args.transformPromise) {
                return await args.transformPromise;
            }
        }
        catch (e) {
            if (!(e instanceof Error))
                throw e;
            this.trace(dev_tunnels_ssh_1.TraceLevel.Warning, dev_tunnels_ssh_1.SshTraceEventIds.portForwardConnectionFailed, `Forwarded port connecting event-handler failed: ${e.message}`);
            return null;
        }
        return stream;
    }
    async forwardFromRemotePort(remoteIPAddress, remotePort, localHostOrCancellation, localPort, cancellation) {
        const localHost = typeof localHostOrCancellation === 'string' ? localHostOrCancellation : '127.0.0.1';
        if (typeof localPort === 'undefined')
            localPort = remotePort;
        if (!remoteIPAddress)
            throw new TypeError('Remote IP address is required.');
        if (!Number.isInteger(remotePort) || remotePort < 0) {
            throw new TypeError('Remote port must be a non-negative integer.');
        }
        if (!localHost)
            throw new TypeError('Local host is required.');
        if (!Number.isInteger(localPort) || localPort <= 0) {
            throw new TypeError('Local port must be a positive integer.');
        }
        if (this.localForwardedPorts.find((p) => p.localPort === localPort)) {
            throw new Error(`Local port ${localPort} is already forwarded.`);
        }
        else if (remotePort > 0 &&
            this.localForwardedPorts.find((p) => p.remotePort === remotePort)) {
            throw new Error(`Remote port ${remotePort} is already forwarded.`);
        }
        const forwarder = new remotePortForwarder_1.RemotePortForwarder(this, this.session, remoteIPAddress, remotePort, localHost, localPort);
        const request = await this.messageFactory.createRequestMessageAsync(remotePort);
        if (!(await forwarder.request(request, cancellation))) {
            // The remote side rejected the forwarding request, or it was a duplicate request.
            return null;
        }
        remotePort = forwarder.remotePort;
        // The remote port is the port sent in the message to the other side,
        // so the connector is indexed on that port number, rather than the local port.
        //
        // Do not track duplicate port forwarders. (The remote side may not have detected the
        // duplicate if not accepting local connections.)
        if (this.remoteConnectors.has(remotePort)) {
            // Do not dispose the forwarder because that would send a message to cancel
            // forwarding of the port.
            return null;
        }
        this.remoteConnectors.set(remotePort, forwarder);
        const forwardedPort = new forwardedPort_1.ForwardedPort(localPort, remotePort, false);
        this.localForwardedPorts.addOrUpdatePort(forwardedPort);
        forwarder.onDisposed(() => {
            this.localForwardedPorts.removePort(forwardedPort);
            this.remoteConnectors.delete(remotePort);
        });
        return forwarder;
    }
    async forwardToRemotePort(localIPAddress, localPort, remoteHostOrCancellation, remotePort, cancellation) {
        const remoteHost = typeof remoteHostOrCancellation === 'string' ? remoteHostOrCancellation : '127.0.0.1';
        if (typeof remotePort === 'undefined')
            remotePort = localPort;
        if (!localIPAddress)
            throw new TypeError('Local IP address is required.');
        if (!Number.isInteger(localPort) || localPort < 0) {
            throw new TypeError('Local port must be a non-negative integer.');
        }
        if (!remoteHost)
            throw new TypeError('Remote host is required.');
        if (!Number.isInteger(remotePort) || remotePort <= 0) {
            throw new TypeError('Remote port must be a positive integer.');
        }
        if (this.localForwarders.has(remotePort)) {
            throw new Error(`Port ${remotePort} is already forwarded.`);
        }
        const forwarder = new localPortForwarder_1.LocalPortForwarder(this, this.session, PortForwardingService_1.reversePortForwardChannelType, localIPAddress, localPort, remoteHost, remotePort);
        await forwarder.startForwarding(cancellation);
        // The remote port is the port sent in the message to the other side,
        // so the forwarder is indexed on that port number, rather than the local port.
        this.localForwarders.set(remotePort, forwarder);
        forwarder.onDisposed(() => {
            this.localForwarders.delete(remotePort);
        });
        return forwarder;
    }
    /**
     * Sends a request to the remote side to listen on a port and forward incoming connections as
     * SSH channels of type 'forwarded-tcpip', which will then be relayed as local streams.
     *
     * @param remoteIPAddress IP address of the interface to bind to on the remote side.
     * @param remotePort The remote port to listen on, or 0 to choose an available port.
     * (The chosen port can then be obtained via the `remotePort` property on the returned object.)
     * @param cancellation Cancellation token for the request; note this cannot cancel forwarding
     * once it has started; use the returned disposable do do that.
     * @returns A disposable object that when disposed will cancel forwarding the port, or `null`
     * if the request was rejected by the remote side, possibly because the remote port was already
     * in use. Handle the `onStreamOpened` event on this object to receive streams.
     */
    async streamFromRemotePort(remoteIPAddress, remotePort, cancellation) {
        if (!remoteIPAddress)
            throw new TypeError('Remote IP address is required.');
        if (!Number.isInteger(remotePort) || remotePort < 0) {
            throw new TypeError('Remote port must be a non-negative integer.');
        }
        const streamer = new remotePortStreamer_1.RemotePortStreamer(this.session, remoteIPAddress, remotePort);
        const request = await this.messageFactory.createRequestMessageAsync(remotePort);
        if (!(await streamer.request(request, cancellation))) {
            streamer.dispose();
            return null;
        }
        remotePort = streamer.remotePort;
        // The remote port is the port sent in the message to the other side,
        // so the connector is indexed on that port number. (There is no local port anyway.)
        this.remoteConnectors.set(remotePort, streamer);
        const forwardedPort = new forwardedPort_1.ForwardedPort(null, remotePort, false);
        this.localForwardedPorts.addOrUpdatePort(forwardedPort);
        streamer.onDisposed(() => {
            this.localForwardedPorts.removePort(forwardedPort);
            this.remoteConnectors.delete(remotePort);
        });
        return streamer;
    }
    /**
     * Opens a stream for an SSH channel of type 'direct-tcpip' that is relayed to remote port,
     * regardless of whether the remote side has explicitly forwarded that port.
     *
     * @param remoteHost The destination hostname or IP address for forwarded connections, to be
     * resolved on the remote side. WARNING: Avoid using the hostname `localhost` as the destination
     * host; use `127.0.0.1` or `::1` instead. (OpenSSH does not recognize `localhost` as a valid
     * destination host.)
     * @param remotePort The destination port for the forwarded stream. (Must not be 0.)
     * @param cancellation Cancellation token for the request; note this cannot cancel streaming
     * once it has started; dipose the returned stream for that.
     * @returns A stream that is relayed to the remote port.
     * @throws `SshChannelError` if the streaming channel could not be opened, either because it
     * was rejected by the remote side, or the remote connection failed.
     */
    async streamToRemotePort(remoteHost, remotePort, cancellation) {
        if (!remoteHost)
            throw new TypeError('Remote host is required.');
        if (!Number.isInteger(remotePort) || remotePort <= 0) {
            throw new TypeError('Remote port must be a positive integer.');
        }
        const channel = await this.openChannel(this.session, PortForwardingService_1.reversePortForwardChannelType, null, null, remoteHost, remotePort, cancellation);
        return new dev_tunnels_ssh_1.SshStream(channel);
    }
    /**
     * Opens a stream for an SSH channel of type 'forwarded-tcpip' that is relayed to a remote
     * port. The port must have been explicitly forwarded by the remote side.
     *
     * It may be necessary to call `waitForForwardedPort` before this method
     * to ensure the port is ready for connections.
     *
     * An error is thrown if the requested port could not be forwarded, possibly because it was
     * rejected by the remote side, or the remote connection failed.
     *
     * @param forwardedPort Remote port number that was forwarded.
     * @param cancellation Cancellation token for the request; note this cannot
     * cancel streaming once it has started; dipose the returned stream for that.
     * @returns A stream that is relayed to the remote forwarded port.
     */
    async connectToForwardedPort(forwardedPort, cancellation) {
        if (!Number.isInteger(forwardedPort) || forwardedPort <= 0) {
            throw new TypeError('Forwarded port must be a positive integer.');
        }
        const channel = await this.openChannel(this.session, PortForwardingService_1.portForwardChannelType, null, null, '127.0.0.1', forwardedPort, cancellation);
        const forwardedStream = await this.forwardedPortConnecting(forwardedPort, false, new dev_tunnels_ssh_1.SshStream(channel), cancellation);
        if (!forwardedStream) {
            channel.close().catch((e) => { });
            throw new dev_tunnels_ssh_1.SshChannelError('The connection to the forwarded port was rejected by the connecting event-handler.');
        }
        return forwardedStream;
    }
    /**
     * Waits asynchronously for the remote side to forward an expected port number.
     *
     * A common pattern for some applications may be to call this method just before
     * `ConnectToForwardedPortAsync`.
     *
     * @param forwardedPort Port number that is expected to be forwarded.
     * @param cancellation Token that can be used to cancel waiting.
     * @returns A promise that completes when the expected port number has been forwarded.
     */
    async waitForForwardedPort(forwardedPort, cancellation) {
        if (this.remoteForwardedPorts.find((p) => p.remotePort === forwardedPort)) {
            // It's already forwarded, so there's no need to wait.
            return;
        }
        const waitCompletion = new dev_tunnels_ssh_1.PromiseCompletionSource();
        let cancellationRegistration;
        if (cancellation) {
            cancellationRegistration = cancellation.onCancellationRequested(() => waitCompletion.reject(new dev_tunnels_ssh_1.CancellationError()));
        }
        let portAddedRegistration;
        let sessionClosedRegistration;
        try {
            portAddedRegistration = this.remoteForwardedPorts.onPortAdded((e) => {
                if (e.port.remotePort === forwardedPort) {
                    waitCompletion.resolve();
                }
            });
            sessionClosedRegistration = this.session.onClosed(() => {
                waitCompletion.reject(new dev_tunnels_ssh_1.ObjectDisposedError('The session was closed.'));
            });
            await waitCompletion.promise;
        }
        finally {
            portAddedRegistration === null || portAddedRegistration === void 0 ? void 0 : portAddedRegistration.dispose();
            sessionClosedRegistration === null || sessionClosedRegistration === void 0 ? void 0 : sessionClosedRegistration.dispose();
            cancellationRegistration === null || cancellationRegistration === void 0 ? void 0 : cancellationRegistration.dispose();
        }
    }
    async onSessionRequest(request, cancellation) {
        if (!request)
            throw new TypeError('Request is required.');
        else if (request.requestType !== PortForwardingService_1.portForwardRequestType &&
            request.requestType !== PortForwardingService_1.cancelPortForwardRequestType) {
            throw new Error(`Unexpected request type: ${request.requestType}`);
        }
        const portForwardRequest = request.request.convertTo(new portForwardRequestMessage_1.PortForwardRequestMessage());
        const localIPAddress = ipAddressConversions_1.IPAddressConversions.fromSshAddress(portForwardRequest.addressToBind);
        if (request.requestType === PortForwardingService_1.portForwardRequestType &&
            portForwardRequest.port !== 0 &&
            this.localForwarders.has(portForwardRequest.port)) {
            // The port is already forwarded, so a failure response will be returned. This may happen
            // when re-connecting, to ensure the state of forwarded ports is consistent.
            //
            // Note duplicate ports are not detected here when AcceptLocalConnectionsForForwardedPorts
            // is false; in that case duplicate port requests may succeed (if authorized below), though
            // they don't really do anything.
            const message = `PortForwardingService port ${portForwardRequest.port} is already forwarded.`;
            this.session.trace(dev_tunnels_ssh_1.TraceLevel.Verbose, dev_tunnels_ssh_1.SshTraceEventIds.portForwardRequestInvalid, message);
            request.isAuthorized = false;
            return;
        }
        const args = new dev_tunnels_ssh_1.SshRequestEventArgs(request.requestType, portForwardRequest, this.session.principal);
        await super.onSessionRequest(args, cancellation);
        let response;
        let localPort = null;
        if (args.isAuthorized) {
            if (request.requestType === PortForwardingService_1.portForwardRequestType) {
                try {
                    localPort = await this.startForwarding(localIPAddress, portForwardRequest.port, cancellation);
                }
                catch (e) {
                    // The error is already traced.
                }
                if (localPort !== null) {
                    // The chosen local port may be different from the requested port. Use the
                    // requested port in the response, unless the request was for a random port.
                    const forwardedPort = portForwardRequest.port === 0 ? localPort : portForwardRequest.port;
                    const portResponse = await this.messageFactory.createSuccessMessageAsync(forwardedPort);
                    portResponse.port = forwardedPort;
                    response = portResponse;
                }
            }
            else if (request.requestType === PortForwardingService_1.cancelPortForwardRequestType) {
                if (await this.cancelForwarding(portForwardRequest.port, cancellation)) {
                    response = new dev_tunnels_ssh_1.SessionRequestSuccessMessage();
                }
            }
        }
        request.responsePromise = Promise.resolve(response !== null && response !== void 0 ? response : new dev_tunnels_ssh_1.SessionRequestFailureMessage());
        // Add to the collection (and raise event) after sending the response,
        // to ensure event-handlers can immediately open a channel.
        if (response instanceof portForwardSuccessMessage_1.PortForwardSuccessMessage) {
            const forwardedPort = new forwardedPort_1.ForwardedPort(localPort !== null && localPort !== void 0 ? localPort : response.port, response.port, true);
            this.remoteForwardedPorts.addOrUpdatePort(forwardedPort);
        }
    }
    async startForwarding(localIPAddress, remotePort, cancellation) {
        if (typeof remotePort !== 'number')
            throw new TypeError('Remote port must be an integer.');
        if (this.acceptLocalConnectionsForForwardedPorts) {
            // The local port is initially set to the remote port, but it may change
            // when starting forwarding, if there was a conflict.
            let localPort = remotePort;
            const forwarder = new localPortForwarder_1.LocalPortForwarder(this, this.session, PortForwardingService_1.portForwardChannelType, localIPAddress, localPort, undefined, remotePort === 0 ? undefined : remotePort);
            await forwarder.startForwarding(cancellation);
            localPort = forwarder.localPort;
            if (remotePort === 0) {
                // The other side requested a random port. Reply with the chosen port number.
                remotePort = localPort;
            }
            if (this.localForwarders.has(remotePort)) {
                // The forwarder (TCP listener factory) chose a port that is already forwarded.
                // This can happen (though its' very unlikely) if a random port was requested.
                // Returning null here causes the forward request to be rejected.
                forwarder.dispose();
                return null;
            }
            // The remote port is the port referenced in exchanged messages,
            // so the forwarder is indexed on that port number, rather than the local port.
            this.localForwarders.set(remotePort, forwarder);
            localPort = forwarder.localPort;
            forwarder.onDisposed(() => {
                const forwardedPort = new forwardedPort_1.ForwardedPort(localPort, remotePort, true);
                this.remoteForwardedPorts.removePort(forwardedPort);
                this.localForwarders.delete(remotePort);
            });
            return localPort;
        }
        else if (remotePort !== 0) {
            return remotePort;
        }
        else {
            return null;
        }
    }
    async cancelForwarding(forwardedPort, cancellation) {
        const forwarder = this.localForwarders.get(forwardedPort);
        if (forwarder) {
            this.localForwarders.delete(forwardedPort);
            forwarder.dispose();
            return true;
        }
        const port = new forwardedPort_1.ForwardedPort(forwardedPort, forwardedPort, true);
        if (this.remoteForwardedPorts.removePort(port)) {
            return true;
        }
        return false;
    }
    async onChannelOpening(request, cancellation) {
        var _a;
        if (!request)
            throw new TypeError('Request is required.');
        const channelType = request.request.channelType;
        if (channelType !== PortForwardingService_1.portForwardChannelType &&
            channelType !== PortForwardingService_1.reversePortForwardChannelType) {
            request.failureReason = dev_tunnels_ssh_1.SshChannelOpenFailureReason.unknownChannelType;
            return;
        }
        let remoteConnector = null;
        const portForwardMessage = request.request instanceof portForwardChannelOpenMessage_1.PortForwardChannelOpenMessage
            ? request.request
            : request.request.convertTo(new portForwardChannelOpenMessage_1.PortForwardChannelOpenMessage());
        if (request.isRemoteRequest) {
            if (channelType === PortForwardingService_1.portForwardChannelType) {
                const remoteIPAddress = ipAddressConversions_1.IPAddressConversions.fromSshAddress(portForwardMessage.host);
                const remoteEndPoint = `${remoteIPAddress}:${portForwardMessage.port}`;
                remoteConnector = (_a = this.remoteConnectors.get(portForwardMessage.port)) !== null && _a !== void 0 ? _a : null;
                if (!remoteConnector) {
                    this.trace(dev_tunnels_ssh_1.TraceLevel.Error, dev_tunnels_ssh_1.SshTraceEventIds.portForwardRequestInvalid, 'PortForwardingService received forwarding channel ' +
                        `for ${remoteEndPoint} that was not requested.`);
                    request.failureReason = dev_tunnels_ssh_1.SshChannelOpenFailureReason.connectFailed;
                    request.failureDescription = 'Forwarding channel was not requested.';
                    return;
                }
            }
            else if (!this.acceptRemoteConnectionsForNonForwardedPorts) {
                const errorMessage = 'The session has disabled connections to non-forwarded ports.';
                this.session.trace(dev_tunnels_ssh_1.TraceLevel.Warning, dev_tunnels_ssh_1.SshTraceEventIds.portForwardChannelOpenFailed, errorMessage);
                request.failureReason = dev_tunnels_ssh_1.SshChannelOpenFailureReason.administrativelyProhibited;
                request.failureDescription = errorMessage;
                return;
            }
        }
        const portForwardRequest = new dev_tunnels_ssh_1.SshChannelOpeningEventArgs(portForwardMessage, request.channel, request.isRemoteRequest);
        await super.onChannelOpening(portForwardRequest, cancellation);
        request.failureReason = portForwardRequest.failureReason;
        request.failureDescription = portForwardRequest.failureDescription;
        request.openingPromise = portForwardRequest.openingPromise;
        if (request.failureReason === dev_tunnels_ssh_1.SshChannelOpenFailureReason.none &&
            request.isRemoteRequest &&
            this.forwardConnectionsToLocalPorts) {
            if (remoteConnector) {
                // The forwarding was initiated by this session.
                await remoteConnector.onPortChannelOpening(request, cancellation);
                const localPort = remoteConnector instanceof remotePortForwarder_1.RemotePortForwarder ? remoteConnector.localPort : null;
                const remotePort = remoteConnector instanceof remotePortForwarder_1.RemotePortForwarder
                    ? remoteConnector.remotePort
                    : portForwardMessage.port;
                const forwardedPort = new forwardedPort_1.ForwardedPort(localPort, remotePort, false);
                this.localForwardedPorts.addChannel(forwardedPort, request.channel);
            }
            else {
                // THe forwarding was initiated by the remote session.
                await remotePortForwarder_1.RemotePortForwarder.forwardChannel(this, request, portForwardMessage.host, portForwardMessage.port, portForwardMessage.port, this.trace, cancellation);
                if (request.failureReason !== dev_tunnels_ssh_1.SshChannelOpenFailureReason.none) {
                    await request.channel.close(cancellation);
                }
            }
        }
    }
    /* @internal */
    async openChannel(session, channelType, originatorIPAddress, originatorPort, host, port, cancellation) {
        let forwardedPort = undefined;
        if (channelType === PortForwardingService_1.portForwardChannelType) {
            forwardedPort = this.remoteForwardedPorts.find((p) => p.remotePort === port || (p.remotePort === null && p.localPort === port));
            if (!forwardedPort) {
                throw new Error(`Port ${port} is not being forwarded.`);
            }
        }
        const openMessage = await this.messageFactory.createChannelOpenMessageAsync(port);
        openMessage.channelType = channelType;
        openMessage.originatorIPAddress = originatorIPAddress !== null && originatorIPAddress !== void 0 ? originatorIPAddress : '';
        openMessage.originatorPort = originatorPort !== null && originatorPort !== void 0 ? originatorPort : 0;
        openMessage.host = host;
        openMessage.port = port;
        const trace = this.session.trace;
        let channel;
        try {
            channel = await session.openChannel(openMessage, null, cancellation);
            trace(dev_tunnels_ssh_1.TraceLevel.Info, dev_tunnels_ssh_1.SshTraceEventIds.portForwardChannelOpened, `PortForwardingService opened ${channelType} channel #${channel.channelId} for ${host}:${port}.`);
        }
        catch (e) {
            if (!(e instanceof Error))
                throw e;
            trace(dev_tunnels_ssh_1.TraceLevel.Error, dev_tunnels_ssh_1.SshTraceEventIds.portForwardChannelOpenFailed, `PortForwardingService failed to open ${channelType} channel for ${host}:${port}: ${e.message}`, e);
            throw e;
        }
        if (channelType === PortForwardingService_1.portForwardChannelType) {
            this.remoteForwardedPorts.addChannel(forwardedPort, channel);
        }
        return channel;
    }
    dispose() {
        // Do not dispose StreamForwarder objects here, since they may be transferred to
        // other sessions when reconnecting. The StreamForwarders will self-dispose when
        // their underlying transport streams are closed.
        const disposables = [
            ...this.localForwarders.values(),
            ...this.remoteConnectors.values(),
        ];
        this.streamForwarders.splice(0, this.streamForwarders.length);
        this.localForwarders.clear();
        this.remoteConnectors.clear();
        for (const disposable of disposables) {
            disposable.dispose();
        }
        super.dispose();
    }
};
PortForwardingService.portForwardRequestType = 'tcpip-forward';
PortForwardingService.cancelPortForwardRequestType = 'cancel-tcpip-forward';
PortForwardingService.portForwardChannelType = 'forwarded-tcpip';
PortForwardingService.reversePortForwardChannelType = 'direct-tcpip';
PortForwardingService = PortForwardingService_1 = __decorate([
    (0, dev_tunnels_ssh_1.serviceActivation)({ sessionRequest: PortForwardingService_1.portForwardRequestType }),
    (0, dev_tunnels_ssh_1.serviceActivation)({ sessionRequest: PortForwardingService_1.cancelPortForwardRequestType }),
    (0, dev_tunnels_ssh_1.serviceActivation)({ channelType: PortForwardingService_1.portForwardChannelType }),
    (0, dev_tunnels_ssh_1.serviceActivation)({ channelType: PortForwardingService_1.reversePortForwardChannelType })
], PortForwardingService);
exports.PortForwardingService = PortForwardingService;
//# sourceMappingURL=https://github.com/VSCodium/sourcemaps/releases/download/stable-221e0a382c0be3a673a4e4cab0601344a0b3de3a/node_modules-@microsoft-dev-tunnels-ssh-tcp-services-portForwardingService.js.map