|
|
@@ -0,0 +1,69 @@
|
|
|
+const crypto = require('crypto');
|
|
|
+
|
|
|
+const OPCODES = { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG: 0x0A };
|
|
|
+const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
|
|
|
+
|
|
|
+function computeAcceptKey(clientKey) {
|
|
|
+ return crypto.createHash('sha1').update(clientKey + WS_MAGIC).digest('base64');
|
|
|
+}
|
|
|
+
|
|
|
+function encodeFrame(opcode, payload) {
|
|
|
+ const fin = 0x80;
|
|
|
+ const len = payload.length;
|
|
|
+ let header;
|
|
|
+
|
|
|
+ if (len < 126) {
|
|
|
+ header = Buffer.alloc(2);
|
|
|
+ header[0] = fin | opcode;
|
|
|
+ header[1] = len;
|
|
|
+ } else if (len < 65536) {
|
|
|
+ header = Buffer.alloc(4);
|
|
|
+ header[0] = fin | opcode;
|
|
|
+ header[1] = 126;
|
|
|
+ header.writeUInt16BE(len, 2);
|
|
|
+ } else {
|
|
|
+ header = Buffer.alloc(10);
|
|
|
+ header[0] = fin | opcode;
|
|
|
+ header[1] = 127;
|
|
|
+ header.writeBigUInt64BE(BigInt(len), 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ return Buffer.concat([header, payload]);
|
|
|
+}
|
|
|
+
|
|
|
+function decodeFrame(buffer) {
|
|
|
+ if (buffer.length < 2) return null;
|
|
|
+
|
|
|
+ const secondByte = buffer[1];
|
|
|
+ const opcode = buffer[0] & 0x0F;
|
|
|
+ const masked = (secondByte & 0x80) !== 0;
|
|
|
+ let payloadLen = secondByte & 0x7F;
|
|
|
+ let offset = 2;
|
|
|
+
|
|
|
+ if (!masked) throw new Error('Client frames must be masked');
|
|
|
+
|
|
|
+ if (payloadLen === 126) {
|
|
|
+ if (buffer.length < 4) return null;
|
|
|
+ payloadLen = buffer.readUInt16BE(2);
|
|
|
+ offset = 4;
|
|
|
+ } else if (payloadLen === 127) {
|
|
|
+ if (buffer.length < 10) return null;
|
|
|
+ payloadLen = Number(buffer.readBigUInt64BE(2));
|
|
|
+ offset = 10;
|
|
|
+ }
|
|
|
+
|
|
|
+ const maskOffset = offset;
|
|
|
+ const dataOffset = offset + 4;
|
|
|
+ const totalLen = dataOffset + payloadLen;
|
|
|
+ if (buffer.length < totalLen) return null;
|
|
|
+
|
|
|
+ const mask = buffer.slice(maskOffset, dataOffset);
|
|
|
+ const data = Buffer.alloc(payloadLen);
|
|
|
+ for (let i = 0; i < payloadLen; i++) {
|
|
|
+ data[i] = buffer[dataOffset + i] ^ mask[i % 4];
|
|
|
+ }
|
|
|
+
|
|
|
+ return { opcode, payload: data, bytesConsumed: totalLen };
|
|
|
+}
|
|
|
+
|
|
|
+module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES };
|