148 lines
3.4 KiB
JavaScript
148 lines
3.4 KiB
JavaScript
'use strict';
|
|
|
|
const { maxUnsigned16Bit, opcodes } = require('./constants');
|
|
|
|
const BUFFER_SIZE = 8 * 1024;
|
|
|
|
/** @type {import('crypto')} */
|
|
let crypto;
|
|
let buffer = null;
|
|
let bufIdx = BUFFER_SIZE;
|
|
|
|
try {
|
|
crypto = require('node:crypto');
|
|
/* c8 ignore next 3 */
|
|
} catch {
|
|
crypto = {
|
|
// not full compatibility, but minimum.
|
|
randomFillSync: function randomFillSync(buffer, _offset, _size) {
|
|
for (let i = 0; i < buffer.length; ++i) {
|
|
buffer[i] = (Math.random() * 255) | 0;
|
|
}
|
|
return buffer;
|
|
},
|
|
};
|
|
}
|
|
|
|
function generateMask() {
|
|
if (bufIdx === BUFFER_SIZE) {
|
|
bufIdx = 0;
|
|
crypto.randomFillSync(
|
|
(buffer ??= Buffer.allocUnsafeSlow(BUFFER_SIZE)),
|
|
0,
|
|
BUFFER_SIZE
|
|
);
|
|
}
|
|
return [
|
|
buffer[bufIdx++],
|
|
buffer[bufIdx++],
|
|
buffer[bufIdx++],
|
|
buffer[bufIdx++],
|
|
];
|
|
}
|
|
|
|
class WebsocketFrameSend {
|
|
/**
|
|
* @param {Buffer|undefined} data
|
|
*/
|
|
constructor(data) {
|
|
this.frameData = data;
|
|
}
|
|
|
|
createFrame(opcode) {
|
|
const frameData = this.frameData;
|
|
const maskKey = generateMask();
|
|
const bodyLength = frameData?.byteLength ?? 0;
|
|
|
|
/** @type {number} */
|
|
let payloadLength = bodyLength; // 0-125
|
|
let offset = 6;
|
|
|
|
if (bodyLength > maxUnsigned16Bit) {
|
|
offset += 8; // payload length is next 8 bytes
|
|
payloadLength = 127;
|
|
} else if (bodyLength > 125) {
|
|
offset += 2; // payload length is next 2 bytes
|
|
payloadLength = 126;
|
|
}
|
|
|
|
const buffer = Buffer.allocUnsafe(bodyLength + offset);
|
|
|
|
// Clear first 2 bytes, everything else is overwritten
|
|
buffer[0] = buffer[1] = 0;
|
|
buffer[0] |= 0x80; // FIN
|
|
buffer[0] = (buffer[0] & 0xf0) + opcode; // opcode
|
|
|
|
/*! ws. MIT License. Einar Otto Stangvik <einaros@gmail.com> */
|
|
buffer[offset - 4] = maskKey[0];
|
|
buffer[offset - 3] = maskKey[1];
|
|
buffer[offset - 2] = maskKey[2];
|
|
buffer[offset - 1] = maskKey[3];
|
|
|
|
buffer[1] = payloadLength;
|
|
|
|
if (payloadLength === 126) {
|
|
buffer.writeUInt16BE(bodyLength, 2);
|
|
} else if (payloadLength === 127) {
|
|
// Clear extended payload length
|
|
buffer[2] = buffer[3] = 0;
|
|
buffer.writeUIntBE(bodyLength, 4, 6);
|
|
}
|
|
|
|
buffer[1] |= 0x80; // MASK
|
|
|
|
// mask body
|
|
for (let i = 0; i < bodyLength; ++i) {
|
|
buffer[offset + i] = frameData[i] ^ maskKey[i & 3];
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
/**
|
|
* @param {Uint8Array} buffer
|
|
*/
|
|
static createFastTextFrame(buffer) {
|
|
const maskKey = generateMask();
|
|
|
|
const bodyLength = buffer.length;
|
|
|
|
// mask body
|
|
for (let i = 0; i < bodyLength; ++i) {
|
|
buffer[i] ^= maskKey[i & 3];
|
|
}
|
|
|
|
let payloadLength = bodyLength;
|
|
let offset = 6;
|
|
|
|
if (bodyLength > maxUnsigned16Bit) {
|
|
offset += 8; // payload length is next 8 bytes
|
|
payloadLength = 127;
|
|
} else if (bodyLength > 125) {
|
|
offset += 2; // payload length is next 2 bytes
|
|
payloadLength = 126;
|
|
}
|
|
const head = Buffer.allocUnsafeSlow(offset);
|
|
|
|
head[0] = 0x80 /* FIN */ | opcodes.TEXT; /* opcode TEXT */
|
|
head[1] = payloadLength | 0x80; /* MASK */
|
|
head[offset - 4] = maskKey[0];
|
|
head[offset - 3] = maskKey[1];
|
|
head[offset - 2] = maskKey[2];
|
|
head[offset - 1] = maskKey[3];
|
|
|
|
if (payloadLength === 126) {
|
|
head.writeUInt16BE(bodyLength, 2);
|
|
} else if (payloadLength === 127) {
|
|
head[2] = head[3] = 0;
|
|
head.writeUIntBE(bodyLength, 4, 6);
|
|
}
|
|
|
|
return [head, buffer];
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
WebsocketFrameSend,
|
|
};
|