multiaddr
Version:
multiaddr implementation (binary + string representation of network addresses)
283 lines (239 loc) • 5.76 kB
JavaScript
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
}