Files
openclaw/extensions/reef/protocol/encoding.ts
2026-07-14 14:20:59 -07:00

99 lines
3.2 KiB
TypeScript

export { bytesToHex as hex, equalBytes, utf8ToBytes as utf8 } from "@noble/ciphers/utils.js";
const decoder = new TextDecoder("utf-8", { fatal: true });
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
const base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
export function decodeUtf8(value: Uint8Array): string {
return decoder.decode(value);
}
export function base64url(value: Uint8Array): string {
let output = "";
for (let index = 0; index < value.length; index += 3) {
const a = value[index] ?? 0;
const b = value[index + 1] ?? 0;
const c = value[index + 2] ?? 0;
const bits = (a << 16) | (b << 8) | c;
output += alphabet[(bits >>> 18) & 63];
output += alphabet[(bits >>> 12) & 63];
if (index + 1 < value.length) {
output += alphabet[(bits >>> 6) & 63];
}
if (index + 2 < value.length) {
output += alphabet[bits & 63];
}
}
return output;
}
export function fromBase64url(value: string): Uint8Array {
if (!/^[A-Za-z0-9_-]*$/.test(value) || value.length % 4 === 1) {
throw new Error("invalid base64url");
}
const output = new Uint8Array(Math.floor((value.length * 6) / 8));
let bits = 0;
let count = 0;
let offset = 0;
for (const character of value) {
const digit = alphabet.indexOf(character);
bits = (bits << 6) | digit;
count += 6;
if (count >= 8) {
count -= 8;
output[offset++] = (bits >>> count) & 0xff;
}
}
if (count > 0 && (bits & ((1 << count) - 1)) !== 0) {
throw new Error("invalid base64url padding");
}
return output;
}
export function base64(value: Uint8Array): string {
let output = "";
for (let index = 0; index < value.length; index += 3) {
const a = value[index] ?? 0;
const b = value[index + 1] ?? 0;
const c = value[index + 2] ?? 0;
const bits = (a << 16) | (b << 8) | c;
output += base64Alphabet[(bits >>> 18) & 63];
output += base64Alphabet[(bits >>> 12) & 63];
output += index + 1 < value.length ? base64Alphabet[(bits >>> 6) & 63] : "=";
output += index + 2 < value.length ? base64Alphabet[bits & 63] : "=";
}
return output;
}
export function fromBase64(value: string): Uint8Array {
if (
value.length % 4 !== 0 ||
!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)
) {
throw new Error("invalid base64");
}
const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
const output = new Uint8Array((value.length / 4) * 3 - padding);
let offset = 0;
for (let index = 0; index < value.length; index += 4) {
const digits = [value[index]!, value[index + 1]!, value[index + 2]!, value[index + 3]!].map(
(character) => (character === "=" ? 0 : base64Alphabet.indexOf(character)),
);
const bits = (digits[0]! << 18) | (digits[1]! << 12) | (digits[2]! << 6) | digits[3]!;
if (offset < output.length) {
output[offset++] = (bits >>> 16) & 0xff;
}
if (offset < output.length) {
output[offset++] = (bits >>> 8) & 0xff;
}
if (offset < output.length) {
output[offset++] = bits & 0xff;
}
}
if (base64(output) !== value) {
throw new Error("non-canonical base64");
}
return output;
}