Module multiformats.multiaddr.raw

Implementation of raw encodings used by multiaddr protocols.

For expected address bytestring sizes, see the multiaddr table.

Expand source code
"""
    Implementation of raw encodings used by multiaddr protocols.

    For expected address bytestring sizes, see the [multiaddr table](https://github.com/multiformats/multiaddr/blob/master/protocols.csv).
"""

from ipaddress import IPv4Address, IPv6Address, AddressValueError
from typing import Callable, Dict, Optional, Tuple
from typing_validation import validate

from multiformats.varint import BytesLike
from . import err

RawEncoder = Callable[[str], bytes]
RawDecoder = Callable[[BytesLike], str]
ProtoImpl = Tuple[Optional[RawEncoder], Optional[RawDecoder], Optional[int]]

_proto_impl: Dict[str, ProtoImpl] = {}

def get(name: str) -> ProtoImpl:
    """
        Gets the implementation `(raw_encoder, raw_decoder, addr_size)` for a protocol with given name.
        Raises `err.KeyError` if no such encoding exists.

        The `addr_size` component is the size in bytes for the binary representation of protocol addresses:

        - for protocols with no address, `addr_size` is 0
        - for protocols with addresses of variable binary size, `addr_size` is `None`
        - for all other protocols, size is a positive `int`

        Example usage:

        ```py
        >>> multiaddr.raw.get("ip4")
        (
         <function ip4_encoder at 0x000002DDE1655550>,
         <function ip4_decoder at 0x000002DDE16555E0>,
         4
        )
        ```
    """
    validate(name, str)
    if name not in _proto_impl:
        raise err.KeyError(f"No implementation for protocol {repr(name)}.")
    return _proto_impl[name]


def exists(name: str) -> bool:
    """
        Checks whether the protocol with given name has an implementation.

        Example usage:

        ```py
        >>> multiaddr.raw.exists("ip4")
        True
        ```
    """
    validate(name, str)
    return name in _proto_impl


def register(name: str, raw_encoder: Optional[RawEncoder], raw_decoder: Optional[RawDecoder], addr_size: Optional[int], *,
             overwrite: bool = False) -> None:
    """
        Registers an implementation for the protocol by given name.
        The optional keyword argument `overwrite` (default: `False`) can be used to overwrite an
        existing implementation.
        If `addr_size` is 0, `raw_encoder` and `raw_decoder` should both be None (because the protocol admits no address).

        It is expected that `raw_encoder` raises `err.ValueError` if the string passed to it is not a valid
        string representatio for an address of this protocol.
        It is expected that `raw_decoder` raises `err.ValueError` if the bytestring passed to it is not a valid
        binary representatio for an address of this protocol.

        If `overwrite` is `False`, raises `err.ValueError` if an implementation for the same name already exists.

        Example usage for protocol requiring address value:

        ```py
            def ip4_encoder(s: str) -> bytes:
                validate(s, str)
                return IPv4Address(s).packed

            def ip4_decoder(b: BytesLike) -> str:
                validate(b, BytesLike)
                _validate_size('ip4', b, 4)
                return str(IPv4Address(b))

            multiformats.raw.register("ip4", ip4_encoder, ip4_decoder, 4)
        ```


        Example usage for protocol not requiring address value:

        ```py
            multiformats.raw.register("quic", None, None, 0)
        ```
    """
    validate(name, str)
    # validate(raw_encoder, Optional[RawEncoder]) # TODO: typing-validation does not yet support this
    # validate(raw_decoder, Optional[RawDecoder]) # TODO: typing-validation does not yet support this
    validate(addr_size, Optional[int])
    validate(overwrite, bool)
    if addr_size is not None and addr_size < 0:
        raise err.ValueError("Size must be None or non-negative integer.")
    if addr_size == 0 and (raw_encoder is not None or raw_decoder is not None):
        raise err.ValueError("Protocol admits no address (addr_size=0), set raw encoder and decoder to None.")
    if not overwrite and name in _proto_impl:
        raise err.ValueError(f"Implementation for protocol {repr(name)} already exists.")
    _proto_impl[name] = (raw_encoder, raw_decoder, addr_size)


def unregister(name: str) -> None:
    """
        Unregisters the implementatio for the protocol by given name.
        Raises `err.KeyError` if no such implementation exists.

        Example usage:

        ```py
        >>> multiformats.raw.unregister("ip4")
        >>> multiformats.raw.exists("ip4")
        False
        ```
    """
    validate(name, str)
    if name not in _proto_impl:
        raise err.KeyError(f"Implementation for protocol {repr(name)} does not exist.")
    del _proto_impl[name]

def _validate_size(name: str, b: BytesLike, size: int) -> None:
    if len(b) != size:
        raise err.ValueError(f"Incorrect length for {repr(name)} bytes: found {len(b)}, expected {size}.")

def ip4_encoder(s: str) -> bytes:
    """ Encoder for 'ip4' protocol. """
    validate(s, str)
    try:
        return IPv4Address(s).packed
    except AddressValueError as e:
        raise err.ValueError(str(e)) from e

def ip4_decoder(b: BytesLike) -> str:
    """ Decoder for 'ip4' protocol. """
    validate(b, BytesLike)
    _validate_size('ip4', b, 4)
    try:
        return str(IPv4Address(b))
    except AddressValueError as e:
        raise err.ValueError(str(e)) from e

register("ip4", ip4_encoder, ip4_decoder, 4)

def ip6_encoder(s: str) -> bytes:
    """ Encoder for 'ip6' protocol. """
    validate(s, str)
    try:
        return IPv6Address(s).packed
    except AddressValueError as e:
        raise err.ValueError(str(e)) from e

def ip6_decoder(b: BytesLike) -> str:
    """ Decoder for 'ip6' protocol. """
    validate(b, BytesLike)
    _validate_size('ip6', b, 16)
    try:
        return str(IPv6Address(b))
    except AddressValueError as e:
        raise err.ValueError(str(e)) from e

register("ip6", ip6_encoder, ip6_decoder, 16)

def tcp_udp_encoder(s: str) -> bytes:
    """ Encoder for 'tcp' and 'udp' protocols. """
    validate(s, str)
    if not s.isdigit():
        raise err.ValueError(f"Invalid UDP port {repr(s)}.")
    x = int(s)
    if not 0 <= x < 65536:
        raise err.ValueError(f"UDP port {repr(s)} out of range.")
    return x.to_bytes(2, byteorder="big")

def tcp_udp_decoder(b: BytesLike) -> str:
    """ Decoder for 'tcp' and 'udp' protocol. """
    validate(b, BytesLike)
    _validate_size('udp', b, 2)
    return str(int.from_bytes(b, byteorder="big"))

register("tcp", tcp_udp_encoder, tcp_udp_decoder, 2)
register("udp", tcp_udp_encoder, tcp_udp_decoder, 2)

# TODO: dccp, 2 bytes
# TODO: ip6zone, variable, rfc4007 IPv6 zone
# TODO: dns, variable
# TODO: dns4, variable
# TODO: dns6, variable
# TODO: dnsaddr, variable
# TODO: sctp, 2 bytes
# TODO: unix, variable
# TODO: p2p, variable
# TODO: ipfs, variable, backwards compatibility; equivalent to /p2p
# TODO: onion, 12 bytes
# TODO: onion3, 37 bytes
# TODO: garlic64, variable
# TODO: garlic32, variable
# TODO: memory, variable, in memory transport for self-dialing and testing; arbitrary

# Protocols without address value:
register("udt", None, None, 0)
register("utp", None, None, 0)
register("tls", None, None, 0)
register("noise", None, None, 0)
register("quic", None, None, 0)
register("http", None, None, 0)
register("https", None, None, 0) # deprecated alias for /tls/http
register("ws", None, None, 0) # WebSockets
register("wss", None, None, 0) # deprecated alias for /tls/ws
register("p2p-websocket-star", None, None, 0)
register("p2p-stardust", None, None, 0)
register("p2p-webrtc-star", None, None, 0)
register("p2p-webrtc-direct", None, None, 0)
register("p2p-circuit", None, None, 0)

Functions

def exists(name: str) ‑> bool

Checks whether the protocol with given name has an implementation.

Example usage:

>>> multiaddr.raw.exists("ip4")
True
Expand source code
def exists(name: str) -> bool:
    """
        Checks whether the protocol with given name has an implementation.

        Example usage:

        ```py
        >>> multiaddr.raw.exists("ip4")
        True
        ```
    """
    validate(name, str)
    return name in _proto_impl
def get(name: str) ‑> Tuple[Optional[Callable[[str], bytes]], Optional[Callable[[Union[bytes, bytearray, memoryview]], str]], Optional[int]]

Gets the implementation (raw_encoder, raw_decoder, addr_size) for a protocol with given name. Raises err.KeyError if no such encoding exists.

The addr_size component is the size in bytes for the binary representation of protocol addresses:

  • for protocols with no address, addr_size is 0
  • for protocols with addresses of variable binary size, addr_size is None
  • for all other protocols, size is a positive int

Example usage:

>>> multiaddr.raw.get("ip4")
(
 <function ip4_encoder at 0x000002DDE1655550>,
 <function ip4_decoder at 0x000002DDE16555E0>,
 4
)
Expand source code
def get(name: str) -> ProtoImpl:
    """
        Gets the implementation `(raw_encoder, raw_decoder, addr_size)` for a protocol with given name.
        Raises `err.KeyError` if no such encoding exists.

        The `addr_size` component is the size in bytes for the binary representation of protocol addresses:

        - for protocols with no address, `addr_size` is 0
        - for protocols with addresses of variable binary size, `addr_size` is `None`
        - for all other protocols, size is a positive `int`

        Example usage:

        ```py
        >>> multiaddr.raw.get("ip4")
        (
         <function ip4_encoder at 0x000002DDE1655550>,
         <function ip4_decoder at 0x000002DDE16555E0>,
         4
        )
        ```
    """
    validate(name, str)
    if name not in _proto_impl:
        raise err.KeyError(f"No implementation for protocol {repr(name)}.")
    return _proto_impl[name]
def ip4_decoder(b: Union[bytes, bytearray, memoryview]) ‑> str

Decoder for 'ip4' protocol.

Expand source code
def ip4_decoder(b: BytesLike) -> str:
    """ Decoder for 'ip4' protocol. """
    validate(b, BytesLike)
    _validate_size('ip4', b, 4)
    try:
        return str(IPv4Address(b))
    except AddressValueError as e:
        raise err.ValueError(str(e)) from e
def ip4_encoder(s: str) ‑> bytes

Encoder for 'ip4' protocol.

Expand source code
def ip4_encoder(s: str) -> bytes:
    """ Encoder for 'ip4' protocol. """
    validate(s, str)
    try:
        return IPv4Address(s).packed
    except AddressValueError as e:
        raise err.ValueError(str(e)) from e
def ip6_decoder(b: Union[bytes, bytearray, memoryview]) ‑> str

Decoder for 'ip6' protocol.

Expand source code
def ip6_decoder(b: BytesLike) -> str:
    """ Decoder for 'ip6' protocol. """
    validate(b, BytesLike)
    _validate_size('ip6', b, 16)
    try:
        return str(IPv6Address(b))
    except AddressValueError as e:
        raise err.ValueError(str(e)) from e
def ip6_encoder(s: str) ‑> bytes

Encoder for 'ip6' protocol.

Expand source code
def ip6_encoder(s: str) -> bytes:
    """ Encoder for 'ip6' protocol. """
    validate(s, str)
    try:
        return IPv6Address(s).packed
    except AddressValueError as e:
        raise err.ValueError(str(e)) from e
def register(name: str, raw_encoder: Optional[Callable[[str], bytes]], raw_decoder: Optional[Callable[[Union[bytes, bytearray, memoryview]], str]], addr_size: Optional[None], *, overwrite: bool = False) ‑> None

Registers an implementation for the protocol by given name. The optional keyword argument overwrite (default: False) can be used to overwrite an existing implementation. If addr_size is 0, raw_encoder and raw_decoder should both be None (because the protocol admits no address).

It is expected that raw_encoder raises err.ValueError if the string passed to it is not a valid string representatio for an address of this protocol. It is expected that raw_decoder raises err.ValueError if the bytestring passed to it is not a valid binary representatio for an address of this protocol.

If overwrite is False, raises err.ValueError if an implementation for the same name already exists.

Example usage for protocol requiring address value:

    def ip4_encoder(s: str) -> bytes:
        validate(s, str)
        return IPv4Address(s).packed

    def ip4_decoder(b: BytesLike) -> str:
        validate(b, BytesLike)
        _validate_size('ip4', b, 4)
        return str(IPv4Address(b))

    multiformats.raw.register("ip4", ip4_encoder, ip4_decoder, 4)

Example usage for protocol not requiring address value:

    multiformats.raw.register("quic", None, None, 0)
Expand source code
def register(name: str, raw_encoder: Optional[RawEncoder], raw_decoder: Optional[RawDecoder], addr_size: Optional[int], *,
             overwrite: bool = False) -> None:
    """
        Registers an implementation for the protocol by given name.
        The optional keyword argument `overwrite` (default: `False`) can be used to overwrite an
        existing implementation.
        If `addr_size` is 0, `raw_encoder` and `raw_decoder` should both be None (because the protocol admits no address).

        It is expected that `raw_encoder` raises `err.ValueError` if the string passed to it is not a valid
        string representatio for an address of this protocol.
        It is expected that `raw_decoder` raises `err.ValueError` if the bytestring passed to it is not a valid
        binary representatio for an address of this protocol.

        If `overwrite` is `False`, raises `err.ValueError` if an implementation for the same name already exists.

        Example usage for protocol requiring address value:

        ```py
            def ip4_encoder(s: str) -> bytes:
                validate(s, str)
                return IPv4Address(s).packed

            def ip4_decoder(b: BytesLike) -> str:
                validate(b, BytesLike)
                _validate_size('ip4', b, 4)
                return str(IPv4Address(b))

            multiformats.raw.register("ip4", ip4_encoder, ip4_decoder, 4)
        ```


        Example usage for protocol not requiring address value:

        ```py
            multiformats.raw.register("quic", None, None, 0)
        ```
    """
    validate(name, str)
    # validate(raw_encoder, Optional[RawEncoder]) # TODO: typing-validation does not yet support this
    # validate(raw_decoder, Optional[RawDecoder]) # TODO: typing-validation does not yet support this
    validate(addr_size, Optional[int])
    validate(overwrite, bool)
    if addr_size is not None and addr_size < 0:
        raise err.ValueError("Size must be None or non-negative integer.")
    if addr_size == 0 and (raw_encoder is not None or raw_decoder is not None):
        raise err.ValueError("Protocol admits no address (addr_size=0), set raw encoder and decoder to None.")
    if not overwrite and name in _proto_impl:
        raise err.ValueError(f"Implementation for protocol {repr(name)} already exists.")
    _proto_impl[name] = (raw_encoder, raw_decoder, addr_size)
def tcp_udp_decoder(b: Union[bytes, bytearray, memoryview]) ‑> str

Decoder for 'tcp' and 'udp' protocol.

Expand source code
def tcp_udp_decoder(b: BytesLike) -> str:
    """ Decoder for 'tcp' and 'udp' protocol. """
    validate(b, BytesLike)
    _validate_size('udp', b, 2)
    return str(int.from_bytes(b, byteorder="big"))
def tcp_udp_encoder(s: str) ‑> bytes

Encoder for 'tcp' and 'udp' protocols.

Expand source code
def tcp_udp_encoder(s: str) -> bytes:
    """ Encoder for 'tcp' and 'udp' protocols. """
    validate(s, str)
    if not s.isdigit():
        raise err.ValueError(f"Invalid UDP port {repr(s)}.")
    x = int(s)
    if not 0 <= x < 65536:
        raise err.ValueError(f"UDP port {repr(s)} out of range.")
    return x.to_bytes(2, byteorder="big")
def unregister(name: str) ‑> None

Unregisters the implementatio for the protocol by given name. Raises err.KeyError if no such implementation exists.

Example usage:

>>> multiformats.raw.unregister("ip4")
>>> multiformats.raw.exists("ip4")
False
Expand source code
def unregister(name: str) -> None:
    """
        Unregisters the implementatio for the protocol by given name.
        Raises `err.KeyError` if no such implementation exists.

        Example usage:

        ```py
        >>> multiformats.raw.unregister("ip4")
        >>> multiformats.raw.exists("ip4")
        False
        ```
    """
    validate(name, str)
    if name not in _proto_impl:
        raise err.KeyError(f"Implementation for protocol {repr(name)} does not exist.")
    del _proto_impl[name]