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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/* VLESS request-header helpers — pure functions, no runtime deps.
Request header layout:
+------+------+-------+------+------+------+--------+
| VER | UUID | M | CMD | PORT | ATYP | ADDR.. |
| 1 B | 16 B | 1B+M | 1 B | 2 BE | 1 B | var |
+------+------+-------+------+------+------+--------+
We only need the first 18 bytes to identify the user (VER + UUID + M).
*/
export function bytesToUuid(b) {
if (b.length !== 16) throw new Error('uuid must be 16 bytes');
const h = Array.from(b, x => x.toString(16).padStart(2, '0')).join('');
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`.toLowerCase();
}
export function uuidToBytes(u) {
const h = String(u).replace(/-/g, '').toLowerCase();
if (!/^[0-9a-f]{32}$/.test(h)) throw new Error('bad uuid: ' + u);
const out = new Uint8Array(16);
for (let i = 0; i < 16; i++) out[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16);
return out;
}
export function isValidUuid(s) {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(String(s));
}
export function newUuid() {
const b = new Uint8Array(16);
crypto.getRandomValues(b);
b[6] = (b[6] & 0x0f) | 0x40; // v4
b[8] = (b[8] & 0x3f) | 0x80; // variant
return bytesToUuid(b);
}
/* Parse the VLESS header at the start of buf. Needs >= 18 bytes.
Returns { uuid, uuidBytes, totalHeaderHint }. Throws on bad data. */
export function parseVlessHeader(buf) {
if (!(buf instanceof Uint8Array)) throw new Error('buf must be Uint8Array');
if (buf.length < 18) throw new Error('need at least 18 bytes');
if (buf[0] !== 0x00) throw new Error('unsupported vless version: ' + buf[0]);
const uuidBytes = buf.slice(1, 17);
const uuid = bytesToUuid(uuidBytes);
const m = buf[17]; // addon (options) length
return { uuid, uuidBytes, addonLen: m, minLength: 18 + m };
}
/* Return a copy of buf with the 16-byte UUID field replaced. */
export function rewriteUuid(buf, newUuidBytes) {
if (newUuidBytes.length !== 16) throw new Error('new uuid must be 16 bytes');
if (buf.length < 17) throw new Error('buffer too short for uuid rewrite');
const out = new Uint8Array(buf);
out.set(newUuidBytes, 1);
return out;
}
/* Build a minimal VLESS request header (tests / synthetic client).
addr as [a,b,c,d] for ATYP 0x01, or domain string for ATYP 0x02. */
export function buildVlessHeader(uuid, { command = 0x01, port = 80, addr = [1, 1, 1, 1] } = {}) {
const ub = uuidToBytes(uuid);
let atyp, addrBytes;
if (Array.isArray(addr)) { atyp = 0x01; addrBytes = Uint8Array.from(addr); }
else {
atyp = 0x02;
const enc = new TextEncoder().encode(addr);
addrBytes = new Uint8Array(1 + enc.length);
addrBytes[0] = enc.length; addrBytes.set(enc, 1);
}
const out = new Uint8Array(1 + 16 + 1 + 1 + 2 + 1 + addrBytes.length);
let o = 0;
out[o++] = 0x00;
out.set(ub, o); o += 16;
out[o++] = 0x00; // M = 0 (no addons)
out[o++] = command;
out[o++] = (port >> 8) & 0xff; out[o++] = port & 0xff;
out[o++] = atyp;
out.set(addrBytes, o);
return out;
}