UNPKG

18.7 kBJavaScriptView Raw
1'use strict'
2
3const codec = require('./codec')
4const protocols = require('./protocols-table')
5const varint = require('varint')
6const { CID } = require('multiformats/cid')
7const { base58btc } = require('multiformats/bases/base58')
8const errCode = require('err-code')
9const inspect = Symbol.for('nodejs.util.inspect.custom')
10const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
11const { equals: uint8ArrayEquals } = require('uint8arrays/equals')
12
13/**
14 * @typedef {(addr: Multiaddr) => Promise<string[]>} Resolver
15 * @typedef {string | Multiaddr | Uint8Array | null} MultiaddrInput
16 * @typedef {import('./types').MultiaddrObject} MultiaddrObject
17 * @typedef {import('./types').Protocol} Protocol
18 */
19
20/** @type {Map<string, Resolver>} */
21const resolvers = new Map()
22const symbol = Symbol.for('@multiformats/js-multiaddr/multiaddr')
23
24/**
25 * Creates a [multiaddr](https://github.com/multiformats/multiaddr) from
26 * a Uint8Array, String or another Multiaddr instance
27 * public key.
28 *
29 */
30class Multiaddr {
31 /**
32 * @example
33 * ```js
34 * new Multiaddr('/ip4/127.0.0.1/tcp/4001')
35 * // <Multiaddr 047f000001060fa1 - /ip4/127.0.0.1/tcp/4001>
36 * ```
37 *
38 * @param {MultiaddrInput} [addr] - If String or Uint8Array, needs to adhere to the address format of a [multiaddr](https://github.com/multiformats/multiaddr#string-format)
39 */
40 constructor (addr) {
41 // default
42 if (addr == null) {
43 addr = ''
44 }
45
46 // Define symbol
47 Object.defineProperty(this, symbol, { value: true })
48
49 if (addr instanceof Uint8Array) {
50 /** @type {Uint8Array} - The raw bytes representing this multiaddress */
51 this.bytes = codec.fromBytes(addr)
52 } else if (typeof addr === 'string') {
53 if (addr.length > 0 && addr.charAt(0) !== '/') {
54 throw new Error(`multiaddr "${addr}" must start with a "/"`)
55 }
56 this.bytes = codec.fromString(addr)
57 } else if (Multiaddr.isMultiaddr(addr)) { // Multiaddr
58 this.bytes = codec.fromBytes(addr.bytes) // validate + copy buffer
59 } else {
60 throw new Error('addr must be a string, Buffer, or another Multiaddr')
61 }
62 }
63
64 /**
65 * Returns Multiaddr as a String
66 *
67 * @example
68 * ```js
69 * new Multiaddr('/ip4/127.0.0.1/tcp/4001').toString()
70 * // '/ip4/127.0.0.1/tcp/4001'
71 * ```
72 */
73 toString () {
74 return codec.bytesToString(this.bytes)
75 }
76
77 /**
78 * Returns Multiaddr as a JSON encoded object
79 *
80 * @example
81 * ```js
82 * JSON.stringify(new Multiaddr('/ip4/127.0.0.1/tcp/4001'))
83 * // '/ip4/127.0.0.1/tcp/4001'
84 * ```
85 */
86 toJSON () {
87 return this.toString()
88 }
89
90 /**
91 * Returns Multiaddr as a convinient options object to be used with net.createConnection
92 *
93 * @example
94 * ```js
95 * new Multiaddr('/ip4/127.0.0.1/tcp/4001').toOptions()
96 * // { family: 4, host: '127.0.0.1', transport: 'tcp', port: 4001 }
97 * ```
98 */
99 toOptions () {
100 /** @type {MultiaddrObject} */
101 const opts = {}
102 const parsed = this.toString().split('/')
103 opts.family = parsed[1] === 'ip4' ? 4 : 6
104 opts.host = parsed[2]
105 opts.transport = parsed[3]
106 opts.port = parseInt(parsed[4])
107 return opts
108 }
109
110 /**
111 * Returns the protocols the Multiaddr is defined with, as an array of objects, in
112 * left-to-right order. Each object contains the protocol code, protocol name,
113 * and the size of its address space in bits.
114 * [See list of protocols](https://github.com/multiformats/multiaddr/blob/master/protocols.csv)
115 *
116 * @example
117 * ```js
118 * new Multiaddr('/ip4/127.0.0.1/tcp/4001').protos()
119 * // [ { code: 4, size: 32, name: 'ip4' },
120 * // { code: 6, size: 16, name: 'tcp' } ]
121 * ```
122 *
123 * @returns {Protocol[]} protocols - All the protocols the address is composed of
124 */
125 protos () {
126 return this.protoCodes().map(code => Object.assign({}, protocols(code)))
127 }
128
129 /**
130 * Returns the codes of the protocols in left-to-right order.
131 * [See list of protocols](https://github.com/multiformats/multiaddr/blob/master/protocols.csv)
132 *
133 * @example
134 * ```js
135 * Multiaddr('/ip4/127.0.0.1/tcp/4001').protoCodes()
136 * // [ 4, 6 ]
137 * ```
138 *
139 * @returns {number[]} protocol codes
140 */
141 protoCodes () {
142 const codes = []
143 const buf = this.bytes
144 let i = 0
145 while (i < buf.length) {
146 const code = varint.decode(buf, i)
147 const n = varint.decode.bytes
148
149 const p = protocols(code)
150 const size = codec.sizeForAddr(p, buf.slice(i + n))
151
152 i += (size + n)
153 codes.push(code)
154 }
155
156 return codes
157 }
158
159 /**
160 * Returns the names of the protocols in left-to-right order.
161 * [See list of protocols](https://github.com/multiformats/multiaddr/blob/master/protocols.csv)
162 *
163 * @example
164 * ```js
165 * new Multiaddr('/ip4/127.0.0.1/tcp/4001').protoNames()
166 * // [ 'ip4', 'tcp' ]
167 * ```
168 *
169 * @returns {string[]} protocol names
170 */
171 protoNames () {
172 return this.protos().map(proto => proto.name)
173 }
174
175 /**
176 * Returns a tuple of parts
177 *
178 * @example
179 * ```js
180 * new Multiaddr("/ip4/127.0.0.1/tcp/4001").tuples()
181 * // [ [ 4, <Buffer 7f 00 00 01> ], [ 6, <Buffer 0f a1> ] ]
182 * ```
183 */
184 tuples () {
185 return codec.bytesToTuples(this.bytes)
186 }
187
188 /**
189 * Returns a tuple of string/number parts
190 * - tuples[][0] = code of protocol
191 * - tuples[][1] = contents of address
192 *
193 * @example
194 * ```js
195 * new Multiaddr("/ip4/127.0.0.1/tcp/4001").stringTuples()
196 * // [ [ 4, '127.0.0.1' ], [ 6, '4001' ] ]
197 * ```
198 */
199 stringTuples () {
200 const t = codec.bytesToTuples(this.bytes)
201 return codec.tuplesToStringTuples(t)
202 }
203
204 /**
205 * Encapsulates a Multiaddr in another Multiaddr
206 *
207 * @example
208 * ```js
209 * const mh1 = new Multiaddr('/ip4/8.8.8.8/tcp/1080')
210 * // <Multiaddr 0408080808060438 - /ip4/8.8.8.8/tcp/1080>
211 *
212 * const mh2 = new Multiaddr('/ip4/127.0.0.1/tcp/4001')
213 * // <Multiaddr 047f000001060fa1 - /ip4/127.0.0.1/tcp/4001>
214 *
215 * const mh3 = mh1.encapsulate(mh2)
216 * // <Multiaddr 0408080808060438047f000001060fa1 - /ip4/8.8.8.8/tcp/1080/ip4/127.0.0.1/tcp/4001>
217 *
218 * mh3.toString()
219 * // '/ip4/8.8.8.8/tcp/1080/ip4/127.0.0.1/tcp/4001'
220 * ```
221 *
222 * @param {MultiaddrInput} addr - Multiaddr to add into this Multiaddr
223 */
224 encapsulate (addr) {
225 addr = new Multiaddr(addr)
226 return new Multiaddr(this.toString() + addr.toString())
227 }
228
229 /**
230 * Decapsulates a Multiaddr from another Multiaddr
231 *
232 * @example
233 * ```js
234 * const mh1 = new Multiaddr('/ip4/8.8.8.8/tcp/1080')
235 * // <Multiaddr 0408080808060438 - /ip4/8.8.8.8/tcp/1080>
236 *
237 * const mh2 = new Multiaddr('/ip4/127.0.0.1/tcp/4001')
238 * // <Multiaddr 047f000001060fa1 - /ip4/127.0.0.1/tcp/4001>
239 *
240 * const mh3 = mh1.encapsulate(mh2)
241 * // <Multiaddr 0408080808060438047f000001060fa1 - /ip4/8.8.8.8/tcp/1080/ip4/127.0.0.1/tcp/4001>
242 *
243 * mh3.decapsulate(mh2).toString()
244 * // '/ip4/8.8.8.8/tcp/1080'
245 * ```
246 *
247 * @param {Multiaddr | string} addr - Multiaddr to remove from this Multiaddr
248 * @returns {Multiaddr}
249 */
250 decapsulate (addr) {
251 const addrString = addr.toString()
252 const s = this.toString()
253 const i = s.lastIndexOf(addrString)
254 if (i < 0) {
255 throw new Error('Address ' + this + ' does not contain subaddress: ' + addr)
256 }
257 return new Multiaddr(s.slice(0, i))
258 }
259
260 /**
261 * A more reliable version of `decapsulate` if you are targeting a
262 * specific code, such as 421 (the `p2p` protocol code). The last index of the code
263 * will be removed from the `Multiaddr`, and a new instance will be returned.
264 * If the code is not present, the original `Multiaddr` is returned.
265 *
266 * @example
267 * ```js
268 * const addr = new Multiaddr('/ip4/0.0.0.0/tcp/8080/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC')
269 * // <Multiaddr 0400... - /ip4/0.0.0.0/tcp/8080/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC>
270 *
271 * addr.decapsulateCode(421).toString()
272 * // '/ip4/0.0.0.0/tcp/8080'
273 *
274 * new Multiaddr('/ip4/127.0.0.1/tcp/8080').decapsulateCode(421).toString()
275 * // '/ip4/127.0.0.1/tcp/8080'
276 * ```
277 *
278 * @param {number} code - The code of the protocol to decapsulate from this Multiaddr
279 * @returns {Multiaddr}
280 */
281 decapsulateCode (code) {
282 const tuples = this.tuples()
283 for (let i = tuples.length - 1; i >= 0; i--) {
284 if (tuples[i][0] === code) {
285 return new Multiaddr(codec.tuplesToBytes(tuples.slice(0, i)))
286 }
287 }
288 return this
289 }
290
291 /**
292 * Extract the peerId if the multiaddr contains one
293 *
294 * @example
295 * ```js
296 * const mh1 = new Multiaddr('/ip4/8.8.8.8/tcp/1080/ipfs/QmValidBase58string')
297 * // <Multiaddr 0408080808060438 - /ip4/8.8.8.8/tcp/1080/ipfs/QmValidBase58string>
298 *
299 * // should return QmValidBase58string or null if the id is missing or invalid
300 * const peerId = mh1.getPeerId()
301 * ```
302 *
303 * @returns {string | null} peerId - The id of the peer or null if invalid or missing from the ma
304 */
305 getPeerId () {
306 try {
307 const tuples = this.stringTuples().filter((tuple) => {
308 if (tuple[0] === protocols.names.ipfs.code) {
309 return true
310 }
311 return false
312 })
313
314 // Get the last ipfs tuple ['ipfs', 'peerid string']
315 const tuple = tuples.pop()
316 if (tuple && tuple[1]) {
317 const peerIdStr = tuple[1]
318
319 // peer id is base58btc encoded string but not multibase encoded so add the `z`
320 // prefix so we can validate that it is correctly encoded
321 if (peerIdStr[0] === 'Q' || peerIdStr[0] === '1') {
322 return uint8ArrayToString(base58btc.decode(`z${peerIdStr}`), 'base58btc')
323 }
324
325 // try to parse peer id as CID
326 return uint8ArrayToString(CID.parse(peerIdStr).multihash.bytes, 'base58btc')
327 }
328
329 return null
330 } catch (e) {
331 return null
332 }
333 }
334
335 /**
336 * Extract the path if the multiaddr contains one
337 *
338 * @example
339 * ```js
340 * const mh1 = new Multiaddr('/ip4/8.8.8.8/tcp/1080/unix/tmp/p2p.sock')
341 * // <Multiaddr 0408080808060438 - /ip4/8.8.8.8/tcp/1080/unix/tmp/p2p.sock>
342 *
343 * // should return utf8 string or null if the id is missing or invalid
344 * const path = mh1.getPath()
345 * ```js
346 *
347 * @returns {string | null} path - The path of the multiaddr, or null if no path protocol is present
348 */
349 getPath () {
350 let path = null
351 try {
352 path = this.stringTuples().filter((tuple) => {
353 const proto = protocols(tuple[0])
354 if (proto.path) {
355 return true
356 }
357 return false
358 })[0][1]
359
360 if (!path) {
361 path = null
362 }
363 } catch (e) {
364 path = null
365 }
366 return path
367 }
368
369 /**
370 * Checks if two Multiaddrs are the same
371 *
372 * @example
373 * ```js
374 * const mh1 = new Multiaddr('/ip4/8.8.8.8/tcp/1080')
375 * // <Multiaddr 0408080808060438 - /ip4/8.8.8.8/tcp/1080>
376 *
377 * const mh2 = new Multiaddr('/ip4/127.0.0.1/tcp/4001')
378 * // <Multiaddr 047f000001060fa1 - /ip4/127.0.0.1/tcp/4001>
379 *
380 * mh1.equals(mh1)
381 * // true
382 *
383 * mh1.equals(mh2)
384 * // false
385 * ```
386 *
387 * @param {Multiaddr} addr
388 * @returns {boolean}
389 */
390 equals (addr) {
391 return uint8ArrayEquals(this.bytes, addr.bytes)
392 }
393
394 /**
395 * Resolve multiaddr if containing resolvable hostname.
396 *
397 * @example
398 * ```js
399 * Multiaddr.resolvers.set('dnsaddr', resolverFunction)
400 * const mh1 = new Multiaddr('/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb')
401 * const resolvedMultiaddrs = await mh1.resolve()
402 * // [
403 * // <Multiaddr 04934b5353060fa1a503221220c10f9319dac35c270a6b74cd644cb3acfc1f6efc8c821f8eb282599fd1814f64 - /ip4/147.75.83.83/tcp/4001/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb>,
404 * // <Multiaddr 04934b53530601bbde03a503221220c10f9319dac35c270a6b74cd644cb3acfc1f6efc8c821f8eb282599fd1814f64 - /ip4/147.75.83.83/tcp/443/wss/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb>,
405 * // <Multiaddr 04934b535391020fa1cc03a503221220c10f9319dac35c270a6b74cd644cb3acfc1f6efc8c821f8eb282599fd1814f64 - /ip4/147.75.83.83/udp/4001/quic/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb>
406 * // ]
407 * ```
408 *
409 * @returns {Promise<Array<Multiaddr>>}
410 */
411 async resolve () {
412 const resolvableProto = this.protos().find((p) => p.resolvable)
413
414 // Multiaddr is not resolvable?
415 if (!resolvableProto) {
416 return [this]
417 }
418
419 const resolver = resolvers.get(resolvableProto.name)
420 if (!resolver) {
421 throw errCode(new Error(`no available resolver for ${resolvableProto.name}`), 'ERR_NO_AVAILABLE_RESOLVER')
422 }
423
424 const addresses = await resolver(this)
425 return addresses.map((a) => new Multiaddr(a))
426 }
427
428 /**
429 * Gets a Multiaddrs node-friendly address object. Note that protocol information
430 * is left out: in Node (and most network systems) the protocol is unknowable
431 * given only the address.
432 *
433 * Has to be a ThinWaist Address, otherwise throws error
434 *
435 * @example
436 * ```js
437 * new Multiaddr('/ip4/127.0.0.1/tcp/4001').nodeAddress()
438 * // {family: 4, address: '127.0.0.1', port: 4001}
439 * ```
440 *
441 * @returns {{family: 4 | 6, address: string, port: number}}
442 * @throws {Error} Throws error if Multiaddr is not a Thin Waist address
443 */
444 nodeAddress () {
445 const codes = this.protoCodes()
446 const names = this.protoNames()
447 const parts = this.toString().split('/').slice(1)
448
449 if (parts.length < 4) {
450 throw new Error('multiaddr must have a valid format: "/{ip4, ip6, dns4, dns6}/{address}/{tcp, udp}/{port}".')
451 } else if (codes[0] !== 4 && codes[0] !== 41 && codes[0] !== 54 && codes[0] !== 55) {
452 throw new Error(`no protocol with name: "'${names[0]}'". Must have a valid family name: "{ip4, ip6, dns4, dns6}".`)
453 } else if (parts[2] !== 'tcp' && parts[2] !== 'udp') {
454 throw new Error(`no protocol with name: "'${names[1]}'". Must have a valid transport protocol: "{tcp, udp}".`)
455 }
456
457 return {
458 family: (codes[0] === 41 || codes[0] === 55) ? 6 : 4,
459 address: parts[1],
460 port: parseInt(parts[3]) // tcp or udp port
461 }
462 }
463
464 /**
465 * Returns if a Multiaddr is a Thin Waist address or not.
466 *
467 * Thin Waist is if a Multiaddr adheres to the standard combination of:
468 *
469 * `{IPv4, IPv6}/{TCP, UDP}`
470 *
471 * @example
472 * ```js
473 * const mh1 = new Multiaddr('/ip4/127.0.0.1/tcp/4001')
474 * // <Multiaddr 047f000001060fa1 - /ip4/127.0.0.1/tcp/4001>
475 * const mh2 = new Multiaddr('/ip4/192.168.2.1/tcp/5001')
476 * // <Multiaddr 04c0a80201061389 - /ip4/192.168.2.1/tcp/5001>
477 * const mh3 = mh1.encapsulate(mh2)
478 * // <Multiaddr 047f000001060fa104c0a80201061389 - /ip4/127.0.0.1/tcp/4001/ip4/192.168.2.1/tcp/5001>
479 * const mh4 = new Multiaddr('/ip4/127.0.0.1/tcp/2000/wss/p2p-webrtc-star/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSooo2a')
480 * // <Multiaddr 047f0000010607d0de039302a503221220d52ebb89d85b02a284948203a62ff28389c57c9f42beec4ec20db76a64835843 - /ip4/127.0.0.1/tcp/2000/wss/p2p-webrtc-star/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSooo2a>
481 * mh1.isThinWaistAddress()
482 * // true
483 * mh2.isThinWaistAddress()
484 * // true
485 * mh3.isThinWaistAddress()
486 * // false
487 * mh4.isThinWaistAddress()
488 * // false
489 * ```
490 *
491 * @param {Multiaddr} [addr] - Defaults to using `this` instance
492 */
493 isThinWaistAddress (addr) {
494 const protos = (addr || this).protos()
495
496 if (protos.length !== 2) {
497 return false
498 }
499
500 if (protos[0].code !== 4 && protos[0].code !== 41) {
501 return false
502 }
503 if (protos[1].code !== 6 && protos[1].code !== 273) {
504 return false
505 }
506 return true
507 }
508
509 /**
510 * Creates a Multiaddr from a node-friendly address object
511 *
512 * @example
513 * ```js
514 * Multiaddr.fromNodeAddress({address: '127.0.0.1', port: '4001'}, 'tcp')
515 * // <Multiaddr 047f000001060fa1 - /ip4/127.0.0.1/tcp/4001>
516 * ```
517 *
518 * @param {{family: 4 | 6, address: string, port: number}} addr
519 * @param {string} transport
520 */
521 static fromNodeAddress (addr, transport) {
522 if (!addr) { throw new Error('requires node address object') }
523 if (!transport) { throw new Error('requires transport protocol') }
524 let ip
525 switch (addr.family) {
526 case 4:
527 ip = 'ip4'
528 break
529 case 6:
530 ip = 'ip6'
531 break
532 default:
533 throw Error(`Invalid addr family. Got '${addr.family}' instead of 4 or 6`)
534 }
535 return new Multiaddr('/' + [ip, addr.address, transport, addr.port].join('/'))
536 }
537
538 /**
539 * Returns if something is a Multiaddr that is a name
540 *
541 * @param {Multiaddr} addr
542 * @returns {boolean} isName
543 */
544 static isName (addr) {
545 if (!Multiaddr.isMultiaddr(addr)) {
546 return false
547 }
548
549 // if a part of the multiaddr is resolvable, then return true
550 return addr.protos().some((proto) => proto.resolvable)
551 }
552
553 /**
554 * Check if object is a CID instance
555 *
556 * @param {any} value
557 * @returns {value is Multiaddr}
558 */
559 static isMultiaddr (value) {
560 return value instanceof Multiaddr || Boolean(value && value[symbol])
561 }
562
563 /**
564 * Returns Multiaddr as a human-readable string.
565 * For post Node.js v10.0.0.
566 * https://nodejs.org/api/deprecations.html#deprecations_dep0079_custom_inspection_function_on_objects_via_inspect
567 *
568 * @example
569 * ```js
570 * console.log(new Multiaddr('/ip4/127.0.0.1/tcp/4001'))
571 * // '<Multiaddr 047f000001060fa1 - /ip4/127.0.0.1/tcp/4001>'
572 * ```
573 *
574 * @returns {string}
575 */
576 [inspect] () {
577 return '<Multiaddr ' +
578 uint8ArrayToString(this.bytes, 'base16') + ' - ' +
579 codec.bytesToString(this.bytes) + '>'
580 }
581
582 /**
583 * Returns Multiaddr as a human-readable string.
584 * Fallback for pre Node.js v10.0.0.
585 * https://nodejs.org/api/deprecations.html#deprecations_dep0079_custom_inspection_function_on_objects_via_inspect
586 *
587 * @example
588 * ```js
589 * new Multiaddr('/ip4/127.0.0.1/tcp/4001').inspect()
590 * // '<Multiaddr 047f000001060fa1 - /ip4/127.0.0.1/tcp/4001>'
591 * ```
592 *
593 * @returns {string}
594 */
595 inspect () {
596 return '<Multiaddr ' +
597 uint8ArrayToString(this.bytes, 'base16') + ' - ' +
598 codec.bytesToString(this.bytes) + '>'
599 }
600}
601
602/**
603 * Object containing table, names and codes of all supported protocols.
604 * To get the protocol values from a Multiaddr, you can use
605 * [`.protos()`](#multiaddrprotos),
606 * [`.protoCodes()`](#multiaddrprotocodes) or
607 * [`.protoNames()`](#multiaddrprotonames)
608 *
609 * @returns {{table: Array, names: Object, codes: Object}}
610 */
611Multiaddr.protocols = protocols
612
613Multiaddr.resolvers = resolvers
614
615/**
616 * Static factory
617 *
618 * @param {MultiaddrInput} addr
619 */
620function multiaddr (addr) {
621 return new Multiaddr(addr)
622}
623
624module.exports = { Multiaddr, multiaddr, protocols, resolvers }