implement basic parsing
This commit is contained in:
parent
1b8dcd0a49
commit
7fd4c589df
2 changed files with 107 additions and 1 deletions
101
lib/Parser.js
Normal file
101
lib/Parser.js
Normal file
|
@ -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
|
||||||
|
};
|
|
@ -1,4 +1,5 @@
|
||||||
const constants = require("./constants");
|
const constants = require("./constants");
|
||||||
|
const parser = require('./Parser');
|
||||||
|
|
||||||
class Socket {
|
class Socket {
|
||||||
constructor({ initialState='CONNECTING', socket }) {
|
constructor({ initialState='CONNECTING', socket }) {
|
||||||
|
@ -10,11 +11,15 @@ class Socket {
|
||||||
this._socket.on('data', (e) => {
|
this._socket.on('data', (e) => {
|
||||||
if (this._state !== constants.states.OPEN) return;
|
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) {
|
Socket.prototype._setConnectionState = function(state) {
|
||||||
this._state = state;
|
this._state = state;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue