UNPKG

multiformats

Version:

Interface for multihash, multicodec, multibase and CID

206 lines 6.27 kB
import { coerce } from "../bytes.js"; import basex from '../vendor/base-x.js'; /** * Class represents both BaseEncoder and MultibaseEncoder meaning it * can be used to encode to multibase or base encode without multibase * prefix. */ class Encoder { name; prefix; baseEncode; constructor(name, prefix, baseEncode) { this.name = name; this.prefix = prefix; this.baseEncode = baseEncode; } encode(bytes) { if (bytes instanceof Uint8Array) { return `${this.prefix}${this.baseEncode(bytes)}`; } else { throw Error('Unknown type, must be binary type'); } } } /** * Class represents both BaseDecoder and MultibaseDecoder so it could be used * to decode multibases (with matching prefix) or just base decode strings * with corresponding base encoding. */ class Decoder { name; prefix; baseDecode; prefixCodePoint; constructor(name, prefix, baseDecode) { this.name = name; this.prefix = prefix; const prefixCodePoint = prefix.codePointAt(0); /* c8 ignore next 3 */ if (prefixCodePoint === undefined) { throw new Error('Invalid prefix character'); } this.prefixCodePoint = prefixCodePoint; this.baseDecode = baseDecode; } decode(text) { if (typeof text === 'string') { if (text.codePointAt(0) !== this.prefixCodePoint) { throw Error(`Unable to decode multibase string ${JSON.stringify(text)}, ${this.name} decoder only supports inputs prefixed with ${this.prefix}`); } return this.baseDecode(text.slice(this.prefix.length)); } else { throw Error('Can only multibase decode strings'); } } or(decoder) { return or(this, decoder); } } class ComposedDecoder { decoders; constructor(decoders) { this.decoders = decoders; } or(decoder) { return or(this, decoder); } decode(input) { const prefix = input[0]; const decoder = this.decoders[prefix]; if (decoder != null) { return decoder.decode(input); } else { throw RangeError(`Unable to decode multibase string ${JSON.stringify(input)}, only inputs prefixed with ${Object.keys(this.decoders)} are supported`); } } } export function or(left, right) { return new ComposedDecoder({ ...(left.decoders ?? { [left.prefix]: left }), ...(right.decoders ?? { [right.prefix]: right }) }); } export class Codec { name; prefix; baseEncode; baseDecode; encoder; decoder; constructor(name, prefix, baseEncode, baseDecode) { this.name = name; this.prefix = prefix; this.baseEncode = baseEncode; this.baseDecode = baseDecode; this.encoder = new Encoder(name, prefix, baseEncode); this.decoder = new Decoder(name, prefix, baseDecode); } encode(input) { return this.encoder.encode(input); } decode(input) { return this.decoder.decode(input); } } export function from({ name, prefix, encode, decode }) { return new Codec(name, prefix, encode, decode); } export function baseX({ name, prefix, alphabet }) { const { encode, decode } = basex(alphabet, name); return from({ prefix, name, encode, decode: (text) => coerce(decode(text)) }); } function decode(string, alphabetIdx, bitsPerChar, name) { // Count the padding bytes: let end = string.length; while (string[end - 1] === '=') { --end; } // Allocate the output: const out = new Uint8Array((end * bitsPerChar / 8) | 0); // Parse the data: let bits = 0; // Number of bits currently in the buffer let buffer = 0; // Bits waiting to be written out, MSB first let written = 0; // Next byte to write for (let i = 0; i < end; ++i) { // Read one character from the string: const value = alphabetIdx[string[i]]; if (value === undefined) { throw new SyntaxError(`Non-${name} character`); } // Append the bits to the buffer: buffer = (buffer << bitsPerChar) | value; bits += bitsPerChar; // Write out some bits if the buffer has a byte's worth: if (bits >= 8) { bits -= 8; out[written++] = 0xff & (buffer >> bits); } } // Verify that we have received just enough bits: if (bits >= bitsPerChar || (0xff & (buffer << (8 - bits))) !== 0) { throw new SyntaxError('Unexpected end of data'); } return out; } function encode(data, alphabet, bitsPerChar) { const pad = alphabet[alphabet.length - 1] === '='; const mask = (1 << bitsPerChar) - 1; let out = ''; let bits = 0; // Number of bits currently in the buffer let buffer = 0; // Bits waiting to be written out, MSB first for (let i = 0; i < data.length; ++i) { // Slurp data into the buffer: buffer = (buffer << 8) | data[i]; bits += 8; // Write out as much as we can: while (bits > bitsPerChar) { bits -= bitsPerChar; out += alphabet[mask & (buffer >> bits)]; } } // Partial character: if (bits !== 0) { out += alphabet[mask & (buffer << (bitsPerChar - bits))]; } // Add padding characters until we hit a byte boundary: if (pad) { while (((out.length * bitsPerChar) & 7) !== 0) { out += '='; } } return out; } function createAlphabetIdx(alphabet) { // Build the character lookup table: const alphabetIdx = {}; for (let i = 0; i < alphabet.length; ++i) { alphabetIdx[alphabet[i]] = i; } return alphabetIdx; } /** * RFC4648 Factory */ export function rfc4648({ name, prefix, bitsPerChar, alphabet }) { const alphabetIdx = createAlphabetIdx(alphabet); return from({ prefix, name, encode(input) { return encode(input, alphabet, bitsPerChar); }, decode(input) { return decode(input, alphabetIdx, bitsPerChar, name); } }); } //# sourceMappingURL=base.js.map