1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/* Minimal WebSocket echo server (node:net) for integration tests.
Verifies the first message's VLESS UUID == expected, then echoes everything. */
import { createServer } from 'node:net';
import { createHash } from 'node:crypto';
const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; // RFC 6455 §1.3
function sendFrame(socket, opcode, payload) {
const len = payload.length;
let head;
if (len < 126) head = Buffer.from([0x80 | opcode, len]);
else if (len < 65536) { head = Buffer.alloc(4); head[0] = 0x80 | opcode; head[1] = 126; head.writeUInt16BE(len, 2); }
else { head = Buffer.alloc(10); head[0] = 0x80 | opcode; head[1] = 127; head.writeBigUInt64BE(BigInt(len), 2); }
socket.write(Buffer.concat([head, Buffer.from(payload)]));
}
export function startEchoServer(port, expectedUuidBytes, onEvent) {
const expected = Buffer.from(expectedUuidBytes);
const server = createServer((socket) => {
let buf = Buffer.alloc(0), handshaked = false, firstMsg = true;
socket.on('data', (chunk) => {
buf = Buffer.concat([buf, chunk]);
if (!handshaked) {
const i = buf.indexOf('\r\n\r\n');
if (i < 0) return;
const head = buf.slice(0, i).toString('latin1');
buf = buf.slice(i + 4);
const m = /sec-websocket-key:\s*(.+)/i.exec(head);
if (!/upgrade:\s*websocket/i.test(head) || !m) { socket.destroy(); return; }
const accept = createHash('sha1').update(m[1].trim() + GUID).digest('base64');
socket.write('HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ' + accept + '\r\n\r\n');
handshaked = true;
}
for (;;) {
if (buf.length < 2) return;
const b0 = buf[0], b1 = buf[1], opcode = b0 & 0x0f, masked = (b1 & 0x80) !== 0;
let len = b1 & 0x7f, off = 2;
if (len === 126) { if (buf.length < 4) return; len = buf.readUInt16BE(2); off = 4; }
else if (len === 127) { if (buf.length < 10) return; len = Number(buf.readBigUInt64BE(2)); off = 10; }
if (!masked) { socket.destroy(); return; }
if (buf.length < off + 4 + len) return;
const mask = buf.subarray(off, off + 4);
const payload = buf.subarray(off + 4, off + 4 + len);
buf = buf.slice(off + 4 + len);
for (let i = 0; i < payload.length; i++) payload[i] ^= mask[i % 4];
if (opcode === 0x8) { sendFrame(socket, 0x8, payload); socket.end(); return; }
if (opcode === 0x9) { sendFrame(socket, 0xA, payload); continue; }
if (opcode === 0x1 || opcode === 0x2) {
if (firstMsg) {
firstMsg = false;
const ok = payload.length >= 17 && Buffer.from(payload.subarray(1, 17)).equals(expected);
onEvent && onEvent({ type: 'header', ok, len: payload.length });
if (!ok) { socket.destroy(); return; }
}
sendFrame(socket, 0x2, payload);
}
}
});
socket.on('error', () => {});
});
return new Promise((res) => server.listen(port, '127.0.0.1', () => res(server)));
}