arblant
panel-gateway / src / vless.js
vless.js79 lines · 3.0 KB
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;
}