Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use Multiaddr as class name #352

Merged
merged 1 commit into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
277 changes: 4 additions & 273 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,8 @@
* ```
*/

import { CodeError } from '@libp2p/interface/errors'
import { base58btc } from 'multiformats/bases/base58'
import { CID } from 'multiformats/cid'
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { bytesToMultiaddrParts, stringToMultiaddrParts, type MultiaddrParts, tuplesToBytes } from './codec.js'
import { getProtocol, names } from './protocols-table.js'

const inspect = Symbol.for('nodejs.util.inspect.custom')

const DNS_CODES = [
getProtocol('dns').code,
getProtocol('dns4').code,
getProtocol('dns6').code,
getProtocol('dnsaddr').code
]
import { Multiaddr as MultiaddrClass, symbol } from './multiaddr.js'
import { getProtocol } from './protocols-table.js'

/**
* Protocols are present in the protocol table
Expand Down Expand Up @@ -91,7 +77,6 @@ export interface AbortOptions {
* All configured {@link Resolver}s
*/
export const resolvers = new Map<string, Resolver>()
const symbol = Symbol.for('@multiformats/js-multiaddr/multiaddr')

export { MultiaddrFilter } from './filter/multiaddr-filter.js'

Expand Down Expand Up @@ -444,7 +429,7 @@ export function fromNodeAddress (addr: NodeAddress, transport: string): Multiadd
default:
throw Error('Invalid addr family, should be 4 or 6.')
}
return new DefaultMultiaddr('/' + [ip, host, transport, addr.port].join('/'))
return new MultiaddrClass('/' + [ip, host, transport, addr.port].join('/'))
}

/**
Expand Down Expand Up @@ -488,260 +473,6 @@ export function isMultiaddr (value: any): value is Multiaddr {
return Boolean(value?.[symbol])
}

/**
* Creates a {@link Multiaddr} from a {@link MultiaddrInput}
*/
class DefaultMultiaddr implements Multiaddr {
public bytes: Uint8Array
#string: string
#tuples: Tuple[]
#stringTuples: StringTuple[]
#path: string | null

[symbol]: boolean = true

constructor (addr?: MultiaddrInput) {
// default
if (addr == null) {
addr = ''
}

let parts: MultiaddrParts
if (addr instanceof Uint8Array) {
parts = bytesToMultiaddrParts(addr)
} else if (typeof addr === 'string') {
if (addr.length > 0 && addr.charAt(0) !== '/') {
throw new Error(`multiaddr "${addr}" must start with a "/"`)
}
parts = stringToMultiaddrParts(addr)
} else if (isMultiaddr(addr)) { // Multiaddr
parts = bytesToMultiaddrParts(addr.bytes)
} else {
throw new Error('addr must be a string, Buffer, or another Multiaddr')
}

this.bytes = parts.bytes
this.#string = parts.string
this.#tuples = parts.tuples
this.#stringTuples = parts.stringTuples
this.#path = parts.path
}

toString (): string {
return this.#string
}

toJSON (): string {
return this.toString()
}

toOptions (): MultiaddrObject {
let family: 4 | 6 | undefined
let transport: string | undefined
let host: string | undefined
let port: number | undefined
let zone = ''

const tcp = getProtocol('tcp')
const udp = getProtocol('udp')
const ip4 = getProtocol('ip4')
const ip6 = getProtocol('ip6')
const dns6 = getProtocol('dns6')
const ip6zone = getProtocol('ip6zone')

for (const [code, value] of this.stringTuples()) {
if (code === ip6zone.code) {
zone = `%${value ?? ''}`
}

// default to https when protocol & port are omitted from DNS addrs
if (DNS_CODES.includes(code)) {
transport = tcp.name
port = 443
host = `${value ?? ''}${zone}`
family = code === dns6.code ? 6 : 4
}

if (code === tcp.code || code === udp.code) {
transport = getProtocol(code).name
port = parseInt(value ?? '')
}

if (code === ip4.code || code === ip6.code) {
transport = getProtocol(code).name
host = `${value ?? ''}${zone}`
family = code === ip6.code ? 6 : 4
}
}

if (family == null || transport == null || host == null || port == null) {
throw new Error('multiaddr must have a valid format: "/{ip4, ip6, dns4, dns6, dnsaddr}/{address}/{tcp, udp}/{port}".')
}

const opts: MultiaddrObject = {
family,
host,
transport,
port
}

return opts
}

protos (): Protocol[] {
return this.#tuples.map(([code]) => Object.assign({}, getProtocol(code)))
}

protoCodes (): number[] {
return this.#tuples.map(([code]) => code)
}

protoNames (): string[] {
return this.#tuples.map(([code]) => getProtocol(code).name)
}

tuples (): Array<[number, Uint8Array?]> {
return this.#tuples
}

stringTuples (): Array<[number, string?]> {
return this.#stringTuples
}

encapsulate (addr: MultiaddrInput): Multiaddr {
addr = new DefaultMultiaddr(addr)
return new DefaultMultiaddr(this.toString() + addr.toString())
}

decapsulate (addr: Multiaddr | string): Multiaddr {
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 DefaultMultiaddr(s.slice(0, i))
}

decapsulateCode (code: number): Multiaddr {
const tuples = this.tuples()
for (let i = tuples.length - 1; i >= 0; i--) {
if (tuples[i][0] === code) {
return new DefaultMultiaddr(tuplesToBytes(tuples.slice(0, i)))
}
}
return this
}

getPeerId (): string | null {
try {
let tuples: Array<[number, string | undefined]> = []

this.stringTuples().forEach(([code, name]) => {
if (code === names.p2p.code) {
tuples.push([code, name])
}

// if this is a p2p-circuit address, return the target peer id if present
// not the peer id of the relay
if (code === names['p2p-circuit'].code) {
tuples = []
}
})

// Get the last ipfs tuple ['p2p', '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
}
}

getPath (): string | null {
return this.#path
}

equals (addr: { bytes: Uint8Array }): boolean {
return uint8ArrayEquals(this.bytes, addr.bytes)
}

async resolve (options?: AbortOptions): Promise<Multiaddr[]> {
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 new CodeError(`no available resolver for ${resolvableProto.name}`, 'ERR_NO_AVAILABLE_RESOLVER')
}

const addresses = await resolver(this, options)
return addresses.map((a) => new DefaultMultiaddr(a))
}

nodeAddress (): NodeAddress {
const options = this.toOptions()

if (options.transport !== 'tcp' && options.transport !== 'udp') {
throw new Error(`multiaddr must have a valid format - no protocol with name: "${options.transport}". Must have a valid transport protocol: "{tcp, udp}"`)
}

return {
family: options.family,
address: options.host,
port: options.port
}
}

isThinWaistAddress (addr?: Multiaddr): boolean {
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
}

/**
* Returns Multiaddr as a human-readable string
* https://nodejs.org/api/util.html#utilinspectcustom
*
* @example
* ```js
* import { multiaddr } from '@multiformats/multiaddr'
*
* console.info(multiaddr('/ip4/127.0.0.1/tcp/4001'))
* // 'Multiaddr(/ip4/127.0.0.1/tcp/4001)'
* ```
*/
[inspect] (): string {
return `Multiaddr(${this.#string})`
}
}

/**
* A function that takes a {@link MultiaddrInput} and returns a {@link Multiaddr}
*
Expand All @@ -756,7 +487,7 @@ class DefaultMultiaddr implements Multiaddr {
* @param {MultiaddrInput} [addr] - If String or Uint8Array, needs to adhere to the address format of a [multiaddr](https://github.com/multiformats/multiaddr#string-format)
*/
export function multiaddr (addr?: MultiaddrInput): Multiaddr {
return new DefaultMultiaddr(addr)
return new MultiaddrClass(addr)
}

export { getProtocol as protocols }
Loading