@microsoft/dev-tunnels-ssh
Version:
SSH library for Dev Tunnels
130 lines • 5.45 kB
JavaScript
"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=sshRpcMessageStream.js.map