summaryrefslogtreecommitdiff
path: root/std/ws/test.ts
blob: 3f5475c80c0a0c336e4daf7a97be195a7589f4e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { BufReader, BufWriter } from "../io/bufio.ts";
import { assert, assertEquals, assertThrowsAsync } from "../testing/asserts.ts";
const { test } = Deno;
import { TextProtoReader } from "../textproto/mod.ts";
import * as bytes from "../bytes/mod.ts";
import {
  acceptable,
  connectWebSocket,
  createSecAccept,
  createSecKey,
  handshake,
  OpCode,
  readFrame,
  unmask,
  writeFrame,
  createWebSocket,
  SocketClosedError
} from "./mod.ts";
import { encode, decode } from "../strings/mod.ts";
import Writer = Deno.Writer;
import Reader = Deno.Reader;
import Conn = Deno.Conn;
import Buffer = Deno.Buffer;

test(async function wsReadUnmaskedTextFrame(): Promise<void> {
  // unmasked single text frame with payload "Hello"
  const buf = new BufReader(
    new Buffer(new Uint8Array([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]))
  );
  const frame = await readFrame(buf);
  assertEquals(frame.opcode, OpCode.TextFrame);
  assertEquals(frame.mask, undefined);
  assertEquals(new Buffer(frame.payload).toString(), "Hello");
  assertEquals(frame.isLastFrame, true);
});

test(async function wsReadMaskedTextFrame(): Promise<void> {
  // a masked single text frame with payload "Hello"
  const buf = new BufReader(
    new Buffer(
      new Uint8Array([
        0x81,
        0x85,
        0x37,
        0xfa,
        0x21,
        0x3d,
        0x7f,
        0x9f,
        0x4d,
        0x51,
        0x58
      ])
    )
  );
  const frame = await readFrame(buf);
  assertEquals(frame.opcode, OpCode.TextFrame);
  unmask(frame.payload, frame.mask);
  assertEquals(new Buffer(frame.payload).toString(), "Hello");
  assertEquals(frame.isLastFrame, true);
});

test(async function wsReadUnmaskedSplitTextFrames(): Promise<void> {
  const buf1 = new BufReader(
    new Buffer(new Uint8Array([0x01, 0x03, 0x48, 0x65, 0x6c]))
  );
  const buf2 = new BufReader(
    new Buffer(new Uint8Array([0x80, 0x02, 0x6c, 0x6f]))
  );
  const [f1, f2] = await Promise.all([readFrame(buf1), readFrame(buf2)]);
  assertEquals(f1.isLastFrame, false);
  assertEquals(f1.mask, undefined);
  assertEquals(f1.opcode, OpCode.TextFrame);
  assertEquals(new Buffer(f1.payload).toString(), "Hel");

  assertEquals(f2.isLastFrame, true);
  assertEquals(f2.mask, undefined);
  assertEquals(f2.opcode, OpCode.Continue);
  assertEquals(new Buffer(f2.payload).toString(), "lo");
});

test(async function wsReadUnmaskedPingPongFrame(): Promise<void> {
  // unmasked ping with payload "Hello"
  const buf = new BufReader(
    new Buffer(new Uint8Array([0x89, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]))
  );
  const ping = await readFrame(buf);
  assertEquals(ping.opcode, OpCode.Ping);
  assertEquals(new Buffer(ping.payload).toString(), "Hello");

  const buf2 = new BufReader(
    new Buffer(
      new Uint8Array([
        0x8a,
        0x85,
        0x37,
        0xfa,
        0x21,
        0x3d,
        0x7f,
        0x9f,
        0x4d,
        0x51,
        0x58
      ])
    )
  );
  const pong = await readFrame(buf2);
  assertEquals(pong.opcode, OpCode.Pong);
  assert(pong.mask !== undefined);
  unmask(pong.payload, pong.mask);
  assertEquals(new Buffer(pong.payload).toString(), "Hello");
});

test(async function wsReadUnmaskedBigBinaryFrame(): Promise<void> {
  const payloadLength = 0x100;
  const a = [0x82, 0x7e, 0x01, 0x00];
  for (let i = 0; i < payloadLength; i++) {
    a.push(i);
  }
  const buf = new BufReader(new Buffer(new Uint8Array(a)));
  const bin = await readFrame(buf);
  assertEquals(bin.opcode, OpCode.BinaryFrame);
  assertEquals(bin.isLastFrame, true);
  assertEquals(bin.mask, undefined);
  assertEquals(bin.payload.length, payloadLength);
});

test(async function wsReadUnmaskedBigBigBinaryFrame(): Promise<void> {
  const payloadLength = 0x10000;
  const a = [0x82, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00];
  for (let i = 0; i < payloadLength; i++) {
    a.push(i);
  }
  const buf = new BufReader(new Buffer(new Uint8Array(a)));
  const bin = await readFrame(buf);
  assertEquals(bin.opcode, OpCode.BinaryFrame);
  assertEquals(bin.isLastFrame, true);
  assertEquals(bin.mask, undefined);
  assertEquals(bin.payload.length, payloadLength);
});

test(async function wsCreateSecAccept(): Promise<void> {
  const nonce = "dGhlIHNhbXBsZSBub25jZQ==";
  const d = createSecAccept(nonce);
  assertEquals(d, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=");
});

test(function wsAcceptable(): void {
  const ret = acceptable({
    headers: new Headers({
      upgrade: "websocket",
      "sec-websocket-key": "aaa"
    })
  });
  assertEquals(ret, true);

  assert(
    acceptable({
      headers: new Headers([
        ["connection", "Upgrade"],
        ["host", "127.0.0.1:9229"],
        [
          "sec-websocket-extensions",
          "permessage-deflate; client_max_window_bits"
        ],
        ["sec-websocket-key", "dGhlIHNhbXBsZSBub25jZQ=="],
        ["sec-websocket-version", "13"],
        ["upgrade", "WebSocket"]
      ])
    })
  );
});

test(function wsAcceptableInvalid(): void {
  assertEquals(
    acceptable({
      headers: new Headers({ "sec-websocket-key": "aaa" })
    }),
    false
  );
  assertEquals(
    acceptable({
      headers: new Headers({ upgrade: "websocket" })
    }),
    false
  );
  assertEquals(
    acceptable({
      headers: new Headers({ upgrade: "invalid", "sec-websocket-key": "aaa" })
    }),
    false
  );
  assertEquals(
    acceptable({
      headers: new Headers({ upgrade: "websocket", "sec-websocket-ky": "" })
    }),
    false
  );
});

test("connectWebSocket should throw invalid scheme of url", async (): Promise<
  void
> => {
  await assertThrowsAsync(
    async (): Promise<void> => {
      await connectWebSocket("file://hoge/hoge");
    }
  );
});

test(async function wsWriteReadMaskedFrame(): Promise<void> {
  const mask = new Uint8Array([0, 1, 2, 3]);
  const msg = "hello";
  const buf = new Buffer();
  const r = new BufReader(buf);
  await writeFrame(
    {
      isLastFrame: true,
      mask,
      opcode: OpCode.TextFrame,
      payload: encode(msg)
    },
    buf
  );
  const frame = await readFrame(r);
  assertEquals(frame.opcode, OpCode.TextFrame);
  assertEquals(frame.isLastFrame, true);
  assertEquals(frame.mask, mask);
  unmask(frame.payload, frame.mask);
  assertEquals(frame.payload, encode(msg));
});

test("handshake should not send search when it's empty", async function wsHandshakeWithEmptySearch(): Promise<
  void
> {
  const writer = new Buffer();
  const reader = new Buffer(encode("HTTP/1.1 400\r\n"));

  await assertThrowsAsync(
    async (): Promise<void> => {
      await handshake(
        new URL("ws://example.com"),
        new Headers(),
        new BufReader(reader),
        new BufWriter(writer)
      );
    }
  );

  const tpReader = new TextProtoReader(new BufReader(writer));
  const statusLine = await tpReader.readLine();

  assertEquals(statusLine, "GET / HTTP/1.1");
});

test("handshake should send search correctly", async function wsHandshakeWithSearch(): Promise<
  void
> {
  const writer = new Buffer();
  const reader = new Buffer(encode("HTTP/1.1 400\r\n"));

  await assertThrowsAsync(
    async (): Promise<void> => {
      await handshake(
        new URL("ws://example.com?a=1"),
        new Headers(),
        new BufReader(reader),
        new BufWriter(writer)
      );
    }
  );

  const tpReader = new TextProtoReader(new BufReader(writer));
  const statusLine = await tpReader.readLine();

  assertEquals(statusLine, "GET /?a=1 HTTP/1.1");
});

function dummyConn(r: Reader, w: Writer): Conn {
  return {
    rid: -1,
    closeRead: (): void => {},
    closeWrite: (): void => {},
    read: (x): Promise<number | Deno.EOF> => r.read(x),
    write: (x): Promise<number> => w.write(x),
    close: (): void => {},
    localAddr: { transport: "tcp", hostname: "0.0.0.0", port: 0 },
    remoteAddr: { transport: "tcp", hostname: "0.0.0.0", port: 0 }
  };
}

function delayedWriter(ms: number, dest: Writer): Writer {
  return {
    write(p: Uint8Array): Promise<number> {
      return new Promise<number>(resolve => {
        setTimeout(async (): Promise<void> => {
          resolve(await dest.write(p));
        }, ms);
      });
    }
  };
}
test("WebSocket.send(), WebSocket.ping() should be exclusive", async (): Promise<
  void
> => {
  const buf = new Buffer();
  const conn = dummyConn(new Buffer(), delayedWriter(1, buf));
  const sock = createWebSocket({ conn });
  // Ensure send call
  await Promise.all([
    sock.send("first"),
    sock.send("second"),
    sock.ping(),
    sock.send(new Uint8Array([3]))
  ]);
  const bufr = new BufReader(buf);
  const first = await readFrame(bufr);
  const second = await readFrame(bufr);
  const ping = await readFrame(bufr);
  const third = await readFrame(bufr);
  assertEquals(first.opcode, OpCode.TextFrame);
  assertEquals(decode(first.payload), "first");
  assertEquals(first.opcode, OpCode.TextFrame);
  assertEquals(decode(second.payload), "second");
  assertEquals(ping.opcode, OpCode.Ping);
  assertEquals(third.opcode, OpCode.BinaryFrame);
  assertEquals(bytes.equal(third.payload, new Uint8Array([3])), true);
});

test(function createSecKeyHasCorrectLength(): void {
  // Note: relies on --seed=86 being passed to deno to reproduce failure in
  // #4063.
  const secKey = createSecKey();
  assertEquals(atob(secKey).length, 16);
});

test("WebSocket should throw SocketClosedError when peer closed connection without close frame", async () => {
  const buf = new Buffer();
  const eofReader: Deno.Reader = {
    async read(_: Uint8Array): Promise<number | Deno.EOF> {
      return Deno.EOF;
    }
  };
  const conn = dummyConn(eofReader, buf);
  const sock = createWebSocket({ conn });
  sock.closeForce();
  await assertThrowsAsync(() => sock.send("hello"), SocketClosedError);
  await assertThrowsAsync(() => sock.ping(), SocketClosedError);
  await assertThrowsAsync(() => sock.close(0), SocketClosedError);
});

test("WebSocket shouldn't throw UnexpectedEOFError on recive()", async () => {
  const buf = new Buffer();
  const eofReader: Deno.Reader = {
    async read(_: Uint8Array): Promise<number | Deno.EOF> {
      return Deno.EOF;
    }
  };
  const conn = dummyConn(eofReader, buf);
  const sock = createWebSocket({ conn });
  const it = sock.receive();
  const { value, done } = await it.next();
  assertEquals(value, undefined);
  assertEquals(done, true);
});

test("WebSocket should reject sending promise when connection reset forcely", async () => {
  const buf = new Buffer();
  let timer: number | undefined;
  const lazyWriter: Deno.Writer = {
    async write(_: Uint8Array): Promise<number> {
      return new Promise(resolve => {
        timer = setTimeout(() => resolve(0), 1000);
      });
    }
  };
  const conn = dummyConn(buf, lazyWriter);
  const sock = createWebSocket({ conn });
  const onError = (e: unknown): unknown => e;
  const p = Promise.all([
    sock.send("hello").catch(onError),
    sock.send(new Uint8Array([1, 2])).catch(onError),
    sock.ping().catch(onError)
  ]);
  sock.closeForce();
  assertEquals(sock.isClosed, true);
  const [a, b, c] = await p;
  assert(a instanceof SocketClosedError);
  assert(b instanceof SocketClosedError);
  assert(c instanceof SocketClosedError);
  clearTimeout(timer);
});