import { stringToUnit8Array } from "../../bit/funcs/stringToUnit8Array" import { stringFromUnit8Array } from "../../bit/funcs/stringFromUnit8Array" export const z85 = { /** * 对数据进行 Z85 编码,返回字符串 * * Z85 编码是 Ascii85 编码机制的衍生版本,避开了 双引号 (")、反斜杠 (\) 和控制字符 * 尤其适用于源代码,对于 JS/JSON 来说是 Base64 的替代方案。 * * Z85 的膨胀率是 25% 低于 Base64 的 33% * * 参考:https://rfc.zeromq.org/spec/32/ * 注意:如果输入长度不是 4 的倍数,会自动填充 0 并在编码末尾附加原始长度信息 */ encode(input: string | ArrayBuffer | Uint8Array): string { const [data, originalLength] = toUint8ArrayWithPadding(input) if (data.length === 0) { return "" } const numBlocks = data.length / 4 let result = "" for (let i = 0; i < numBlocks; i++) { const offset = i * 4 // 将 4 字节作为大端序 32 位无符号整数 let value = (data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3] // 转换为无符号 32 位整数 value = value >>> 0 // 编码为 5 个字符 (从最高位到最低位) const chars = new Array(5) for (let j = 4; j >= 0; j--) { chars[j] = Z85_ENCODER[value % 85] value = Math.floor(value / 85) } result += chars.join("") } // 如果有填充,在末尾附加原始长度信息 const paddingSize = data.length - originalLength if (paddingSize > 0) { // 使用特殊后缀标记填充大小 (1-3) result += paddingSize.toString() } return result }, /** 解码一个 z85 字符串,返回字符串形式的结果 */ decode(input: string): string { return stringFromUnit8Array(z85.decodeToUint8Array(input)) }, /** 解码一个 z85 字符串,返回 Uint8Array 格式的数据 */ decodeToUint8Array(input: string): Uint8Array { if (input.length === 0) { return new Uint8Array(0) } // 检查是否有填充标记 (末尾的 1, 2, 或 3) let paddingSize = 0 let encodedStr = input const lastChar = input[input.length - 1] if (lastChar >= "1" && lastChar <= "3") { // 检查倒数第二个字符,确认这是填充标记而非编码数据 // 如果去掉这个字符后长度是 5 的倍数,则认为是填充标记 const testStr = input.slice(0, -1) if (testStr.length % 5 === 0 && testStr.length > 0) { paddingSize = parseInt(lastChar, 10) encodedStr = testStr } } if (encodedStr.length % 5 !== 0) { throw new Error("Z85: 输入字符串长度必须是 5 的倍数") } const numBlocks = encodedStr.length / 5 const result = new Uint8Array(numBlocks * 4 - paddingSize) const fullResult = new Uint8Array(numBlocks * 4) for (let i = 0; i < numBlocks; i++) { const offset = i * 5 let value = 0 // 将 5 个字符解码为 32 位值 for (let j = 0; j < 5; j++) { const charCode = encodedStr.charCodeAt(offset + j) if (charCode >= 128) { throw new Error(`Z85: 无效字符 '${encodedStr[offset + j]}'`) } const decoded = Z85_DECODER[charCode] if (decoded === INVALID_CHAR) { throw new Error(`Z85: 无效字符 '${encodedStr[offset + j]}'`) } value = value * 85 + decoded } // 将 32 位值拆分为 4 字节 (大端序) const byteOffset = i * 4 fullResult[byteOffset] = (value >>> 24) & 0xff fullResult[byteOffset + 1] = (value >>> 16) & 0xff fullResult[byteOffset + 2] = (value >>> 8) & 0xff fullResult[byteOffset + 3] = value & 0xff } // 如果有填充,截取到原始长度 if (paddingSize > 0) { return fullResult.slice(0, fullResult.length - paddingSize) } return fullResult }, } // Z85 编码表 (0-84 对应的字符) const Z85_ENCODER = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#" // 无效字符标记 const INVALID_CHAR = 255 // Z85 解码表 (字符 ASCII 码对应的值,无效字符标记为 255) const Z85_DECODER = new Uint8Array(128).fill(INVALID_CHAR) for (let i = 0; i < 85; i++) { Z85_DECODER[Z85_ENCODER.charCodeAt(i)] = i } /** * 将输入转换为 Uint8Array,必要时进行填充使其长度为 4 的倍数 * 返回 [填充后的数据, 原始长度] */ function toUint8ArrayWithPadding(input: string | ArrayBuffer | Uint8Array): [Uint8Array, number] { let data: Uint8Array if (typeof input === "string") { data = stringToUnit8Array(input) } else if (input instanceof ArrayBuffer) { data = new Uint8Array(input) } else { data = input } const originalLength = data.length const remainder = data.length % 4 if (remainder === 0) { return [data, originalLength] } // 填充到 4 的倍数 const paddedLength = data.length + (4 - remainder) const paddedData = new Uint8Array(paddedLength) paddedData.set(data) // 剩余位置默认为 0 return [paddedData, originalLength] } const Z85 = z85 export { Z85 }