From 7df26af3e3750d915496365e511828fcac365497 Mon Sep 17 00:00:00 2001 From: Heorhii Date: Wed, 8 Nov 2023 15:20:38 +0100 Subject: [PATCH] Rewrite class Frame --- lib/websocket.js | 90 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 09f6884b..80b4bfac 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -26,6 +26,91 @@ const DEFAULT_OPTIONS = { const xor = (cond1, cond2) => (cond1 || cond2) && !(cond1 && cond2); class Frame { + #frame = null; + #masked = null; + #dataOffset = null; + #mask = null; + #length = null; + + constructor(frame) { + this.#frame = frame; + const length = BigInt(this.#frame[1] & 0x7f); + this.#masked = true; + switch (length) { + case 127n: + this.#dataOffset = 2 + 8 + MASK_LENGTH; + this.#mask = frame.subarray(2 + 8, 10 + MASK_LENGTH); + this.#length = frame.readBigUInt64BE(2); + break; + case 126n: + this.#dataOffset = 2 + 2 + MASK_LENGTH; + this.#mask = frame.subarray(2 + 2, 4 + MASK_LENGTH); + this.#length = BigInt(this.#frame.readUInt16BE(2)); + break; + default: + this.#dataOffset = 2 + MASK_LENGTH; + this.#mask = frame.subarray(2, 2 + MASK_LENGTH); + this.#length = length; + } + } + + unmask() { + if (!this.#masked) return; + for (let i = 0n; i < this.#length; ++i) { + this.#frame[BigInt(this.#dataOffset) + i] ^= + this.#mask[i & 0x0000000000000003n]; + } + } + + toString() { + return this.#frame.toString( + 'utf8', + this.#dataOffset, + Number(BigInt(this.#dataOffset) + this.#length), + ); + } + + get frame() { + return this.#frame; + } + + static from(data) { + if (Buffer.isBuffer(data)) { + if (data.length === 0) throw new Error('Empty frame!'); + // let invalidStructureCheck = false; + if ((data[1] & 0x80) !== 0x80) throw new Error('1002: protocol error'); + return new Frame(data); + } + + if (typeof data === 'string') { + if (data.length === 0) throw new Error('Empty string!'); + const payload = Buffer.from(data); + const length = payload.length; + let meta = Buffer.alloc(2); + meta[0] = 0x81; // FIN = 1, RSV = 0b000, opcode = 0b0001 (text frame) + if (length < LEN_16_BIT) { + meta[1] = length; + } else if (length < MAX_16_BIT) { + const len = Buffer.alloc(2); + len.writeUint16BE(length, 0); + meta[1] = LEN_16_BIT; + meta = Buffer.concat([meta, len]); + } else if (length < MAX_64_BIT) { + const len = Buffer.alloc(8); + len.writeBigUInt64BE(BigInt(length), 0); + meta[1] = LEN_64_BIT; + this.meta = Buffer.concat([meta, len]); + } else { + throw new Error('string is too long to encode in one frame!'); + } + const frame = Buffer.concat([meta, payload]); + return new Frame(Buffer.from(frame)); + } + + throw new Error('Unsupported'); + } +} +class FrameO { #frame = null; #mask = null; #dataOffset = 6; @@ -147,14 +232,15 @@ class Connection { } send(text) { - const frame = new Frame(text); + const frame = Frame.from(text); this.socket.write(frame.frame); } receive(data) { console.log('data: ', data[0], data.length); if (data[0] !== OPCODE_SHORT) return; - const frame = new Frame(data); + const frame = Frame.from(data); + frame.unmask(); const text = frame.toString(); console.log('Message:', text); this.send(`Echo "${text}"`);