const constants = require("./constants"); const { createLog } = require('./logger'); const parserLog = createLog([ 'Wormhole', 'Parser' ]); 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; this.PayloadLenEx = undefined; // This is for when PayloadLen needs to be extended // 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; } } WebsocketFrame.prototype.toBuffer = function() { const packet = []; const firstByte = this.FIN << 0x07 | this.RSVx << 0x06 | this.Opcode; const secondByte = this.MASK << 0x07 | this.PayloadLen; const headerBuffer = Buffer.from([firstByte, secondByte]); packet.push(headerBuffer); let lenBuffer; if (this.PayloadLen === 126) { lenBuffer = Buffer.alloc(2); lenBuffer.writeUint16BE(this.PayloadLenEx); } else if (this.PayloadLen === 127) { lenBuffer = Buffer.alloc(8); lenBuffer.writeBigUint64BE(BigInt(this.PayloadLenEx)); } lenBuffer && packet.push(lenBuffer); this.MASK && packet.push(this.MaskingKey); packet.push(this.PayloadData); return Buffer.concat(packet); }; const parseWebsocketFrame = (data) => { const firstByte = data.getUint8(0); const secondByte = data.getUint8(1); const frame = new WebsocketFrame(); // 1 byte - first byte frame.FIN = (firstByte >> 7); // >> 7 - Get most significant bit (FIN) frame.RSVx = (firstByte & 0x70) >> 4; // 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 >> 7); // >> 7 - 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.PayloadLenEx = 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.PayloadLenEx = data.getBigUint64(2); maskingKeyOffset = 10; // 10 byte offset, because we also read 8 bytes with getBigUint64 above (2 bytes (normal size) + 8 bytes) } else { frame.PayloadLenEx = frame.PayloadLen; } let maskingKeyEnd = frame.MASK ? maskingKeyOffset + 4 : maskingKeyOffset; // (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, Number(BigInt(maskingKeyEnd) + BigInt(frame.PayloadLenEx))); // 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 constants.opcodes.TEXT_FRAME: { // 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 constants.opcodes.BINARY_FRAME: { // Denotes a binary frame 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 = Uint8Array.from(decoded); break; } } } return frame; } module.exports = { WebsocketFrame, parseWebsocketFrame };