1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-27 01:29:14 -05:00

Refactor WebSockets (#173)

* Use assert.equal instead of deprecated assertEqual
* Replace let with const where possible
* Add WebSocketMessage type
* Use OpCode in WebSocketFrame
* Use const where possible in WS
* Separate sha1 tests, use const instead of let
This commit is contained in:
Dmitry Sharshakov 2019-03-05 02:23:04 +03:00 committed by Ryan Dahl
parent 41bdd096f0
commit 385f866a54
4 changed files with 87 additions and 73 deletions

View file

@ -43,10 +43,16 @@ export function isWebSocketPongEvent(a): a is WebSocketPongEvent {
return Array.isArray(a) && a[0] === "pong" && a[1] instanceof Uint8Array; return Array.isArray(a) && a[0] === "pong" && a[1] instanceof Uint8Array;
} }
export type WebSocketMessage = string | Uint8Array;
// TODO move this to common/util module // TODO move this to common/util module
export function append(a: Uint8Array, b: Uint8Array) { export function append(a: Uint8Array, b: Uint8Array) {
if (a == null || !a.length) return b; if (a == null || !a.length) {
if (b == null || !b.length) return a; return b;
}
if (b == null || !b.length) {
return a;
}
const output = new Uint8Array(a.length + b.length); const output = new Uint8Array(a.length + b.length);
output.set(a, 0); output.set(a, 0);
output.set(b, a.length); output.set(b, a.length);
@ -57,7 +63,7 @@ export class SocketClosedError extends Error {}
export type WebSocketFrame = { export type WebSocketFrame = {
isLastFrame: boolean; isLastFrame: boolean;
opcode: number; opcode: OpCode;
mask?: Uint8Array; mask?: Uint8Array;
payload: Uint8Array; payload: Uint8Array;
}; };
@ -65,8 +71,8 @@ export type WebSocketFrame = {
export type WebSocket = { export type WebSocket = {
readonly isClosed: boolean; readonly isClosed: boolean;
receive(): AsyncIterableIterator<WebSocketEvent>; receive(): AsyncIterableIterator<WebSocketEvent>;
send(data: string | Uint8Array): Promise<void>; send(data: WebSocketMessage): Promise<void>;
ping(data?: string | Uint8Array): Promise<void>; ping(data?: WebSocketMessage): Promise<void>;
close(code: number, reason?: string): Promise<void>; close(code: number, reason?: string): Promise<void>;
}; };
@ -118,11 +124,12 @@ class WebSocketImpl implements WebSocket {
case OpCode.Pong: case OpCode.Pong:
yield ["pong", frame.payload] as WebSocketPongEvent; yield ["pong", frame.payload] as WebSocketPongEvent;
break; break;
default:
} }
} }
} }
async send(data: string | Uint8Array): Promise<void> { async send(data: WebSocketMessage): Promise<void> {
if (this.isClosed) { if (this.isClosed) {
throw new SocketClosedError("socket has been closed"); throw new SocketClosedError("socket has been closed");
} }
@ -141,7 +148,7 @@ class WebSocketImpl implements WebSocket {
); );
} }
async ping(data: string | Uint8Array): Promise<void> { async ping(data: WebSocketMessage): Promise<void> {
const payload = typeof data === "string" ? this.encoder.encode(data) : data; const payload = typeof data === "string" ? this.encoder.encode(data) : data;
await writeFrame( await writeFrame(
{ {
@ -188,7 +195,10 @@ class WebSocketImpl implements WebSocket {
} }
private ensureSocketClosed(): Error { private ensureSocketClosed(): Error {
if (this.isClosed) return; if (this.isClosed) {
return;
}
try { try {
this.conn.close(); this.conn.close();
} catch (e) { } catch (e) {
@ -203,7 +213,7 @@ export async function* receiveFrame(
conn: Conn conn: Conn
): AsyncIterableIterator<WebSocketFrame> { ): AsyncIterableIterator<WebSocketFrame> {
let receiving = true; let receiving = true;
let isLastFrame = true; const isLastFrame = true;
const reader = new BufReader(conn); const reader = new BufReader(conn);
while (receiving) { while (receiving) {
const frame = await readFrame(reader); const frame = await readFrame(reader);
@ -241,12 +251,13 @@ export async function* receiveFrame(
case OpCode.Pong: case OpCode.Pong:
yield frame; yield frame;
break; break;
default:
} }
} }
} }
export async function writeFrame(frame: WebSocketFrame, writer: Writer) { export async function writeFrame(frame: WebSocketFrame, writer: Writer) {
let payloadLength = frame.payload.byteLength; const payloadLength = frame.payload.byteLength;
let header: Uint8Array; let header: Uint8Array;
const hasMask = frame.mask ? 0x80 : 0; const hasMask = frame.mask ? 0x80 : 0;
if (payloadLength < 126) { if (payloadLength < 126) {

View file

@ -61,12 +61,12 @@ export class Sha1 {
notString = false; notString = false;
message = String(data); message = String(data);
} }
let code, let code;
index = 0, let index = 0;
i, let i;
start = this._start, const start = this._start;
length = message.length || 0, const length = message.length || 0;
blocks = this._blocks; const blocks = this._blocks;
while (index < length) { while (index < length) {
if (this._hashed) { if (this._hashed) {
@ -125,8 +125,8 @@ export class Sha1 {
return; return;
} }
this._finalized = true; this._finalized = true;
let blocks = this._blocks, const blocks = this._blocks;
i = this._lastByteIndex; const i = this._lastByteIndex;
blocks[16] = this._block; blocks[16] = this._block;
blocks[i >> 2] |= EXTRA[i & 3]; blocks[i >> 2] |= EXTRA[i & 3];
this._block = blocks[16]; this._block = blocks[16];
@ -143,15 +143,13 @@ export class Sha1 {
} }
hash() { hash() {
let a = this._h0, let a = this._h0;
b = this._h1, let b = this._h1;
c = this._h2, let c = this._h2;
d = this._h3, let d = this._h3;
e = this._h4; let e = this._h4;
let f, let f, j, t;
j, const blocks = this._blocks;
t,
blocks = this._blocks;
for (j = 16; j < 80; ++j) { for (j = 16; j < 80; ++j) {
t = blocks[j - 3] ^ blocks[j - 8] ^ blocks[j - 14] ^ blocks[j - 16]; t = blocks[j - 3] ^ blocks[j - 8] ^ blocks[j - 14] ^ blocks[j - 16];
@ -276,11 +274,11 @@ export class Sha1 {
hex() { hex() {
this.finalize(); this.finalize();
let h0 = this._h0, const h0 = this._h0;
h1 = this._h1, const h1 = this._h1;
h2 = this._h2, const h2 = this._h2;
h3 = this._h3, const h3 = this._h3;
h4 = this._h4; const h4 = this._h4;
return ( return (
HEX_CHARS[(h0 >> 28) & 0x0f] + HEX_CHARS[(h0 >> 28) & 0x0f] +
@ -333,11 +331,11 @@ export class Sha1 {
digest() { digest() {
this.finalize(); this.finalize();
let h0 = this._h0, const h0 = this._h0;
h1 = this._h1, const h1 = this._h1;
h2 = this._h2, const h2 = this._h2;
h3 = this._h3, const h3 = this._h3;
h4 = this._h4; const h4 = this._h4;
return [ return [
(h0 >> 24) & 0xff, (h0 >> 24) & 0xff,

View file

@ -3,16 +3,21 @@ import { assert, test } from "../testing/mod.ts";
import { Sha1 } from "./sha1.ts"; import { Sha1 } from "./sha1.ts";
test(function testSha1() { test(function testSha1() {
let sha1 = new Sha1(); const sha1 = new Sha1();
sha1.update("abcde"); sha1.update("abcde");
assert.equal(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334"); assert.equal(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334");
});
test(function testSha1WithArray() {
const data = Uint8Array.of(0x61, 0x62, 0x63, 0x64, 0x65); const data = Uint8Array.of(0x61, 0x62, 0x63, 0x64, 0x65);
sha1 = new Sha1(); const sha1 = new Sha1();
sha1.update(data); sha1.update(data);
assert.equal(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334"); assert.equal(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334");
});
sha1 = new Sha1(); test(function testSha1WithBuffer() {
const data = Uint8Array.of(0x61, 0x62, 0x63, 0x64, 0x65);
const sha1 = new Sha1();
sha1.update(data.buffer); sha1.update(data.buffer);
assert.equal(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334"); assert.equal(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334");
}); });

View file

@ -3,7 +3,7 @@ import "./sha1_test.ts";
const { Buffer } = Deno; const { Buffer } = Deno;
import { BufReader } from "../io/bufio.ts"; import { BufReader } from "../io/bufio.ts";
import { assert, assertEqual, test } from "../testing/mod.ts"; import { test, assert } from "../testing/mod.ts";
import { import {
acceptable, acceptable,
createSecAccept, createSecAccept,
@ -18,10 +18,10 @@ test(async function testReadUnmaskedTextFrame() {
new Buffer(new Uint8Array([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f])) new Buffer(new Uint8Array([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]))
); );
const frame = await readFrame(buf); const frame = await readFrame(buf);
assertEqual(frame.opcode, OpCode.TextFrame); assert.equal(frame.opcode, OpCode.TextFrame);
assertEqual(frame.mask, undefined); assert.equal(frame.mask, undefined);
assertEqual(new Buffer(frame.payload).toString(), "Hello"); assert.equal(new Buffer(frame.payload).toString(), "Hello");
assertEqual(frame.isLastFrame, true); assert.equal(frame.isLastFrame, true);
}); });
test(async function testReadMakedTextFrame() { test(async function testReadMakedTextFrame() {
@ -45,10 +45,10 @@ test(async function testReadMakedTextFrame() {
); );
const frame = await readFrame(buf); const frame = await readFrame(buf);
console.dir(frame); console.dir(frame);
assertEqual(frame.opcode, OpCode.TextFrame); assert.equal(frame.opcode, OpCode.TextFrame);
unmask(frame.payload, frame.mask); unmask(frame.payload, frame.mask);
assertEqual(new Buffer(frame.payload).toString(), "Hello"); assert.equal(new Buffer(frame.payload).toString(), "Hello");
assertEqual(frame.isLastFrame, true); assert.equal(frame.isLastFrame, true);
}); });
test(async function testReadUnmaskedSplittedTextFrames() { test(async function testReadUnmaskedSplittedTextFrames() {
@ -59,15 +59,15 @@ test(async function testReadUnmaskedSplittedTextFrames() {
new Buffer(new Uint8Array([0x80, 0x02, 0x6c, 0x6f])) new Buffer(new Uint8Array([0x80, 0x02, 0x6c, 0x6f]))
); );
const [f1, f2] = await Promise.all([readFrame(buf1), readFrame(buf2)]); const [f1, f2] = await Promise.all([readFrame(buf1), readFrame(buf2)]);
assertEqual(f1.isLastFrame, false); assert.equal(f1.isLastFrame, false);
assertEqual(f1.mask, undefined); assert.equal(f1.mask, undefined);
assertEqual(f1.opcode, OpCode.TextFrame); assert.equal(f1.opcode, OpCode.TextFrame);
assertEqual(new Buffer(f1.payload).toString(), "Hel"); assert.equal(new Buffer(f1.payload).toString(), "Hel");
assertEqual(f2.isLastFrame, true); assert.equal(f2.isLastFrame, true);
assertEqual(f2.mask, undefined); assert.equal(f2.mask, undefined);
assertEqual(f2.opcode, OpCode.Continue); assert.equal(f2.opcode, OpCode.Continue);
assertEqual(new Buffer(f2.payload).toString(), "lo"); assert.equal(new Buffer(f2.payload).toString(), "lo");
}); });
test(async function testReadUnmaksedPingPongFrame() { test(async function testReadUnmaksedPingPongFrame() {
@ -76,8 +76,8 @@ test(async function testReadUnmaksedPingPongFrame() {
new Buffer(new Uint8Array([0x89, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f])) new Buffer(new Uint8Array([0x89, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]))
); );
const ping = await readFrame(buf); const ping = await readFrame(buf);
assertEqual(ping.opcode, OpCode.Ping); assert.equal(ping.opcode, OpCode.Ping);
assertEqual(new Buffer(ping.payload).toString(), "Hello"); assert.equal(new Buffer(ping.payload).toString(), "Hello");
const buf2 = new BufReader( const buf2 = new BufReader(
new Buffer( new Buffer(
@ -97,42 +97,42 @@ test(async function testReadUnmaksedPingPongFrame() {
) )
); );
const pong = await readFrame(buf2); const pong = await readFrame(buf2);
assertEqual(pong.opcode, OpCode.Pong); assert.equal(pong.opcode, OpCode.Pong);
assert(pong.mask !== undefined); assert(pong.mask !== undefined);
unmask(pong.payload, pong.mask); unmask(pong.payload, pong.mask);
assertEqual(new Buffer(pong.payload).toString(), "Hello"); assert.equal(new Buffer(pong.payload).toString(), "Hello");
}); });
test(async function testReadUnmaksedBigBinaryFrame() { test(async function testReadUnmaksedBigBinaryFrame() {
let a = [0x82, 0x7e, 0x01, 0x00]; const a = [0x82, 0x7e, 0x01, 0x00];
for (let i = 0; i < 256; i++) { for (let i = 0; i < 256; i++) {
a.push(i); a.push(i);
} }
const buf = new BufReader(new Buffer(new Uint8Array(a))); const buf = new BufReader(new Buffer(new Uint8Array(a)));
const bin = await readFrame(buf); const bin = await readFrame(buf);
assertEqual(bin.opcode, OpCode.BinaryFrame); assert.equal(bin.opcode, OpCode.BinaryFrame);
assertEqual(bin.isLastFrame, true); assert.equal(bin.isLastFrame, true);
assertEqual(bin.mask, undefined); assert.equal(bin.mask, undefined);
assertEqual(bin.payload.length, 256); assert.equal(bin.payload.length, 256);
}); });
test(async function testReadUnmaskedBigBigBinaryFrame() { test(async function testReadUnmaskedBigBigBinaryFrame() {
let a = [0x82, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00]; const a = [0x82, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00];
for (let i = 0; i < 0xffff; i++) { for (let i = 0; i < 0xffff; i++) {
a.push(i); a.push(i);
} }
const buf = new BufReader(new Buffer(new Uint8Array(a))); const buf = new BufReader(new Buffer(new Uint8Array(a)));
const bin = await readFrame(buf); const bin = await readFrame(buf);
assertEqual(bin.opcode, OpCode.BinaryFrame); assert.equal(bin.opcode, OpCode.BinaryFrame);
assertEqual(bin.isLastFrame, true); assert.equal(bin.isLastFrame, true);
assertEqual(bin.mask, undefined); assert.equal(bin.mask, undefined);
assertEqual(bin.payload.length, 0xffff + 1); assert.equal(bin.payload.length, 0xffff + 1);
}); });
test(async function testCreateSecAccept() { test(async function testCreateSecAccept() {
const nonce = "dGhlIHNhbXBsZSBub25jZQ=="; const nonce = "dGhlIHNhbXBsZSBub25jZQ==";
const d = createSecAccept(nonce); const d = createSecAccept(nonce);
assertEqual(d, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="); assert.equal(d, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=");
}); });
test(function testAcceptable() { test(function testAcceptable() {
@ -142,7 +142,7 @@ test(function testAcceptable() {
"sec-websocket-key": "aaa" "sec-websocket-key": "aaa"
}) })
}); });
assertEqual(ret, true); assert.equal(ret, true);
}); });
const invalidHeaders = [ const invalidHeaders = [
@ -157,6 +157,6 @@ test(function testAcceptableInvalid() {
const ret = acceptable({ const ret = acceptable({
headers: new Headers(pat) headers: new Headers(pat)
}); });
assertEqual(ret, false); assert.equal(ret, false);
} }
}); });