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); buf.writeUint16BE(this.PayloadLenEx); } else if (this.PayloadLen === 127) { lenBuffer = Buffer.alloc(8); buf.writeBigUint64BE(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 & 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.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; } 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.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 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 };