"use strict";
//
//  Copyright (c) Microsoft Corporation. All rights reserved.
//
Object.defineProperty(exports, "__esModule", { value: true });
exports.SshRpcMessageStream = void 0;
const rpc = require("vscode-jsonrpc");
const buffer_1 = require("buffer");
const sshData_1 = require("./io/sshData");
const contentLengthHeaderPrefix = 'Content-Length: ';
const headersSeparator = '\r\n\r\n';
class SshRpcMessageReader {
    constructor(channel) {
        this.channel = channel;
        this.errorEmitter = new rpc.Emitter();
        this.closeEmitter = new rpc.Emitter();
        this.partialMessageEmitter = new rpc.Emitter();
        this.callback = null;
        this.messageBuffer = new sshData_1.SshDataWriter(buffer_1.Buffer.alloc(1024));
        this.headersLength = null;
        this.messageLength = null;
        this.onError = this.errorEmitter.event;
        this.onClose = this.closeEmitter.event;
        this.onPartialMessage = this.partialMessageEmitter.event;
        this.eventRegistration = this.channel.onDataReceived(this.onDataReceived.bind(this));
        this.channel.onClosed((e) => {
            if (e.error) {
                this.errorEmitter.fire(e.error);
            }
            // Note: we always want to fire a close event to avoid the rpc connection
            // to be used. After the event any usage of the rpc message connection will
            // throw an error with this code: ConnectionErrors.Closed
            this.closeEmitter.fire();
        });
    }
    listen(callback) {
        this.callback = callback;
        return rpc.Disposable.create(() => {
            this.callback = null;
        });
    }
    dispose() {
        if (this.eventRegistration) {
            this.eventRegistration.dispose();
        }
    }
    onDataReceived(data) {
        this.messageBuffer.write(data);
        this.channel.adjustWindow(data.length);
        // In case of recursion, the `data` might have already been a slice of the message buffer,
        // but it could have been invalidated by expansion during write() above.
        data = this.messageBuffer.toBuffer();
        if (this.messageLength === null) {
            const headersEnd = data.indexOf(headersSeparator);
            if (headersEnd < 0) {
                return; // Wait for more data.
            }
            const headers = data.slice(0, headersEnd).toString();
            if (!headers.startsWith(contentLengthHeaderPrefix)) {
                throw new Error(`Message does not start with JSON-RPC headers.\n${headers}`);
            }
            this.headersLength = headersEnd + headersSeparator.length;
            this.messageLength = parseInt(headers.substr(contentLengthHeaderPrefix.length, headersEnd - contentLengthHeaderPrefix.length), 10);
        }
        const position = this.messageBuffer.position;
        const totalLength = this.headersLength + this.messageLength;
        if (position >= totalLength) {
            if (this.callback) {
                const messageJson = data.slice(this.headersLength, totalLength).toString();
                let message;
                try {
                    message = JSON.parse(messageJson);
                }
                catch (e) {
                    if (!(e instanceof Error))
                        throw e;
                    throw new Error(`Failed to parse JSON-RPC message: ${e.message}\n${messageJson}`);
                }
                this.callback(message);
            }
            this.messageLength = null;
            this.messageBuffer.position = 0;
            if (position > totalLength) {
                // Recursively receive the remaining data, which will cause it
                // to be copied to the beginning of the buffer;
                this.onDataReceived(data.slice(totalLength));
            }
        }
    }
}
class SshRpcMessageWriter {
    constructor(channel) {
        this.channel = channel;
        this.errorEmitter = new rpc.Emitter();
        this.closeEmitter = new rpc.Emitter();
        this.onError = this.errorEmitter.event;
        this.onClose = this.closeEmitter.event;
        this.channel.onClosed((e) => {
            if (e.error) {
                this.errorEmitter.fire([
                    e.error,
                    (e.errorMessage && { jsonrpc: e.errorMessage }) || undefined,
                    e.exitStatus,
                ]);
            }
            this.closeEmitter.fire();
        });
    }
    write(message) {
        const messageJson = JSON.stringify(message);
        const messageData = buffer_1.Buffer.from(messageJson);
        const headerData = buffer_1.Buffer.from(contentLengthHeaderPrefix + messageData.length + headersSeparator);
        const data = buffer_1.Buffer.alloc(headerData.length + messageData.length);
        headerData.copy(data, 0);
        messageData.copy(data, headerData.length);
        return this.channel.send(data).catch((e) => {
            this.errorEmitter.fire([e, undefined, undefined]);
        });
    }
    end() { }
    dispose() { }
}
class SshRpcMessageStream {
    constructor(channel) {
        this.reader = new SshRpcMessageReader(channel);
        this.writer = new SshRpcMessageWriter(channel);
    }
}
exports.SshRpcMessageStream = SshRpcMessageStream;
//# sourceMappingURL=https://main.vscode-cdn.net/sourcemaps/8761a5560cfd65fdd19ce7e2bd18dab5c0a4d84e/node_modules/@microsoft/dev-tunnels-ssh/sshRpcMessageStream.js.map