Skip to content
Permalink
dbc801ab70
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
602 lines (548 sloc) 17.3 KB
import * as codec from './codec.js'
import { getProtocol, names } from './protocols-table.js'
import varint from 'varint'
import { CID } from 'multiformats/cid'
import { base58btc } from 'multiformats/bases/base58'
import errCode from 'err-code'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
const inspect = Symbol.for('nodejs.util.inspect.custom')
export interface Protocol {
code: number
size: number
name: string
resolvable?: boolean | undefined
path?: boolean | undefined
}
export interface MultiaddrObject {
family: 4 | 6
host: string
transport: string
port: number
}
export interface NodeAddress {
family: 4 | 6
address: string
port: number
}
export type MultiaddrInput = string | Multiaddr | Uint8Array | null
export interface Resolver { (addr: Multiaddr): Promise<string[]> }
const resolvers = new Map<string, Resolver>()
const symbol = Symbol.for('@multiformats/js-multiaddr/multiaddr')
/**
* Creates a [multiaddr](https://github.com/multiformats/multiaddr) from
* a Uint8Array, String or another Multiaddr instance
* public key.
*
*/
export class Multiaddr {
static resolvers = resolvers
public bytes: Uint8Array
/**
* @example
* ```js
* new Multiaddr('/ip4/127.0.0.1/tcp/4001')
* // <Multiaddr 047f000001060fa1 - /ip4/127.0.0.1/tcp/4001>
* ```
*
* @param {MultiaddrInput} [addr] - If String or Uint8Array, needs to adhere to the address format of a [multiaddr](https://github.com/multiformats/multiaddr#string-format)
*/
constructor (addr?: MultiaddrInput) {
// default
if (addr == null) {
addr = ''
}
// Define symbol
Object.defineProperty(this, symbol, { value: true })
if (addr instanceof Uint8Array) {
/** @type {Uint8Array} - The raw bytes representing this multiaddress */
this.bytes = codec.fromBytes(addr)
} else if (typeof addr === 'string') {
if (addr.length > 0 && addr.charAt(0) !== '/') {
throw new Error(`multiaddr "${addr}" must start with a "/"`)
}
this.bytes = codec.fromString(addr)
} else if (Multiaddr.isMultiaddr(addr)) { // Multiaddr
this.bytes = codec.fromBytes(addr.bytes) // validate + copy buffer
} else {
throw new Error('addr must be a string, Buffer, or another Multiaddr')
}
}
/**
* Returns Multiaddr as a String
*
* @example
* ```js
* new Multiaddr('/ip4/127.0.0.1/tcp/4001').toString()
* // '/ip4/127.0.0.1/tcp/4001'
* ```
*/
toString () {
return codec.bytesToString(this.bytes)
}
/**
* Returns Multiaddr as a JSON encoded object
*
* @example
* ```js
* JSON.stringify(new Multiaddr('/ip4/127.0.0.1/tcp/4001'))
* // '/ip4/127.0.0.1/tcp/4001'
* ```
*/
toJSON () {
return this.toString()
}
/**
* Returns Multiaddr as a convinient options object to be used with net.createConnection
*
* @example
* ```js
* new Multiaddr('/ip4/127.0.0.1/tcp/4001').toOptions()
* // { family: 4, host: '127.0.0.1', transport: 'tcp', port: 4001 }
* ```
*/
toOptions (): MultiaddrObject {
const parsed = this.toString().split('/')
const opts: MultiaddrObject = {
family: parsed[1] === 'ip4' ? 4 : 6,
host: parsed[2],
transport: parsed[3],
port: parseInt(parsed[4])
}
return opts
}
/**
* Returns the protocols the Multiaddr is defined with, as an array of objects, in
* left-to-right order. Each object contains the protocol code, protocol name,
* and the size of its address space in bits.
* [See list of protocols](https://github.com/multiformats/multiaddr/blob/master/protocols.csv)
*
* @example
* ```js
* new Multiaddr('/ip4/127.0.0.1/tcp/4001').protos()
* // [ { code: 4, size: 32, name: 'ip4' },
* // { code: 6, size: 16, name: 'tcp' } ]
* ```
*/
protos () {
return this.protoCodes().map(code => Object.assign({}, getProtocol(code)))
}
/**
* Returns the codes of the protocols in left-to-right order.
* [See list of protocols](https://github.com/multiformats/multiaddr/blob/master/protocols.csv)
*
* @example
* ```js
* Multiaddr('/ip4/127.0.0.1/tcp/4001').protoCodes()
* // [ 4, 6 ]
* ```
*/
protoCodes () {
const codes: number[] = []
const buf = this.bytes
let i = 0
while (i < buf.length) {
const code = varint.decode(buf, i)
const n = varint.decode.bytes
const p = getProtocol(code)
const size = codec.sizeForAddr(p, buf.slice(i + n))
i += (size + n)
codes.push(code)
}
return codes
}
/**
* Returns the names of the protocols in left-to-right order.
* [See list of protocols](https://github.com/multiformats/multiaddr/blob/master/protocols.csv)
*
* @example
* ```js
* new Multiaddr('/ip4/127.0.0.1/tcp/4001').protoNames()
* // [ 'ip4', 'tcp' ]
* ```
*/
protoNames () {
return this.protos().map(proto => proto.name)
}
/**
* Returns a tuple of parts
*
* @example
* ```js
* new Multiaddr("/ip4/127.0.0.1/tcp/4001").tuples()
* // [ [ 4, <Buffer 7f 00 00 01> ], [ 6, <Buffer 0f a1> ] ]
* ```
*/
tuples () {
return codec.bytesToTuples(this.bytes)
}
/**
* Returns a tuple of string/number parts
* - tuples[][0] = code of protocol
* - tuples[][1] = contents of address
*
* @example
* ```js
* new Multiaddr("/ip4/127.0.0.1/tcp/4001").stringTuples()
* // [ [ 4, '127.0.0.1' ], [ 6, '4001' ] ]
* ```
*/
stringTuples () {
const t = codec.bytesToTuples(this.bytes)
return codec.tuplesToStringTuples(t)
}
/**
* Encapsulates a Multiaddr in another Multiaddr
*
* @example
* ```js
* const mh1 = new Multiaddr('/ip4/8.8.8.8/tcp/1080')
* // <Multiaddr 0408080808060438 - /ip4/8.8.8.8/tcp/1080>
*
* const mh2 = new Multiaddr('/ip4/127.0.0.1/tcp/4001')
* // <Multiaddr 047f000001060fa1 - /ip4/127.0.0.1/tcp/4001>
*
* const mh3 = mh1.encapsulate(mh2)
* // <Multiaddr 0408080808060438047f000001060fa1 - /ip4/8.8.8.8/tcp/1080/ip4/127.0.0.1/tcp/4001>
*
* mh3.toString()
* // '/ip4/8.8.8.8/tcp/1080/ip4/127.0.0.1/tcp/4001'
* ```
*
* @param {MultiaddrInput} addr - Multiaddr to add into this Multiaddr
*/
encapsulate (addr: MultiaddrInput) {
addr = new Multiaddr(addr)
return new Multiaddr(this.toString() + addr.toString())
}
/**
* Decapsulates a Multiaddr from another Multiaddr
*
* @example
* ```js
* const mh1 = new Multiaddr('/ip4/8.8.8.8/tcp/1080')
* // <Multiaddr 0408080808060438 - /ip4/8.8.8.8/tcp/1080>
*
* const mh2 = new Multiaddr('/ip4/127.0.0.1/tcp/4001')
* // <Multiaddr 047f000001060fa1 - /ip4/127.0.0.1/tcp/4001>
*
* const mh3 = mh1.encapsulate(mh2)
* // <Multiaddr 0408080808060438047f000001060fa1 - /ip4/8.8.8.8/tcp/1080/ip4/127.0.0.1/tcp/4001>
*
* mh3.decapsulate(mh2).toString()
* // '/ip4/8.8.8.8/tcp/1080'
* ```
*
* @param {Multiaddr | string} addr - Multiaddr to remove from this Multiaddr
*/
decapsulate (addr: Multiaddr | string) {
const addrString = addr.toString()
const s = this.toString()
const i = s.lastIndexOf(addrString)
if (i < 0) {
throw new Error(`Address ${this.toString()} does not contain subaddress: ${addr.toString()}`)
}
return new Multiaddr(s.slice(0, i))
}
/**
* A more reliable version of `decapsulate` if you are targeting a
* specific code, such as 421 (the `p2p` protocol code). The last index of the code
* will be removed from the `Multiaddr`, and a new instance will be returned.
* If the code is not present, the original `Multiaddr` is returned.
*
* @example
* ```js
* const addr = new Multiaddr('/ip4/0.0.0.0/tcp/8080/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC')
* // <Multiaddr 0400... - /ip4/0.0.0.0/tcp/8080/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC>
*
* addr.decapsulateCode(421).toString()
* // '/ip4/0.0.0.0/tcp/8080'
*
* new Multiaddr('/ip4/127.0.0.1/tcp/8080').decapsulateCode(421).toString()
* // '/ip4/127.0.0.1/tcp/8080'
* ```
*/
decapsulateCode (code: number) {
const tuples = this.tuples()
for (let i = tuples.length - 1; i >= 0; i--) {
if (tuples[i][0] === code) {
return new Multiaddr(codec.tuplesToBytes(tuples.slice(0, i)))
}
}
return this
}
/**
* Extract the peerId if the multiaddr contains one
*
* @example
* ```js
* const mh1 = new Multiaddr('/ip4/8.8.8.8/tcp/1080/ipfs/QmValidBase58string')
* // <Multiaddr 0408080808060438 - /ip4/8.8.8.8/tcp/1080/ipfs/QmValidBase58string>
*
* // should return QmValidBase58string or null if the id is missing or invalid
* const peerId = mh1.getPeerId()
* ```
*/
getPeerId (): string | null {
try {
const tuples = this.stringTuples().filter((tuple) => {
if (tuple[0] === names.ipfs.code) {
return true
}
return false
})
// Get the last ipfs tuple ['ipfs', 'peerid string']
const tuple = tuples.pop()
if (tuple?.[1] != null) {
const peerIdStr = tuple[1]
// peer id is base58btc encoded string but not multibase encoded so add the `z`
// prefix so we can validate that it is correctly encoded
if (peerIdStr[0] === 'Q' || peerIdStr[0] === '1') {
return uint8ArrayToString(base58btc.decode(`z${peerIdStr}`), 'base58btc')
}
// try to parse peer id as CID
return uint8ArrayToString(CID.parse(peerIdStr).multihash.bytes, 'base58btc')
}
return null
} catch (e) {
return null
}
}
/**
* Extract the path if the multiaddr contains one
*
* @example
* ```js
* const mh1 = new Multiaddr('/ip4/8.8.8.8/tcp/1080/unix/tmp/p2p.sock')
* // <Multiaddr 0408080808060438 - /ip4/8.8.8.8/tcp/1080/unix/tmp/p2p.sock>
*
* // should return utf8 string or null if the id is missing or invalid
* const path = mh1.getPath()
* ```
*/
getPath (): string | null {
let path = null
try {
path = this.stringTuples().filter((tuple) => {
const proto = getProtocol(tuple[0])
if (proto.path === true) {
return true
}
return false
})[0][1]
if (path == null) {
path = null
}
} catch {
path = null
}
return path
}
/**
* Checks if two Multiaddrs are the same
*
* @example
* ```js
* const mh1 = new Multiaddr('/ip4/8.8.8.8/tcp/1080')
* // <Multiaddr 0408080808060438 - /ip4/8.8.8.8/tcp/1080>
*
* const mh2 = new Multiaddr('/ip4/127.0.0.1/tcp/4001')
* // <Multiaddr 047f000001060fa1 - /ip4/127.0.0.1/tcp/4001>
*
* mh1.equals(mh1)
* // true
*
* mh1.equals(mh2)
* // false
* ```
*/
equals (addr: { bytes: Uint8Array }) {
return uint8ArrayEquals(this.bytes, addr.bytes)
}
/**
* Resolve multiaddr if containing resolvable hostname.
*
* @example
* ```js
* Multiaddr.resolvers.set('dnsaddr', resolverFunction)
* const mh1 = new Multiaddr('/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb')
* const resolvedMultiaddrs = await mh1.resolve()
* // [
* // <Multiaddr 04934b5353060fa1a503221220c10f9319dac35c270a6b74cd644cb3acfc1f6efc8c821f8eb282599fd1814f64 - /ip4/147.75.83.83/tcp/4001/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb>,
* // <Multiaddr 04934b53530601bbde03a503221220c10f9319dac35c270a6b74cd644cb3acfc1f6efc8c821f8eb282599fd1814f64 - /ip4/147.75.83.83/tcp/443/wss/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb>,
* // <Multiaddr 04934b535391020fa1cc03a503221220c10f9319dac35c270a6b74cd644cb3acfc1f6efc8c821f8eb282599fd1814f64 - /ip4/147.75.83.83/udp/4001/quic/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb>
* // ]
* ```
*/
async resolve () {
const resolvableProto = this.protos().find((p) => p.resolvable)
// Multiaddr is not resolvable?
if (resolvableProto == null) {
return [this]
}
const resolver = resolvers.get(resolvableProto.name)
if (resolver == null) {
throw errCode(new Error(`no available resolver for ${resolvableProto.name}`), 'ERR_NO_AVAILABLE_RESOLVER')
}
const addresses = await resolver(this)
return addresses.map((a) => new Multiaddr(a))
}
/**
* Gets a Multiaddrs node-friendly address object. Note that protocol information
* is left out: in Node (and most network systems) the protocol is unknowable
* given only the address.
*
* Has to be a ThinWaist Address, otherwise throws error
*
* @example
* ```js
* new Multiaddr('/ip4/127.0.0.1/tcp/4001').nodeAddress()
* // {family: 4, address: '127.0.0.1', port: 4001}
* ```
*/
nodeAddress (): NodeAddress {
const codes = this.protoCodes()
const names = this.protoNames()
const parts = this.toString().split('/').slice(1)
if (parts.length < 4) {
throw new Error('multiaddr must have a valid format: "/{ip4, ip6, dns4, dns6}/{address}/{tcp, udp}/{port}".')
} else if (codes[0] !== 4 && codes[0] !== 41 && codes[0] !== 54 && codes[0] !== 55) {
throw new Error(`no protocol with name: "'${names[0]}'". Must have a valid family name: "{ip4, ip6, dns4, dns6}".`)
} else if (parts[2] !== 'tcp' && parts[2] !== 'udp') {
throw new Error(`no protocol with name: "'${names[1]}'". Must have a valid transport protocol: "{tcp, udp}".`)
}
return {
family: (codes[0] === 41 || codes[0] === 55) ? 6 : 4,
address: parts[1],
port: parseInt(parts[3]) // tcp or udp port
}
}
/**
* Returns if a Multiaddr is a Thin Waist address or not.
*
* Thin Waist is if a Multiaddr adheres to the standard combination of:
*
* `{IPv4, IPv6}/{TCP, UDP}`
*
* @example
* ```js
* const mh1 = new Multiaddr('/ip4/127.0.0.1/tcp/4001')
* // <Multiaddr 047f000001060fa1 - /ip4/127.0.0.1/tcp/4001>
* const mh2 = new Multiaddr('/ip4/192.168.2.1/tcp/5001')
* // <Multiaddr 04c0a80201061389 - /ip4/192.168.2.1/tcp/5001>
* const mh3 = mh1.encapsulate(mh2)
* // <Multiaddr 047f000001060fa104c0a80201061389 - /ip4/127.0.0.1/tcp/4001/ip4/192.168.2.1/tcp/5001>
* const mh4 = new Multiaddr('/ip4/127.0.0.1/tcp/2000/wss/p2p-webrtc-star/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSooo2a')
* // <Multiaddr 047f0000010607d0de039302a503221220d52ebb89d85b02a284948203a62ff28389c57c9f42beec4ec20db76a64835843 - /ip4/127.0.0.1/tcp/2000/wss/p2p-webrtc-star/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSooo2a>
* mh1.isThinWaistAddress()
* // true
* mh2.isThinWaistAddress()
* // true
* mh3.isThinWaistAddress()
* // false
* mh4.isThinWaistAddress()
* // false
* ```
*/
isThinWaistAddress (addr?: Multiaddr) {
const protos = (addr ?? this).protos()
if (protos.length !== 2) {
return false
}
if (protos[0].code !== 4 && protos[0].code !== 41) {
return false
}
if (protos[1].code !== 6 && protos[1].code !== 273) {
return false
}
return true
}
/**
* Creates a Multiaddr from a node-friendly address object
*
* @example
* ```js
* Multiaddr.fromNodeAddress({address: '127.0.0.1', port: '4001'}, 'tcp')
* // <Multiaddr 047f000001060fa1 - /ip4/127.0.0.1/tcp/4001>
* ```
*
* @param {{family: 4 | 6, address: string, port: number}} addr
* @param {string} transport
*/
static fromNodeAddress (addr: NodeAddress, transport: string) {
if (addr == null) {
throw new Error('requires node address object')
}
if (transport == null) {
throw new Error('requires transport protocol')
}
let ip
switch (addr.family) {
case 4:
ip = 'ip4'
break
case 6:
ip = 'ip6'
break
default:
throw Error('Invalid addr family, should be 4 or 6.')
}
return new Multiaddr('/' + [ip, addr.address, transport, addr.port].join('/'))
}
/**
* Returns if something is a Multiaddr that is a name
*/
static isName (addr: Multiaddr) {
if (!Multiaddr.isMultiaddr(addr)) {
return false
}
// if a part of the multiaddr is resolvable, then return true
return addr.protos().some((proto) => proto.resolvable)
}
/**
* Check if object is a CID instance
*/
static isMultiaddr (value: any) {
return value instanceof Multiaddr ?? Boolean(value?.[symbol])
}
/**
* Returns Multiaddr as a human-readable string.
* For post Node.js v10.0.0.
* https://nodejs.org/api/deprecations.html#deprecations_dep0079_custom_inspection_function_on_objects_via_inspect
*
* @example
* ```js
* console.log(new Multiaddr('/ip4/127.0.0.1/tcp/4001'))
* // '<Multiaddr 047f000001060fa1 - /ip4/127.0.0.1/tcp/4001>'
* ```
*/
[inspect] () {
return '<Multiaddr ' +
uint8ArrayToString(this.bytes, 'base16') + ' - ' +
codec.bytesToString(this.bytes) + '>'
}
/**
* Returns Multiaddr as a human-readable string.
* Fallback for pre Node.js v10.0.0.
* https://nodejs.org/api/deprecations.html#deprecations_dep0079_custom_inspection_function_on_objects_via_inspect
*
* @example
* ```js
* new Multiaddr('/ip4/127.0.0.1/tcp/4001').inspect()
* // '<Multiaddr 047f000001060fa1 - /ip4/127.0.0.1/tcp/4001>'
* ```
*/
inspect () {
return '<Multiaddr ' +
uint8ArrayToString(this.bytes, 'base16') + ' - ' +
codec.bytesToString(this.bytes) + '>'
}
}
/**
* Static factory
*/
export function multiaddr (addr: MultiaddrInput) {
return new Multiaddr(addr)
}
export { getProtocol as protocols }
export { resolvers }