"use strict";
//
//  Copyright (c) Microsoft Corporation. All rights reserved.
//
Object.defineProperty(exports, "__esModule", { value: true });
exports.LocalPortForwarder = void 0;
const dev_tunnels_ssh_1 = require("@microsoft/dev-tunnels-ssh");
const streamForwarder_1 = require("./streamForwarder");
/**
 * Listens on a local port and forwards incoming connections as SSH channels.
 */
class LocalPortForwarder extends dev_tunnels_ssh_1.SshService {
    /* @internal */
    constructor(pfs, session, channelType, localIPAddress, localPort, remoteHost, remotePort) {
        super(session);
        this.pfs = pfs;
        this.channelType = channelType;
        this.localIPAddress = localIPAddress;
        this.port = localPort;
        this.remoteHost = remoteHost;
        // The remote port defaults to the same as the local port, if the remote port
        // was unspecified and a specific (nonzero) local port was specified. Whether
        // or not a specific local port was specified, the local port may be changed
        // by the TCP listener factory. In that case the remote port does not change.
        this.remotePort = remotePort !== null && remotePort !== void 0 ? remotePort : (localPort !== 0 ? localPort : undefined);
    }
    /**
     * Local port that the forwarder is listening on.
     */
    get localPort() {
        return this.port;
    }
    /* @internal */
    async startForwarding(cancellation) {
        var _a;
        let listenAddress = this.localIPAddress;
        try {
            this.tcpListener = await this.pfs.tcpListenerFactory.createTcpListener(this.remotePort, listenAddress, this.port, true);
            const serverAddress = this.tcpListener.address();
            if (!(serverAddress.port > 0)) {
                this.tcpListener.close();
                throw new Error('Could not get server port.');
            }
            this.port = serverAddress.port;
            // The SSH protocol specifies that "localhost" or "" (any) should be dual-mode.
            // So 2 TCP listener instances are required in those cases.
            if (this.localIPAddress === '127.0.0.1' || this.localIPAddress === '0.0.0.0') {
                // Call the factory again to create another listener, but this time with the
                // corresponding IPv6 local address, and not allowing a port change.
                listenAddress = this.localIPAddress === '0.0.0.0' ? '::' : '::1';
                try {
                    this.tcpListener2 = await this.pfs.tcpListenerFactory.createTcpListener(this.remotePort, listenAddress, this.port, false);
                }
                catch (e) {
                    if (!(e instanceof Error) || e.code !== 'EADDRNOTAVAIL') {
                        throw e;
                    }
                    // The OS may not support IPv6 or there may be no IPv6 network interfaces.
                    this.trace(dev_tunnels_ssh_1.TraceLevel.Warning, dev_tunnels_ssh_1.SshTraceEventIds.portForwardServerListenFailed, 'PortForwardingService failed to listen on ' +
                        `${listenAddress}:{this.port}: ${e.message}`, e);
                    // Do not rethrow, just skip IPv6 in this case.
                }
            }
        }
        catch (e) {
            if (!(e instanceof Error))
                throw e;
            this.trace(dev_tunnels_ssh_1.TraceLevel.Error, dev_tunnels_ssh_1.SshTraceEventIds.portForwardServerListenFailed, `PortForwardingService failed to listen on ${listenAddress}:${this.port}: ${e.message}`, e);
            throw e;
        }
        this.tcpListener.on('connection', this.acceptConnection.bind(this));
        (_a = this.tcpListener2) === null || _a === void 0 ? void 0 : _a.on('connection', this.acceptConnection.bind(this));
        this.trace(dev_tunnels_ssh_1.TraceLevel.Info, dev_tunnels_ssh_1.SshTraceEventIds.portForwardServerListening, `PortForwardingService listening on ${this.localIPAddress}:${this.port}.`);
        if (this.tcpListener2) {
            this.trace(dev_tunnels_ssh_1.TraceLevel.Info, dev_tunnels_ssh_1.SshTraceEventIds.portForwardServerListening, `PortForwardingService also listening on ${listenAddress}:${this.port}.`);
        }
    }
    async acceptConnection(socket) {
        var _a, _b, _c, _d, _e;
        this.trace(dev_tunnels_ssh_1.TraceLevel.Info, dev_tunnels_ssh_1.SshTraceEventIds.portForwardConnectionAccepted, 'PortForwardingService accepted connection from: ' +
            `${socket.remoteAddress} on port ${this.port}`);
        // TODO: Set socket options?
        let channel;
        try {
            channel = await this.pfs.openChannel(this.session, this.channelType, (_a = socket.remoteAddress) !== null && _a !== void 0 ? _a : null, (_b = socket.remotePort) !== null && _b !== void 0 ? _b : null, (_c = this.remoteHost) !== null && _c !== void 0 ? _c : this.localIPAddress, (_d = this.remotePort) !== null && _d !== void 0 ? _d : this.localPort);
        }
        catch (e) {
            if (!(e instanceof Error))
                throw e;
            // TODO: Destroy the socket in a way that causes a connection reset:
            // https://github.com/nodejs/node/issues/27428
            socket.destroy();
            // Don't re-throw. This is an async event handler so the caller isn't awaiting.
            // The error details have already been traced.
            return;
        }
        // The event handler may return a transformed stream.
        const forwardedStream = await this.pfs.forwardedPortConnecting((_e = this.remotePort) !== null && _e !== void 0 ? _e : this.localPort, false, new dev_tunnels_ssh_1.SshStream(channel));
        if (!forwardedStream) {
            // The event handler rejected the connection.
            return;
        }
        const forwarder = new streamForwarder_1.StreamForwarder(socket, forwardedStream, channel.session.trace);
        this.pfs.streamForwarders.push(forwarder);
    }
    dispose() {
        var _a, _b;
        // Note stopping the listener does not disconnect any already-accepted sockets.
        (_a = this.tcpListener) === null || _a === void 0 ? void 0 : _a.close();
        (_b = this.tcpListener2) === null || _b === void 0 ? void 0 : _b.close();
        this.trace(dev_tunnels_ssh_1.TraceLevel.Info, dev_tunnels_ssh_1.SshTraceEventIds.portForwardServerListening, `PortForwardingService stopped listening on ${this.localIPAddress}:${this.port}.`);
        super.dispose();
    }
}
exports.LocalPortForwarder = LocalPortForwarder;
//# sourceMappingURL=https://main.vscode-cdn.net/sourcemaps/f6cfa2ea2403534de03f069bdf160d06451ed282/node_modules/@microsoft/dev-tunnels-ssh-tcp/services/localPortForwarder.js.map