multiaddr
Version:
multiaddr implementation (binary + string representation of network addresses)
255 lines (222 loc) • 6.26 kB
JavaScript
'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
}