未验证 提交 f38b24d6 编写于 作者: A Alexandru Dima 提交者: GitHub

Merge pull request #133768 from microsoft/alex/respect-rsv1-bit

Respect RSV1 bit (which describes if a message is compressed or not)
......@@ -181,6 +181,8 @@ export class WebSocketNodeSocket extends Disposable implements ISocket {
state: ReadState.PeekHeader,
readLen: Constants.MinHeaderByteSize,
fin: 0,
compressed: false,
firstFrameOfMessage: true,
mask: 0
};
......@@ -396,6 +398,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket {
const peekHeader = this._incomingData.peek(this._state.readLen);
const firstByte = peekHeader.readUInt8(0);
const finBit = (firstByte & 0b10000000) >>> 7;
const rsv1Bit = (firstByte & 0b01000000) >>> 6;
const secondByte = peekHeader.readUInt8(1);
const hasMask = (secondByte & 0b10000000) >>> 7;
const len = (secondByte & 0b01111111);
......@@ -403,6 +406,11 @@ export class WebSocketNodeSocket extends Disposable implements ISocket {
this._state.state = ReadState.ReadHeader;
this._state.readLen = Constants.MinHeaderByteSize + (hasMask ? 4 : 0) + (len === 126 ? 2 : 0) + (len === 127 ? 8 : 0);
this._state.fin = finBit;
if (this._state.firstFrameOfMessage) {
// if the frame is compressed, the RSV1 bit is set only for the first frame of the message
this._state.compressed = Boolean(rsv1Bit);
}
this._state.firstFrameOfMessage = Boolean(finBit);
this._state.mask = 0;
} else if (this._state.state === ReadState.ReadHeader) {
......@@ -455,7 +463,12 @@ export class WebSocketNodeSocket extends Disposable implements ISocket {
this._state.readLen = Constants.MinHeaderByteSize;
this._state.mask = 0;
if (this._zlibInflate) {
if (this._zlibInflate && this._state.compressed) {
// See https://datatracker.ietf.org/doc/html/rfc7692#section-9.2
// Even if permessageDeflate is negotiated, it is possible
// that the other side might decide to send uncompressed messages
// So only decompress messages that have the RSV 1 bit set
//
// See https://tools.ietf.org/html/rfc7692#section-7.2.2
if (this._recordInflateBytes) {
this._recordedInflateBytes.push(Buffer.from(<Buffer>body.buffer));
......
......@@ -7,11 +7,12 @@ import * as assert from 'assert';
import { EventEmitter } from 'events';
import { createServer, Socket } from 'net';
import { tmpdir } from 'os';
import { timeout } from 'vs/base/common/async';
import { Barrier, timeout } from 'vs/base/common/async';
import { VSBuffer } from 'vs/base/common/buffer';
import { Disposable } from 'vs/base/common/lifecycle';
import { ILoadEstimator, PersistentProtocol, Protocol, ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net';
import { createRandomIPCHandle, createStaticIPCHandle, NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { Emitter } from 'vs/base/common/event';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { ILoadEstimator, PersistentProtocol, Protocol, ProtocolConstants, SocketCloseEvent } from 'vs/base/parts/ipc/common/ipc.net';
import { createRandomIPCHandle, createStaticIPCHandle, NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import product from 'vs/platform/product/common/product';
......@@ -396,3 +397,130 @@ suite('IPC, create handle', () => {
}
});
suite('WebSocketNodeSocket', () => {
function toUint8Array(data: number[]): Uint8Array {
const result = new Uint8Array(data.length);
for (let i = 0; i < data.length; i++) {
result[i] = data[i];
}
return result;
}
function fromUint8Array(data: Uint8Array): number[] {
const result = [];
for (let i = 0; i < data.length; i++) {
result[i] = data[i];
}
return result;
}
function fromCharCodeArray(data: number[]): string {
let result = '';
for (let i = 0; i < data.length; i++) {
result += String.fromCharCode(data[i]);
}
return result;
}
class FakeNodeSocket extends Disposable {
private readonly _onData = new Emitter<VSBuffer>();
public readonly onData = this._onData.event;
private readonly _onClose = new Emitter<SocketCloseEvent>();
public readonly onClose = this._onClose.event;
constructor() {
super();
}
public fireData(data: number[]): void {
this._onData.fire(VSBuffer.wrap(toUint8Array(data)));
}
}
async function testReading(frames: number[][], permessageDeflate: boolean): Promise<string> {
const disposables = new DisposableStore();
const socket = new FakeNodeSocket();
const webSocket = disposables.add(new WebSocketNodeSocket(<any>socket, permessageDeflate, null, false));
const barrier = new Barrier();
let remainingFrameCount = frames.length;
let receivedData: string = '';
disposables.add(webSocket.onData((buff) => {
receivedData += fromCharCodeArray(fromUint8Array(buff.buffer));
remainingFrameCount--;
if (remainingFrameCount === 0) {
barrier.open();
}
}));
for (let i = 0; i < frames.length; i++) {
socket.fireData(frames[i]);
}
await barrier.wait();
disposables.dispose();
return receivedData;
}
test('A single-frame unmasked text message', async () => {
const frames = [
[0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f] // contains "Hello"
];
const actual = await testReading(frames, false);
assert.deepStrictEqual(actual, 'Hello');
});
test('A single-frame masked text message', async () => {
const frames = [
[0x81, 0x85, 0x37, 0xfa, 0x21, 0x3d, 0x7f, 0x9f, 0x4d, 0x51, 0x58] // contains "Hello"
];
const actual = await testReading(frames, false);
assert.deepStrictEqual(actual, 'Hello');
});
test('A fragmented unmasked text message', async () => {
// contains "Hello"
const frames = [
[0x01, 0x03, 0x48, 0x65, 0x6c], // contains "Hel"
[0x80, 0x02, 0x6c, 0x6f], // contains "lo"
];
const actual = await testReading(frames, false);
assert.deepStrictEqual(actual, 'Hello');
});
suite('compression', () => {
test('A single-frame compressed text message', async () => {
// contains "Hello"
const frames = [
[0xc1, 0x07, 0xf2, 0x48, 0xcd, 0xc9, 0xc9, 0x07, 0x00], // contains "Hello"
];
const actual = await testReading(frames, true);
assert.deepStrictEqual(actual, 'Hello');
});
test('A fragmented compressed text message', async () => {
// contains "Hello"
const frames = [ // contains "Hello"
[0x41, 0x03, 0xf2, 0x48, 0xcd],
[0x80, 0x04, 0xc9, 0xc9, 0x07, 0x00]
];
const actual = await testReading(frames, true);
assert.deepStrictEqual(actual, 'Hello');
});
test('A single-frame non-compressed text message', async () => {
const frames = [
[0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f] // contains "Hello"
];
const actual = await testReading(frames, true);
assert.deepStrictEqual(actual, 'Hello');
});
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册