a collection of lightweight TypeScript packages for AT Protocol, the protocol powering Bluesky
atproto bluesky typescript npm
101
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(multibase): alternative base32 encoding implementation

Mary b9e9152d a0f92688

+49 -34
+5
.changeset/fine-houses-raise.md
··· 1 + --- 2 + "@atcute/multibase": patch 3 + --- 4 + 5 + alternative base32 encoding implementation
+9 -5
packages/utilities/multibase/lib/bases/base32.test.ts
··· 2 2 3 3 import { fromBase32, toBase32 } from './base32.ts'; 4 4 5 - vi.mock('@atcute/uint8array', () => ({ 6 - allocUnsafe: (size: number): Uint8Array => { 7 - return crypto.getRandomValues(new Uint8Array(size)); 8 - }, 9 - })); 5 + vi.mock('@atcute/uint8array', async (importOriginal) => { 6 + const actual = await importOriginal<typeof import('@atcute/uint8array')>(); 7 + return { 8 + ...actual, 9 + allocUnsafe: (size: number): Uint8Array => { 10 + return crypto.getRandomValues(new Uint8Array(size)); 11 + }, 12 + }; 13 + }); 10 14 11 15 const inputs = [ 12 16 {
+35 -29
packages/utilities/multibase/lib/bases/base32.ts
··· 1 - import { allocUnsafe } from '@atcute/uint8array'; 1 + import { allocUnsafe, decodeUtf8From } from '@atcute/uint8array'; 2 2 3 3 const ALPHABET = 'abcdefghijklmnopqrstuvwxyz234567'; 4 4 5 5 // #region encode 6 6 7 - // 2-character lookup table: lut2[(i << 5) | j] = alphabet[i] + alphabet[j] 8 - // avoids per-character string concatenation in the hot loop 9 - const _lut2: string[] = /*#__PURE__*/ (() => { 10 - const t: string[] = Array.from({ length: 1024 }); 7 + // charCode lookup table: _encLut[i] = ALPHABET.charCodeAt(i) for i in 0..31 8 + const _encLut: Uint8Array = /*#__PURE__*/ (() => { 9 + const t = new Uint8Array(32); 11 10 for (let i = 0; i < 32; i++) { 12 - for (let j = 0; j < 32; j++) { 13 - t[(i << 5) | j] = ALPHABET[i] + ALPHABET[j]; 14 - } 11 + t[i] = ALPHABET.charCodeAt(i); 15 12 } 16 13 return t; 17 14 })(); 15 + 16 + // output length for a given remainder (0-4 trailing bytes after full 5-byte groups) 17 + const _remOutLen = [0, 2, 4, 5, 7]; 18 18 19 19 /** 20 20 * encodes a Uint8Array to an unpadded RFC 4648 base32 (lowercase) string ··· 23 23 */ 24 24 export const toBase32 = (bytes: Uint8Array): string => { 25 25 const len = bytes.length; 26 - let str = ''; 26 + const full = (len / 5) | 0; 27 + const rem = len - full * 5; 28 + const outLen = full * 8 + _remOutLen[rem]; 29 + const out = allocUnsafe(outLen); 30 + const cc = _encLut; 27 31 28 - // process 5-byte groups (= 40 bits = 8 base32 characters each), 29 - // using the 2-char lookup table to emit pairs of characters at a time 30 - let i = 0; 31 - const fullGroups = len - (len % 5); 32 - for (; i < fullGroups; i += 5) { 33 - const b0 = bytes[i]; 34 - const b1 = bytes[i + 1]; 35 - const b2 = bytes[i + 2]; 36 - const b3 = bytes[i + 3]; 37 - const b4 = bytes[i + 4]; 32 + // process 5-byte groups (= 40 bits = 8 base32 characters each) 33 + let ip = 0; 34 + let op = 0; 35 + for (let g = 0; g < full; g++) { 36 + const b0 = bytes[ip++]; 37 + const b1 = bytes[ip++]; 38 + const b2 = bytes[ip++]; 39 + const b3 = bytes[ip++]; 40 + const b4 = bytes[ip++]; 38 41 39 - str += 40 - _lut2[((b0 >>> 3) << 5) | (((b0 << 2) | (b1 >>> 6)) & 0x1f)] + 41 - _lut2[(((b1 >>> 1) & 0x1f) << 5) | (((b1 << 4) | (b2 >>> 4)) & 0x1f)] + 42 - _lut2[((((b2 << 1) | (b3 >>> 7)) & 0x1f) << 5) | ((b3 >>> 2) & 0x1f)] + 43 - _lut2[((((b3 << 3) | (b4 >>> 5)) & 0x1f) << 5) | (b4 & 0x1f)]; 42 + out[op++] = cc[b0 >>> 3]; 43 + out[op++] = cc[((b0 << 2) | (b1 >>> 6)) & 0x1f]; 44 + out[op++] = cc[(b1 >>> 1) & 0x1f]; 45 + out[op++] = cc[((b1 << 4) | (b2 >>> 4)) & 0x1f]; 46 + out[op++] = cc[((b2 << 1) | (b3 >>> 7)) & 0x1f]; 47 + out[op++] = cc[(b3 >>> 2) & 0x1f]; 48 + out[op++] = cc[((b3 << 3) | (b4 >>> 5)) & 0x1f]; 49 + out[op++] = cc[b4 & 0x1f]; 44 50 } 45 51 46 52 // handle remaining 1-4 bytes 47 - if (i < len) { 53 + if (rem > 0) { 48 54 let buffer = 0; 49 55 let bits = 0; 50 - for (; i < len; i++) { 56 + for (let i = ip; i < len; i++) { 51 57 buffer = (buffer << 8) | bytes[i]; 52 58 bits += 8; 53 59 } 54 60 while (bits > 0) { 55 61 if (bits >= 5) { 56 62 bits -= 5; 57 - str += ALPHABET[(buffer >>> bits) & 0x1f]; 63 + out[op++] = cc[(buffer >>> bits) & 0x1f]; 58 64 } else { 59 - str += ALPHABET[(buffer << (5 - bits)) & 0x1f]; 65 + out[op++] = cc[(buffer << (5 - bits)) & 0x1f]; 60 66 bits = 0; 61 67 } 62 68 } 63 69 } 64 70 65 - return str; 71 + return decodeUtf8From(out); 66 72 }; 67 73 68 74 // #endregion