@multiformats/multiaddr-matcher
Version:
Match different multiaddr formats
498 lines (451 loc) • 12.4 kB
text/typescript
/**
* @packageDocumentation
*
* This module exports various matchers that can be used to infer the type of a
* passed multiaddr.
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { DNS } from '@multiformats/multiaddr-matcher'
*
* const ma = multiaddr('/dnsaddr/example.org')
*
* DNS.matches(ma) // true - this is a multiaddr with a DNS address at the start
* ```
*
* @example
*
* The default matching behaviour ignores any subsequent tuples in the multiaddr.
* If you want stricter matching you can use `.exactMatch`:
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { DNS, Circuit } from '@multiformats/multiaddr-matcher'
*
* const ma = multiaddr('/dnsaddr/example.org/p2p/QmFoo/p2p-circuit/p2p/QmBar')
*
* DNS.exactMatch(ma) // false - this address has extra tuples after the DNS component
* Circuit.matches(ma) // true
* Circuit.exactMatch(ma) // true - the extra tuples are circuit relay related
* ```
*/
import { isIPv4, isIPv6 } from '@chainsafe/is-ip'
import { and, or, literal, string, peerId, optional, fmt, func, number, certhash } from './utils.js'
import type { Multiaddr } from '@multiformats/multiaddr'
/**
* A matcher accepts multiaddr components and either fails to match and returns
* false or returns a sublist of unmatched components
*/
export interface Matcher {
match(parts: string[]): string[] | false
pattern: string
}
/**
* A MultiaddrMatcher allows interpreting a multiaddr as a certain type of
* multiaddr
*/
export interface MultiaddrMatcher {
/**
* The matchers that make up this MultiaddrMatcher - useful if you want to
* make your own custom matchers
*/
matchers: Matcher[]
/**
* Returns true if the passed multiaddr can be treated as this type of
* multiaddr
*/
matches(ma: Multiaddr): boolean
/**
* Returns true if the passed multiaddr terminates as this type of
* multiaddr
*/
exactMatch(ma: Multiaddr): boolean
}
/**
* Matches PeerId addresses
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { PEER_ID } from '@multiformats/multiaddr-matcher'
*
* PEER_ID.matches(multiaddr('/p2p/Qmfoo')) // true
* PEER_ID.matches(multiaddr('/ipfs/Qmfoo')) // true
* ```
*/
const _PEER_ID = peerId()
export const PEER_ID = fmt(_PEER_ID)
/**
* DNS matchers
*/
const _DNS4 = and(literal('dns4'), string())
const _DNS6 = and(literal('dns6'), string())
const _DNSADDR = and(literal('dnsaddr'), string())
const _DNS = and(literal('dns'), string())
/**
* Matches dns4 addresses.
*
* Use {@link DNS DNS} instead to match any type of DNS address.
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { DNS4 } from '@multiformats/multiaddr-matcher'
*
* DNS4.matches(multiaddr('/dns4/example.org')) // true
* ```
*/
export const DNS4 = fmt(_DNS4, optional(peerId()))
/**
* Matches dns6 addresses.
*
* Use {@link DNS DNS} instead to match any type of DNS address.
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { DNS6 } from '@multiformats/multiaddr-matcher'
*
* DNS6.matches(multiaddr('/dns6/example.org')) // true
* ```
*/
export const DNS6 = fmt(_DNS6, optional(peerId()))
/**
* Matches dnsaddr addresses.
*
* Use {@link DNS DNS} instead to match any type of DNS address.
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { DNSADDR } from '@multiformats/multiaddr-matcher'
*
* DNSADDR.matches(multiaddr('/dnsaddr/example.org')) // true
* DNSADDR.matches(multiaddr('/dnsaddr/example.org/p2p/Qmfoo')) // true
* ```
*/
export const DNSADDR = fmt(_DNSADDR, optional(peerId()))
/**
* Matches any dns address.
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { DNS } from '@multiformats/multiaddr-matcher'
*
* DNS.matches(multiaddr('/dnsaddr/example.org')) // true
* DNS.matches(multiaddr('/dns4/example.org')) // true
* DNS.matches(multiaddr('/dns6/example.org')) // true
* DNS.matches(multiaddr('/dns6/example.org/p2p/Qmfoo')) // true
* ```
*/
export const DNS = fmt(or(_DNS, _DNSADDR, _DNS4, _DNS6), optional(peerId()))
const _IP4 = and(literal('ip4'), func(isIPv4))
const _IP6 = and(literal('ip6'), func(isIPv6))
const _IP = or(_IP4, _IP6)
const _IP_OR_DOMAIN = or(_IP, _DNS, _DNS4, _DNS6, _DNSADDR)
/**
* A matcher for addresses that start with IP or DNS tuples.
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { IP_OR_DOMAIN } from '@multiformats/multiaddr-matcher'
*
* IP_OR_DOMAIN.matches(multiaddr('/ip4/123.123.123.123')) // true
* IP_OR_DOMAIN.matches(multiaddr('/ip4/123.123.123.123/p2p/QmFoo')) // true
* IP_OR_DOMAIN.matches(multiaddr('/dns/example.com/p2p/QmFoo')) // true
* IP_OR_DOMAIN.matches(multiaddr('/p2p/QmFoo')) // false
* ```
*/
export const IP_OR_DOMAIN = fmt(or(_IP, and(or(_DNS, _DNSADDR, _DNS4, _DNS6), optional(peerId()))))
/**
* Matches ip4 addresses.
*
* Use {@link IP IP} instead to match any ip4/ip6 address.
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { IP4 } from '@multiformats/multiaddr-matcher'
*
* const ma = multiaddr('/ip4/123.123.123.123')
*
* IP4.matches(ma) // true
* ```
*/
export const IP4 = fmt(_IP4)
/**
* Matches ip6 addresses.
*
* Use {@link IP IP} instead to match any ip4/ip6 address.
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { IP6 } from '@multiformats/multiaddr-matcher'
*
* const ma = multiaddr('/ip6/fe80::1cc1:a3b8:322f:cf22')
*
* IP6.matches(ma) // true
* ```
*/
export const IP6 = fmt(_IP6)
/**
* Matches ip4 or ip6 addresses.
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { IP } from '@multiformats/multiaddr-matcher'
*
* IP.matches(multiaddr('/ip4/123.123.123.123')) // true
* IP.matches(multiaddr('/ip6/fe80::1cc1:a3b8:322f:cf22')) // true
* ```
*/
export const IP = fmt(_IP)
const _TCP = and(_IP_OR_DOMAIN, literal('tcp'), number())
const _UDP = and(_IP_OR_DOMAIN, literal('udp'), number())
/**
* Matches TCP addresses.
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { TCP } from '@multiformats/multiaddr-matcher'
*
* TCP.matches(multiaddr('/ip4/123.123.123.123/tcp/1234')) // true
* ```
*/
export const TCP = fmt(and(_TCP, optional(peerId())))
/**
* Matches UDP addresses.
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { UDP } from '@multiformats/multiaddr-matcher'
*
* UDP.matches(multiaddr('/ip4/123.123.123.123/udp/1234')) // true
* ```
*/
export const UDP = fmt(_UDP)
const _QUIC = and(_UDP, literal('quic'), optional(peerId()))
const _QUICV1 = and(_UDP, literal('quic-v1'), optional(peerId()))
const QUIC_V0_OR_V1 = or(_QUIC, _QUICV1)
/**
* Matches QUIC addresses.
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { QUIC } from '@multiformats/multiaddr-matcher'
*
* QUIC.matches(multiaddr('/ip4/123.123.123.123/udp/1234/quic')) // true
* ```
*/
export const QUIC = fmt(_QUIC)
/**
* Matches QUICv1 addresses.
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { QUICV1 } from '@multiformats/multiaddr-matcher'
*
* QUICV1.matches(multiaddr('/ip4/123.123.123.123/udp/1234/quic-v1')) // true
* ```
*/
export const QUICV1 = fmt(_QUICV1)
const _WEB = or(
_IP_OR_DOMAIN,
_TCP,
_UDP,
_QUIC,
_QUICV1
)
const _WebSockets = or(
and(_WEB, literal('ws'), optional(peerId()))
)
/**
* Matches WebSocket addresses.
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { WebSockets } from '@multiformats/multiaddr-matcher'
*
* WebSockets.matches(multiaddr('/ip4/123.123.123.123/tcp/1234/ws')) // true
* ```
*/
export const WebSockets = fmt(_WebSockets)
const _WebSocketsSecure = or(
and(_WEB, literal('wss'), optional(peerId())),
and(_WEB, literal('tls'), optional(and(literal('sni'), string())), literal('ws'), optional(peerId()))
)
/**
* Matches secure WebSocket addresses.
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { WebSocketsSecure } from '@multiformats/multiaddr-matcher'
*
* WebSocketsSecure.matches(multiaddr('/ip4/123.123.123.123/tcp/1234/wss')) // true
* ```
*/
export const WebSocketsSecure = fmt(_WebSocketsSecure)
const _WebRTCDirect = and(_UDP, literal('webrtc-direct'), optional(certhash()), optional(certhash()), optional(peerId()))
/**
* Matches WebRTC-direct addresses.
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { WebRTCDirect } from '@multiformats/multiaddr-matcher'
*
* WebRTCDirect.matches(multiaddr('/ip4/123.123.123.123/tcp/1234/p2p/QmFoo/webrtc-direct/certhash/u....')) // true
* ```
*/
export const WebRTCDirect = fmt(_WebRTCDirect)
const _WebTransport = and(_QUICV1, literal('webtransport'), optional(certhash()), optional(certhash()), optional(peerId()))
/**
* Matches WebTransport addresses.
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { WebRTCDirect } from '@multiformats/multiaddr-matcher'
*
* WebRTCDirect.matches(multiaddr('/ip4/123.123.123.123/udp/1234/quic-v1/webtransport/certhash/u..../certhash/u..../p2p/QmFoo')) // true
* ```
*/
export const WebTransport = fmt(_WebTransport)
const _P2P = or(
_WebSockets,
_WebSocketsSecure,
and(_TCP, optional(peerId())),
and(QUIC_V0_OR_V1, optional(peerId())),
and(_IP_OR_DOMAIN, optional(peerId())),
_WebRTCDirect,
_WebTransport,
peerId()
)
/**
* Matches peer addresses
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { P2P } from '@multiformats/multiaddr-matcher'
*
* P2P.matches(multiaddr('/ip4/123.123.123.123/tcp/1234/p2p/QmFoo')) // true
* ```
*/
export const P2P = fmt(_P2P)
const _Circuit = and(_P2P, literal('p2p-circuit'), peerId())
/**
* Matches circuit relay addresses
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { Circuit } from '@multiformats/multiaddr-matcher'
*
* Circuit.matches(multiaddr('/ip4/123.123.123.123/tcp/1234/p2p/QmRelay/p2p-circuit/p2p/QmTarget')) // true
* ```
*/
export const Circuit = fmt(_Circuit)
const _WebRTC = or(
and(_P2P, literal('p2p-circuit'), literal('webrtc'), optional(peerId())),
and(_P2P, literal('webrtc'), optional(peerId())),
and(literal('webrtc'), optional(peerId()))
)
/**
* Matches WebRTC addresses
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { WebRTC } from '@multiformats/multiaddr-matcher'
*
* WebRTC.matches(multiaddr('/ip4/123.123.123.123/tcp/1234/p2p/QmRelay/p2p-circuit/webrtc/p2p/QmTarget')) // true
* ```
*/
export const WebRTC = fmt(_WebRTC)
const _HTTP = or(
and(_IP_OR_DOMAIN, literal('tcp'), number(), literal('http'), optional(peerId())),
and(_IP_OR_DOMAIN, literal('http'), optional(peerId()))
)
/**
* Matches HTTP addresses
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { HTTP } from '@multiformats/multiaddr-matcher'
*
* HTTP.matches(multiaddr('/dns/example.org/http')) // true
* ```
*/
export const HTTP = fmt(_HTTP)
const _HTTPS = or(
and(_IP_OR_DOMAIN, literal('tcp'), or(
and(literal('443'), literal('http')),
and(number(), literal('https'))
), optional(peerId())),
and(_IP_OR_DOMAIN, literal('tls'), literal('http'), optional(peerId())),
and(_IP_OR_DOMAIN, literal('https'), optional(peerId()))
)
/**
* Matches HTTPS addresses
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { HTTP } from '@multiformats/multiaddr-matcher'
*
* HTTP.matches(multiaddr('/dns/example.org/tls/http')) // true
* ```
*/
export const HTTPS = fmt(_HTTPS)
const _Memory = or(
and(literal('memory'), string(), optional(peerId()))
)
/**
* Matches Memory addresses
*
* @example
*
* ```ts
* import { multiaddr } from '@multiformats/multiaddr'
* import { Memory } from '@multiformats/multiaddr-matcher'
*
* Memory.matches(multiaddr('/memory/0xDEADBEEF')) // true
* ```
*/
export const Memory = fmt(_Memory)