1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-22 15:10:44 -05:00

io: refactor BufReader/Writer interfaces to be more idiomatic (denoland/deno_std#444)

Thanks Vincent Le Goff (@zekth) for porting over the CSV reader
implementation.

Fixes: denoland/deno_std#436

Original: 0ee6334b69
This commit is contained in:
Bert Belder 2019-05-23 19:04:06 -07:00
parent 5b37b560fb
commit b95f79d74c
14 changed files with 779 additions and 652 deletions

View file

@ -2,7 +2,7 @@
// https://github.com/golang/go/blob/go1.12.5/src/encoding/csv/
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
import { BufReader, BufState } from "../io/bufio.ts";
import { BufReader, EOF } from "../io/bufio.ts";
import { TextProtoReader } from "../textproto/mod.ts";
const INVALID_RUNE = ["\r", "\n", '"'];
@ -25,30 +25,29 @@ export interface ParseOptions {
fieldsPerRecord?: number;
}
function chkOptions(opt: ParseOptions): Error | null {
function chkOptions(opt: ParseOptions): void {
if (
INVALID_RUNE.includes(opt.comma) ||
INVALID_RUNE.includes(opt.comment) ||
opt.comma === opt.comment
) {
return Error("Invalid Delimiter");
throw new Error("Invalid Delimiter");
}
return null;
}
export async function read(
Startline: number,
reader: BufReader,
opt: ParseOptions = { comma: ",", comment: "#", trimLeadingSpace: false }
): Promise<[string[], BufState]> {
): Promise<string[] | EOF> {
const tp = new TextProtoReader(reader);
let err: BufState;
let line: string;
let result: string[] = [];
let lineIndex = Startline;
[line, err] = await tp.readLine();
const r = await tp.readLine();
if (r === EOF) return EOF;
line = r;
// Normalize \r\n to \n on all input lines.
if (
line.length >= 2 &&
@ -61,12 +60,12 @@ export async function read(
const trimmedLine = line.trimLeft();
if (trimmedLine.length === 0) {
return [[], err];
return [];
}
// line starting with comment character is ignored
if (opt.comment && trimmedLine[0] === opt.comment) {
return [result, err];
return [];
}
result = line.split(opt.comma);
@ -92,12 +91,9 @@ export async function read(
}
);
if (quoteError) {
return [
[],
new ParseError(Startline, lineIndex, 'bare " in non-quoted-field')
];
throw new ParseError(Startline, lineIndex, 'bare " in non-quoted-field');
}
return [result, err];
return result;
}
export async function readAll(
@ -107,19 +103,18 @@ export async function readAll(
trimLeadingSpace: false,
lazyQuotes: false
}
): Promise<[string[][], BufState]> {
): Promise<string[][]> {
const result: string[][] = [];
let _nbFields: number;
let err: BufState;
let lineResult: string[];
let first = true;
let lineIndex = 0;
err = chkOptions(opt);
if (err) return [result, err];
chkOptions(opt);
for (;;) {
[lineResult, err] = await read(lineIndex, reader, opt);
if (err) break;
const r = await read(lineIndex, reader, opt);
if (r === EOF) break;
lineResult = r;
lineIndex++;
// If fieldsPerRecord is 0, Read sets it to
// the number of fields in the first record
@ -136,16 +131,10 @@ export async function readAll(
if (lineResult.length > 0) {
if (_nbFields && _nbFields !== lineResult.length) {
return [
null,
new ParseError(lineIndex, lineIndex, "wrong number of fields")
];
throw new ParseError(lineIndex, lineIndex, "wrong number of fields");
}
result.push(lineResult);
}
}
if (err !== "EOF") {
return [result, err];
}
return [result, null];
return result;
}

View file

@ -437,20 +437,31 @@ for (const t of testCases) {
if (t.LazyQuotes) {
lazyquote = t.LazyQuotes;
}
const actual = await readAll(new BufReader(new StringReader(t.Input)), {
comma: comma,
comment: comment,
trimLeadingSpace: trim,
fieldsPerRecord: fieldsPerRec,
lazyQuotes: lazyquote
});
let actual;
if (t.Error) {
assert(!!actual[1]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const e: any = actual[1];
assertEquals(e.message, t.Error);
let err;
try {
actual = await readAll(new BufReader(new StringReader(t.Input)), {
comma: comma,
comment: comment,
trimLeadingSpace: trim,
fieldsPerRecord: fieldsPerRec,
lazyQuotes: lazyquote
});
} catch (e) {
err = e;
}
assert(err);
assertEquals(err.message, t.Error);
} else {
const expected = [t.Output, null];
actual = await readAll(new BufReader(new StringReader(t.Input)), {
comma: comma,
comment: comment,
trimLeadingSpace: trim,
fieldsPerRecord: fieldsPerRec,
lazyQuotes: lazyquote
});
const expected = t.Output;
assertEquals(actual, expected);
}
}

View file

@ -3,7 +3,7 @@ const { readFile, run } = Deno;
import { test } from "../testing/mod.ts";
import { assert, assertEquals } from "../testing/asserts.ts";
import { BufReader } from "../io/bufio.ts";
import { BufReader, EOF } from "../io/bufio.ts";
import { TextProtoReader } from "../textproto/mod.ts";
let fileServer;
@ -22,10 +22,10 @@ async function startFileServer(): Promise<void> {
});
// Once fileServer is ready it will write to its stdout.
const r = new TextProtoReader(new BufReader(fileServer.stdout));
const [s, err] = await r.readLine();
assert(err == null);
assert(s.includes("server listening"));
const s = await r.readLine();
assert(s !== EOF && s.includes("server listening"));
}
function killFileServer(): void {
fileServer.close();
fileServer.stdout.close();

View file

@ -1,8 +1,8 @@
const { dial, run } = Deno;
import { test } from "../testing/mod.ts";
import { test, runIfMain } from "../testing/mod.ts";
import { assert, assertEquals } from "../testing/asserts.ts";
import { BufReader } from "../io/bufio.ts";
import { BufReader, EOF } from "../io/bufio.ts";
import { TextProtoReader } from "../textproto/mod.ts";
let server;
@ -13,9 +13,8 @@ async function startServer(): Promise<void> {
});
// Once fileServer is ready it will write to its stdout.
const r = new TextProtoReader(new BufReader(server.stdout));
const [s, err] = await r.readLine();
assert(err == null);
assert(s.includes("Racing server listening..."));
const s = await r.readLine();
assert(s !== EOF && s.includes("Racing server listening..."));
}
function killServer(): void {
server.close();
@ -57,9 +56,10 @@ test(async function serverPipelineRace(): Promise<void> {
const outLines = output.split("\n");
// length - 1 to disregard last empty line
for (let i = 0; i < outLines.length - 1; i++) {
const [s, err] = await r.readLine();
assert(!err);
const s = await r.readLine();
assertEquals(s, outLines[i]);
}
killServer();
});
runIfMain(import.meta);

View file

@ -4,10 +4,10 @@ type Listener = Deno.Listener;
type Conn = Deno.Conn;
type Reader = Deno.Reader;
type Writer = Deno.Writer;
import { BufReader, BufState, BufWriter } from "../io/bufio.ts";
import { BufReader, BufWriter, EOF, UnexpectedEOFError } from "../io/bufio.ts";
import { TextProtoReader } from "../textproto/mod.ts";
import { STATUS_TEXT } from "./http_status.ts";
import { assert, fail } from "../testing/asserts.ts";
import { assert } from "../testing/asserts.ts";
import {
collectUint8Arrays,
deferred,
@ -134,7 +134,8 @@ export class ServerRequest {
if (transferEncodings.includes("chunked")) {
// Based on https://tools.ietf.org/html/rfc2616#section-19.4.6
const tp = new TextProtoReader(this.r);
let [line] = await tp.readLine();
let line = await tp.readLine();
if (line === EOF) throw new UnexpectedEOFError();
// TODO: handle chunk extension
let [chunkSizeString] = line.split(";");
let chunkSize = parseInt(chunkSizeString, 16);
@ -142,18 +143,18 @@ export class ServerRequest {
throw new Error("Invalid chunk size");
}
while (chunkSize > 0) {
let data = new Uint8Array(chunkSize);
let [nread] = await this.r.readFull(data);
if (nread !== chunkSize) {
throw new Error("Chunk data does not match size");
const data = new Uint8Array(chunkSize);
if ((await this.r.readFull(data)) === EOF) {
throw new UnexpectedEOFError();
}
yield data;
await this.r.readLine(); // Consume \r\n
[line] = await tp.readLine();
line = await tp.readLine();
if (line === EOF) throw new UnexpectedEOFError();
chunkSize = parseInt(line, 16);
}
const [entityHeaders, err] = await tp.readMIMEHeader();
if (!err) {
const entityHeaders = await tp.readMIMEHeader();
if (entityHeaders !== EOF) {
for (let [k, v] of entityHeaders) {
this.headers.set(k, v);
}
@ -220,70 +221,78 @@ function fixLength(req: ServerRequest): void {
// ParseHTTPVersion parses a HTTP version string.
// "HTTP/1.0" returns (1, 0, true).
// Ported from https://github.com/golang/go/blob/f5c43b9/src/net/http/request.go#L766-L792
export function parseHTTPVersion(vers: string): [number, number, boolean] {
const Big = 1000000; // arbitrary upper bound
const digitReg = /^\d+$/; // test if string is only digit
let major: number;
let minor: number;
export function parseHTTPVersion(vers: string): [number, number] {
switch (vers) {
case "HTTP/1.1":
return [1, 1, true];
return [1, 1];
case "HTTP/1.0":
return [1, 0, true];
return [1, 0];
default: {
const Big = 1000000; // arbitrary upper bound
const digitReg = /^\d+$/; // test if string is only digit
let major: number;
let minor: number;
if (!vers.startsWith("HTTP/")) {
break;
}
const dot = vers.indexOf(".");
if (dot < 0) {
break;
}
let majorStr = vers.substring(vers.indexOf("/") + 1, dot);
major = parseInt(majorStr);
if (
!digitReg.test(majorStr) ||
isNaN(major) ||
major < 0 ||
major > Big
) {
break;
}
let minorStr = vers.substring(dot + 1);
minor = parseInt(minorStr);
if (
!digitReg.test(minorStr) ||
isNaN(minor) ||
minor < 0 ||
minor > Big
) {
break;
}
return [major, minor];
}
}
if (!vers.startsWith("HTTP/")) {
return [0, 0, false];
}
const dot = vers.indexOf(".");
if (dot < 0) {
return [0, 0, false];
}
let majorStr = vers.substring(vers.indexOf("/") + 1, dot);
major = parseInt(majorStr);
if (!digitReg.test(majorStr) || isNaN(major) || major < 0 || major > Big) {
return [0, 0, false];
}
let minorStr = vers.substring(dot + 1);
minor = parseInt(minorStr);
if (!digitReg.test(minorStr) || isNaN(minor) || minor < 0 || minor > Big) {
return [0, 0, false];
}
return [major, minor, true];
throw new Error(`malformed HTTP version ${vers}`);
}
export async function readRequest(
bufr: BufReader
): Promise<[ServerRequest, BufState]> {
): Promise<ServerRequest | EOF> {
const tp = new TextProtoReader(bufr);
const firstLine = await tp.readLine(); // e.g. GET /index.html HTTP/1.0
if (firstLine === EOF) return EOF;
const headers = await tp.readMIMEHeader();
if (headers === EOF) throw new UnexpectedEOFError();
const req = new ServerRequest();
req.r = bufr;
const tp = new TextProtoReader(bufr);
let err: BufState;
// First line: GET /index.html HTTP/1.0
let firstLine: string;
[firstLine, err] = await tp.readLine();
if (err) {
return [null, err];
}
[req.method, req.url, req.proto] = firstLine.split(" ", 3);
let ok: boolean;
[req.protoMinor, req.protoMajor, ok] = parseHTTPVersion(req.proto);
if (!ok) {
throw Error(`malformed HTTP version ${req.proto}`);
}
[req.headers, err] = await tp.readMIMEHeader();
[req.protoMinor, req.protoMajor] = parseHTTPVersion(req.proto);
req.headers = headers;
fixLength(req);
// TODO(zekth) : add parsing of headers eg:
// rfc: https://tools.ietf.org/html/rfc7230#section-3.3.2
// A sender MUST NOT send a Content-Length header field in any message
// that contains a Transfer-Encoding header field.
return [req, err];
return req;
}
export class Server implements AsyncIterable<ServerRequest> {
@ -302,36 +311,39 @@ export class Server implements AsyncIterable<ServerRequest> {
): AsyncIterableIterator<ServerRequest> {
const bufr = new BufReader(conn);
const w = new BufWriter(conn);
let bufStateErr: BufState;
let req: ServerRequest;
let req: ServerRequest | EOF;
let err: Error | undefined;
while (!this.closing) {
try {
[req, bufStateErr] = await readRequest(bufr);
} catch (err) {
bufStateErr = err;
req = await readRequest(bufr);
} catch (e) {
err = e;
break;
}
if (bufStateErr) break;
if (req === EOF) {
break;
}
req.w = w;
yield req;
// Wait for the request to be processed before we accept a new request on
// this connection.
await req.done;
}
if (bufStateErr === "EOF") {
if (req === EOF) {
// The connection was gracefully closed.
} else if (bufStateErr instanceof Error) {
} else if (err) {
// An error was thrown while parsing request headers.
await writeResponse(req.w, {
status: 400,
body: new TextEncoder().encode(`${bufStateErr.message}\r\n\r\n`)
body: new TextEncoder().encode(`${err.message}\r\n\r\n`)
});
} else if (this.closing) {
// There are more requests incoming but the server is closing.
// TODO(ry): send a back a HTTP 503 Service Unavailable status.
} else {
fail(`unexpected BufState: ${bufStateErr}`);
}
conn.close();

View file

@ -7,7 +7,7 @@
const { Buffer } = Deno;
import { test, runIfMain } from "../testing/mod.ts";
import { assert, assertEquals } from "../testing/asserts.ts";
import { assert, assertEquals, assertNotEquals } from "../testing/asserts.ts";
import {
Response,
ServerRequest,
@ -15,9 +15,20 @@ import {
readRequest,
parseHTTPVersion
} from "./server.ts";
import { BufReader, BufWriter } from "../io/bufio.ts";
import {
BufReader,
BufWriter,
EOF,
ReadLineResult,
UnexpectedEOFError
} from "../io/bufio.ts";
import { StringReader } from "../io/readers.ts";
function assertNotEOF<T extends {}>(val: T | EOF): T {
assertNotEquals(val, EOF);
return val as T;
}
interface ResponseTest {
response: Response;
raw: string;
@ -247,21 +258,25 @@ test(async function writeUint8ArrayResponse(): Promise<void> {
const decoder = new TextDecoder("utf-8");
const reader = new BufReader(buf);
let line: Uint8Array;
line = (await reader.readLine())[0];
assertEquals(decoder.decode(line), "HTTP/1.1 200 OK");
let r: ReadLineResult;
r = assertNotEOF(await reader.readLine());
assertEquals(decoder.decode(r.line), "HTTP/1.1 200 OK");
assertEquals(r.more, false);
line = (await reader.readLine())[0];
assertEquals(decoder.decode(line), `content-length: ${shortText.length}`);
r = assertNotEOF(await reader.readLine());
assertEquals(decoder.decode(r.line), `content-length: ${shortText.length}`);
assertEquals(r.more, false);
line = (await reader.readLine())[0];
assertEquals(line.byteLength, 0);
r = assertNotEOF(await reader.readLine());
assertEquals(r.line.byteLength, 0);
assertEquals(r.more, false);
line = (await reader.readLine())[0];
assertEquals(decoder.decode(line), shortText);
r = assertNotEOF(await reader.readLine());
assertEquals(decoder.decode(r.line), shortText);
assertEquals(r.more, false);
line = (await reader.readLine())[0];
assertEquals(line.byteLength, 0);
const eof = await reader.readLine();
assertEquals(eof, EOF);
});
test(async function writeStringReaderResponse(): Promise<void> {
@ -276,24 +291,30 @@ test(async function writeStringReaderResponse(): Promise<void> {
const decoder = new TextDecoder("utf-8");
const reader = new BufReader(buf);
let line: Uint8Array;
line = (await reader.readLine())[0];
assertEquals(decoder.decode(line), "HTTP/1.1 200 OK");
let r: ReadLineResult;
r = assertNotEOF(await reader.readLine());
assertEquals(decoder.decode(r.line), "HTTP/1.1 200 OK");
assertEquals(r.more, false);
line = (await reader.readLine())[0];
assertEquals(decoder.decode(line), "transfer-encoding: chunked");
r = assertNotEOF(await reader.readLine());
assertEquals(decoder.decode(r.line), "transfer-encoding: chunked");
assertEquals(r.more, false);
line = (await reader.readLine())[0];
assertEquals(line.byteLength, 0);
r = assertNotEOF(await reader.readLine());
assertEquals(r.line.byteLength, 0);
assertEquals(r.more, false);
line = (await reader.readLine())[0];
assertEquals(decoder.decode(line), shortText.length.toString());
r = assertNotEOF(await reader.readLine());
assertEquals(decoder.decode(r.line), shortText.length.toString());
assertEquals(r.more, false);
line = (await reader.readLine())[0];
assertEquals(decoder.decode(line), shortText);
r = assertNotEOF(await reader.readLine());
assertEquals(decoder.decode(r.line), shortText);
assertEquals(r.more, false);
line = (await reader.readLine())[0];
assertEquals(decoder.decode(line), "0");
r = assertNotEOF(await reader.readLine());
assertEquals(decoder.decode(r.line), "0");
assertEquals(r.more, false);
});
test(async function readRequestError(): Promise<void> {
@ -318,19 +339,20 @@ test(async function testReadRequestError(): Promise<void> {
const testCases = {
0: {
in: "GET / HTTP/1.1\r\nheader: foo\r\n\r\n",
headers: [{ key: "header", value: "foo" }],
err: null
headers: [{ key: "header", value: "foo" }]
},
1: { in: "GET / HTTP/1.1\r\nheader:foo\r\n", err: "EOF", headers: [] },
2: { in: "", err: "EOF", headers: [] },
1: {
in: "GET / HTTP/1.1\r\nheader:foo\r\n",
err: UnexpectedEOFError
},
2: { in: "", err: EOF },
3: {
in: "HEAD / HTTP/1.1\r\nContent-Length:4\r\n\r\n",
err: "http: method cannot contain a Content-Length"
},
4: {
in: "HEAD / HTTP/1.1\r\n\r\n",
headers: [],
err: null
headers: []
},
// Multiple Content-Length values should either be
// deduplicated if same or reject otherwise
@ -348,7 +370,6 @@ test(async function testReadRequestError(): Promise<void> {
7: {
in:
"PUT / HTTP/1.1\r\nContent-Length: 6 \r\nContent-Length: 6\r\nContent-Length:6\r\n\r\nGopher\r\n",
err: null,
headers: [{ key: "Content-Length", value: "6" }]
},
8: {
@ -363,24 +384,28 @@ test(async function testReadRequestError(): Promise<void> {
// },
10: {
in: "HEAD / HTTP/1.1\r\nContent-Length:0\r\nContent-Length: 0\r\n\r\n",
headers: [{ key: "Content-Length", value: "0" }],
err: null
headers: [{ key: "Content-Length", value: "0" }]
}
};
for (const p in testCases) {
const test = testCases[p];
const reader = new BufReader(new StringReader(test.in));
let _err;
if (test.err && test.err != "EOF") {
try {
await readRequest(reader);
} catch (e) {
_err = e;
}
assertEquals(_err.message, test.err);
let err;
let req;
try {
req = await readRequest(reader);
} catch (e) {
err = e;
}
if (test.err === EOF) {
assertEquals(req, EOF);
} else if (typeof test.err === "string") {
assertEquals(err.message, test.err);
} else if (test.err) {
assert(err instanceof test.err);
} else {
const [req, err] = await readRequest(reader);
assertEquals(test.err, err);
assertEquals(err, undefined);
assertNotEquals(req, EOF);
for (const h of test.headers) {
assertEquals(req.headers.get(h.key), h.value);
}
@ -393,21 +418,31 @@ test({
name: "[http] parseHttpVersion",
fn(): void {
const testCases = [
{ in: "HTTP/0.9", want: [0, 9, true] },
{ in: "HTTP/1.0", want: [1, 0, true] },
{ in: "HTTP/1.1", want: [1, 1, true] },
{ in: "HTTP/3.14", want: [3, 14, true] },
{ in: "HTTP", want: [0, 0, false] },
{ in: "HTTP/one.one", want: [0, 0, false] },
{ in: "HTTP/1.1/", want: [0, 0, false] },
{ in: "HTTP/-1.0", want: [0, 0, false] },
{ in: "HTTP/0.-1", want: [0, 0, false] },
{ in: "HTTP/", want: [0, 0, false] },
{ in: "HTTP/1,0", want: [0, 0, false] }
{ in: "HTTP/0.9", want: [0, 9] },
{ in: "HTTP/1.0", want: [1, 0] },
{ in: "HTTP/1.1", want: [1, 1] },
{ in: "HTTP/3.14", want: [3, 14] },
{ in: "HTTP", err: true },
{ in: "HTTP/one.one", err: true },
{ in: "HTTP/1.1/", err: true },
{ in: "HTTP/-1.0", err: true },
{ in: "HTTP/0.-1", err: true },
{ in: "HTTP/", err: true },
{ in: "HTTP/1,0", err: true }
];
for (const t of testCases) {
const r = parseHTTPVersion(t.in);
assertEquals(r, t.want, t.in);
let r, err;
try {
r = parseHTTPVersion(t.in);
} catch (e) {
err = e;
}
if (t.err) {
assert(err instanceof Error, t.in);
} else {
assertEquals(err, undefined);
assertEquals(r, t.want, t.in);
}
}
}
});

View file

@ -15,13 +15,28 @@ const MAX_CONSECUTIVE_EMPTY_READS = 100;
const CR = charCode("\r");
const LF = charCode("\n");
export type BufState =
| null
| "EOF"
| "BufferFull"
| "ShortWrite"
| "NoProgress"
| Error;
export class BufferFullError extends Error {
name = "BufferFullError";
constructor(public partial: Uint8Array) {
super("Buffer full");
}
}
export class UnexpectedEOFError extends Error {
name = "UnexpectedEOFError";
constructor() {
super("Unexpected EOF");
}
}
export const EOF: unique symbol = Symbol("EOF");
export type EOF = typeof EOF;
/** Result type returned by of BufReader.readLine(). */
export interface ReadLineResult {
line: Uint8Array;
more: boolean;
}
/** BufReader implements buffering for a Reader object. */
export class BufReader implements Reader {
@ -29,9 +44,9 @@ export class BufReader implements Reader {
private rd: Reader; // Reader provided by caller.
private r = 0; // buf read position.
private w = 0; // buf write position.
private lastByte: number;
private lastCharSize: number;
private err: BufState;
private eof = false;
// private lastByte: number;
// private lastCharSize: number;
/** return new BufReader unless r is BufReader */
static create(r: Reader, size = DEFAULT_BUF_SIZE): BufReader {
@ -54,12 +69,6 @@ export class BufReader implements Reader {
return this.w - this.r;
}
private _readErr(): BufState {
const err = this.err;
this.err = null;
return err;
}
// Reads a new chunk into the buffer.
private async _fill(): Promise<void> {
// Slide existing data to beginning.
@ -75,24 +84,21 @@ export class BufReader implements Reader {
// Read new data: try a limited number of times.
for (let i = MAX_CONSECUTIVE_EMPTY_READS; i > 0; i--) {
let rr: ReadResult;
try {
rr = await this.rd.read(this.buf.subarray(this.w));
} catch (e) {
this.err = e;
return;
}
let rr: ReadResult = await this.rd.read(this.buf.subarray(this.w));
assert(rr.nread >= 0, "negative read");
this.w += rr.nread;
if (rr.eof) {
this.err = "EOF";
this.eof = true;
return;
}
if (rr.nread > 0) {
return;
}
}
this.err = "NoProgress";
throw new Error(
`No progress after ${MAX_CONSECUTIVE_EMPTY_READS} read() calls`
);
}
/** Discards any buffered data, resets all state, and switches
@ -105,108 +111,96 @@ export class BufReader implements Reader {
private _reset(buf: Uint8Array, rd: Reader): void {
this.buf = buf;
this.rd = rd;
this.lastByte = -1;
// this.lastRuneSize = -1;
this.eof = false;
// this.lastByte = -1;
// this.lastCharSize = -1;
}
/** reads data into p.
* It returns the number of bytes read into p.
* The bytes are taken from at most one Read on the underlying Reader,
* hence n may be less than len(p).
* At EOF, the count will be zero and err will be io.EOF.
* To read exactly len(p) bytes, use io.ReadFull(b, p).
*/
async read(p: Uint8Array): Promise<ReadResult> {
let rr: ReadResult = { nread: p.byteLength, eof: false };
if (rr.nread === 0) {
if (this.err) {
throw this._readErr();
}
return rr;
}
if (p.byteLength === 0) return rr;
if (this.r === this.w) {
if (this.err) {
throw this._readErr();
}
if (p.byteLength >= this.buf.byteLength) {
// Large read, empty buffer.
// Read directly into p to avoid copy.
rr = await this.rd.read(p);
const rr = await this.rd.read(p);
assert(rr.nread >= 0, "negative read");
if (rr.nread > 0) {
this.lastByte = p[rr.nread - 1];
// this.lastRuneSize = -1;
}
if (this.err) {
throw this._readErr();
}
// if (rr.nread > 0) {
// this.lastByte = p[rr.nread - 1];
// this.lastCharSize = -1;
// }
return rr;
}
// One read.
// Do not use this.fill, which will loop.
this.r = 0;
this.w = 0;
try {
rr = await this.rd.read(this.buf);
} catch (e) {
this.err = e;
}
rr = await this.rd.read(this.buf);
assert(rr.nread >= 0, "negative read");
if (rr.nread === 0) {
if (this.err) {
throw this._readErr();
}
return rr;
}
if (rr.nread === 0) return rr;
this.w += rr.nread;
}
// copy as much as we can
rr.nread = copyBytes(p as Uint8Array, this.buf.subarray(this.r, this.w), 0);
rr.nread = copyBytes(p, this.buf.subarray(this.r, this.w), 0);
this.r += rr.nread;
this.lastByte = this.buf[this.r - 1];
// this.lastRuneSize = -1;
// this.lastByte = this.buf[this.r - 1];
// this.lastCharSize = -1;
return rr;
}
/** reads exactly len(p) bytes into p.
/** reads exactly `p.length` bytes into `p`.
*
* If successful, `p` is returned.
*
* If the end of the underlying stream has been reached, and there are no more
* bytes available in the buffer, `readFull()` returns `EOF` instead.
*
* An error is thrown if some bytes could be read, but not enough to fill `p`
* entirely before the underlying stream reported an error or EOF. Any error
* thrown will have a `partial` property that indicates the slice of the
* buffer that has been successfully filled with data.
*
* Ported from https://golang.org/pkg/io/#ReadFull
* It returns the number of bytes copied and an error if fewer bytes were read.
* The error is EOF only if no bytes were read.
* If an EOF happens after reading some but not all the bytes,
* readFull returns ErrUnexpectedEOF. ("EOF" for current impl)
* On return, n == len(p) if and only if err == nil.
* If r returns an error having read at least len(buf) bytes,
* the error is dropped.
*/
async readFull(p: Uint8Array): Promise<[number, BufState]> {
let rr = await this.read(p);
let nread = rr.nread;
if (rr.eof) {
return [nread, nread < p.length ? "EOF" : null];
async readFull(p: Uint8Array): Promise<Uint8Array | EOF> {
let bytesRead = 0;
while (bytesRead < p.length) {
try {
const rr = await this.read(p.subarray(bytesRead));
bytesRead += rr.nread;
if (rr.eof) {
if (bytesRead === 0) {
return EOF;
} else {
throw new UnexpectedEOFError();
}
}
} catch (err) {
err.partial = p.subarray(0, bytesRead);
throw err;
}
}
while (!rr.eof && nread < p.length) {
rr = await this.read(p.subarray(nread));
nread += rr.nread;
}
return [nread, nread < p.length ? "EOF" : null];
return p;
}
/** Returns the next byte [0, 255] or -1 if EOF. */
async readByte(): Promise<number> {
while (this.r === this.w) {
if (this.eof) return -1;
await this._fill(); // buffer is empty.
if (this.err == "EOF") {
return -1;
}
if (this.err != null) {
throw this._readErr();
}
}
const c = this.buf[this.r];
this.r++;
this.lastByte = c;
// this.lastByte = c;
return c;
}
@ -218,46 +212,73 @@ export class BufReader implements Reader {
* delim.
* For simple uses, a Scanner may be more convenient.
*/
async readString(_delim: string): Promise<string> {
async readString(_delim: string): Promise<string | EOF> {
throw new Error("Not implemented");
}
/** readLine() is a low-level line-reading primitive. Most callers should use
* readBytes('\n') or readString('\n') instead or use a Scanner.
/** `readLine()` is a low-level line-reading primitive. Most callers should
* use `readString('\n')` instead or use a Scanner.
*
* readLine tries to return a single line, not including the end-of-line bytes.
* If the line was too long for the buffer then isPrefix is set and the
* `readLine()` tries to return a single line, not including the end-of-line
* bytes. If the line was too long for the buffer then `more` is set and the
* beginning of the line is returned. The rest of the line will be returned
* from future calls. isPrefix will be false when returning the last fragment
* from future calls. `more` will be false when returning the last fragment
* of the line. The returned buffer is only valid until the next call to
* ReadLine. ReadLine either returns a non-nil line or it returns an error,
* never both.
* `readLine()`.
*
* The text returned from ReadLine does not include the line end ("\r\n" or "\n").
* No indication or error is given if the input ends without a final line end.
* Calling UnreadByte after ReadLine will always unread the last byte read
* (possibly a character belonging to the line end) even if that byte is not
* part of the line returned by ReadLine.
* The text returned from ReadLine does not include the line end ("\r\n" or
* "\n").
*
* When the end of the underlying stream is reached, the final bytes in the
* stream are returned. No indication or error is given if the input ends
* without a final line end. When there are no more trailing bytes to read,
* `readLine()` returns the `EOF` symbol.
*
* Calling `unreadByte()` after `readLine()` will always unread the last byte
* read (possibly a character belonging to the line end) even if that byte is
* not part of the line returned by `readLine()`.
*/
async readLine(): Promise<[Uint8Array, boolean, BufState]> {
let [line, err] = await this.readSlice(LF);
async readLine(): Promise<ReadLineResult | EOF> {
let line: Uint8Array | EOF;
try {
line = await this.readSlice(LF);
} catch (err) {
let { partial } = err;
assert(
partial instanceof Uint8Array,
"bufio: caught error from `readSlice()` without `partial` property"
);
// Don't throw if `readSlice()` failed with `BufferFullError`, instead we
// just return whatever is available and set the `more` flag.
if (!(err instanceof BufferFullError)) {
throw err;
}
if (err === "BufferFull") {
// Handle the case where "\r\n" straddles the buffer.
if (line.byteLength > 0 && line[line.byteLength - 1] === CR) {
if (
!this.eof &&
partial.byteLength > 0 &&
partial[partial.byteLength - 1] === CR
) {
// Put the '\r' back on buf and drop it from line.
// Let the next call to ReadLine check for "\r\n".
assert(this.r > 0, "bufio: tried to rewind past start of buffer");
this.r--;
line = line.subarray(0, line.byteLength - 1);
partial = partial.subarray(0, partial.byteLength - 1);
}
return [line, true, null];
return { line: partial, more: !this.eof };
}
if (line === EOF) {
return EOF;
}
if (line.byteLength === 0) {
return [line, false, err];
return { line, more: false };
}
err = null;
if (line[line.byteLength - 1] == LF) {
let drop = 1;
@ -266,98 +287,112 @@ export class BufReader implements Reader {
}
line = line.subarray(0, line.byteLength - drop);
}
return [line, false, err];
return { line, more: false };
}
/** readSlice() reads until the first occurrence of delim in the input,
/** `readSlice()` reads until the first occurrence of `delim` in the input,
* returning a slice pointing at the bytes in the buffer. The bytes stop
* being valid at the next read. If readSlice() encounters an error before
* finding a delimiter, it returns all the data in the buffer and the error
* itself (often io.EOF). readSlice() fails with error ErrBufferFull if the
* buffer fills without a delim. Because the data returned from readSlice()
* will be overwritten by the next I/O operation, most clients should use
* readBytes() or readString() instead. readSlice() returns err != nil if and
* only if line does not end in delim.
* being valid at the next read.
*
* If `readSlice()` encounters an error before finding a delimiter, or the
* buffer fills without finding a delimiter, it throws an error with a
* `partial` property that contains the entire buffer.
*
* If `readSlice()` encounters the end of the underlying stream and there are
* any bytes left in the buffer, the rest of the buffer is returned. In other
* words, EOF is always treated as a delimiter. Once the buffer is empty,
* it returns `EOF`.
*
* Because the data returned from `readSlice()` will be overwritten by the
* next I/O operation, most clients should use `readString()` instead.
*/
async readSlice(delim: number): Promise<[Uint8Array, BufState]> {
async readSlice(delim: number): Promise<Uint8Array | EOF> {
let s = 0; // search start index
let line: Uint8Array;
let err: BufState;
let slice: Uint8Array;
while (true) {
// Search buffer.
let i = this.buf.subarray(this.r + s, this.w).indexOf(delim);
if (i >= 0) {
i += s;
line = this.buf.subarray(this.r, this.r + i + 1);
slice = this.buf.subarray(this.r, this.r + i + 1);
this.r += i + 1;
break;
}
// Pending error?
if (this.err) {
line = this.buf.subarray(this.r, this.w);
// EOF?
if (this.eof) {
if (this.r === this.w) {
return EOF;
}
slice = this.buf.subarray(this.r, this.w);
this.r = this.w;
err = this._readErr();
break;
}
// Buffer full?
if (this.buffered() >= this.buf.byteLength) {
this.r = this.w;
line = this.buf;
err = "BufferFull";
break;
throw new BufferFullError(this.buf);
}
s = this.w - this.r; // do not rescan area we scanned before
await this._fill(); // buffer is not full
// Buffer is not full.
try {
await this._fill();
} catch (err) {
err.partial = slice;
throw err;
}
}
// Handle last byte, if any.
let i = line.byteLength - 1;
if (i >= 0) {
this.lastByte = line[i];
// this.lastRuneSize = -1
}
// const i = slice.byteLength - 1;
// if (i >= 0) {
// this.lastByte = slice[i];
// this.lastCharSize = -1
// }
return [line, err];
return slice;
}
/** Peek returns the next n bytes without advancing the reader. The bytes stop
* being valid at the next read call. If Peek returns fewer than n bytes, it
* also returns an error explaining why the read is short. The error is
* ErrBufferFull if n is larger than b's buffer size.
/** `peek()` returns the next `n` bytes without advancing the reader. The
* bytes stop being valid at the next read call.
*
* When the end of the underlying stream is reached, but there are unread
* bytes left in the buffer, those bytes are returned. If there are no bytes
* left in the buffer, it returns `EOF`.
*
* If an error is encountered before `n` bytes are available, `peek()` throws
* an error with the `partial` property set to a slice of the buffer that
* contains the bytes that were available before the error occurred.
*/
async peek(n: number): Promise<[Uint8Array, BufState]> {
async peek(n: number): Promise<Uint8Array | EOF> {
if (n < 0) {
throw Error("negative count");
}
while (
this.w - this.r < n &&
this.w - this.r < this.buf.byteLength &&
this.err == null
) {
await this._fill(); // this.w - this.r < len(this.buf) => buffer is not full
}
if (n > this.buf.byteLength) {
return [this.buf.subarray(this.r, this.w), "BufferFull"];
}
// 0 <= n <= len(this.buf)
let err: BufState;
let avail = this.w - this.r;
if (avail < n) {
// not enough data in buffer
n = avail;
err = this._readErr();
if (!err) {
err = "BufferFull";
while (avail < n && avail < this.buf.byteLength && !this.eof) {
try {
await this._fill();
} catch (err) {
err.partial = this.buf.subarray(this.r, this.w);
throw err;
}
avail = this.w - this.r;
}
return [this.buf.subarray(this.r, this.r + n), err];
if (avail === 0 && this.eof) {
return EOF;
} else if (avail < n && this.eof) {
return this.buf.subarray(this.r, this.r + avail);
} else if (avail < n) {
throw new BufferFullError(this.buf.subarray(this.r, this.w));
}
return this.buf.subarray(this.r, this.r + n);
}
}
@ -371,7 +406,7 @@ export class BufReader implements Reader {
export class BufWriter implements Writer {
buf: Uint8Array;
n: number = 0;
err: null | BufState = null;
err: Error | null = null;
/** return new BufWriter unless w is BufWriter */
static create(w: Writer, size = DEFAULT_BUF_SIZE): BufWriter {
@ -400,34 +435,27 @@ export class BufWriter implements Writer {
}
/** Flush writes any buffered data to the underlying io.Writer. */
async flush(): Promise<BufState> {
if (this.err != null) {
return this.err;
}
if (this.n == 0) {
return null;
}
async flush(): Promise<void> {
if (this.err !== null) throw this.err;
if (this.n === 0) return;
let n: number;
let err: BufState = null;
try {
n = await this.wr.write(this.buf.subarray(0, this.n));
} catch (e) {
err = e;
this.err = e;
throw e;
}
if (n < this.n && err == null) {
err = "ShortWrite";
}
if (err != null) {
if (n > 0 && n < this.n) {
if (n < this.n) {
if (n > 0) {
this.buf.copyWithin(0, n, this.n);
this.n -= n;
}
this.n -= n;
this.err = err;
return err;
this.err = new Error("Short write");
throw this.err;
}
this.n = 0;
}
@ -447,16 +475,20 @@ export class BufWriter implements Writer {
* Returns the number of bytes written.
*/
async write(p: Uint8Array): Promise<number> {
if (this.err !== null) throw this.err;
if (p.length === 0) return 0;
let nn = 0;
let n: number;
while (p.byteLength > this.available() && !this.err) {
if (this.buffered() == 0) {
while (p.byteLength > this.available()) {
if (this.buffered() === 0) {
// Large write, empty buffer.
// Write directly from p to avoid copy.
try {
n = await this.wr.write(p);
} catch (e) {
this.err = e;
throw e;
}
} else {
n = copyBytes(this.buf, p, this.n);
@ -466,9 +498,7 @@ export class BufWriter implements Writer {
nn += n;
p = p.subarray(n);
}
if (this.err) {
throw this.err;
}
n = copyBytes(this.buf, p, this.n);
this.n += n;
nn += n;

View file

@ -6,14 +6,30 @@
const { Buffer } = Deno;
type Reader = Deno.Reader;
type ReadResult = Deno.ReadResult;
import { test } from "../testing/mod.ts";
import { assert, assertEquals } from "../testing/asserts.ts";
import { BufReader, BufWriter } from "./bufio.ts";
import { test, runIfMain } from "../testing/mod.ts";
import {
assert,
assertEquals,
assertNotEquals,
fail
} from "../testing/asserts.ts";
import {
BufReader,
BufWriter,
EOF,
BufferFullError,
UnexpectedEOFError
} from "./bufio.ts";
import * as iotest from "./iotest.ts";
import { charCode, copyBytes, stringsReader } from "./util.ts";
const encoder = new TextEncoder();
function assertNotEOF<T extends {}>(val: T | EOF): T {
assertNotEquals(val, EOF);
return val as T;
}
async function readBytes(buf: BufReader): Promise<string> {
const b = new Uint8Array(1000);
let nb = 0;
@ -129,17 +145,20 @@ test(async function bufioBufferFull(): Promise<void> {
const longString =
"And now, hello, world! It is the time for all good men to come to the aid of their party";
const buf = new BufReader(stringsReader(longString), MIN_READ_BUFFER_SIZE);
let [line, err] = await buf.readSlice(charCode("!"));
const decoder = new TextDecoder();
let actual = decoder.decode(line);
assertEquals(err, "BufferFull");
assertEquals(actual, "And now, hello, ");
[line, err] = await buf.readSlice(charCode("!"));
actual = decoder.decode(line);
try {
await buf.readSlice(charCode("!"));
fail("readSlice should throw");
} catch (err) {
assert(err instanceof BufferFullError);
assert(err.partial instanceof Uint8Array);
assertEquals(decoder.decode(err.partial), "And now, hello, ");
}
const line = assertNotEOF(await buf.readSlice(charCode("!")));
const actual = decoder.decode(line);
assertEquals(actual, "world!");
assert(err == null);
});
const testInput = encoder.encode(
@ -178,14 +197,12 @@ async function testReadLine(input: Uint8Array): Promise<void> {
let reader = new TestReader(input, stride);
let l = new BufReader(reader, input.byteLength + 1);
while (true) {
let [line, isPrefix, err] = await l.readLine();
if (line.byteLength > 0 && err != null) {
throw Error("readLine returned both data and error");
}
assertEquals(isPrefix, false);
if (err == "EOF") {
const r = await l.readLine();
if (r === EOF) {
break;
}
const { line, more } = r;
assertEquals(more, false);
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
let want = testOutput.subarray(done, done + line.byteLength);
assertEquals(
@ -218,56 +235,51 @@ test(async function bufioPeek(): Promise<void> {
MIN_READ_BUFFER_SIZE
);
let [actual, err] = await buf.peek(1);
let actual = assertNotEOF(await buf.peek(1));
assertEquals(decoder.decode(actual), "a");
assert(err == null);
[actual, err] = await buf.peek(4);
actual = assertNotEOF(await buf.peek(4));
assertEquals(decoder.decode(actual), "abcd");
assert(err == null);
[actual, err] = await buf.peek(32);
assertEquals(decoder.decode(actual), "abcdefghijklmnop");
assertEquals(err, "BufferFull");
try {
await buf.peek(32);
fail("peek() should throw");
} catch (err) {
assert(err instanceof BufferFullError);
assert(err.partial instanceof Uint8Array);
assertEquals(decoder.decode(err.partial), "abcdefghijklmnop");
}
await buf.read(p.subarray(0, 3));
assertEquals(decoder.decode(p.subarray(0, 3)), "abc");
[actual, err] = await buf.peek(1);
actual = assertNotEOF(await buf.peek(1));
assertEquals(decoder.decode(actual), "d");
assert(err == null);
[actual, err] = await buf.peek(1);
actual = assertNotEOF(await buf.peek(1));
assertEquals(decoder.decode(actual), "d");
assert(err == null);
[actual, err] = await buf.peek(1);
actual = assertNotEOF(await buf.peek(1));
assertEquals(decoder.decode(actual), "d");
assert(err == null);
[actual, err] = await buf.peek(2);
actual = assertNotEOF(await buf.peek(2));
assertEquals(decoder.decode(actual), "de");
assert(err == null);
let { eof } = await buf.read(p.subarray(0, 3));
assertEquals(decoder.decode(p.subarray(0, 3)), "def");
assert(!eof);
assert(err == null);
[actual, err] = await buf.peek(4);
actual = assertNotEOF(await buf.peek(4));
assertEquals(decoder.decode(actual), "ghij");
assert(err == null);
await buf.read(p);
assertEquals(decoder.decode(p), "ghijklmnop");
[actual, err] = await buf.peek(0);
actual = assertNotEOF(await buf.peek(0));
assertEquals(decoder.decode(actual), "");
assert(err == null);
[actual, err] = await buf.peek(1);
assertEquals(decoder.decode(actual), "");
assert(err == "EOF");
const r = await buf.peek(1);
assert(r === EOF);
/* TODO
// Test for issue 3022, not exposing a reader's error on a successful Peek.
buf = NewReaderSize(dataAndEOFReader("abcd"), 32)
@ -328,16 +340,22 @@ test(async function bufReaderReadFull(): Promise<void> {
const bufr = new BufReader(data, 3);
{
const buf = new Uint8Array(6);
const [nread, err] = await bufr.readFull(buf);
assertEquals(nread, 6);
assert(!err);
const r = assertNotEOF(await bufr.readFull(buf));
assertEquals(r, buf);
assertEquals(dec.decode(buf), "Hello ");
}
{
const buf = new Uint8Array(6);
const [nread, err] = await bufr.readFull(buf);
assertEquals(nread, 5);
assertEquals(err, "EOF");
assertEquals(dec.decode(buf.subarray(0, 5)), "World");
try {
await bufr.readFull(buf);
fail("readFull() should throw");
} catch (err) {
assert(err instanceof UnexpectedEOFError);
assert(err.partial instanceof Uint8Array);
assertEquals(err.partial.length, 5);
assertEquals(dec.decode(buf.subarray(0, 5)), "World");
}
}
});
runIfMain(import.meta);

View file

@ -1,19 +1,21 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
const { Buffer, copy, remove } = Deno;
const { min, max } = Math;
type Closer = Deno.Closer;
type Reader = Deno.Reader;
type ReadResult = Deno.ReadResult;
type Writer = Deno.Writer;
import { FormFile } from "../multipart/formfile.ts";
import * as bytes from "../bytes/mod.ts";
import { equal, findIndex, findLastIndex, hasPrefix } from "../bytes/mod.ts";
import { extname } from "../fs/path.ts";
import { copyN } from "../io/ioutil.ts";
import { MultiReader } from "../io/readers.ts";
import { tempFile } from "../io/util.ts";
import { BufReader, BufState, BufWriter } from "../io/bufio.ts";
import { TextProtoReader } from "../textproto/mod.ts";
import { BufReader, BufWriter, EOF, UnexpectedEOFError } from "../io/bufio.ts";
import { encoder } from "../strings/mod.ts";
import * as path from "../fs/path.ts";
import { assertStrictEq } from "../testing/asserts.ts";
import { TextProtoReader } from "../textproto/mod.ts";
function randomBoundary(): string {
let boundary = "--------------------------";
@ -23,18 +25,31 @@ function randomBoundary(): string {
return boundary;
}
/**
* Checks whether `buf` should be considered to match the boundary.
*
* The prefix is "--boundary" or "\r\n--boundary" or "\n--boundary", and the
* caller has verified already that `hasPrefix(buf, prefix)` is true.
*
* `matchAfterPrefix()` returns `1` if the buffer does match the boundary,
* meaning the prefix is followed by a dash, space, tab, cr, nl, or EOF.
*
* It returns `-1` if the buffer definitely does NOT match the boundary,
* meaning the prefix is followed by some other character.
* For example, "--foobar" does not match "--foo".
*
* It returns `0` more input needs to be read to make the decision,
* meaning that `buf.length` and `prefix.length` are the same.
*/
export function matchAfterPrefix(
a: Uint8Array,
buf: Uint8Array,
prefix: Uint8Array,
bufState: BufState
): number {
if (a.length === prefix.length) {
if (bufState) {
return 1;
}
return 0;
eof: boolean
): -1 | 0 | 1 {
if (buf.length === prefix.length) {
return eof ? 1 : 0;
}
const c = a[prefix.length];
const c = buf[prefix.length];
if (
c === " ".charCodeAt(0) ||
c === "\t".charCodeAt(0) ||
@ -47,105 +62,117 @@ export function matchAfterPrefix(
return -1;
}
/**
* Scans `buf` to identify how much of it can be safely returned as part of the
* `PartReader` body.
*
* @param buf - The buffer to search for boundaries.
* @param dashBoundary - Is "--boundary".
* @param newLineDashBoundary - Is "\r\n--boundary" or "\n--boundary", depending
* on what mode we are in. The comments below (and the name) assume
* "\n--boundary", but either is accepted.
* @param total - The number of bytes read out so far. If total == 0, then a
* leading "--boundary" is recognized.
* @param eof - Whether `buf` contains the final bytes in the stream before EOF.
* If `eof` is false, more bytes are expected to follow.
* @returns The number of data bytes from buf that can be returned as part of
* the `PartReader` body.
*/
export function scanUntilBoundary(
buf: Uint8Array,
dashBoundary: Uint8Array,
newLineDashBoundary: Uint8Array,
total: number,
state: BufState
): [number, BufState] {
eof: boolean
): number | EOF {
if (total === 0) {
if (bytes.hasPrefix(buf, dashBoundary)) {
switch (matchAfterPrefix(buf, dashBoundary, state)) {
// At beginning of body, allow dashBoundary.
if (hasPrefix(buf, dashBoundary)) {
switch (matchAfterPrefix(buf, dashBoundary, eof)) {
case -1:
return [dashBoundary.length, null];
return dashBoundary.length;
case 0:
return [0, null];
return 0;
case 1:
return [0, "EOF"];
}
if (bytes.hasPrefix(dashBoundary, buf)) {
return [0, state];
return EOF;
}
}
if (hasPrefix(dashBoundary, buf)) {
return 0;
}
}
const i = bytes.findIndex(buf, newLineDashBoundary);
// Search for "\n--boundary".
const i = findIndex(buf, newLineDashBoundary);
if (i >= 0) {
switch (matchAfterPrefix(buf.slice(i), newLineDashBoundary, state)) {
switch (matchAfterPrefix(buf.slice(i), newLineDashBoundary, eof)) {
case -1:
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
return [i + newLineDashBoundary.length, null];
return i + newLineDashBoundary.length;
case 0:
return [i, null];
return i;
case 1:
return [i, "EOF"];
return i > 0 ? i : EOF;
}
}
if (bytes.hasPrefix(newLineDashBoundary, buf)) {
return [0, state];
if (hasPrefix(newLineDashBoundary, buf)) {
return 0;
}
const j = bytes.findLastIndex(buf, newLineDashBoundary.slice(0, 1));
if (j >= 0 && bytes.hasPrefix(newLineDashBoundary, buf.slice(j))) {
return [j, null];
// Otherwise, anything up to the final \n is not part of the boundary and so
// must be part of the body. Also, if the section from the final \n onward is
// not a prefix of the boundary, it too must be part of the body.
const j = findLastIndex(buf, newLineDashBoundary.slice(0, 1));
if (j >= 0 && hasPrefix(newLineDashBoundary, buf.slice(j))) {
return j;
}
return [buf.length, state];
return buf.length;
}
let i = 0;
class PartReader implements Reader, Closer {
n: number = 0;
n: number | EOF = 0;
total: number = 0;
bufState: BufState = null;
index = i++;
constructor(private mr: MultipartReader, public readonly headers: Headers) {}
async read(p: Uint8Array): Promise<ReadResult> {
const br = this.mr.bufReader;
const returnResult = (nread: number, bufState: BufState): ReadResult => {
if (bufState && bufState !== "EOF") {
throw bufState;
// Read into buffer until we identify some data to return,
// or we find a reason to stop (boundary or EOF).
let peekLength = 1;
while (this.n === 0) {
peekLength = max(peekLength, br.buffered());
const peekBuf = await br.peek(peekLength);
if (peekBuf === EOF) {
throw new UnexpectedEOFError();
}
return { nread, eof: bufState === "EOF" };
};
if (this.n === 0 && !this.bufState) {
const [peek] = await br.peek(br.buffered());
const [n, state] = scanUntilBoundary(
peek,
const eof = peekBuf.length < peekLength;
this.n = scanUntilBoundary(
peekBuf,
this.mr.dashBoundary,
this.mr.newLineDashBoundary,
this.total,
this.bufState
eof
);
this.n = n;
this.bufState = state;
if (this.n === 0 && !this.bufState) {
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
const [, state] = await br.peek(peek.length + 1);
this.bufState = state;
if (this.bufState === "EOF") {
this.bufState = new RangeError("unexpected eof");
}
if (this.n === 0) {
// Force buffered I/O to read more into buffer.
assertStrictEq(eof, false);
peekLength++;
}
}
if (this.n === 0) {
return returnResult(0, this.bufState);
if (this.n === EOF) {
return { nread: 0, eof: true };
}
let n = 0;
if (p.byteLength > this.n) {
n = this.n;
}
const buf = p.slice(0, n);
const [nread] = await this.mr.bufReader.readFull(buf);
p.set(buf);
this.total += nread;
const nread = min(p.length, this.n);
const buf = p.subarray(0, nread);
const r = await br.readFull(buf);
assertStrictEq(r, buf);
this.n -= nread;
if (this.n === 0) {
return returnResult(n, this.bufState);
}
return returnResult(n, null);
this.total += nread;
return { nread, eof: false };
}
close(): void {}
@ -212,7 +239,7 @@ export class MultipartReader {
readonly dashBoundary = encoder.encode(`--${this.boundary}`);
readonly bufReader: BufReader;
constructor(private reader: Reader, private boundary: string) {
constructor(reader: Reader, private boundary: string) {
this.bufReader = new BufReader(reader);
}
@ -228,7 +255,7 @@ export class MultipartReader {
const buf = new Buffer(new Uint8Array(maxValueBytes));
for (;;) {
const p = await this.nextPart();
if (!p) {
if (p === EOF) {
break;
}
if (p.formName === "") {
@ -251,7 +278,7 @@ export class MultipartReader {
const n = await copy(buf, p);
if (n > maxMemory) {
// too big, write to disk and flush buffer
const ext = path.extname(p.fileName);
const ext = extname(p.fileName);
const { file, filepath } = await tempFile(".", {
prefix: "multipart-",
postfix: ext
@ -277,7 +304,7 @@ export class MultipartReader {
filename: p.fileName,
type: p.headers.get("content-type"),
content: buf.bytes(),
size: buf.bytes().byteLength
size: buf.length
};
maxMemory -= n;
maxValueBytes -= n;
@ -290,35 +317,32 @@ export class MultipartReader {
private currentPart: PartReader;
private partsRead: number;
private async nextPart(): Promise<PartReader> {
private async nextPart(): Promise<PartReader | EOF> {
if (this.currentPart) {
this.currentPart.close();
}
if (bytes.equal(this.dashBoundary, encoder.encode("--"))) {
if (equal(this.dashBoundary, encoder.encode("--"))) {
throw new Error("boundary is empty");
}
let expectNewPart = false;
for (;;) {
const [line, state] = await this.bufReader.readSlice("\n".charCodeAt(0));
if (state === "EOF" && this.isFinalBoundary(line)) {
break;
}
if (state) {
throw new Error(`aa${state.toString()}`);
const line = await this.bufReader.readSlice("\n".charCodeAt(0));
if (line === EOF) {
throw new UnexpectedEOFError();
}
if (this.isBoundaryDelimiterLine(line)) {
this.partsRead++;
const r = new TextProtoReader(this.bufReader);
const [headers, state] = await r.readMIMEHeader();
if (state) {
throw state;
const headers = await r.readMIMEHeader();
if (headers === EOF) {
throw new UnexpectedEOFError();
}
const np = new PartReader(this, headers);
this.currentPart = np;
return np;
}
if (this.isFinalBoundary(line)) {
break;
return EOF;
}
if (expectNewPart) {
throw new Error(`expecting a new Part; got line ${line}`);
@ -326,28 +350,28 @@ export class MultipartReader {
if (this.partsRead === 0) {
continue;
}
if (bytes.equal(line, this.newLine)) {
if (equal(line, this.newLine)) {
expectNewPart = true;
continue;
}
throw new Error(`unexpected line in next(): ${line}`);
throw new Error(`unexpected line in nextPart(): ${line}`);
}
}
private isFinalBoundary(line: Uint8Array): boolean {
if (!bytes.hasPrefix(line, this.dashBoundaryDash)) {
if (!hasPrefix(line, this.dashBoundaryDash)) {
return false;
}
let rest = line.slice(this.dashBoundaryDash.length, line.length);
return rest.length === 0 || bytes.equal(skipLWSPChar(rest), this.newLine);
return rest.length === 0 || equal(skipLWSPChar(rest), this.newLine);
}
private isBoundaryDelimiterLine(line: Uint8Array): boolean {
if (!bytes.hasPrefix(line, this.dashBoundary)) {
if (!hasPrefix(line, this.dashBoundary)) {
return false;
}
const rest = line.slice(this.dashBoundary.length);
return bytes.equal(skipLWSPChar(rest), this.newLine);
return equal(skipLWSPChar(rest), this.newLine);
}
}
@ -478,7 +502,7 @@ export class MultipartWriter {
await copy(f, file);
}
private flush(): Promise<BufState> {
private flush(): Promise<void> {
return this.bufWriter.flush();
}

View file

@ -7,7 +7,7 @@ import {
assertThrows,
assertThrowsAsync
} from "../testing/asserts.ts";
import { test } from "../testing/mod.ts";
import { test, runIfMain } from "../testing/mod.ts";
import {
matchAfterPrefix,
MultipartReader,
@ -16,6 +16,7 @@ import {
} from "./multipart.ts";
import * as path from "../fs/path.ts";
import { FormFile, isFormFile } from "../multipart/formfile.ts";
import { EOF } from "../io/bufio.ts";
import { StringWriter } from "../io/writers.ts";
const e = new TextEncoder();
@ -25,71 +26,67 @@ const nlDashBoundary = e.encode("\r\n--" + boundary);
test(function multipartScanUntilBoundary1(): void {
const data = `--${boundary}`;
const [n, err] = scanUntilBoundary(
const n = scanUntilBoundary(
e.encode(data),
dashBoundary,
nlDashBoundary,
0,
"EOF"
true
);
assertEquals(n, 0);
assertEquals(err, "EOF");
assertEquals(n, EOF);
});
test(function multipartScanUntilBoundary2(): void {
const data = `foo\r\n--${boundary}`;
const [n, err] = scanUntilBoundary(
const n = scanUntilBoundary(
e.encode(data),
dashBoundary,
nlDashBoundary,
0,
"EOF"
true
);
assertEquals(n, 3);
assertEquals(err, "EOF");
});
test(function multipartScanUntilBoundary4(): void {
const data = `foo\r\n--`;
const [n, err] = scanUntilBoundary(
e.encode(data),
dashBoundary,
nlDashBoundary,
0,
null
);
assertEquals(n, 3);
assertEquals(err, null);
});
test(function multipartScanUntilBoundary3(): void {
const data = `foobar`;
const [n, err] = scanUntilBoundary(
const n = scanUntilBoundary(
e.encode(data),
dashBoundary,
nlDashBoundary,
0,
null
false
);
assertEquals(n, data.length);
assertEquals(err, null);
});
test(function multipartScanUntilBoundary4(): void {
const data = `foo\r\n--`;
const n = scanUntilBoundary(
e.encode(data),
dashBoundary,
nlDashBoundary,
0,
false
);
assertEquals(n, 3);
});
test(function multipartMatchAfterPrefix1(): void {
const data = `${boundary}\r`;
const v = matchAfterPrefix(e.encode(data), e.encode(boundary), null);
const v = matchAfterPrefix(e.encode(data), e.encode(boundary), false);
assertEquals(v, 1);
});
test(function multipartMatchAfterPrefix2(): void {
const data = `${boundary}hoge`;
const v = matchAfterPrefix(e.encode(data), e.encode(boundary), null);
const v = matchAfterPrefix(e.encode(data), e.encode(boundary), false);
assertEquals(v, -1);
});
test(function multipartMatchAfterPrefix3(): void {
const data = `${boundary}`;
const v = matchAfterPrefix(e.encode(data), e.encode(boundary), null);
const v = matchAfterPrefix(e.encode(data), e.encode(boundary), false);
assertEquals(v, 0);
});
@ -211,3 +208,5 @@ test(async function multipartMultipartReader2(): Promise<void> {
await remove(file.tempfile);
}
});
runIfMain(import.meta);

View file

@ -3,7 +3,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import { BufReader, BufState } from "../io/bufio.ts";
import { BufReader, EOF, UnexpectedEOFError } from "../io/bufio.ts";
import { charCode } from "../io/util.ts";
const asciiDecoder = new TextDecoder();
@ -39,9 +39,10 @@ export class TextProtoReader {
/** readLine() reads a single line from the TextProtoReader,
* eliding the final \n or \r\n from the returned string.
*/
async readLine(): Promise<[string, BufState]> {
let [line, err] = await this.readLineSlice();
return [str(line), err];
async readLine(): Promise<string | EOF> {
const s = await this.readLineSlice();
if (s === EOF) return EOF;
return str(s);
}
/** ReadMIMEHeader reads a MIME-style header from r.
@ -64,29 +65,31 @@ export class TextProtoReader {
* "Long-Key": {"Even Longer Value"},
* }
*/
async readMIMEHeader(): Promise<[Headers, BufState]> {
async readMIMEHeader(): Promise<Headers | EOF> {
let m = new Headers();
let line: Uint8Array;
// The first line cannot start with a leading space.
let [buf, err] = await this.r.peek(1);
if (buf[0] == charCode(" ") || buf[0] == charCode("\t")) {
[line, err] = await this.readLineSlice();
let buf = await this.r.peek(1);
if (buf === EOF) {
return EOF;
} else if (buf[0] == charCode(" ") || buf[0] == charCode("\t")) {
line = (await this.readLineSlice()) as Uint8Array;
}
[buf, err] = await this.r.peek(1);
if (err == null && (buf[0] == charCode(" ") || buf[0] == charCode("\t"))) {
buf = await this.r.peek(1);
if (buf === EOF) {
throw new UnexpectedEOFError();
} else if (buf[0] == charCode(" ") || buf[0] == charCode("\t")) {
throw new ProtocolError(
`malformed MIME header initial line: ${str(line)}`
);
}
while (true) {
let [kv, err] = await this.readLineSlice(); // readContinuedLineSlice
if (kv.byteLength === 0) {
return [m, err];
}
let kv = await this.readLineSlice(); // readContinuedLineSlice
if (kv === EOF) throw new UnexpectedEOFError();
if (kv.byteLength === 0) return m;
// Key ends at first colon; should not have trailing spaces
// but they appear in the wild, violating specs, so we remove
@ -125,29 +128,26 @@ export class TextProtoReader {
try {
m.append(key, value);
} catch {}
if (err != null) {
throw err;
}
}
}
async readLineSlice(): Promise<[Uint8Array, BufState]> {
async readLineSlice(): Promise<Uint8Array | EOF> {
// this.closeDot();
let line: Uint8Array;
while (true) {
let [l, more, err] = await this.r.readLine();
if (err != null) {
// Go's len(typed nil) works fine, but not in JS
return [new Uint8Array(0), err];
}
const r = await this.r.readLine();
if (r === EOF) return EOF;
const { line: l, more } = r;
// Avoid the copy if the first call produced a full line.
if (line == null && !more) {
if (!line && !more) {
// TODO(ry):
// This skipSpace() is definitely misplaced, but I don't know where it
// comes from nor how to fix it.
if (this.skipSpace(l) === 0) {
return [new Uint8Array(0), null];
return new Uint8Array(0);
}
return [l, null];
return l;
}
line = append(line, l);
@ -155,7 +155,7 @@ export class TextProtoReader {
break;
}
}
return [line, null];
return line;
}
skipSpace(l: Uint8Array): number {

View file

@ -3,11 +3,21 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import { BufReader } from "../io/bufio.ts";
import { BufReader, EOF } from "../io/bufio.ts";
import { TextProtoReader, ProtocolError } from "./mod.ts";
import { stringsReader } from "../io/util.ts";
import { assert, assertEquals, assertThrows } from "../testing/asserts.ts";
import { test } from "../testing/mod.ts";
import {
assert,
assertEquals,
assertNotEquals,
assertThrows
} from "../testing/asserts.ts";
import { test, runIfMain } from "../testing/mod.ts";
function assertNotEOF<T extends {}>(val: T | EOF): T {
assertNotEquals(val, EOF);
return val as T;
}
function reader(s: string): TextProtoReader {
return new TextProtoReader(new BufReader(stringsReader(s)));
@ -21,25 +31,21 @@ function reader(s: string): TextProtoReader {
// });
test(async function textprotoReadEmpty(): Promise<void> {
let r = reader("");
let [, err] = await r.readMIMEHeader();
// Should not crash!
assertEquals(err, "EOF");
const r = reader("");
const m = await r.readMIMEHeader();
assertEquals(m, EOF);
});
test(async function textprotoReader(): Promise<void> {
let r = reader("line1\nline2\n");
let [s, err] = await r.readLine();
const r = reader("line1\nline2\n");
let s = await r.readLine();
assertEquals(s, "line1");
assert(err == null);
[s, err] = await r.readLine();
s = await r.readLine();
assertEquals(s, "line2");
assert(err == null);
[s, err] = await r.readLine();
assertEquals(s, "");
assert(err == "EOF");
s = await r.readLine();
assert(s === EOF);
});
test({
@ -48,10 +54,9 @@ test({
const input =
"my-key: Value 1 \r\nLong-key: Even Longer Value\r\nmy-Key: Value 2\r\n\n";
const r = reader(input);
const [m, err] = await r.readMIMEHeader();
const m = assertNotEOF(await r.readMIMEHeader());
assertEquals(m.get("My-Key"), "Value 1, Value 2");
assertEquals(m.get("Long-key"), "Even Longer Value");
assert(!err);
}
});
@ -60,9 +65,8 @@ test({
async fn(): Promise<void> {
const input = "Foo: bar\n\n";
const r = reader(input);
let [m, err] = await r.readMIMEHeader();
const m = assertNotEOF(await r.readMIMEHeader());
assertEquals(m.get("Foo"), "bar");
assert(!err);
}
});
@ -71,9 +75,8 @@ test({
async fn(): Promise<void> {
const input = ": bar\ntest-1: 1\n\n";
const r = reader(input);
let [m, err] = await r.readMIMEHeader();
const m = assertNotEOF(await r.readMIMEHeader());
assertEquals(m.get("Test-1"), "1");
assert(!err);
}
});
@ -86,11 +89,9 @@ test({
data.push("x");
}
const sdata = data.join("");
const r = reader(`Cookie: ${sdata}\r\n`);
let [m] = await r.readMIMEHeader();
const r = reader(`Cookie: ${sdata}\r\n\r\n`);
const m = assertNotEOF(await r.readMIMEHeader());
assertEquals(m.get("Cookie"), sdata);
// TODO re-enable, here err === "EOF" is has to be null
// assert(!err);
}
});
@ -106,12 +107,11 @@ test({
"Audio Mode : None\r\n" +
"Privilege : 127\r\n\r\n";
const r = reader(input);
let [m, err] = await r.readMIMEHeader();
const m = assertNotEOF(await r.readMIMEHeader());
assertEquals(m.get("Foo"), "bar");
assertEquals(m.get("Content-Language"), "en");
assertEquals(m.get("SID"), "0");
assertEquals(m.get("Privilege"), "127");
assert(!err);
// Not a legal http header
assertThrows(
(): void => {
@ -176,9 +176,10 @@ test({
"------WebKitFormBoundaryimeZ2Le9LjohiUiG--\r\n\n"
];
const r = reader(input.join(""));
let [m, err] = await r.readMIMEHeader();
const m = assertNotEOF(await r.readMIMEHeader());
assertEquals(m.get("Accept"), "*/*");
assertEquals(m.get("Content-Disposition"), 'form-data; name="test"');
assert(!err);
}
});
runIfMain(import.meta);

126
ws/mod.ts
View file

@ -4,7 +4,7 @@ import { decode, encode } from "../strings/mod.ts";
type Conn = Deno.Conn;
type Writer = Deno.Writer;
import { BufReader, BufWriter } from "../io/bufio.ts";
import { BufReader, BufWriter, EOF, UnexpectedEOFError } from "../io/bufio.ts";
import { readLong, readShort, sliceLongToBytes } from "../io/ioutil.ts";
import { Sha1 } from "./sha1.ts";
import { writeResponse } from "../http/server.ts";
@ -130,8 +130,7 @@ export async function writeFrame(
header = append(header, frame.payload);
const w = BufWriter.create(writer);
await w.write(header);
const err = await w.flush();
if (err) throw err;
await w.flush();
}
/** Read websocket frame from given BufReader */
@ -403,15 +402,71 @@ export function createSecKey(): string {
return btoa(key);
}
async function handshake(
url: URL,
headers: Headers,
bufReader: BufReader,
bufWriter: BufWriter
): Promise<void> {
const { hostname, pathname, searchParams } = url;
const key = createSecKey();
if (!headers.has("host")) {
headers.set("host", hostname);
}
headers.set("upgrade", "websocket");
headers.set("connection", "upgrade");
headers.set("sec-websocket-key", key);
let headerStr = `GET ${pathname}?${searchParams || ""} HTTP/1.1\r\n`;
for (const [key, value] of headers) {
headerStr += `${key}: ${value}\r\n`;
}
headerStr += "\r\n";
await bufWriter.write(encode(headerStr));
await bufWriter.flush();
const tpReader = new TextProtoReader(bufReader);
const statusLine = await tpReader.readLine();
if (statusLine === EOF) {
throw new UnexpectedEOFError();
}
const m = statusLine.match(/^(?<version>\S+) (?<statusCode>\S+) /);
if (!m) {
throw new Error("ws: invalid status line: " + statusLine);
}
const { version, statusCode } = m.groups;
if (version !== "HTTP/1.1" || statusCode !== "101") {
throw new Error(
`ws: server didn't accept handshake: ` +
`version=${version}, statusCode=${statusCode}`
);
}
const responseHeaders = await tpReader.readMIMEHeader();
if (responseHeaders === EOF) {
throw new UnexpectedEOFError();
}
const expectedSecAccept = createSecAccept(key);
const secAccept = responseHeaders.get("sec-websocket-accept");
if (secAccept !== expectedSecAccept) {
throw new Error(
`ws: unexpected sec-websocket-accept header: ` +
`expected=${expectedSecAccept}, actual=${secAccept}`
);
}
}
/** Connect to given websocket endpoint url. Endpoint must be acceptable for URL */
export async function connectWebSocket(
endpoint: string,
headers: Headers = new Headers()
): Promise<WebSocket> {
const url = new URL(endpoint);
const { hostname, pathname, searchParams } = url;
let port = url.port;
if (!url.port) {
let { hostname, port } = url;
if (!port) {
if (url.protocol === "http" || url.protocol === "ws") {
port = "80";
} else if (url.protocol === "https" || url.protocol === "wss") {
@ -419,62 +474,13 @@ export async function connectWebSocket(
}
}
const conn = await Deno.dial("tcp", `${hostname}:${port}`);
const abortHandshake = (err: Error): void => {
conn.close();
throw err;
};
const bufWriter = new BufWriter(conn);
const bufReader = new BufReader(conn);
await bufWriter.write(
encode(`GET ${pathname}?${searchParams || ""} HTTP/1.1\r\n`)
);
const key = createSecKey();
if (!headers.has("host")) {
headers.set("host", hostname);
}
headers.set("upgrade", "websocket");
headers.set("connection", "upgrade");
headers.set("sec-websocket-key", key);
let headerStr = "";
for (const [key, value] of headers) {
headerStr += `${key}: ${value}\r\n`;
}
headerStr += "\r\n";
await bufWriter.write(encode(headerStr));
let err, statusLine, responseHeaders;
err = await bufWriter.flush();
if (err) {
throw new Error("ws: failed to send handshake: " + err);
}
const tpReader = new TextProtoReader(bufReader);
[statusLine, err] = await tpReader.readLine();
if (err) {
abortHandshake(new Error("ws: failed to read status line: " + err));
}
const m = statusLine.match(/^(.+?) (.+?) (.+?)$/);
if (!m) {
abortHandshake(new Error("ws: invalid status line: " + statusLine));
}
const [_, version, statusCode] = m;
if (version !== "HTTP/1.1" || statusCode !== "101") {
abortHandshake(
new Error(
`ws: server didn't accept handshake: version=${version}, statusCode=${statusCode}`
)
);
}
[responseHeaders, err] = await tpReader.readMIMEHeader();
if (err) {
abortHandshake(new Error("ws: failed to parse response headers: " + err));
}
const expectedSecAccept = createSecAccept(key);
const secAccept = responseHeaders.get("sec-websocket-accept");
if (secAccept !== expectedSecAccept) {
abortHandshake(
new Error(
`ws: unexpected sec-websocket-accept header: expected=${expectedSecAccept}, actual=${secAccept}`
)
);
try {
await handshake(url, headers, bufReader, bufWriter);
} catch (err) {
conn.close();
throw err;
}
return new WebSocketImpl(conn, {
bufWriter,

View file

@ -107,8 +107,9 @@ test(async function wsReadUnmaskedPingPongFrame(): Promise<void> {
});
test(async function wsReadUnmaskedBigBinaryFrame(): Promise<void> {
const payloadLength = 0x100;
const a = [0x82, 0x7e, 0x01, 0x00];
for (let i = 0; i < 256; i++) {
for (let i = 0; i < payloadLength; i++) {
a.push(i);
}
const buf = new BufReader(new Buffer(new Uint8Array(a)));
@ -116,12 +117,13 @@ test(async function wsReadUnmaskedBigBinaryFrame(): Promise<void> {
assertEquals(bin.opcode, OpCode.BinaryFrame);
assertEquals(bin.isLastFrame, true);
assertEquals(bin.mask, undefined);
assertEquals(bin.payload.length, 256);
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 < 0xffff; i++) {
for (let i = 0; i < payloadLength; i++) {
a.push(i);
}
const buf = new BufReader(new Buffer(new Uint8Array(a)));
@ -129,7 +131,7 @@ test(async function wsReadUnmaskedBigBigBinaryFrame(): Promise<void> {
assertEquals(bin.opcode, OpCode.BinaryFrame);
assertEquals(bin.isLastFrame, true);
assertEquals(bin.mask, undefined);
assertEquals(bin.payload.length, 0xffff + 1);
assertEquals(bin.payload.length, payloadLength);
});
test(async function wsCreateSecAccept(): Promise<void> {