UNPKG

multiaddr

Version:

multiaddr implementation (binary + string representation of network addresses)

255 lines (222 loc) 6.26 kB
'use strict' const ip = require('./ip') const protocols = require('./protocols-table') const { CID } = require('multiformats/cid') const { base32 } = require('multiformats/bases/base32') const { base58btc } = require('multiformats/bases/base58') const Digest = require('multiformats/hashes/digest') const varint = require('varint') const { toString: uint8ArrayToString } = require('uint8arrays/to-string') const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') const { concat: uint8ArrayConcat } = require('uint8arrays/concat') module.exports = Convert // converts (serializes) addresses /** * @param {string} proto * @param {string | Uint8Array} a */ function Convert (proto, a) { if (a instanceof Uint8Array) { return Convert.toString(proto, a) } else { return Convert.toBytes(proto, a) } } /** * Convert [code,Uint8Array] to string * * @param {number|string} proto * @param {Uint8Array} buf * @returns {string} */ Convert.toString = function convertToString (proto, buf) { const protocol = protocols(proto) switch (protocol.code) { case 4: // ipv4 case 41: // ipv6 return bytes2ip(buf) case 6: // tcp case 273: // udp case 33: // dccp case 132: // sctp return bytes2port(buf).toString() case 53: // dns case 54: // dns4 case 55: // dns6 case 56: // dnsaddr case 400: // unix case 777: // memory return bytes2str(buf) case 421: // ipfs return bytes2mh(buf) case 444: // onion return bytes2onion(buf) case 445: // onion3 return bytes2onion(buf) default: return uint8ArrayToString(buf, 'base16') // no clue. convert to hex } } Convert.toBytes = function convertToBytes (/** @type {string | number } */ proto, /** @type {string} */ str) { const protocol = protocols(proto) switch (protocol.code) { case 4: // ipv4 return ip2bytes(str) case 41: // ipv6 return ip2bytes(str) case 6: // tcp case 273: // udp case 33: // dccp case 132: // sctp return port2bytes(parseInt(str, 10)) case 53: // dns case 54: // dns4 case 55: // dns6 case 56: // dnsaddr case 400: // unix case 777: // memory return str2bytes(str) case 421: // ipfs return mh2bytes(str) case 444: // onion return onion2bytes(str) case 445: // onion3 return onion32bytes(str) default: return uint8ArrayFromString(str, 'base16') // no clue. convert from hex } } /** * @param {string} ipString */ function ip2bytes (ipString) { if (!ip.isIP(ipString)) { throw new Error('invalid ip address') } return ip.toBytes(ipString) } /** * @param {Uint8Array} ipBuff */ function bytes2ip (ipBuff) { const ipString = ip.toString(ipBuff) if (!ipString || !ip.isIP(ipString)) { throw new Error('invalid ip address') } return ipString } /** * @param {number} port */ function port2bytes (port) { const buf = new ArrayBuffer(2) const view = new DataView(buf) view.setUint16(0, port) return new Uint8Array(buf) } /** * @param {Uint8Array} buf */ function bytes2port (buf) { const view = new DataView(buf.buffer) return view.getUint16(buf.byteOffset) } /** * @param {string} str */ function str2bytes (str) { const buf = uint8ArrayFromString(str) const size = Uint8Array.from(varint.encode(buf.length)) return uint8ArrayConcat([size, buf], size.length + buf.length) } /** * @param {Uint8Array} buf */ function bytes2str (buf) { const size = varint.decode(buf) buf = buf.slice(varint.decode.bytes) if (buf.length !== size) { throw new Error('inconsistent lengths') } return uint8ArrayToString(buf) } /** * @param {string} hash - base58btc string */ function mh2bytes (hash) { let mh if (hash[0] === 'Q' || hash[0] === '1') { mh = Digest.decode(base58btc.decode(`z${hash}`)).bytes } else { mh = CID.parse(hash).multihash.bytes } // the address is a varint prefixed multihash string representation const size = Uint8Array.from(varint.encode(mh.length)) return uint8ArrayConcat([size, mh], size.length + mh.length) } /** * Converts bytes to bas58btc string * * @param {Uint8Array} buf * @returns {string} base58btc string */ function bytes2mh (buf) { const size = varint.decode(buf) const address = buf.slice(varint.decode.bytes) if (address.length !== size) { throw new Error('inconsistent lengths') } return uint8ArrayToString(address, 'base58btc') } /** * @param {string} str */ function onion2bytes (str) { const addr = str.split(':') if (addr.length !== 2) { throw new Error('failed to parse onion addr: ' + addr + ' does not contain a port number') } if (addr[0].length !== 16) { throw new Error('failed to parse onion addr: ' + addr[0] + ' not a Tor onion address.') } // onion addresses do not include the multibase prefix, add it before decoding const buf = base32.decode('b' + addr[0]) // onion port number const port = parseInt(addr[1], 10) if (port < 1 || port > 65536) { throw new Error('Port number is not in range(1, 65536)') } const portBuf = port2bytes(port) return uint8ArrayConcat([buf, portBuf], buf.length + portBuf.length) } /** * @param {string} str */ function onion32bytes (str) { const addr = str.split(':') if (addr.length !== 2) { throw new Error('failed to parse onion addr: ' + addr + ' does not contain a port number') } if (addr[0].length !== 56) { throw new Error('failed to parse onion addr: ' + addr[0] + ' not a Tor onion3 address.') } // onion addresses do not include the multibase prefix, add it before decoding const buf = base32.decode('b' + addr[0]) // onion port number const port = parseInt(addr[1], 10) if (port < 1 || port > 65536) { throw new Error('Port number is not in range(1, 65536)') } const portBuf = port2bytes(port) return uint8ArrayConcat([buf, portBuf], buf.length + portBuf.length) } /** * @param {Uint8Array} buf */ function bytes2onion (buf) { const addrBytes = buf.slice(0, buf.length - 2) const portBytes = buf.slice(buf.length - 2) const addr = uint8ArrayToString(addrBytes, 'base32') const port = bytes2port(portBytes) return addr + ':' + port }