"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
Object.defineProperty(exports, "__esModule", { value: true });
exports.TunnelConnectionSession = void 0;
const dev_tunnels_contracts_1 = require("@microsoft/dev-tunnels-contracts");
const dev_tunnels_management_1 = require("@microsoft/dev-tunnels-management");
const dev_tunnels_ssh_1 = require("@microsoft/dev-tunnels-ssh");
const vscode_jsonrpc_1 = require("vscode-jsonrpc");
const connectionStatus_1 = require("./connectionStatus");
const relayTunnelConnector_1 = require("./relayTunnelConnector");
const utils_1 = require("./utils");
const tunnelConnectionBase_1 = require("./tunnelConnectionBase");
const dev_tunnels_ssh_tcp_1 = require("@microsoft/dev-tunnels-ssh-tcp");
const portRelayRequestMessage_1 = require("./messages/portRelayRequestMessage");
const portRelayConnectRequestMessage_1 = require("./messages/portRelayConnectRequestMessage");
const refreshingTunnelEventArgs_1 = require("./refreshingTunnelEventArgs");
const defaultTunnelRelayStreamFactory_1 = require("./defaultTunnelRelayStreamFactory");
const uuid_1 = require("uuid");
/**
 * Tunnel connection session.
 */
class TunnelConnectionSession extends tunnelConnectionBase_1.TunnelConnectionBase {
    /**
     * Name of the protocol used to connect to the tunnel.
     */
    get connectionProtocol() {
        return this.connectionProtocolValue;
    }
    set connectionProtocol(value) {
        this.connectionProtocolValue = value;
    }
    /**
     * Gets an ID that is unique to this instance of `TunnelConnectionSession`,
     * useful for correlating connection events over time.
     */
    get connectionId() {
        return this.uniqueConnectionId;
    }
    /**
     * A value indicating if this is a client tunnel connection (as opposed to host connection).
     */
    get isClientConnection() {
        return this.tunnelAccessScope === dev_tunnels_contracts_1.TunnelAccessScopes.Connect;
    }
    /**
     * tunnel connection role, either "client", or "host", depending on @link tunnelAccessScope.
     */
    get connectionRole() {
        return this.isClientConnection ? 'client' : 'host';
    }
    /**
     * @internal onRetrying override to report tunnel events.
     */
    onRetrying(event) {
        var _a;
        // Report tunnel event for retry
        if (this.tunnel && this.managementClient) {
            const retryingEvent = {
                name: `${this.connectionRole}_connect_retrying`,
                severity: dev_tunnels_contracts_1.TunnelEvent.warning,
                details: (_a = event.error) === null || _a === void 0 ? void 0 : _a.toString(),
                properties: {
                    'Retry': event.retry.toString(),
                    'Delay': event.delayMs.toString()
                },
            };
            this.managementClient.reportEvent(this.tunnel, retryingEvent);
        }
        super.onRetrying(event);
    }
    /**
     * @internal onConnectionStatusChanged override to report tunnel events.
     */
    onConnectionStatusChanged(previousStatus, status) {
        // Report tunnel event for connection status change
        if (this.tunnel && this.managementClient) {
            const statusEvent = {
                name: `${this.connectionRole}_connection_status`,
                severity: dev_tunnels_contracts_1.TunnelEvent.info,
                details: undefined,
                properties: {
                    ConnectionStatus: status.toString(),
                    PreviousConnectionStatus: previousStatus.toString()
                },
            };
            if (previousStatus !== connectionStatus_1.ConnectionStatus.None) {
                // Format the duration as a TimeSpan in the form "00:00:00.000"
                const duration = Date.now() - this.connectionStartTime;
                const formattedDuration = new Date(duration).toISOString().substring(11, 23);
                statusEvent.properties[`${previousStatus}Duration`] = formattedDuration;
            }
            if (this.isClientConnection) {
                // For client sessions, report the SSH session ID property, which is derived from
                // the SSH key-exchange such that both host and client have the same ID.
                statusEvent.properties.ClientSessionId = this.getShortSessionId(this.sshSession);
            }
            else {
                // For host sessions, there is no SSH encryption or key-exchange.
                // Just use a locally-generated GUID that is unique to this session.
                statusEvent.properties.HostSessionId = this.connectionId;
            }
            this.managementClient.reportEvent(this.tunnel, statusEvent);
        }
        this.connectionStartTime = Date.now();
        super.onConnectionStatusChanged(previousStatus, status);
    }
    constructor(tunnelAccessScope, connectionProtocols, 
    /**
     * Gets the management client used for the connection.
     */
    managementClient, trace) {
        super(tunnelAccessScope);
        this.connectionProtocols = connectionProtocols;
        this.managementClient = managementClient;
        this.connectedTunnel = null;
        this.connectionStartTime = Date.now();
        this.uniqueConnectionId = (0, uuid_1.v4)();
        this.refreshingTunnelEmitter = new utils_1.TrackingEmitter();
        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 a factory for creating relay streams.
         */
        this.streamFactory = new defaultTunnelRelayStreamFactory_1.DefaultTunnelRelayStreamFactory();
        this.sshSessionDisposables = [];
        /**
         * An event which fires when tunnel connection refreshes tunnel.
         */
        this.refreshingTunnel = this.refreshingTunnelEmitter.event;
        /**
         * Determines whether E2E encryption is requested when opening connections through the tunnel
         * (V2 protocol only).
         *
         * The default value is true, but applications may set this to false (for slightly faster
         * connections).
         *
         * Note when this is true, E2E encryption is not strictly required. The tunnel relay and
         * tunnel host can decide whether or not to enable E2E encryption for each connection,
         * depending on policies and capabilities. Applications can verify the status of E2EE by
         * handling the `forwardedPortConnecting` event and checking the related property on the
         * channel request or response message.
         */
        this.enableE2EEncryption = true;
        this.trace = trace !== null && trace !== void 0 ? trace : (() => { });
        this.httpAgent = managementClient === null || managementClient === void 0 ? void 0 : managementClient.httpsAgent;
    }
    /* @internal */
    raiseReportProgress(progress, sessionNumber) {
        const args = {
            progress,
            sessionNumber,
        };
        this.reportProgressEmitter.fire(args);
    }
    /**
     * Get the tunnel of this tunnel connection.
     */
    get tunnel() {
        return this.connectedTunnel;
    }
    set tunnel(value) {
        if (value !== this.connectedTunnel) {
            this.connectedTunnel = value;
            this.tunnelChanged();
        }
    }
    /**
     * Tunnel has been assigned to or changed.
     */
    tunnelChanged() {
        if (this.tunnel) {
            this.accessToken = dev_tunnels_management_1.TunnelAccessTokenProperties.getTunnelAccessToken(this.tunnel, this.tunnelAccessScope);
        }
        else {
            this.accessToken = undefined;
        }
    }
    /**
     * Gets a value indicating that this connection has already created its connector
     * and so can be reconnected if needed.
     */
    get isReconnectable() {
        return !!this.connector;
    }
    /**
     * Gets the disconnection reason.
     * {@link SshDisconnectReason.none } if not yet disconnected.
     * {@link SshDisconnectReason.connectionLost} if network connection was lost and reconnects are not enabled or unsuccesfull.
     * {@link SshDisconnectReason.byApplication} if connection was disposed.
     * {@link SshDisconnectReason.tooManyConnections} if host connection was disconnected because another host connected for the same tunnel.
     */
    get disconnectReason() {
        return this.disconnectionReason;
    }
    /**
     * Sets the disconnect reason that caused disconnection.
     */
    set disconnectReason(reason) {
        this.disconnectionReason = reason;
    }
    /**
     * @internal Creates a stream to the tunnel.
     */
    async createSessionStream(options, cancellation) {
        if (!this.relayUri) {
            throw new Error('Cannot create tunnel session stream. Tunnel relay endpoint URI is missing');
        }
        if (this.isClientConnection) {
            this.raiseReportProgress(dev_tunnels_ssh_1.Progress.OpeningClientConnectionToRelay);
        }
        else {
            this.raiseReportProgress(dev_tunnels_ssh_1.Progress.OpeningHostConnectionToRelay);
        }
        this.trace(dev_tunnels_ssh_1.TraceLevel.Info, 0, `Connecting to ${this.connectionRole} tunnel relay ${this.relayUri}`);
        this.trace(dev_tunnels_ssh_1.TraceLevel.Verbose, 0, `Sec-WebSocket-Protocol: ${this.connectionProtocols.join(', ')}`);
        if (this.accessToken) {
            const tokenTrace = dev_tunnels_management_1.TunnelAccessTokenProperties.getTokenTrace(this.accessToken);
            this.trace(dev_tunnels_ssh_1.TraceLevel.Verbose, 0, `Authorization: tunnel <${tokenTrace}>`);
        }
        const clientConfig = {
            tlsOptions: {
                agent: this.httpAgent,
            },
        };
        const streamAndProtocol = await this.streamFactory.createRelayStream(this.relayUri, this.connectionProtocols, this.accessToken, clientConfig);
        this.trace(dev_tunnels_ssh_1.TraceLevel.Verbose, 0, `Connected with subprotocol '${streamAndProtocol.protocol}'`);
        if (this.isClientConnection) {
            this.raiseReportProgress(dev_tunnels_ssh_1.Progress.OpenedClientConnectionToRelay);
        }
        else {
            this.raiseReportProgress(dev_tunnels_ssh_1.Progress.OpenedHostConnectionToRelay);
        }
        return streamAndProtocol;
    }
    /**
     * @internal Configures the tunnel session with the given stream.
     */
    configureSession(stream, protocol, isReconnect, cancellation) {
        throw new Error('Not implemented');
    }
    /**
     * @internal Closes the tunnel session due to an error.
     */
    async closeSession(reason, error) {
        this.unsubscribeSessionEvents();
        const session = this.sshSession;
        if (!session) {
            return;
        }
        if (!session.isClosed) {
            await session.close(reason || dev_tunnels_ssh_1.SshDisconnectReason.none, undefined, error);
        }
        else {
            this.sshSession = undefined;
        }
        // Closing the SSH session does nothing if the session is in disconnected state,
        // which may happen for a reconnectable session when the connection drops.
        // Disposing of the session forces closing and frees up the resources.
        session.dispose();
    }
    /**
     * Disposes this tunnel session, closing the SSH session used for it.
     */
    async dispose() {
        if (this.disconnectReason === dev_tunnels_ssh_1.SshDisconnectReason.none ||
            this.disconnectReason === undefined) {
            this.disconnectReason = dev_tunnels_ssh_1.SshDisconnectReason.byApplication;
        }
        await super.dispose();
        try {
            await this.closeSession(this.disconnectReason, this.disconnectError);
        }
        catch (e) {
            if (!(e instanceof dev_tunnels_ssh_1.ObjectDisposedError))
                throw e;
        }
    }
    /**
     * Refreshes the tunnel access token. This may be useful when the Relay service responds with 401 Unauthorized.
     * Does nothing if the object is disposed, or there is no way to refresh the token.
     * @internal
     */
    async refreshTunnelAccessToken(cancellation) {
        var _a;
        if (this.isDisposed) {
            return false;
        }
        if (!this.isRefreshingTunnelAccessTokenEventHandled && !this.canRefreshTunnel) {
            return false;
        }
        this.connectionStatus = connectionStatus_1.ConnectionStatus.RefreshingTunnelAccessToken;
        try {
            this.traceVerbose(`Refreshing tunnel access token. Current token: ${dev_tunnels_management_1.TunnelAccessTokenProperties.getTokenTrace(this.accessToken)}`);
            if (this.isRefreshingTunnelAccessTokenEventHandled) {
                this.accessToken = (_a = await this.getFreshTunnelAccessToken(cancellation)) !== null && _a !== void 0 ? _a : undefined;
            }
            else {
                await this.refreshTunnel(false, cancellation);
            }
            this.traceVerbose(`Refreshed tunnel access token. New token: ${dev_tunnels_management_1.TunnelAccessTokenProperties.getTokenTrace(this.accessToken)}`);
            return true;
        }
        finally {
            this.connectionStatus = connectionStatus_1.ConnectionStatus.Connecting;
        }
    }
    /**
     * @internal Start connecting relay client.
     */
    startConnecting() {
        this.connectionStatus = connectionStatus_1.ConnectionStatus.Connecting;
    }
    /**
     * @internal Finish connecting relay client.
     */
    finishConnecting(reason, disconnectError) {
        if (reason === undefined || reason === dev_tunnels_ssh_1.SshDisconnectReason.none) {
            if (this.connectionStatus === connectionStatus_1.ConnectionStatus.Connecting) {
                // If there were temporary connection issue, disconnectError may contain the old error.
                // Since we have successfully connected after all, clean it up.
                this.disconnectError = undefined;
                this.disconnectReason = undefined;
            }
            this.connectionStatus = connectionStatus_1.ConnectionStatus.Connected;
        }
        else if (this.connectionStatus !== connectionStatus_1.ConnectionStatus.Disconnected) {
            // Do not overwrite disconnect error and reason if already disconnected.
            this.disconnectReason = reason;
            if (disconnectError) {
                this.disconnectError = disconnectError;
            }
            this.connectionStatus = connectionStatus_1.ConnectionStatus.Disconnected;
        }
    }
    /**
     * Get a value indicating whether this session can attempt refreshing tunnel.
     * Note: tunnel refresh may still fail if the tunnel doesn't exist in the service,
     * tunnel access has changed, or tunnel access token has expired.
     */
    get canRefreshTunnel() {
        return (this.tunnel && this.managementClient) || this.refreshingTunnelEmitter.isSubscribed;
    }
    /**
     * Fetch the tunnel from the service if {@link managementClient} and {@link tunnel} are set.
     */
    async refreshTunnel(includePorts, cancellation) {
        this.traceInfo('Refreshing tunnel.');
        let isRefreshed = false;
        const e = new refreshingTunnelEventArgs_1.RefreshingTunnelEventArgs(this.tunnelAccessScope, this.tunnel, !!includePorts, this.managementClient, cancellation);
        this.refreshingTunnelEmitter.fire(e);
        if (e.tunnelPromise) {
            this.tunnel = await e.tunnelPromise;
            isRefreshed = true;
        }
        if (!isRefreshed && this.tunnel && this.managementClient) {
            const options = {
                tokenScopes: [this.tunnelAccessScope],
                includePorts,
            };
            this.tunnel = await (0, utils_1.withCancellation)(this.managementClient.getTunnel(this.tunnel, options), cancellation);
            isRefreshed = true;
        }
        if (isRefreshed) {
            if (this.tunnel) {
                this.traceInfo('Refreshed tunnel.');
            }
            else {
                this.traceInfo('Tunnel not found.');
            }
        }
        return true;
    }
    /**
     * Creates a tunnel connector
     */
    createTunnelConnector() {
        return new relayTunnelConnector_1.RelayTunnelConnector(this);
    }
    /**
     * Trace info message.
     */
    traceInfo(msg) {
        this.trace(dev_tunnels_ssh_1.TraceLevel.Info, 0, msg);
    }
    /**
     * Trace verbose message.
     */
    traceVerbose(msg) {
        this.trace(dev_tunnels_ssh_1.TraceLevel.Verbose, 0, msg);
    }
    /**
     * Trace warning message.
     */
    traceWarning(msg, err) {
        this.trace(dev_tunnels_ssh_1.TraceLevel.Warning, 0, msg, err);
    }
    /**
     * Trace error message.
     */
    traceError(msg, err) {
        this.trace(dev_tunnels_ssh_1.TraceLevel.Error, 0, msg, err);
    }
    /**
     * SSH session closed event handler. Child classes may use it unsubscribe session events and maybe start reconnecting.
     */
    onSshSessionClosed(e) {
        this.unsubscribeSessionEvents();
        this.sshSession = undefined;
        this.maybeStartReconnecting(e.reason, e.message, e.error);
    }
    /**
     * Start reconnecting if the tunnel connection is not yet disposed.
     */
    maybeStartReconnecting(reason, message, error) {
        var _a, _b, _c, _d;
        const traceMessage = `Connection to ${this.connectionRole} tunnel relay closed.${this.getDisconnectReason(reason, message, error)}`;
        if (this.isDisposed || this.connectionStatus === connectionStatus_1.ConnectionStatus.Disconnected) {
            // Disposed or disconnected already.
            // This reconnection attempt may be caused by closing SSH session on dispose.
            this.traceInfo(traceMessage);
            return;
        }
        if (error) {
            this.disconnectError = error;
            this.disconnectReason = reason;
        }
        if (this.connectionStatus !== connectionStatus_1.ConnectionStatus.Connected || this.reconnectPromise) {
            // Not connected or already connecting.
            this.traceInfo(traceMessage);
            return;
        }
        // Reconnect if connection is lost, reconnect is enabled, and connector exists.
        // The connector may be undefined if the tunnel client/host was created directly from a stream.
        if (((_b = (_a = this.connectionOptions) === null || _a === void 0 ? void 0 : _a.enableReconnect) !== null && _b !== void 0 ? _b : true) &&
            reason === dev_tunnels_ssh_1.SshDisconnectReason.connectionLost &&
            this.connector) {
            // Report reconnect event
            if (this.tunnel && this.managementClient) {
                const reconnectEvent = {
                    name: `${this.connectionRole}_reconnect`,
                    severity: dev_tunnels_contracts_1.TunnelEvent.warning,
                    details: (_c = error === null || error === void 0 ? void 0 : error.toString()) !== null && _c !== void 0 ? _c : message,
                    properties: {
                        ClientSessionId: this.getShortSessionId(this.sshSession),
                    },
                };
                this.managementClient.reportEvent(this.tunnel, reconnectEvent);
            }
            this.traceInfo(`${traceMessage} Reconnecting.`);
            this.reconnectPromise = (async () => {
                try {
                    await this.connectTunnelSession();
                }
                catch (ex) {
                    // Report reconnect failed event
                    if (this.tunnel && this.managementClient) {
                        const reconnectFailedEvent = {
                            name: `${this.connectionRole}_reconnect_failed`,
                            severity: dev_tunnels_contracts_1.TunnelEvent.error,
                            details: ex instanceof Error ? ex.toString() : String(ex),
                            properties: {
                                ClientSessionId: this.getShortSessionId(this.sshSession),
                            },
                        };
                        this.managementClient.reportEvent(this.tunnel, reconnectFailedEvent);
                    }
                    // Tracing of the error has already been done by connectTunnelSession.
                    // As reconnection is an async process, there is nobody watching it throw.
                    // The error, if it was not cancellation, is stored in disconnectError property.
                    // There might have been connectionStatusChanged event fired as well.
                }
                this.reconnectPromise = undefined;
            })();
        }
        else {
            // Report disconnect event
            if (this.tunnel && this.managementClient) {
                const disconnectEvent = {
                    name: `${this.connectionRole}_disconnect`,
                    severity: dev_tunnels_contracts_1.TunnelEvent.warning,
                    details: (_d = error === null || error === void 0 ? void 0 : error.toString()) !== null && _d !== void 0 ? _d : message,
                    properties: {
                        ClientSessionId: this.getShortSessionId(this.sshSession),
                    },
                };
                this.managementClient.reportEvent(this.tunnel, disconnectEvent);
            }
            this.traceInfo(traceMessage);
            this.connectionStatus = connectionStatus_1.ConnectionStatus.Disconnected;
        }
    }
    /**
     * Get a user-readable reason for SSH session disconnection, or an empty string.
     */
    getDisconnectReason(reason, message, error) {
        switch (reason) {
            case dev_tunnels_ssh_1.SshDisconnectReason.connectionLost:
                return ` ${message || (error === null || error === void 0 ? void 0 : error.message) || 'Connection lost.'}`;
            case dev_tunnels_ssh_1.SshDisconnectReason.authCancelledByUser:
            case dev_tunnels_ssh_1.SshDisconnectReason.noMoreAuthMethodsAvailable:
            case dev_tunnels_ssh_1.SshDisconnectReason.hostNotAllowedToConnect:
            case dev_tunnels_ssh_1.SshDisconnectReason.illegalUserName:
                return ' Not authorized.';
            case dev_tunnels_ssh_1.SshDisconnectReason.serviceNotAvailable:
                return ' Service not available.';
            case dev_tunnels_ssh_1.SshDisconnectReason.compressionError:
            case dev_tunnels_ssh_1.SshDisconnectReason.keyExchangeFailed:
            case dev_tunnels_ssh_1.SshDisconnectReason.macError:
            case dev_tunnels_ssh_1.SshDisconnectReason.protocolError:
                return ' Protocol error.';
            case dev_tunnels_ssh_1.SshDisconnectReason.tooManyConnections:
                return this.isClientConnection ? ' Too many client connections.' : ' Another host for the tunnel has connected.';
            default:
                return '';
        }
    }
    /**
     * Connect to the tunnel session by running the provided {@link action}.
     */
    async connectSession(action) {
        try {
            await action();
        }
        catch (e) {
            if (!(e instanceof dev_tunnels_ssh_1.CancellationError)) {
                if (e instanceof Error) {
                    this.traceError(`Error connecting ${this.connectionRole} tunnel session: ${e.message}`, e);
                }
                else {
                    const message = `Error connecting ${this.connectionRole} tunnel session: ${e}`;
                    this.traceError(message);
                }
                if (this.tunnel && this.managementClient) {
                    const connectFailedEvent = {
                        name: `${this.connectionRole}_connect_failed`,
                        severity: dev_tunnels_contracts_1.TunnelEvent.error,
                        details: e instanceof Error ? e.toString() : String(e),
                    };
                    this.managementClient.reportEvent(this.tunnel, connectFailedEvent);
                }
            }
            throw e;
        }
    }
    /**
     * Connect to the tunnel session with the tunnel connector.
     * @param tunnel Tunnel to use for the connection.
     *     Undefined if the connection information is already known and the tunnel is not needed.
     *     Tunnel object to get the connection information from that tunnel.
     */
    async connectTunnelSession(tunnel, options, cancellation) {
        var _a;
        if (tunnel) {
            this.tunnel = tunnel;
        }
        if (options) {
            this.connectionOptions = options;
            (_a = this.httpAgent) !== null && _a !== void 0 ? _a : (this.httpAgent = options === null || options === void 0 ? void 0 : options.httpAgent);
        }
        await this.connectSession(async () => {
            const isReconnect = this.isReconnectable && !tunnel;
            await this.onConnectingToTunnel();
            if (!this.connector) {
                this.connector = this.createTunnelConnector();
            }
            const disposables = [];
            if (cancellation) {
                // Link the provided cancellation token with the dispose token.
                const linkedCancellationSource = new vscode_jsonrpc_1.CancellationTokenSource();
                disposables.push(linkedCancellationSource, cancellation.onCancellationRequested(() => linkedCancellationSource.cancel()), this.disposeToken.onCancellationRequested(() => linkedCancellationSource.cancel()));
                cancellation = linkedCancellationSource.token;
            }
            else {
                cancellation = this.disposeToken;
            }
            try {
                await this.connector.connectSession(isReconnect, options, cancellation);
            }
            catch (e) {
                if (e instanceof dev_tunnels_ssh_1.CancellationError) {
                    this.throwIfDisposed(`CancelationError: ${e.message}`, e.stack);
                }
                throw e;
            }
            finally {
                for (const disposable of disposables)
                    disposable.dispose();
            }
        });
    }
    /**
     * Validate the {@link tunnel} and get data needed to connect to it, if the tunnel is provided;
     * otherwise, ensure that there is already sufficient data to connect to a tunnel.
     */
    onConnectingToTunnel() {
        return Promise.resolve();
    }
    /**
     * Validates tunnel access token if it's present. Returns the token.
     * Note: uses client's system time for the validation.
     */
    validateAccessToken() {
        if (this.accessToken) {
            dev_tunnels_management_1.TunnelAccessTokenProperties.validateTokenExpiration(this.accessToken);
            return this.accessToken;
        }
    }
    /** @internal */
    createRequestMessageAsync(port) {
        const message = new portRelayRequestMessage_1.PortRelayRequestMessage();
        message.accessToken = this.accessToken;
        return Promise.resolve(message);
    }
    /** @internal */
    createSuccessMessageAsync(port) {
        const message = new dev_tunnels_ssh_tcp_1.PortForwardSuccessMessage();
        return Promise.resolve(message);
    }
    /** @internal */
    createChannelOpenMessageAsync(port) {
        const message = new portRelayConnectRequestMessage_1.PortRelayConnectRequestMessage();
        message.accessToken = this.accessToken;
        message.isE2EEncryptionRequested = this.enableE2EEncryption;
        return Promise.resolve(message);
    }
    /**
     * Unsubscribe SSH session events in @link TunnelSshConnectionSession.sshSessionDisposables
     */
    unsubscribeSessionEvents() {
        this.sshSessionDisposables.forEach((d) => d.dispose());
        this.sshSessionDisposables = [];
    }
    /** @internal */
    getShortSessionId(session) {
        const b = session === null || session === void 0 ? void 0 : session.sessionId;
        if (!b || b.length < 16) {
            return '';
        }
        // Format as a GUID. This cannot use uuid.stringify() because
        // the bytes might not be technically valid for a UUID.
        return b.subarray(0, 4).toString('hex') + '-' +
            b.subarray(4, 6).toString('hex') + '-' +
            b.subarray(6, 8).toString('hex') + '-' +
            b.subarray(8, 10).toString('hex') + '-' +
            b.subarray(10, 16).toString('hex');
    }
}
exports.TunnelConnectionSession = TunnelConnectionSession;
//# sourceMappingURL=https://main.vscode-cdn.net/sourcemaps/f6cfa2ea2403534de03f069bdf160d06451ed282/node_modules/@microsoft/dev-tunnels-connections/tunnelConnectionSession.js.map