1// Base32 is ideal for physical names because it's denser than hex (5 bits per char vs 4)
2// and compatible with DNS/S3 (as opposed to base64 which contains uppercase letters and symbols).
3
4// base32.ts
5// Concise, fast RFC 4648 Base32 encoder (no padding), lowercase output.
6// Charset: a-z2-7 (DNS/S3 friendly)
7const ALPH = "abcdefghijklmnopqrstuvwxyz234567";
8
9/**
10 * Encode bytes into RFC4648 Base32 (no padding), lowercase.
11 *
12 * Performance notes:
13 * - O(n) single pass, no big-int
14 * - Avoids per-byte string concatenation by using a char array
15 */
16export function base32(bytes: Uint8Array): string {
17 const n = bytes.length;
18 if (n === 0) return "";
19
20 // Base32 length without padding: ceil(n*8/5)
21 const outLen = ((n * 8 + 4) / 5) | 0;
22 const out = Array.from<string>({ length: outLen });
23
24 let buffer = 0;
25 let bits = 0;
26 let o = 0;
27
28 for (let i = 0; i < n; i++) {
29 buffer = (buffer << 8) | bytes[i];
30 bits += 8;
31
32 while (bits >= 5) {
33 out[o++] = ALPH[(buffer >>> (bits - 5)) & 31];
34 bits -= 5;
35 }
36 }
37
38 if (bits > 0) {
39 out[o++] = ALPH[(buffer << (5 - bits)) & 31];
40 }
41
42 // outLen computed as exact ceiling; o should match, but slice defensively.
43 return o === outLen ? out.join("") : out.slice(0, o).join("");
44}