diff --git a/tractor/discovery/_multiaddr.py b/tractor/discovery/_multiaddr.py
index b9f1c4ed..99dd620e 100644
--- a/tractor/discovery/_multiaddr.py
+++ b/tractor/discovery/_multiaddr.py
@@ -15,145 +15,61 @@
# along with this program. If not, see .
'''
-Multiaddress parser and utils according the spec(s) defined by
-`libp2p` and used in dependent project such as `ipfs`:
+Multiaddress support using the upstream `py-multiaddr` lib
+(a `libp2p` community standard) instead of our own NIH parser.
-- https://docs.libp2p.io/concepts/fundamentals/addressing/
-- https://github.com/libp2p/specs/blob/master/addressing/README.md
+- https://github.com/multiformats/multiaddr
+- https://github.com/multiformats/py-multiaddr
+- https://github.com/multiformats/multiaddr/blob/master/protocols.csv
+- https://github.com/multiformats/multiaddr/blob/master/protocols/unix.md
'''
-from typing import Iterator
+import ipaddress
+from pathlib import Path
+from typing import TYPE_CHECKING
-from bidict import bidict
+from multiaddr import Multiaddr
-# TODO: see if we can leverage libp2p ecosys projects instead of
-# rolling our own (parser) impls of the above addressing specs:
-# - https://github.com/libp2p/py-libp2p
-# - https://docs.libp2p.io/concepts/nat/circuit-relay/#relay-addresses
-# prots: bidict[int, str] = bidict({
-prots: bidict[int, str] = {
- 'ipv4': 3,
- 'ipv6': 3,
- 'wg': 3,
+if TYPE_CHECKING:
+ from tractor.discovery._addr import Address
- 'tcp': 4,
- 'udp': 4,
- 'uds': 4,
-
- # TODO: support the next-gen shite Bo
- # 'quic': 4,
- # 'ssh': 7, # via rsyscall bootstrapping
-}
-
-prot_params: dict[str, tuple[str]] = {
- 'ipv4': ('addr',),
- 'ipv6': ('addr',),
- 'wg': ('addr', 'port', 'pubkey'),
-
- 'tcp': ('port',),
- 'udp': ('port',),
- 'uds': ('path',),
-
- # 'quic': ('port',),
- # 'ssh': ('port',),
+# map from tractor-internal `proto_key` identifiers
+# to the standard multiaddr protocol name strings.
+_tpt_proto_to_maddr: dict[str, str] = {
+ 'tcp': 'tcp',
+ 'uds': 'unix',
}
-def iter_prot_layers(
- multiaddr: str,
-) -> Iterator[
- tuple[
- int,
- list[str]
- ]
-]:
+def mk_maddr(
+ addr: 'Address',
+) -> Multiaddr:
'''
- Unpack a libp2p style "multiaddress" into multiple "segments"
- for each "layer" of the protocoll stack (in OSI terms).
+ Construct a `Multiaddr` from a tractor `Address` instance,
+ dispatching on the `.proto_key` to build the correct
+ multiaddr-spec-compliant protocol path.
'''
- tokens: list[str] = multiaddr.split('/')
- root, tokens = tokens[0], tokens[1:]
- assert not root # there is a root '/' on LHS
- itokens = iter(tokens)
+ match addr.proto_key:
+ case 'tcp':
+ host, port = addr.unwrap()
+ ip = ipaddress.ip_address(host)
+ net_proto: str = (
+ 'ip4' if ip.version == 4
+ else 'ip6'
+ )
+ return Multiaddr(
+ f'/{net_proto}/{host}/tcp/{port}'
+ )
- prot: str|None = None
- params: list[str] = []
- for token in itokens:
- # every prot path should start with a known
- # key-str.
- if token in prots:
- if prot is None:
- prot: str = token
- else:
- yield prot, params
- prot = token
+ case 'uds':
+ filedir, filename = addr.unwrap()
+ filepath = Path(filedir) / filename
+ return Multiaddr(
+ f'/unix/{filepath}'
+ )
- params = []
-
- elif token not in prots:
- params.append(token)
-
- else:
- yield prot, params
-
-
-def parse_maddr(
- multiaddr: str,
-) -> dict[
- str,
- str|int|dict,
-]:
- '''
- Parse a libp2p style "multiaddress" into its distinct protocol
- segments where each segment is of the form:
-
- `..////../`
-
- and is loaded into a (order preserving) `layers: dict[str,
- dict[str, Any]` which holds each protocol-layer-segment of the
- original `str` path as a separate entry according to its approx
- OSI "layer number".
-
- Any `paramN` in the path must be distinctly defined by a str-token in the
- (module global) `prot_params` table.
-
- For eg. for wireguard which requires an address, port number and publickey
- the protocol params are specified as the entry:
-
- 'wg': ('addr', 'port', 'pubkey'),
-
- and are thus parsed from a maddr in that order:
- `'/wg/1.1.1.1/51820/'`
-
- '''
- layers: dict[str, str|int|dict] = {}
- for (
- prot_key,
- params,
- ) in iter_prot_layers(multiaddr):
-
- layer: int = prots[prot_key] # OSI layer used for sorting
- ep: dict[str, int|str] = {
- 'layer': layer,
- 'proto': prot_key,
- }
- layers[prot_key] = ep
-
- # TODO; validation and resolving of names:
- # - each param via a validator provided as part of the
- # prot_params def? (also see `"port"` case below..)
- # - do a resolv step that will check addrs against
- # any loaded network.resolv: dict[str, str]
- rparams: list = list(reversed(params))
- for key in prot_params[prot_key]:
- val: str|int = rparams.pop()
-
- # TODO: UGHH, dunno what we should do for validation
- # here, put it in the params spec somehow?
- if key == 'port':
- val = int(val)
-
- ep[key] = val
-
- return layers
+ case _:
+ raise ValueError(
+ f'Unsupported proto_key: {addr.proto_key!r}'
+ )
diff --git a/tractor/ipc/_tcp.py b/tractor/ipc/_tcp.py
index b05f2829..293ae4be 100644
--- a/tractor/ipc/_tcp.py
+++ b/tractor/ipc/_tcp.py
@@ -33,8 +33,10 @@ from trio import (
open_tcp_listeners,
)
+from multiaddr import Multiaddr
from tractor.msg import MsgCodec
from tractor.log import get_logger
+from tractor.discovery._multiaddr import mk_maddr
from tractor.ipc._transport import (
MsgTransport,
MsgpackTransport,
@@ -198,21 +200,8 @@ class MsgpackTCPStream(MsgpackTransport):
layer_key: int = 4
@property
- def maddr(self) -> str:
- host, port = self.raddr.unwrap()
- return (
- # TODO, use `ipaddress` from stdlib to handle
- # first detecting which of `ipv4/6` before
- # choosing the routing prefix part.
- f'/ipv4/{host}'
-
- f'/{self.address_type.proto_key}/{port}'
- # f'/{self.chan.uid[0]}'
- # f'/{self.cid}'
-
- # f'/cid={cid_head}..{cid_tail}'
- # TODO: ? not use this ^ right ?
- )
+ def maddr(self) -> Multiaddr:
+ return mk_maddr(self.raddr)
def connected(self) -> bool:
return self.stream.socket.fileno() != -1
diff --git a/tractor/ipc/_transport.py b/tractor/ipc/_transport.py
index a3f87293..0a38d14d 100644
--- a/tractor/ipc/_transport.py
+++ b/tractor/ipc/_transport.py
@@ -27,6 +27,8 @@ from typing import (
ClassVar,
TYPE_CHECKING,
)
+if TYPE_CHECKING:
+ from multiaddr import Multiaddr
from collections.abc import (
AsyncGenerator,
AsyncIterator,
@@ -118,7 +120,7 @@ class MsgTransport(Protocol):
...
@property
- def maddr(self) -> str:
+ def maddr(self) -> Multiaddr|str:
...
@classmethod
diff --git a/tractor/ipc/_uds.py b/tractor/ipc/_uds.py
index 26174d55..51f21353 100644
--- a/tractor/ipc/_uds.py
+++ b/tractor/ipc/_uds.py
@@ -48,8 +48,10 @@ from trio._highlevel_open_unix_stream import (
has_unix,
)
+from multiaddr import Multiaddr
from tractor.msg import MsgCodec
from tractor.log import get_logger
+from tractor.discovery._multiaddr import mk_maddr
from tractor.ipc._transport import (
MsgpackTransport,
)
@@ -442,19 +444,11 @@ class MsgpackUDSStream(MsgpackTransport):
layer_key: int = 4
@property
- def maddr(self) -> str:
+ def maddr(self) -> Multiaddr|str:
if not self.raddr:
return ''
- filepath: Path = Path(self.raddr.unwrap()[0])
- return (
- f'/{self.address_type.proto_key}/{filepath}'
- # f'/{self.chan.uid[0]}'
- # f'/{self.cid}'
-
- # f'/cid={cid_head}..{cid_tail}'
- # TODO: ? not use this ^ right ?
- )
+ return mk_maddr(self.raddr)
def connected(self) -> bool:
return self.stream.socket.fileno() != -1
diff --git a/tractor/runtime/_runtime.py b/tractor/runtime/_runtime.py
index 477d8f9b..074c339a 100644
--- a/tractor/runtime/_runtime.py
+++ b/tractor/runtime/_runtime.py
@@ -1379,7 +1379,7 @@ class Actor:
# - `Channel.maddr() -> str:` obvi!
# - `Context.maddr() -> str:`
tasks_str += (
- f' |_@ /ipv4/tcp/cid="{ctx.cid[-16:]} .."\n'
+ f' |_@ /ip4/tcp/cid="{ctx.cid[-16:]} .."\n'
f' |>> {ctx._nsf}() -> dict:\n'
)