108 lines
3.5 KiB
JavaScript
108 lines
3.5 KiB
JavaScript
/**
|
|
* Parse canonical GUID string (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
|
|
* into Windows / CFBF byte order.
|
|
*/
|
|
export function parseWindowsGuid(guid) {
|
|
let s = guid.trim();
|
|
// Keep validation readable and strict, avoid lowercasing allocations
|
|
if (s.length !== 36 ||
|
|
s[8] !== "-" ||
|
|
s[13] !== "-" ||
|
|
s[18] !== "-" ||
|
|
s[23] !== "-") {
|
|
throw new Error(`Invalid GUID format: ${guid}`);
|
|
}
|
|
let v;
|
|
const out = new Uint8Array(16);
|
|
// Data1: 8 hex, uint32 little-endian
|
|
v = parseInt(s.slice(0, 8), 16);
|
|
out[0] = v & 0xff;
|
|
out[1] = (v >>> 8) & 0xff;
|
|
out[2] = (v >>> 16) & 0xff;
|
|
out[3] = (v >>> 24) & 0xff;
|
|
// Data2: 4 hex, uint16 little-endian
|
|
v = parseInt(s.slice(9, 13), 16);
|
|
out[4] = v & 0xff;
|
|
out[5] = (v >>> 8) & 0xff;
|
|
// Data3: 4 hex, uint16 little-endian
|
|
v = parseInt(s.slice(14, 18), 16);
|
|
out[6] = v & 0xff;
|
|
out[7] = (v >>> 8) & 0xff;
|
|
// Data4: 4 hex, as-is (string order)
|
|
v = parseInt(s.slice(19, 23), 16);
|
|
out[8] = (v >>> 8) & 0xff;
|
|
out[9] = v & 0xff;
|
|
// Data5: 12 hex, 6 bytes, as-is (string order)
|
|
// Parse as two chunks to avoid any precision worries, keep it simple.
|
|
v = parseInt(s.slice(24, 32), 16); // 8 hex -> 4 bytes
|
|
out[10] = (v >>> 24) & 0xff;
|
|
out[11] = (v >>> 16) & 0xff;
|
|
out[12] = (v >>> 8) & 0xff;
|
|
out[13] = v & 0xff;
|
|
v = parseInt(s.slice(32, 36), 16); // 4 hex -> 2 bytes
|
|
out[14] = (v >>> 8) & 0xff;
|
|
out[15] = v & 0xff;
|
|
// Ensure all parsed parts were valid hex (parseInt can yield NaN)
|
|
for (let i = 0; i < 16; i++) {
|
|
if (!Number.isFinite(out[i])) {
|
|
throw new Error(`Invalid GUID format: ${guid}`);
|
|
}
|
|
}
|
|
// Also catch NaN early (more useful error locality)
|
|
// If any parseInt produced NaN, assignments above would have become 0,
|
|
// so instead validate hex characters directly with a lightweight check.
|
|
// (Keeps code small while staying strict.)
|
|
if (!/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(s)) {
|
|
throw new Error(`Invalid GUID format: ${guid}`);
|
|
}
|
|
return out;
|
|
}
|
|
export class Guid {
|
|
constructor(bytes) {
|
|
if (bytes.length !== 16)
|
|
throw new Error("GUID must be exactly 16 bytes");
|
|
this.bytes = bytes;
|
|
}
|
|
static fromString(guid) {
|
|
return new Guid(parseWindowsGuid(guid));
|
|
}
|
|
/**
|
|
* Convert Windows / CFBF byte order into canonical GUID string:
|
|
* xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
*/
|
|
toString() {
|
|
const b = this.bytes;
|
|
const hx = (n) => n.toString(16).padStart(2, "0");
|
|
// Data1 (uint32 LE) -> big-endian text
|
|
const g1 = hx(b[3]) + hx(b[2]) + hx(b[1]) + hx(b[0]);
|
|
// Data2 (uint16 LE)
|
|
const g2 = hx(b[5]) + hx(b[4]);
|
|
// Data3 (uint16 LE)
|
|
const g3 = hx(b[7]) + hx(b[6]);
|
|
// Data4 (as-is)
|
|
const g4 = hx(b[8]) + hx(b[9]);
|
|
// Data5 (as-is)
|
|
const g5 = hx(b[10]) +
|
|
hx(b[11]) +
|
|
hx(b[12]) +
|
|
hx(b[13]) +
|
|
hx(b[14]) +
|
|
hx(b[15]);
|
|
return `${g1}-${g2}-${g3}-${g4}-${g5}`.toUpperCase();
|
|
}
|
|
/**
|
|
* Compare against a Uint8Array containing GUID bytes
|
|
* in Windows / CFBF layout.
|
|
*/
|
|
equals(buf, offset = 0) {
|
|
if (offset < 0 || buf.length - offset < 16)
|
|
return false;
|
|
const a = this.bytes;
|
|
for (let i = 0; i < 16; i++) {
|
|
if (buf[offset + i] !== a[i])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|