2021-02-02 10:17:49 +02:00
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 ;
2021-02-03 02:18:28 +02:00
this . PayloadLenEx = undefined ; // This is for when PayloadLen needs to be extended
2021-02-02 10:17:49 +02:00
// 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 ;
}
}
2021-02-03 02:18:28 +02:00
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 ) ;
} ;
2021-02-02 10:17:49 +02:00
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 ) {
2021-02-03 02:18:28 +02:00
frame . PayloadLenEx = data . getUint16 ( 2 ) ;
2021-02-02 10:17:49 +02:00
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 ) {
2021-02-03 02:18:28 +02:00
frame . PayloadLenEx = data . getBigUint64 ( 2 ) ;
2021-02-02 10:17:49 +02:00
maskingKeyOffset = 10 ; // 10 byte offset, because we also read 8 bytes with getBigUint64 above (2 bytes (normal size) + 8 bytes)
2021-02-03 02:18:28 +02:00
} else {
frame . PayloadLenEx = frame . PayloadLen ;
2021-02-02 10:17:49 +02:00
}
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
2021-02-03 02:18:28 +02:00
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)
2021-02-02 10:17:49 +02:00
// 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
} ;