"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 ConnectionService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConnectionService = void 0;
const sshService_1 = require("./sshService");
const connectionMessages_1 = require("../messages/connectionMessages");
const promiseCompletionSource_1 = require("../util/promiseCompletionSource");
const sshChannel_1 = require("../sshChannel");
const cancellation_1 = require("../util/cancellation");
const errors_1 = require("../errors");
const sshChannelOpeningEventArgs_1 = require("../events/sshChannelOpeningEventArgs");
const serviceActivation_1 = require("./serviceActivation");
const trace_1 = require("../trace");
const sshExtendedDataEventArgs_1 = require("../events/sshExtendedDataEventArgs");
let ConnectionService = ConnectionService_1 = class ConnectionService extends sshService_1.SshService {
    constructor(session) {
        super(session);
        this.channelCounter = 0;
        this.channelMap = new Map();
        this.nonAcceptedChannels = new Map();
        this.pendingChannels = new Map();
        this.pendingAcceptChannels = new Map();
    }
    get channels() {
        return Array.from(this.channelMap.values());
    }
    close(e) {
        let channelCompletions = [...this.pendingChannels.values()].map((pc) => pc.completionSource);
        if (this.pendingAcceptChannels.size > 0) {
            channelCompletions = channelCompletions.concat([...this.pendingAcceptChannels.values()].reduce((a, b) => a.concat(b)));
        }
        for (const channel of this.channelMap.values()) {
            channel.close(e);
        }
        for (const channelCompletion of channelCompletions) {
            channelCompletion.reject(e);
        }
    }
    dispose() {
        const channels = [...this.channelMap.values()];
        let channelCompletions = [...this.pendingChannels.values()].map((pc) => pc.completionSource);
        if (this.pendingAcceptChannels.size > 0) {
            channelCompletions = channelCompletions.concat([...this.pendingAcceptChannels.values()].reduce((a, b) => a.concat(b)));
        }
        for (const channel of channels) {
            channel.dispose();
        }
        for (const channelCompletion of channelCompletions) {
            channelCompletion.reject(new errors_1.ObjectDisposedError('Session closed.'));
        }
        super.dispose();
    }
    async acceptChannel(channelType, cancellation) {
        const completionSource = new promiseCompletionSource_1.PromiseCompletionSource();
        let cancellationRegistration;
        if (cancellation) {
            if (cancellation.isCancellationRequested)
                throw new cancellation_1.CancellationError();
            cancellationRegistration = cancellation.onCancellationRequested(() => {
                const list = this.pendingAcceptChannels.get(channelType);
                if (list) {
                    const index = list.findIndex((item) => Object.is(item, completionSource));
                    if (index >= 0) {
                        list.splice(index, 1);
                    }
                }
                completionSource.reject(new cancellation_1.CancellationError());
            });
        }
        let channel = null;
        channel =
            Array.from(this.nonAcceptedChannels.values()).find((c) => c.channelType === channelType) ||
                null;
        if (channel) {
            // Found a channel that was already opened but not accepted.
            this.nonAcceptedChannels.delete(channel.channelId);
        }
        else {
            // Set up the completion source to wait for a channel of the requested type.
            let list = this.pendingAcceptChannels.get(channelType);
            if (!list) {
                list = [];
                this.pendingAcceptChannels.set(channelType, list);
            }
            list.push(completionSource);
        }
        try {
            return channel || (await completionSource.promise);
        }
        finally {
            if (cancellationRegistration)
                cancellationRegistration.dispose();
        }
    }
    async openChannel(openMessage, completionSource, cancellation) {
        const channelId = ++this.channelCounter;
        openMessage.senderChannel = channelId;
        let cancellationRegistration = null;
        if (cancellation) {
            if (cancellation.isCancellationRequested)
                throw new cancellation_1.CancellationError();
            cancellationRegistration = cancellation.onCancellationRequested(() => {
                if (this.pendingChannels.delete(channelId)) {
                    completionSource.reject(new cancellation_1.CancellationError());
                }
            });
        }
        this.pendingChannels.set(channelId, {
            openMessage: openMessage,
            completionSource: completionSource,
            cancellationRegistration: cancellationRegistration,
        });
        await this.session.sendMessage(openMessage);
        return channelId;
    }
    handleMessage(message, cancellation) {
        if (message instanceof connectionMessages_1.ChannelDataMessage) {
            return this.handleDataMessage(message);
        }
        else if (message instanceof connectionMessages_1.ChannelExtendedDataMessage) {
            return this.handleExtendedDataMessage(message);
        }
        else if (message instanceof connectionMessages_1.ChannelWindowAdjustMessage) {
            return this.handleAdjustWindowMessage(message);
        }
        else if (message instanceof connectionMessages_1.ChannelEofMessage) {
            return this.handleEofMessage(message);
        }
        else if (message instanceof connectionMessages_1.ChannelOpenMessage) {
            return this.handleOpenMessage(message, cancellation);
        }
        else if (message instanceof connectionMessages_1.ChannelCloseMessage) {
            return this.handleCloseMessage(message);
        }
        else if (message instanceof connectionMessages_1.ChannelOpenConfirmationMessage) {
            return this.handleOpenConfirmationMessage(message, cancellation);
        }
        else if (message instanceof connectionMessages_1.ChannelOpenFailureMessage) {
            return this.handleOpenFailureMessage(message);
        }
        else if (message instanceof connectionMessages_1.ChannelRequestMessage) {
            return this.handleRequestMessage(message, cancellation);
        }
        else if (message instanceof connectionMessages_1.ChannelSuccessMessage) {
            return this.handleSuccessMessage(message);
        }
        else if (message instanceof connectionMessages_1.ChannelFailureMessage) {
            return this.handleFailureMessage(message);
        }
        else {
            throw new Error(`Message not implemented: ${message}`);
        }
    }
    async handleOpenMessage(message, cancellation) {
        var _a;
        const senderChannel = message.senderChannel;
        if (!this.session.canAcceptRequests) {
            this.trace(trace_1.TraceLevel.Warning, trace_1.SshTraceEventIds.channelOpenFailed, 'Channel open request blocked because the session is not yet authenticated.');
            const openFailureMessage = new connectionMessages_1.ChannelOpenFailureMessage();
            openFailureMessage.recipientChannel = senderChannel;
            openFailureMessage.reasonCode = connectionMessages_1.SshChannelOpenFailureReason.administrativelyProhibited;
            openFailureMessage.description = 'Authenticate before opening channels.';
            await this.session.sendMessage(openFailureMessage, cancellation);
            return;
        }
        else if (!message.channelType) {
            const openFailureMessage = new connectionMessages_1.ChannelOpenFailureMessage();
            openFailureMessage.recipientChannel = senderChannel;
            openFailureMessage.reasonCode = connectionMessages_1.SshChannelOpenFailureReason.unknownChannelType;
            openFailureMessage.description = 'Channel type not specified.';
            await this.session.sendMessage(openFailureMessage, cancellation);
            return;
        }
        // Save a copy of the message because its buffer will be overwitten by the next receive.
        message = message.convertTo(new connectionMessages_1.ChannelOpenMessage(), true);
        // The confirmation message may be reassigned if the opening task returns a custom message.
        let confirmationMessage = new connectionMessages_1.ChannelOpenConfirmationMessage();
        const channelId = ++this.channelCounter;
        const channel = new sshChannel_1.SshChannel(this, message.channelType, channelId, senderChannel, message.maxWindowSize, message.maxPacketSize, message, confirmationMessage);
        let responseMessage;
        const args = new sshChannelOpeningEventArgs_1.SshChannelOpeningEventArgs(message, channel, true);
        try {
            await this.session.handleChannelOpening(args, cancellation);
            if (args.openingPromise) {
                responseMessage = await args.openingPromise;
            }
            else if (args.failureReason !== connectionMessages_1.SshChannelOpenFailureReason.none) {
                const failureMessage = new connectionMessages_1.ChannelOpenFailureMessage();
                failureMessage.reasonCode = args.failureReason;
                failureMessage.description = (_a = args.failureDescription) !== null && _a !== void 0 ? _a : undefined;
                responseMessage = failureMessage;
            }
            else {
                responseMessage = confirmationMessage;
            }
        }
        catch (e) {
            channel.dispose();
            throw e;
        }
        if (responseMessage instanceof connectionMessages_1.ChannelOpenFailureMessage) {
            responseMessage.recipientChannel = senderChannel;
            try {
                await this.session.sendMessage(responseMessage, cancellation);
            }
            finally {
                channel.dispose();
            }
            return;
        }
        // The session might have been closed while opening the channel.
        if (this.session.isClosed) {
            channel.dispose();
            return;
        }
        // Prevent any changes to the channel max window size after sending the value in the
        // open confirmation message.
        channel.isMaxWindowSizeLocked = true;
        this.channelMap.set(channel.channelId, channel);
        confirmationMessage = responseMessage;
        confirmationMessage.recipientChannel = channel.remoteChannelId;
        confirmationMessage.senderChannel = channel.channelId;
        confirmationMessage.maxWindowSize = channel.maxWindowSize;
        confirmationMessage.maxPacketSize = channel.maxPacketSize;
        confirmationMessage.rewrite();
        channel.openConfirmationMessage = confirmationMessage;
        await this.session.sendMessage(confirmationMessage, cancellation);
        // Check if there are any accept operations waiting on this channel type.
        let accepted = false;
        const list = this.pendingAcceptChannels.get(channel.channelType);
        while (list && list.length > 0) {
            const acceptCompletionSource = list.shift();
            acceptCompletionSource.resolve(channel);
            accepted = true;
            break;
        }
        if (!accepted) {
            this.nonAcceptedChannels.set(channel.channelId, channel);
        }
        this.onChannelOpenCompleted(channel.channelId, channel);
        channel.enableSending();
    }
    handleCloseMessage(message) {
        const channel = this.findChannelById(message.recipientChannel);
        if (channel) {
            channel.handleClose();
        }
    }
    async handleOpenConfirmationMessage(message, cancellation) {
        var _a;
        let completionSource = null;
        let openMessage;
        const pendingChannel = this.pendingChannels.get(message.recipientChannel);
        if (pendingChannel) {
            openMessage = pendingChannel.openMessage;
            completionSource = pendingChannel.completionSource;
            if (pendingChannel.cancellationRegistration) {
                pendingChannel.cancellationRegistration.dispose();
            }
            this.pendingChannels.delete(message.recipientChannel);
        }
        else if (this.channelMap.has(message.recipientChannel)) {
            throw new Error('Duplicate channel ID.');
        }
        else {
            throw new Error('Channel confirmation was not requested.');
        }
        // Save a copy of the message because its buffer will be overwitten by the next receive.
        message = message.convertTo(new connectionMessages_1.ChannelOpenConfirmationMessage(), true);
        const channel = new sshChannel_1.SshChannel(this, openMessage.channelType || sshChannel_1.SshChannel.sessionChannelType, message.recipientChannel, message.senderChannel, message.maxWindowSize, message.maxPacketSize, openMessage, message);
        // Set the channel max window size property to match the value sent in the open message,
        // (if specified) and lock it to prevent any further changes.
        if (typeof openMessage.maxWindowSize === 'number') {
            channel.maxWindowSize = openMessage.maxWindowSize;
        }
        channel.isMaxWindowSizeLocked = true;
        this.channelMap.set(channel.channelId, channel);
        const args = new sshChannelOpeningEventArgs_1.SshChannelOpeningEventArgs(openMessage, channel, false);
        await this.session.handleChannelOpening(args, cancellation);
        if (completionSource) {
            if (args.failureReason === connectionMessages_1.SshChannelOpenFailureReason.none) {
                completionSource.resolve(channel);
            }
            else {
                completionSource.reject(new errors_1.SshChannelError((_a = args.failureDescription) !== null && _a !== void 0 ? _a : 'Channel open failure.', args.failureReason));
                return;
            }
        }
        else {
            this.onChannelOpenCompleted(channel.channelId, channel);
        }
        channel.enableSending();
    }
    handleOpenFailureMessage(message) {
        let completionSource = null;
        const pendingChannel = this.pendingChannels.get(message.recipientChannel);
        if (pendingChannel) {
            completionSource = pendingChannel.completionSource;
            if (pendingChannel.cancellationRegistration) {
                pendingChannel.cancellationRegistration.dispose();
            }
            this.pendingChannels.delete(message.recipientChannel);
        }
        if (completionSource != null) {
            completionSource.reject(new errors_1.SshChannelError(message.description || 'Channel open rejected.', message.reasonCode));
        }
        else {
            this.onChannelOpenCompleted(message.recipientChannel, null);
        }
    }
    async handleRequestMessage(message, cancellation) {
        const channel = this.tryGetChannelForMessage(message);
        if (!channel)
            return;
        await channel.handleRequest(message, cancellation);
    }
    handleSuccessMessage(message) {
        const channel = this.tryGetChannelForMessage(message);
        channel === null || channel === void 0 ? void 0 : channel.handleResponse(true);
    }
    handleFailureMessage(message) {
        const channel = this.tryGetChannelForMessage(message);
        channel === null || channel === void 0 ? void 0 : channel.handleResponse(false);
    }
    handleDataMessage(message) {
        const channel = this.tryGetChannelForMessage(message);
        channel === null || channel === void 0 ? void 0 : channel.handleDataReceived(message.data);
    }
    handleExtendedDataMessage(message) {
        const channel = this.tryGetChannelForMessage(message);
        channel === null || channel === void 0 ? void 0 : channel.handleExtendedDataReceived(new sshExtendedDataEventArgs_1.SshExtendedDataEventArgs(message.dataTypeCode, message.data));
    }
    handleAdjustWindowMessage(message) {
        const channel = this.tryGetChannelForMessage(message);
        channel === null || channel === void 0 ? void 0 : channel.adjustRemoteWindow(message.bytesToAdd);
    }
    handleEofMessage(message) {
        const channel = this.findChannelById(message.recipientChannel);
        channel === null || channel === void 0 ? void 0 : channel.handleEof();
    }
    onChannelOpenCompleted(channelId, channel) {
        if (channel) {
            this.trace(trace_1.TraceLevel.Info, trace_1.SshTraceEventIds.channelOpened, `${this.session} ChannelOpenCompleted(${channel})`);
        }
        else {
            this.trace(trace_1.TraceLevel.Warning, trace_1.SshTraceEventIds.channelOpenFailed, `${this.session} ChannelOpenCompleted(${channelId} failed)`);
        }
    }
    /**
     * Gets the channel object based on the message `recipientChannel` property.
     * Logs a warning if the channel was not found.
     */
    tryGetChannelForMessage(channelMessage) {
        const channel = this.findChannelById(channelMessage.recipientChannel);
        if (!channel) {
            const messageString = channelMessage instanceof connectionMessages_1.ChannelDataMessage
                ? 'channel data message'
                : channelMessage.toString();
            this.trace(trace_1.TraceLevel.Warning, trace_1.SshTraceEventIds.channelRequestFailed, `Invalid channel ID ${channelMessage.recipientChannel} in ${messageString}.`);
        }
        return channel;
    }
    findChannelById(id) {
        var _a;
        const channel = (_a = this.channelMap.get(id)) !== null && _a !== void 0 ? _a : null;
        return channel;
    }
    /* @internal */
    removeChannel(channel) {
        this.channelMap.delete(channel.channelId);
        this.pendingChannels.delete(channel.channelId);
    }
};
ConnectionService.serviceName = 'ssh-connection';
ConnectionService = ConnectionService_1 = __decorate([
    (0, serviceActivation_1.serviceActivation)({ serviceRequest: ConnectionService_1.serviceName })
], ConnectionService);
exports.ConnectionService = ConnectionService;
//# sourceMappingURL=https://github.com/VSCodium/sourcemaps/releases/download/stable-824c4c46a288b839f13b24022655329c2aeb9f81/node_modules-@microsoft-dev-tunnels-ssh-services-connectionService.js.map