UNPKG

multiaddr

Version:

multiaddr implementation (binary + string representation of network addresses)

283 lines (239 loc) 5.76 kB
'use strict' const convert = require('./convert') const protocols = require('./protocols-table') const varint = require('varint') const { concat: uint8ArrayConcat } = require('uint8arrays/concat') const { toString: uint8ArrayToString } = require('uint8arrays/to-string') // export codec module.exports = { stringToStringTuples, stringTuplesToString, tuplesToStringTuples, stringTuplesToTuples, bytesToTuples, tuplesToBytes, bytesToString, stringToBytes, fromString, fromBytes, validateBytes, isValidBytes, cleanPath, ParseError, protoFromTuple, sizeForAddr } // string -> [[str name, str addr]... ] /** * @param {string} str */ function stringToStringTuples (str) { const tuples = [] const parts = str.split('/').slice(1) // skip first empty elem if (parts.length === 1 && parts[0] === '') { return [] } for (let p = 0; p < parts.length; p++) { const part = parts[p] const proto = protocols(part) if (proto.size === 0) { tuples.push([part]) continue } p++ // advance addr part if (p >= parts.length) { throw ParseError('invalid address: ' + str) } // if it's a path proto, take the rest if (proto.path) { tuples.push([ part, // TODO: should we need to check each path part to see if it's a proto? // This would allow for other protocols to be added after a unix path, // however it would have issues if the path had a protocol name in the path cleanPath(parts.slice(p).join('/')) ]) break } tuples.push([part, parts[p]]) } return tuples } // [[str name, str addr]... ] -> string /** * @param {[number, string?][]} tuples */ function stringTuplesToString (tuples) { /** @type {Array<string | undefined>} */ const parts = [] tuples.map((tup) => { const proto = protoFromTuple(tup) parts.push(proto.name) if (tup.length > 1) { parts.push(tup[1]) } return null }) return cleanPath(parts.join('/')) } // [[str name, str addr]... ] -> [[int code, Uint8Array]... ] /** * @param {Array<string[] | string >} tuples * @returns {[number , Uint8Array?][]} */ function stringTuplesToTuples (tuples) { return tuples.map((tup) => { if (!Array.isArray(tup)) { tup = [tup] } const proto = protoFromTuple(tup) if (tup.length > 1) { return [proto.code, convert.toBytes(proto.code, tup[1])] } return [proto.code] }) } /** * Convert tuples to string tuples * * [[int code, Uint8Array]... ] -> [[int code, str addr]... ] * * @param {Array<[number, Uint8Array?]>} tuples * @returns {Array<[number, string?]>} */ function tuplesToStringTuples (tuples) { return tuples.map(tup => { const proto = protoFromTuple(tup) if (tup[1]) { return [proto.code, convert.toString(proto.code, tup[1])] } return [proto.code] }) } // [[int code, Uint8Array ]... ] -> Uint8Array /** * @param {[number, Uint8Array?][]} tuples */ function tuplesToBytes (tuples) { return fromBytes(uint8ArrayConcat(tuples.map((/** @type {any[]} */ tup) => { const proto = protoFromTuple(tup) let buf = Uint8Array.from(varint.encode(proto.code)) if (tup.length > 1) { buf = uint8ArrayConcat([buf, tup[1]]) // add address buffer } return buf }))) } /** * @param {import("./types").Protocol} p * @param {Uint8Array | number[]} addr */ function sizeForAddr (p, addr) { if (p.size > 0) { return p.size / 8 } else if (p.size === 0) { return 0 } else { const size = varint.decode(addr) return size + varint.decode.bytes } } /** * * @param {Uint8Array} buf * @returns {Array<[number, Uint8Array?]>} */ function bytesToTuples (buf) { /** @type {Array<[number, Uint8Array?]>} */ const tuples = [] let i = 0 while (i < buf.length) { const code = varint.decode(buf, i) const n = varint.decode.bytes const p = protocols(code) const size = sizeForAddr(p, buf.slice(i + n)) if (size === 0) { tuples.push([code]) i += n continue } const addr = buf.slice(i + n, i + n + size) i += (size + n) if (i > buf.length) { // did not end _exactly_ at buffer.length throw ParseError('Invalid address Uint8Array: ' + uint8ArrayToString(buf, 'base16')) } // ok, tuple seems good. tuples.push([code, addr]) } return tuples } // Uint8Array -> String /** * @param {Uint8Array} buf */ function bytesToString (buf) { const a = bytesToTuples(buf) const b = tuplesToStringTuples(a) return stringTuplesToString(b) } // String -> Uint8Array /** * @param {string} str */ function stringToBytes (str) { str = cleanPath(str) const a = stringToStringTuples(str) const b = stringTuplesToTuples(a) return tuplesToBytes(b) } // String -> Uint8Array /** * @param {string} str */ function fromString (str) { return stringToBytes(str) } // Uint8Array -> Uint8Array /** * @param {Uint8Array} buf */ function fromBytes (buf) { const err = validateBytes(buf) if (err) throw err return Uint8Array.from(buf) // copy } /** * @param {Uint8Array} buf */ function validateBytes (buf) { try { bytesToTuples(buf) // try to parse. will throw if breaks } catch (err) { return err } } /** * @param {Uint8Array} buf */ function isValidBytes (buf) { return validateBytes(buf) === undefined } /** * @param {string} str */ function cleanPath (str) { return '/' + str.trim().split('/').filter((/** @type {any} */ a) => a).join('/') } /** * @param {string} str */ function ParseError (str) { return new Error('Error parsing address: ' + str) } /** * @param {any[]} tup */ function protoFromTuple (tup) { const proto = protocols(tup[0]) return proto }