''' `TunnelledAddress` delegation + peeling semantics. A tunnel annotates an existing L4 addr rather than being its own transport, so the contract under test is mostly *delegation*: the runtime must not be able to tell a tunnelled addr from its overlay, and **nothing** about the tunnel may cross the wire. See `ai/tpt-backends/03_wg_tunnel_bindspace.md` §3.1/§3.4. ''' from __future__ import annotations import msgspec import pytest from tractor.discovery import ( TunnelledAddress, WGTunnelSpec, mb_pubkey, strip_tunnels, tunnels_of, wg8_pubkey, ) from tractor.discovery._addr import ( is_wrapped_addr, wrap_address, ) from tractor.ipc._tcp import TCPAddress # a valid-looking std-base64 `wg(8)` pubkey (32B -> 44 chars) _PUBKEY: str = 'g3x7z0AdV1rM6UQU22CC7IL3/ivn4DzrE7ikDhCZ/Dc=' @pytest.fixture def overlay() -> TCPAddress: return TCPAddress('10.0.11.1', 1616) @pytest.fixture def spec() -> WGTunnelSpec: return WGTunnelSpec( peer_pubkey=_PUBKEY, bearer=('192.168.1.50', 51820), ) @pytest.fixture def tunnelled( overlay: TCPAddress, spec: WGTunnelSpec, ) -> TunnelledAddress: return TunnelledAddress(overlay=overlay, tunnel=spec) def test_wg_pubkey_codec_roundtrip(): ''' Standard `wg(8)` base64 keys can contain `/`, which cannot be embedded unchanged in a slash-delimited maddr. Prove the helper emits `u`-prefixed multibase base64url without `/` and decodes it back to the exact original 32-byte key. ''' mb_key: str = mb_pubkey(_PUBKEY) assert mb_key.startswith('u') assert '/' not in mb_key assert wg8_pubkey(mb_key) == _PUBKEY @pytest.mark.parametrize( 'key, converter', [ pytest.param( 'dG9vIHNob3J0', mb_pubkey, id='wg8-base64', ), pytest.param( 'udG9vIHNob3J0', wg8_pubkey, id='multibase', ), ], ) def test_wg_pubkey_codec_rejects_wrong_size( key: str, converter, ): ''' WireGuard silently-corrupt key handling would let an invalid identity reach peer verification. Exercise both input encodings with a short payload and prove conversion rejects it before a tunnel spec or maddr can be constructed. ''' with pytest.raises( ValueError, match='must decode to 32 bytes', ): converter(key) def test_proto_key_delegates( tunnelled: TunnelledAddress, overlay: TCPAddress, ): ''' A tunnel has no transport of its own, so every table lookup must see the *overlay's* proto-key. ''' assert tunnelled.proto_key == overlay.proto_key == 'tcp' def test_unwrap_is_identical_to_overlay( tunnelled: TunnelledAddress, overlay: TCPAddress, ): ''' The whole point: nothing new crosses the wire, so a peer never has to understand tunnels. ''' assert tunnelled.unwrap() == overlay.unwrap() # and it must survive msgpack as-is enc: bytes = msgspec.msgpack.encode(tunnelled.unwrap()) assert msgspec.msgpack.decode(enc) == list(overlay.unwrap()) def test_unwrap_roundtrips_back_to_plain_overlay( tunnelled: TunnelledAddress, overlay: TCPAddress, ): ''' `wrap_address()` on a tunnelled addr's unwrapped form yields the *plain* overlay type — the tunnel is simply absent, which is correct: it was never on the wire. ''' rewrapped = wrap_address(tunnelled.unwrap()) assert type(rewrapped) is TCPAddress assert rewrapped == overlay assert not isinstance(rewrapped, TunnelledAddress) def test_bindspace_and_validity_delegate( tunnelled: TunnelledAddress, overlay: TCPAddress, ): assert tunnelled.bindspace == overlay.bindspace assert tunnelled.is_valid == overlay.is_valid def test_is_wrapped_addr_accepts_tunnelled( tunnelled: TunnelledAddress, overlay: TCPAddress, ): ''' `TunnelledAddress` is deliberately absent from `_address_types`, so `is_wrapped_addr()` needs its own clause. ''' assert is_wrapped_addr(overlay) assert is_wrapped_addr(tunnelled) # the unwrapped form is NOT a wrapped addr assert not is_wrapped_addr(tunnelled.unwrap()) def test_namespace_comes_from_the_tunnel( overlay: TCPAddress, ): ''' First real consumer of `Address.namespace`, spec'd in the protocol since day one and implemented by no backend. ''' # XXX, "no backend implements it" is literal — the member # isn't even declared, so this is `AttributeError` not `None`. # This assert is the guard: when a backend finally declares # `.namespace`, it fails and the `getattr()` fallback in # `TunnelledAddress.namespace` can go. assert not hasattr(overlay, 'namespace') no_ns = TunnelledAddress( overlay=overlay, tunnel=WGTunnelSpec(peer_pubkey=_PUBKEY), ) assert no_ns.namespace is None in_ns = TunnelledAddress( overlay=overlay, tunnel=WGTunnelSpec(peer_pubkey=_PUBKEY, netns='wg-test'), ) assert in_ns.namespace == ('netns', 'wg-test') def test_strip_tunnels( tunnelled: TunnelledAddress, overlay: TCPAddress, spec: WGTunnelSpec, ): # idempotent on a plain addr assert strip_tunnels(overlay) is overlay # peels one assert strip_tunnels(tunnelled) is overlay # and collapses a nested stack in one call nested = TunnelledAddress(overlay=tunnelled, tunnel=spec) assert strip_tunnels(nested) is overlay def test_tunnels_of_is_outermost_first( tunnelled: TunnelledAddress, overlay: TCPAddress, ): assert tunnels_of(overlay) == () assert tunnels_of(tunnelled) == (tunnelled.tunnel,) inner_spec = WGTunnelSpec(peer_pubkey=_PUBKEY, iface='wg1') nested = TunnelledAddress( overlay=tunnelled, tunnel=inner_spec, ) assert tunnels_of(nested) == (inner_spec, tunnelled.tunnel) def test_frozen( tunnelled: TunnelledAddress, ): with pytest.raises(AttributeError): tunnelled.overlay = TCPAddress('127.0.0.1', 1)