From 7fd4c589dfa87b743047c617bcb036f01b48c0d6 Mon Sep 17 00:00:00 2001 From: hippoz Date: Tue, 2 Feb 2021 10:17:49 +0200 Subject: [PATCH] implement basic parsing --- lib/Parser.js | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++ lib/Socket.js | 7 +++- 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 lib/Parser.js diff --git a/lib/Parser.js b/lib/Parser.js new file mode 100644 index 0000000..6a7ed20 --- /dev/null +++ b/lib/Parser.js @@ -0,0 +1,101 @@ +class WebsocketFrame { + constructor() { + // Just defining the structure of the WebsocketFrame, + // I could actually just use a normal object but I + // might need to add some methods to this in the future + // so it's good enough + + // 1 byte - first byte + this.FIN = undefined; + this.RSVx = undefined; + this.Opcode = undefined; + + // 1 byte - second byte + this.MASK = undefined; + this.PayloadLen = undefined; + + // 4 bytes - masking key + this.MaskingKey = undefined; + + // (Extension Data + Application Data[PayloadLen]) (kind of) - payload data + // TODO: Separate extension data from application data + this.PayloadData = undefined; + } +} + +const parseWebsocketFrame = (data) => { + const firstByte = data.getUint8(0); + const secondByte = data.getUint8(1); + + const frame = new WebsocketFrame(); + + // 1 byte - first byte + frame.FIN = (firstByte & 0x01); // 0x01[0b10000000] - Get most significant bit (FIN) + frame.RSVx = (firstByte & 0x70) // 0x70[0b01110000] - Get the 3 bits after the most significant bit (RSVx) + frame.Opcode = (firstByte & 0x0F) // 0xF[0b00001111] - Get the last 4 bits (Opcode) + + // 1 byte - second byte + frame.MASK = (secondByte & 0x01) // 0x01[0b10000000] - Get most significant bit (MASK) + frame.PayloadLen = (secondByte & 0x7F) // 0x7F[0b01111111] - Get last 7 bits (Payload len) + + let maskingKeyOffset = 2; // By default, theres a 2 byte offset. We will modify this in the cases below. + + // Handle Payload len cases and set the masking key offset + if (frame.PayloadLen === 126) { + frame.PayloadLen = data.getUint16(2); + maskingKeyOffset = 4; // 4 byte offset, because we also read 2 bytes with getUint16 above (2 bytes (normal size) + 2 bytes) + } else if (frame.PayloadLen === 127) { + frame.PayloadLen = data.getBigUint64(2); + maskingKeyOffset = 10; // 10 byte offset, because we also read 8 bytes with getBigUint64 above (2 bytes (normal size) + 8 bytes) + } + + const maskingKeyEnd = maskingKeyOffset + 4; // (4 bytes because it is a 32 bit value) + if (frame.MASK) frame.MaskingKey = data.buffer.slice(maskingKeyOffset, maskingKeyEnd); // Create a new buffer starting at the masking key offset and ending at the masking key end (duh). + + // TODO: Separate extension data from application data + frame.PayloadData = data.buffer.slice(maskingKeyEnd, maskingKeyEnd + frame.PayloadLen); // Create a new buffer that starts at the end of the masking key and ends at (the end of the masking key + payload length) + + // TODO: implement unmasked string decoding + if (frame.MASK) { + switch (frame.Opcode) { + case 1: { // Denotes a text frame + const payloadDataView = new DataView(frame.PayloadData); + const maskingKeyView = new DataView(frame.MaskingKey); + + let decoded = ''; + + for (let i = 0; i < frame.PayloadData.byteLength; i++) { + decoded += (String.fromCharCode(payloadDataView.getUint8(i) ^ maskingKeyView.getUint8(i % 4))); + } + + frame.DecodedPayloadData = decoded; + + break; + } + + case 2: { // Denotes a binary frame + // TODO: untested + const payloadDataView = new DataView(frame.PayloadData); + const maskingKeyView = new DataView(frame.MaskingKey); + + const decoded = []; + + for (let i = 0; i < frame.PayloadData.byteLength; i++) { + decoded.push(payloadDataView.getUint8(i) ^ maskingKeyView.getUint8(i % 4)); + } + + frame.DecodedPayloadData = decoded; + + break; + } + } + } + + return frame; +} + +module.exports = { + WebsocketFrame, + + parseWebsocketFrame +}; \ No newline at end of file diff --git a/lib/Socket.js b/lib/Socket.js index 8399329..c51f080 100644 --- a/lib/Socket.js +++ b/lib/Socket.js @@ -1,4 +1,5 @@ const constants = require("./constants"); +const parser = require('./Parser'); class Socket { constructor({ initialState='CONNECTING', socket }) { @@ -10,11 +11,15 @@ class Socket { this._socket.on('data', (e) => { if (this._state !== constants.states.OPEN) return; - console.log(e.toString()); + this._decodePayload(e.buffer); }); } } +Socket.prototype._decodePayload = function(payload) { + console.log(parser.parseWebsocketFrame(new DataView(payload))); +}; + Socket.prototype._setConnectionState = function(state) { this._state = state; };