nerdbank-streams
Version:
Multiplexing of streams
307 lines • 13 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MultiplexingStreamV3Formatter = exports.MultiplexingStreamV2Formatter = exports.MultiplexingStreamV1Formatter = exports.MultiplexingStreamFormatter = void 0;
const MultiplexingStream_1 = require("./MultiplexingStream");
const Utilities_1 = require("./Utilities");
const msgpack = require("msgpack-lite");
const Deferred_1 = require("./Deferred");
const FrameHeader_1 = require("./FrameHeader");
const QualifiedChannelId_1 = require("./QualifiedChannelId");
class MultiplexingStreamFormatter {
static getIsOddRandomData() {
const size = 16;
const buffer = Buffer.alloc(size);
for (let i = 0; i < size; i++) {
buffer[i] = Math.floor(Math.random() * 256);
}
return buffer;
}
static isOdd(localRandom, remoteRandom) {
let isOdd;
for (let i = 0; i < localRandom.length; i++) {
const sent = localRandom[i];
const recv = remoteRandom[i];
if (sent > recv) {
isOdd = true;
break;
}
else if (sent < recv) {
isOdd = false;
break;
}
}
if (isOdd === undefined) {
throw new Error('Unable to determine even/odd party.');
}
return isOdd;
}
createFrameHeader(code, id) {
if (!id) {
return new FrameHeader_1.FrameHeader(code);
}
const channelIsOdd = id % 2 === 1;
// Remember that this is from the remote sender's point of view.
const source = channelIsOdd === this.isOdd ? QualifiedChannelId_1.ChannelSource.Remote : QualifiedChannelId_1.ChannelSource.Local;
return new FrameHeader_1.FrameHeader(code, { id, source });
}
}
exports.MultiplexingStreamFormatter = MultiplexingStreamFormatter;
// tslint:disable-next-line: max-classes-per-file
class MultiplexingStreamV1Formatter extends MultiplexingStreamFormatter {
constructor(stream) {
super();
this.stream = stream;
}
end() {
this.stream.end();
}
async writeHandshakeAsync() {
const randomSendBuffer = MultiplexingStreamFormatter.getIsOddRandomData();
const sendBuffer = Buffer.concat([MultiplexingStreamV1Formatter.protocolMagicNumber, randomSendBuffer]);
await (0, Utilities_1.writeAsync)(this.stream, sendBuffer);
return randomSendBuffer;
}
async readHandshakeAsync(writeHandshakeResult, cancellationToken) {
const localRandomBuffer = writeHandshakeResult;
const recvBuffer = await (0, Utilities_1.getBufferFrom)(this.stream, MultiplexingStreamV1Formatter.protocolMagicNumber.length + 16, false, cancellationToken);
for (let i = 0; i < MultiplexingStreamV1Formatter.protocolMagicNumber.length; i++) {
const expected = MultiplexingStreamV1Formatter.protocolMagicNumber[i];
const actual = recvBuffer.readUInt8(i);
if (expected !== actual) {
throw new Error(`Protocol magic number mismatch. Expected ${expected} but was ${actual}.`);
}
}
const isOdd = MultiplexingStreamFormatter.isOdd(localRandomBuffer, recvBuffer.slice(MultiplexingStreamV1Formatter.protocolMagicNumber.length));
return { isOdd, protocolVersion: { major: 1, minor: 0 } };
}
async writeFrameAsync(header, payload) {
var _a;
const headerBuffer = Buffer.alloc(7);
headerBuffer.writeInt8(header.code, 0);
headerBuffer.writeUInt32BE(((_a = header.channel) === null || _a === void 0 ? void 0 : _a.id) || 0, 1);
headerBuffer.writeUInt16BE((payload === null || payload === void 0 ? void 0 : payload.length) || 0, 5);
await (0, Utilities_1.writeAsync)(this.stream, headerBuffer);
if (payload && payload.length > 0) {
await (0, Utilities_1.writeAsync)(this.stream, payload);
}
}
async readFrameAsync(cancellationToken) {
if (this.isOdd === undefined) {
throw new Error('isOdd must be set first.');
}
const headerBuffer = await (0, Utilities_1.getBufferFrom)(this.stream, 7, true, cancellationToken);
if (headerBuffer === null) {
return null;
}
const header = this.createFrameHeader(headerBuffer.readInt8(0), headerBuffer.readUInt32BE(1));
const payloadLength = headerBuffer.readUInt16BE(5);
const payload = await (0, Utilities_1.getBufferFrom)(this.stream, payloadLength);
return { header, payload };
}
serializeOfferParameters(offer) {
const payload = Buffer.from(offer.name, MultiplexingStream_1.MultiplexingStream.ControlFrameEncoding);
if (payload.length > MultiplexingStream_1.MultiplexingStream.framePayloadMaxLength) {
throw new Error('Name is too long.');
}
return payload;
}
deserializeOfferParameters(payload) {
return {
name: payload.toString(MultiplexingStream_1.MultiplexingStream.ControlFrameEncoding),
};
}
serializeAcceptanceParameters(_) {
return Buffer.from([]);
}
deserializeAcceptanceParameters(_) {
return {};
}
serializeContentProcessed(bytesProcessed) {
throw new Error('Not supported in the V1 protocol.');
}
deserializeContentProcessed(payload) {
throw new Error('Not supported in the V1 protocol.');
}
}
exports.MultiplexingStreamV1Formatter = MultiplexingStreamV1Formatter;
/**
* The magic number to send at the start of communication when using v1 of the protocol.
*/
MultiplexingStreamV1Formatter.protocolMagicNumber = Buffer.from([0x2f, 0xdf, 0x1d, 0x50]);
// tslint:disable-next-line: max-classes-per-file
class MultiplexingStreamV2Formatter extends MultiplexingStreamFormatter {
constructor(stream) {
super();
this.reader = msgpack.createDecodeStream();
stream.pipe(this.reader);
this.writer = stream;
}
end() {
this.writer.end();
}
async writeHandshakeAsync() {
const randomData = MultiplexingStreamFormatter.getIsOddRandomData();
const msgpackObject = [[MultiplexingStreamV2Formatter.protocolVersion.major, MultiplexingStreamV2Formatter.protocolVersion.minor], randomData];
await (0, Utilities_1.writeAsync)(this.writer, msgpack.encode(msgpackObject));
return randomData;
}
async readHandshakeAsync(writeHandshakeResult, cancellationToken) {
if (!writeHandshakeResult) {
throw new Error('Provide the result of writeHandshakeAsync as a first argument.');
}
const handshake = await this.readMessagePackAsync(cancellationToken);
if (handshake === null) {
throw new Error('No data received during handshake.');
}
return {
isOdd: MultiplexingStreamFormatter.isOdd(writeHandshakeResult, handshake[1]),
protocolVersion: { major: handshake[0][0], minor: handshake[0][1] },
};
}
async writeFrameAsync(header, payload) {
var _a;
const msgpackObject = [header.code];
if ((_a = header.channel) === null || _a === void 0 ? void 0 : _a.id) {
msgpackObject.push(header.channel.id);
if (payload && payload.length > 0) {
msgpackObject.push(payload);
}
}
else if (payload && payload.length > 0) {
throw new Error('A frame may not contain payload without a channel ID.');
}
await (0, Utilities_1.writeAsync)(this.writer, msgpack.encode(msgpackObject));
}
async readFrameAsync(cancellationToken) {
if (this.isOdd === undefined) {
throw new Error('isOdd must be set first.');
}
const msgpackObject = (await this.readMessagePackAsync(cancellationToken));
if (msgpackObject === null) {
return null;
}
const header = this.createFrameHeader(msgpackObject[0], msgpackObject[1]);
return {
header,
payload: msgpackObject[2] || Buffer.from([]),
};
}
serializeOfferParameters(offer) {
const payload = [offer.name];
if (offer.remoteWindowSize) {
payload.push(offer.remoteWindowSize);
}
return msgpack.encode(payload);
}
deserializeOfferParameters(payload) {
const msgpackObject = msgpack.decode(payload);
return {
name: msgpackObject[0],
remoteWindowSize: msgpackObject[1],
};
}
serializeAcceptanceParameters(acceptance) {
const payload = [];
if (acceptance.remoteWindowSize) {
payload.push(acceptance.remoteWindowSize);
}
return msgpack.encode(payload);
}
deserializeAcceptanceParameters(payload) {
const msgpackObject = msgpack.decode(payload);
return {
remoteWindowSize: msgpackObject[0],
};
}
serializeContentProcessed(bytesProcessed) {
return msgpack.encode([bytesProcessed]);
}
deserializeContentProcessed(payload) {
return msgpack.decode(payload)[0];
}
serializeException(error) {
// If the error doesn't exist then return an empty buffer.
if (!error) {
return Buffer.alloc(0);
}
const errorMsg = `${error.name}: ${error.message}`;
const payload = [errorMsg];
return msgpack.encode(payload);
}
deserializeException(payload) {
// If the payload is empty then return null.
if (payload.length === 0) {
return null;
}
// Make sure that the message pack object contains a message
const msgpackObject = msgpack.decode(payload);
if (!msgpackObject || msgpackObject.length === 0) {
return null;
}
// Get error message and return the error to the remote side
let errorMsg = msgpack.decode(payload)[0];
errorMsg = `Received error from remote side: ${errorMsg}`;
return new Error(errorMsg);
}
async readMessagePackAsync(cancellationToken) {
const streamEnded = new Deferred_1.Deferred();
while (true) {
const readObject = this.reader.read();
if (readObject === null) {
const bytesAvailable = new Deferred_1.Deferred();
const bytesAvailableCallback = bytesAvailable.resolve.bind(bytesAvailable);
const streamEndedCallback = streamEnded.resolve.bind(streamEnded);
this.reader.once('readable', bytesAvailableCallback);
this.reader.once('end', streamEndedCallback);
const endPromise = Promise.race([bytesAvailable.promise, streamEnded.promise]);
await (cancellationToken ? cancellationToken.racePromise(endPromise) : endPromise);
this.reader.removeListener('readable', bytesAvailableCallback);
this.reader.removeListener('end', streamEndedCallback);
if (bytesAvailable.isCompleted) {
continue;
}
return null;
}
return readObject;
}
}
}
exports.MultiplexingStreamV2Formatter = MultiplexingStreamV2Formatter;
MultiplexingStreamV2Formatter.protocolVersion = { major: 2, minor: 0 };
// tslint:disable-next-line: max-classes-per-file
class MultiplexingStreamV3Formatter extends MultiplexingStreamV2Formatter {
writeHandshakeAsync() {
return Promise.resolve(null);
}
readHandshakeAsync() {
return Promise.resolve({ protocolVersion: MultiplexingStreamV3Formatter.protocolV3Version });
}
async writeFrameAsync(header, payload) {
const msgpackObject = [header.code];
if (header.channel) {
msgpackObject.push(header.channel.id);
msgpackObject.push(header.channel.source);
if (payload && payload.length > 0) {
msgpackObject.push(payload);
}
}
else if (payload && payload.length > 0) {
throw new Error('A frame may not contain payload without a channel ID.');
}
await (0, Utilities_1.writeAsync)(this.writer, msgpack.encode(msgpackObject));
}
async readFrameAsync(cancellationToken) {
const msgpackObject = (await this.readMessagePackAsync(cancellationToken));
if (msgpackObject === null) {
return null;
}
const header = new FrameHeader_1.FrameHeader(msgpackObject[0], msgpackObject.length > 1 ? { id: msgpackObject[1], source: msgpackObject[2] } : undefined);
return {
header,
payload: msgpackObject[3] || Buffer.from([]),
};
}
}
exports.MultiplexingStreamV3Formatter = MultiplexingStreamV3Formatter;
MultiplexingStreamV3Formatter.protocolV3Version = { major: 2, minor: 0 };
//# sourceMappingURL=MultiplexingStreamFormatters.js.map