Skip to content

Instantly share code, notes, and snippets.

@prettydiff
Created September 13, 2021 23:15
Show Gist options
  • Save prettydiff/ce40b6509f72a5fadfc6588b76b76ce8 to your computer and use it in GitHub Desktop.
Save prettydiff/ce40b6509f72a5fadfc6588b76b76ce8 to your computer and use it in GitHub Desktop.
websocket decode
unmask = function terminal_commands_websocket_unmask(socket:socketClient, data:Buffer):Uint8Array {
// reference - https://cookie.engineer/weblog/articles/implementers-guide-to-websockets.html
if (socket.fragment === null) {
socket.fragment = {
opcode: 0x00,
payload: Buffer.alloc(0)
};
}
if (data.length < 3) {
return null;
}
const chunk:socketChunk = {
close: false,
fragment: false,
payload: null,
response: null
},
fragment:socketFragment = socket.fragment,
opcode:number = (data[0] & 15),
opLabel:string = (function terminal_commands_websocket_unmask_opLabel():string {
if (opcode === 0x00) {
return "continuation";
}
if (opcode === 0x01) {
return "text";
}
if (opcode === 0x02) {
return "binary";
}
if (opcode === 0x08) {
return "close";
}
if (opcode === 0x09) {
return "ping";
}
if (opcode === 0x0a) {
return "pong";
}
return "unsupported";
}()),
finFlag:boolean = ((data[0] & 128) === 128),
maskFlag:boolean = ((data[1] & 128) === 128),
frames:socketFrame = {
binary: function terminal_commands_websocket_unmask_binary():void {
// 0x01: Text Frame (possibly fragmented)
// 0x02: Binary Frame (possibly fragmented)
if (finFlag === true) {
chunk.payload = payload;
} else if (payload !== null) {
const payloadPartial:Buffer = Buffer.alloc(fragment.payload.length + dataLength);
fragment.payload.copy(payloadPartial, 0);
payload.set(payloadPartial, fragment.payload.length);
fragment.payload = payloadPartial;
fragment.opcode = opcode;
}
},
close: function terminal_commands_websocket_unmask_close(code:1000|1002):void {
// 0x08: Connection Close Frame
const buffer:Buffer = Buffer.alloc(4);
// close status codes (RFC 6455, 7.4.1)
// * 1000 (normal) normal closure
// * 1001 (normal) going away
// * 1002 (normal) protocol error
// * 1015 (normal, only server) TLS encryption error
//
// * 1003 (exotic) terminate connection due to data error (e.g. only text frame supported, but binary frame received)
// * 1007 (exotic) data inconsistency (e.g. no `utf8` encoded text frame)
// * 1008 (exotic) policy violation
// * 1009 (exotic) message too big to process
// * 1010 (exotic, only client) terminate connection because server did not confirm extensions
// * 1011 (exotic, only server) unexpected error
//
// * 1004-1006 reserved for future use (DO NOT USE)
buffer[0] = 128 + 0x08; // close
buffer[1] = 0 + 0x02; // unmasked (client and server)
buffer[1] = (code >> 8) & 0xff;
buffer[2] = (code >> 0) & 0xff;
chunk.close = true;
chunk.response = buffer;
},
continuation: function terminal_commands_websocket_unmask_continuation():void {
// 0x00: Continuation Frame
if (payload !== null) {
const payloadPartial:Buffer = Buffer.alloc(fragment.payload.length + dataLength);
fragment.payload.copy(payload, 0);
payload.set(payloadPartial, fragment.payload.length);
fragment.payload = payloadPartial;
}
if (finFlag === true) {
chunk.payload = fragment.payload;
fragment.opcode = 0x00;
fragment.payload = Buffer.alloc(0);
}
},
ping: function terminal_commands_websocket_unmask_ping():void {
// 0x09: Ping Frame
const buffer:Buffer = Buffer.alloc(2 + (payload === null ? 0 : dataLength));
buffer[0] = 128 + 0x0a; // fin, pong
buffer[1] = 0 + 0x00; // unmasked
if (payload !== null) {
buffer.write(payload.toString(), 2);
}
chunk.response = buffer;
},
pong: function terminal_commands_websocket_unmask_pong():void {
// 0x0a: Pong Frame
chunk.fragment = true;
},
};
let dataLength:number = data[1] & 127,
mask:Buffer = Buffer.alloc(4),
payload:Uint8Array = null;
if (dataLength < 126) {
// 7 bit payload
if (maskFlag === true) {
mask = data.slice(2, 6);
payload = data.slice(6, 6 + dataLength);
} else {
mask = null;
payload = data.slice(2, 2 + dataLength);
}
} else if (dataLength === 126) {
// 16 bit payload
dataLength = (data[2] << 8) + data[3];
if (dataLength > data.length) {
chunk.fragment = true;
return null;
}
if (maskFlag === true) {
mask = data.slice(4, 8);
payload = data.slice(8, 8 + dataLength);
} else {
mask = null;
payload = data.slice(4, 4 + dataLength);
}
} else {
// 64 bit payload
const hi = (data[2] * 0x1000000) + ((data[3] << 16) | (data[4] << 8) | data[5]),
lo = (data[6] * 0x1000000) + ((data[7] << 16) | (data[8] << 8) | data[9]);
dataLength = (hi * 4294967296) + lo;
if (dataLength > data.length) {
chunk.fragment = true;
return null;
}
if (maskFlag === true) {
mask = data.slice(10, 14);
payload = data.slice(14, 14 + dataLength);
} else {
mask = null;
payload = data.slice(10, 10 + dataLength);
}
}
if (mask !== null) {
payload = payload.map((value, index) => value ^ mask[index % 4]);
}
if (opcode === 0x00) {
frames.continuation();
} else if (opcode === 0x01 || opcode === 0x02) {
frames.binary();
} else if (opcode === 0x08) {
frames.close(1000);
} else if (opcode === 0x09) {
frames.ping();
} else if (opcode === 0x0a) {
frames.pong();
} else {
frames.close(1002);
}
if (chunk.response !== null) {
socket.write(chunk.response);
if (chunk.close === true) {
socket.end();
}
}
return chunk.payload;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment