fleetmesh-node 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fleetmesh_node-0.2.0.dist-info/METADATA +57 -0
- fleetmesh_node-0.2.0.dist-info/RECORD +338 -0
- fleetmesh_node-0.2.0.dist-info/WHEEL +5 -0
- fleetmesh_node-0.2.0.dist-info/entry_points.txt +2 -0
- fleetmesh_node-0.2.0.dist-info/licenses/LICENSE +121 -0
- fleetmesh_node-0.2.0.dist-info/top_level.txt +2 -0
- node/RELEASE +1 -0
- node/__init__.py +35 -0
- node/app/__init__.py +49 -0
- node/app/commands.py +265 -0
- node/app/config.py +131 -0
- node/app/control.py +670 -0
- node/app/core.py +867 -0
- node/app/plugins.py +76 -0
- node/app/router.py +83 -0
- node/app/socket.py +431 -0
- node/app/systemd.py +36 -0
- node/app/views.py +204 -0
- node/channels/__init__.py +378 -0
- node/channels/bus.py +331 -0
- node/channels/journal.py +46 -0
- node/commands/__init__.py +140 -0
- node/commands/adapter.py +210 -0
- node/commands/base.py +278 -0
- node/commands/cluster_cmds.py +183 -0
- node/commands/compute_cmds.py +401 -0
- node/commands/core.py +474 -0
- node/commands/gossip_cmds.py +106 -0
- node/commands/nsite_cmds.py +141 -0
- node/commands/peering.py +402 -0
- node/commands/plugin_cmds.py +135 -0
- node/commands/sidecar_cmds.py +120 -0
- node/commands/sync.py +388 -0
- node/commands/transport_cmds.py +451 -0
- node/commands/video_cmds.py +99 -0
- node/compartment/__init__.py +124 -0
- node/compartment/base.py +868 -0
- node/compartment/command.py +458 -0
- node/compartment/context.py +163 -0
- node/compartment/every.py +108 -0
- node/compartment/handler.py +131 -0
- node/compartment/lifecycle.py +460 -0
- node/compartment/lint.py +388 -0
- node/compartment/need.py +158 -0
- node/compartment/problem.py +521 -0
- node/compartment/refusals.py +120 -0
- node/compartment/registry.py +790 -0
- node/compartment/schema.py +290 -0
- node/compartment/sidecar.py +499 -0
- node/compartment/stores.py +662 -0
- node/compartment/wire.py +225 -0
- node/compartment/worker.py +61 -0
- node/config/__init__.py +43 -0
- node/config/base.py +116 -0
- node/config/change.py +441 -0
- node/config/changestore.py +201 -0
- node/config/compartments.py +1144 -0
- node/config/node.py +318 -0
- node/config/parameter.py +563 -0
- node/config/protocols.py +274 -0
- node/config/records.py +171 -0
- node/examples/__init__.py +1 -0
- node/examples/demo.py +226 -0
- node/examples/follower.py +46 -0
- node/interfaces.py +235 -0
- node/keystore/__init__.py +27 -0
- node/keystore/core.py +103 -0
- node/keystore/enclave.py +688 -0
- node/meshnode.py +111 -0
- node/metrics/__init__.py +396 -0
- node/metrics/adapters.py +102 -0
- node/metrics/face.py +110 -0
- node/planes/__init__.py +21 -0
- node/planes/identity/__init__.py +20 -0
- node/planes/identity/announce/__init__.py +9 -0
- node/planes/identity/announce/compartment.py +447 -0
- node/planes/identity/announce/types.py +26 -0
- node/planes/identity/discovery/__init__.py +9 -0
- node/planes/identity/discovery/compartment.py +522 -0
- node/planes/identity/discovery/types.py +13 -0
- node/planes/identity/linked/__init__.py +1 -0
- node/planes/identity/linked/compartment.py +270 -0
- node/planes/identity/linked/types.py +25 -0
- node/planes/identity/presence/__init__.py +7 -0
- node/planes/identity/presence/compartment.py +409 -0
- node/planes/identity/presence/types.py +14 -0
- node/planes/operations/__init__.py +13 -0
- node/planes/operations/scheduler/__init__.py +9 -0
- node/planes/operations/scheduler/compartment.py +353 -0
- node/planes/operations/scheduler/types.py +21 -0
- node/planes/operations/updates/__init__.py +17 -0
- node/planes/operations/updates/core.py +310 -0
- node/planes/operations/updates/store.py +79 -0
- node/planes/operations/updates/types.py +20 -0
- node/planes/peering/__init__.py +28 -0
- node/planes/peering/budget/__init__.py +43 -0
- node/planes/peering/budget/chain.py +43 -0
- node/planes/peering/budget/core.py +537 -0
- node/planes/peering/budget/evaluation.py +80 -0
- node/planes/peering/budget/meter.py +100 -0
- node/planes/peering/budget/receipts.py +439 -0
- node/planes/peering/budget/shape.py +101 -0
- node/planes/peering/budget/tick.py +110 -0
- node/planes/peering/budget/types.py +67 -0
- node/planes/peering/budget/windows.py +346 -0
- node/planes/peering/capacity/__init__.py +7 -0
- node/planes/peering/capacity/compartment.py +342 -0
- node/planes/peering/capacity/types.py +24 -0
- node/planes/peering/peering/__init__.py +25 -0
- node/planes/peering/peering/core.py +464 -0
- node/planes/peering/peering/envelope.py +142 -0
- node/planes/peering/peering/handshake.py +198 -0
- node/planes/peering/peering/shape.py +80 -0
- node/planes/peering/peering/types.py +39 -0
- node/planes/peering/reporter/__init__.py +42 -0
- node/planes/peering/reporter/constants.py +29 -0
- node/planes/peering/reporter/core.py +299 -0
- node/planes/peering/reporter/finding.py +74 -0
- node/planes/peering/reporter/findings.py +171 -0
- node/planes/peering/reporter/publish.py +285 -0
- node/planes/peering/reporter/reading.py +193 -0
- node/planes/peering/reporter/shape.py +86 -0
- node/planes/peering/reporter/types.py +50 -0
- node/planes/peering/strain/__init__.py +9 -0
- node/planes/peering/strain/compartment.py +204 -0
- node/planes/peering/strain/types.py +21 -0
- node/planes/peering/trust/__init__.py +9 -0
- node/planes/peering/trust/compartment.py +239 -0
- node/planes/peering/trust/types.py +30 -0
- node/planes/services/__init__.py +33 -0
- node/planes/services/compute/__init__.py +160 -0
- node/planes/services/compute/common.py +134 -0
- node/planes/services/compute/compartment.py +681 -0
- node/planes/services/compute/executors/__init__.py +121 -0
- node/planes/services/compute/executors/agent.py +840 -0
- node/planes/services/compute/executors/bacalhau.py +605 -0
- node/planes/services/compute/executors/base.py +127 -0
- node/planes/services/compute/executors/harness.py +439 -0
- node/planes/services/compute/executors/mcp.py +454 -0
- node/planes/services/compute/executors/media.py +898 -0
- node/planes/services/compute/executors/nondet.py +283 -0
- node/planes/services/compute/executors/providers.py +499 -0
- node/planes/services/compute/executors/registry.py +140 -0
- node/planes/services/compute/executors/wasm.py +244 -0
- node/planes/services/compute/executors/zk.py +604 -0
- node/planes/services/compute/jobs.py +227 -0
- node/planes/services/compute/load.py +180 -0
- node/planes/services/compute/market.py +433 -0
- node/planes/services/compute/sandbox.py +282 -0
- node/planes/services/compute/types.py +84 -0
- node/planes/services/compute_wire/__init__.py +58 -0
- node/planes/services/compute_wire/compartment.py +256 -0
- node/planes/services/compute_wire/messages.py +320 -0
- node/planes/services/compute_wire/sealing.py +147 -0
- node/planes/services/compute_wire/session.py +572 -0
- node/planes/services/compute_wire/types.py +49 -0
- node/planes/services/compute_wire/verify.py +331 -0
- node/planes/services/dvm/__init__.py +24 -0
- node/planes/services/dvm/gateway.py +526 -0
- node/planes/services/dvm/protocol.py +35 -0
- node/planes/services/dvm/translate.py +15 -0
- node/planes/services/dvm/types.py +25 -0
- node/planes/services/gateway/__init__.py +40 -0
- node/planes/services/gateway/settle.py +279 -0
- node/planes/services/gateway/translate.py +644 -0
- node/planes/services/gossip/__init__.py +22 -0
- node/planes/services/gossip/core.py +929 -0
- node/planes/services/gossip/envelope.py +154 -0
- node/planes/services/gossip/mesh.py +127 -0
- node/planes/services/gossip/types.py +50 -0
- node/planes/services/nsite/__init__.py +7 -0
- node/planes/services/nsite/client.py +62 -0
- node/planes/services/nsite/compartment.py +752 -0
- node/planes/services/nsite/types.py +44 -0
- node/planes/services/paidfetch/__init__.py +7 -0
- node/planes/services/paidfetch/compartment.py +862 -0
- node/planes/services/paidfetch/types.py +99 -0
- node/planes/services/settle/__init__.py +27 -0
- node/planes/services/settle/compartment.py +384 -0
- node/planes/services/settle/engine.py +456 -0
- node/planes/services/settle/gateways.py +541 -0
- node/planes/services/settle/payment.py +512 -0
- node/planes/services/settle/store.py +74 -0
- node/planes/services/settle/types.py +58 -0
- node/planes/services/sidecars/__init__.py +8 -0
- node/planes/services/sidecars/compartment.py +461 -0
- node/planes/services/sidecars/manifest.py +304 -0
- node/planes/services/sidecars/manifests/10-grasp.json +37 -0
- node/planes/services/sidecars/manifests/20-ncontainer.json +31 -0
- node/planes/services/sidecars/manifests/30-magpie.json +28 -0
- node/planes/services/sidecars/types.py +43 -0
- node/planes/services/video/__init__.py +106 -0
- node/planes/services/video/barter.py +407 -0
- node/planes/services/video/cli.py +158 -0
- node/planes/services/video/compartment.py +932 -0
- node/planes/services/video/ingest.py +524 -0
- node/planes/services/video/ledger.py +705 -0
- node/planes/services/video/packager.py +847 -0
- node/planes/services/video/player.py +456 -0
- node/planes/services/video/timed_segments.py +124 -0
- node/planes/services/video/transit.py +478 -0
- node/planes/store/__init__.py +24 -0
- node/planes/store/blob_replication/__init__.py +7 -0
- node/planes/store/blob_replication/compartment.py +496 -0
- node/planes/store/blob_replication/types.py +35 -0
- node/planes/store/blob_wire/__init__.py +48 -0
- node/planes/store/blob_wire/client.py +57 -0
- node/planes/store/blob_wire/core.py +359 -0
- node/planes/store/blob_wire/intake.py +74 -0
- node/planes/store/blob_wire/messages.py +85 -0
- node/planes/store/projected/__init__.py +7 -0
- node/planes/store/projected/compartment.py +258 -0
- node/planes/store/projected/types.py +29 -0
- node/planes/store/retention/__init__.py +7 -0
- node/planes/store/retention/compartment.py +214 -0
- node/planes/store/retention/types.py +12 -0
- node/planes/store/sync/__init__.py +35 -0
- node/planes/store/sync/core.py +311 -0
- node/planes/store/sync/envelope.py +106 -0
- node/planes/store/sync/handover.py +206 -0
- node/planes/store/sync/initiator.py +128 -0
- node/planes/store/sync/responder.py +114 -0
- node/planes/store/sync/rules.py +179 -0
- node/planes/store/sync/shape.py +80 -0
- node/planes/store/sync/types.py +51 -0
- node/planes/transport/__init__.py +23 -0
- node/planes/transport/bridge/__init__.py +13 -0
- node/planes/transport/bridge/compartment.py +785 -0
- node/planes/transport/bridge/ratelimit.py +44 -0
- node/planes/transport/bridge/types.py +43 -0
- node/planes/transport/lan/__init__.py +9 -0
- node/planes/transport/lan/compartment.py +446 -0
- node/planes/transport/lan/types.py +25 -0
- node/planes/transport/link/__init__.py +7 -0
- node/planes/transport/link/compartment.py +257 -0
- node/planes/transport/link/types.py +27 -0
- node/planes/transport/mediation/__init__.py +45 -0
- node/planes/transport/mediation/compartment.py +238 -0
- node/planes/transport/mediation/leaf.py +107 -0
- node/planes/transport/mediation/messages.py +302 -0
- node/planes/transport/mediation/service.py +233 -0
- node/planes/transport/mediation/store.py +37 -0
- node/planes/transport/mediation/types.py +20 -0
- node/planes/transport/p2p/__init__.py +7 -0
- node/planes/transport/p2p/compartment.py +349 -0
- node/planes/transport/p2p/types.py +22 -0
- node/protocols/__init__.py +277 -0
- node/protocols/activitypub/__init__.py +56 -0
- node/protocols/activitypub/actor.py +230 -0
- node/protocols/activitypub/federation.py +490 -0
- node/protocols/activitypub/media.py +145 -0
- node/protocols/activitypub/models.py +286 -0
- node/protocols/activitypub/package.py +271 -0
- node/protocols/activitypub/store.py +564 -0
- node/protocols/arcade/__init__.py +26 -0
- node/protocols/atproto/__init__.py +13 -0
- node/protocols/atproto/firehose.py +126 -0
- node/protocols/atproto/metering.py +165 -0
- node/protocols/atproto/package.py +155 -0
- node/protocols/atproto/repo.py +250 -0
- node/protocols/atproto/xrpc.py +285 -0
- node/protocols/base.py +109 -0
- node/protocols/circles/__init__.py +29 -0
- node/protocols/dm/__init__.py +50 -0
- node/protocols/grasp/__init__.py +22 -0
- node/protocols/isotime.py +15 -0
- node/protocols/nostr/__init__.py +51 -0
- node/protocols/nostr/blobstore.py +383 -0
- node/protocols/nostr/blossom.py +627 -0
- node/protocols/nostr/client.py +377 -0
- node/protocols/nostr/descriptor.py +211 -0
- node/protocols/nostr/eventstore.py +304 -0
- node/protocols/nostr/package.py +182 -0
- node/protocols/nostr/peers.py +109 -0
- node/protocols/nostr/relayserver.py +656 -0
- node/protocols/p2panda/__init__.py +22 -0
- node/protocols/p2panda/identity.py +19 -0
- node/protocols/p2panda/package.py +236 -0
- node/protocols/p2panda/rung.py +152 -0
- node/protocols/p2panda/store.py +273 -0
- node/protocols/rhio/__init__.py +33 -0
- node/protocols/rhio/package.py +275 -0
- node/protocols/rhio/rung.py +136 -0
- node/protocols/rhio/store.py +1423 -0
- node/protocols/ssb/muxrpc.py +256 -0
- node/protocols/ssb/package.py +267 -0
- node/protocols/textile/__init__.py +111 -0
- node/protocols/textile/transducers.py +259 -0
- node/release.py +119 -0
- node/storage/__init__.py +88 -0
- node/storage/accounts.py +305 -0
- node/storage/adapter.py +161 -0
- node/storage/base.py +385 -0
- node/storage/dialect.py +209 -0
- node/storage/grants.py +317 -0
- node/storage/locator.py +150 -0
- node/storage/postgres.py +294 -0
- node/storage/projected.py +953 -0
- node/storage/sharding.py +448 -0
- node/storage/standing.py +568 -0
- node/testnet/__init__.py +9 -0
- node/testnet/cluster.py +731 -0
- node/testnet/fips_daemon.py +230 -0
- node/transport/__init__.py +94 -0
- node/transport/const.py +110 -0
- node/transport/didcomm_inbound.py +251 -0
- node/transport/dnstxt.py +169 -0
- node/transport/fips_rung/__init__.py +67 -0
- node/transport/fips_rung/core.py +666 -0
- node/transport/fips_rung/listener.py +96 -0
- node/transport/fips_rung/types.py +12 -0
- node/transport/host.py +334 -0
- node/transport/iroh_rung/__init__.py +29 -0
- node/transport/iroh_rung/core.py +477 -0
- node/transport/iroh_rung/quic.py +942 -0
- node/transport/iroh_rung/types.py +22 -0
- node/transport/ladder.py +145 -0
- node/transport/lineproto.py +49 -0
- node/transport/mdns.py +303 -0
- node/transport/netfetch.py +283 -0
- node/transport/relay_rung/__init__.py +23 -0
- node/transport/relay_rung/cooldown.py +87 -0
- node/transport/relay_rung/core.py +138 -0
- node/transport/relay_rung/inbound.py +161 -0
- node/transport/relay_rung/ledger.py +104 -0
- node/transport/relay_rung/listen.py +121 -0
- node/transport/relay_rung/outbound.py +181 -0
- node/transport/relay_rung/shape.py +71 -0
- node/transport/relay_rung/types.py +58 -0
- node/transport/relay_rung/wire.py +63 -0
- node/transport/router.py +761 -0
- node/transport/rung.py +276 -0
- node/transport/ssb_rung.py +166 -0
- node/transport/ws.py +502 -0
- sidecar/fleetmesh_announce.py +338 -0
- sidecar/fleetmesh_p2p.py +373 -0
- sidecar/fleetmesh_rhio.py +352 -0
- sidecar/fleetmesh_ssb.py +603 -0
node/transport/rung.py
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""One rung of the transport ladder, and the four the node has.
|
|
2
|
+
|
|
3
|
+
The router used to know how every rung dials: four if-blocks, each with
|
|
4
|
+
its own seal, its own breaker lookup, its own cascade note. Now a rung is
|
|
5
|
+
one object that says whether it is up, whether it can reach a peer and
|
|
6
|
+
where, and makes one delivery attempt; the router walks the ladder and
|
|
7
|
+
keeps the books. A rung that is a compartment elsewhere (the relay rung,
|
|
8
|
+
the libp2p transport) is reached through a thin adapter here so that it
|
|
9
|
+
need not know the ladder exists.
|
|
10
|
+
|
|
11
|
+
Adapters read their daemon off the router at call time (`router.fips`,
|
|
12
|
+
`router.iroh`, `router.p2p`) rather than at construction: a test swaps a
|
|
13
|
+
fake in after the router is built, and a sidecar registers itself later.
|
|
14
|
+
|
|
15
|
+
Every rung answers the same four questions the same way - up?, where?,
|
|
16
|
+
deliver, and what it did (health(), fault_log()) - so the router walks the
|
|
17
|
+
ladder blindly for all of them. There is no rung the router knows by name.
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
from typing import Any, Callable, Mapping, Optional, Protocol, TypedDict, runtime_checkable
|
|
23
|
+
|
|
24
|
+
from . import fips_rung as fips_transport
|
|
25
|
+
from .const import TransportError
|
|
26
|
+
from .ladder import FaultLog
|
|
27
|
+
|
|
28
|
+
Verdict = dict[str, tuple[bool, str]]
|
|
29
|
+
Jwe = Mapping[str, Any] # a DIDComm JWE as didcomm.encrypt returns it
|
|
30
|
+
Seal = Callable[[Mapping[str, Any], str, bool], Jwe]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RungHealth(TypedDict, total=False):
|
|
34
|
+
"""What a rung reports about itself; the daemon-backed rungs add their own keys."""
|
|
35
|
+
available: bool
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@runtime_checkable
|
|
39
|
+
class RungProtocol(Protocol):
|
|
40
|
+
"""What the ladder needs from a rung, and nothing it does not."""
|
|
41
|
+
|
|
42
|
+
name: str # the scheme negotiate() ranks: 'fips-native', 'iroh', 'libp2p', 'nostr'
|
|
43
|
+
rank: int # this node's preference; lower is tried first
|
|
44
|
+
|
|
45
|
+
def available(self) -> bool:
|
|
46
|
+
"""The local side of this rung is up (daemon reachable, listener bound)."""
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
def target_of(self, desc: Optional[Mapping[str, Any]], theirs: Mapping[str, int]) -> Optional[str]:
|
|
50
|
+
"""Where this rung would reach the peer that published `desc`, or
|
|
51
|
+
None when the descriptor does not advertise it. `theirs` is the
|
|
52
|
+
peer's scheme -> preference map already read from the descriptor."""
|
|
53
|
+
...
|
|
54
|
+
|
|
55
|
+
def deliver(self, message: Mapping[str, Any], to_pubkey: str, target: str, anonymous: bool) -> Verdict:
|
|
56
|
+
"""One attempt. Returns {scheme: (delivered, note)}; raises on failure
|
|
57
|
+
so the ladder records it and moves on."""
|
|
58
|
+
...
|
|
59
|
+
|
|
60
|
+
def health(self) -> Optional[Mapping[str, Any]]:
|
|
61
|
+
"""What this rung reports about itself, or None when its daemon is
|
|
62
|
+
not built; the router keys it by rung name."""
|
|
63
|
+
...
|
|
64
|
+
|
|
65
|
+
def fault_log(self) -> Optional[FaultLog]:
|
|
66
|
+
"""The log of what this rung refused or lost, or None when it keeps
|
|
67
|
+
none; the router merges every log it is given."""
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Rung:
|
|
72
|
+
"""Defaults for a rung that has nothing to report. The four rungs here
|
|
73
|
+
and any test double inherit it so the ladder never probes for a method."""
|
|
74
|
+
|
|
75
|
+
name: str = 'rung'
|
|
76
|
+
rank: int = 50
|
|
77
|
+
|
|
78
|
+
def available(self) -> bool:
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
def target_of(self, desc: Optional[Mapping[str, Any]], theirs: Mapping[str, int]) -> Optional[str]:
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
def deliver(self, message: Mapping[str, Any], to_pubkey: str, target: str, anonymous: bool) -> Verdict:
|
|
85
|
+
raise TransportError('%s rung does not deliver' % self.name)
|
|
86
|
+
|
|
87
|
+
def health(self) -> Optional[Mapping[str, Any]]:
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
def fault_log(self) -> Optional[FaultLog]:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _log_of(owner: Any) -> Optional[FaultLog]:
|
|
95
|
+
log = getattr(owner, 'faults', None)
|
|
96
|
+
return log if isinstance(log, FaultLog) else None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def endpoints_of(desc: Optional[Mapping[str, Any]]) -> tuple[dict[str, int], dict[str, str]]:
|
|
100
|
+
"""A descriptor's advertised rungs: (scheme -> preference, scheme -> address).
|
|
101
|
+
|
|
102
|
+
The 'key' tag with a fips transport key counts as a fips-native endpoint
|
|
103
|
+
at the strongest preference, the way the handshake reads it.
|
|
104
|
+
"""
|
|
105
|
+
theirs: dict[str, int] = {}
|
|
106
|
+
where: dict[str, str] = {}
|
|
107
|
+
if desc is None:
|
|
108
|
+
return theirs, where
|
|
109
|
+
for t in desc.get('tags', []):
|
|
110
|
+
if not t:
|
|
111
|
+
continue
|
|
112
|
+
if t[0] == 'endpoint' and len(t) > 2:
|
|
113
|
+
scheme = 'fips-native' if t[1] == 'fips' else t[1]
|
|
114
|
+
pref = int(t[3]) if len(t) > 3 and str(t[3]).isdigit() else 50
|
|
115
|
+
theirs[scheme] = pref
|
|
116
|
+
where[scheme] = t[2]
|
|
117
|
+
elif t[0] == 'key' and len(t) > 2 and t[1] == 'fips':
|
|
118
|
+
where['fips-native'] = t[2]
|
|
119
|
+
theirs['fips-native'] = 5
|
|
120
|
+
return theirs, where
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class FipsRung(Rung):
|
|
124
|
+
"""Datagram flows through the fips sidecar; mesh-encrypted by the daemon."""
|
|
125
|
+
|
|
126
|
+
name = 'fips-native'
|
|
127
|
+
rank = 5
|
|
128
|
+
|
|
129
|
+
def __init__(self, router: Any, seal: Seal) -> None:
|
|
130
|
+
self.router = router
|
|
131
|
+
self.seal = seal
|
|
132
|
+
|
|
133
|
+
def available(self) -> bool:
|
|
134
|
+
return self.router.fips is not None
|
|
135
|
+
|
|
136
|
+
def target_of(self, desc: Optional[Mapping[str, Any]], theirs: Mapping[str, int]) -> Optional[str]:
|
|
137
|
+
_, where = endpoints_of(desc)
|
|
138
|
+
if 'fips-native' in where:
|
|
139
|
+
return where['fips-native']
|
|
140
|
+
return '' if 'fips-native' in theirs else None
|
|
141
|
+
|
|
142
|
+
def deliver(self, message: Mapping[str, Any], to_pubkey: str, target: str, anonymous: bool) -> Verdict:
|
|
143
|
+
fips = self.router.fips
|
|
144
|
+
peer_key = target or to_pubkey
|
|
145
|
+
port = self.router.fips_port
|
|
146
|
+
if ':' in peer_key and not peer_key.startswith('did:'):
|
|
147
|
+
host, _, p = peer_key.partition(':')
|
|
148
|
+
if p.isdigit():
|
|
149
|
+
peer_key, port = host, int(p)
|
|
150
|
+
their_agree = self.router.peers.agreement(to_pubkey)
|
|
151
|
+
if their_agree is None:
|
|
152
|
+
raise TransportError('no live agreement key for peer %s' % to_pubkey[:16])
|
|
153
|
+
flow = fips.open_flow(peer_key, port=port)
|
|
154
|
+
jwe = self.seal(message, to_pubkey, anonymous)
|
|
155
|
+
raw = json.dumps(jwe, separators=(',', ':'), sort_keys=True).encode('utf-8')
|
|
156
|
+
# Fragments to the flow's max_payload; raises rather than silently
|
|
157
|
+
# dropping an oversize datagram, so the ladder cascades.
|
|
158
|
+
fips.send_message(flow, raw, flags=fips_transport.FLAG_DIDCOMM)
|
|
159
|
+
return {self.name: (True, 'delivered via fips native flow to %s:%d' % (peer_key, port))}
|
|
160
|
+
|
|
161
|
+
def health(self) -> Optional[Mapping[str, Any]]:
|
|
162
|
+
fips = self.router.fips
|
|
163
|
+
if fips is None:
|
|
164
|
+
return None
|
|
165
|
+
return {
|
|
166
|
+
'available': fips.is_available(),
|
|
167
|
+
'sock_path': getattr(fips, 'sock_path', None),
|
|
168
|
+
'port': self.router.fips_port,
|
|
169
|
+
'listening': getattr(self.router, '_fips_listener_sock', None) is not None,
|
|
170
|
+
'flows_count': len(getattr(fips, 'open_flows', {})),
|
|
171
|
+
'listeners_count': len(getattr(fips, 'listeners', {})),
|
|
172
|
+
'success_count': self.router.success_counts['fips-native'],
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class IrohRung(Rung):
|
|
177
|
+
"""EXPERIMENTAL. A direct stream the wire itself neither encrypts nor
|
|
178
|
+
authenticates; only the JWE inside does. Built only when configured."""
|
|
179
|
+
|
|
180
|
+
name = 'iroh'
|
|
181
|
+
rank = 10
|
|
182
|
+
|
|
183
|
+
def __init__(self, router: Any) -> None:
|
|
184
|
+
self.router = router
|
|
185
|
+
|
|
186
|
+
def available(self) -> bool:
|
|
187
|
+
return self.router.iroh is not None
|
|
188
|
+
|
|
189
|
+
def target_of(self, desc: Optional[Mapping[str, Any]], theirs: Mapping[str, int]) -> Optional[str]:
|
|
190
|
+
_, where = endpoints_of(desc)
|
|
191
|
+
# Without a ticket there is nothing to dial, whatever the preference says.
|
|
192
|
+
return where.get('iroh') or None
|
|
193
|
+
|
|
194
|
+
def deliver(self, message: Mapping[str, Any], to_pubkey: str, target: str, anonymous: bool) -> Verdict:
|
|
195
|
+
res: Verdict = self.router.iroh.send(message, to_pubkey, ticket=target, anonymous=anonymous)
|
|
196
|
+
return res
|
|
197
|
+
|
|
198
|
+
def health(self) -> Optional[Mapping[str, Any]]:
|
|
199
|
+
iroh = self.router.iroh
|
|
200
|
+
return None if iroh is None else dict(iroh.health)
|
|
201
|
+
|
|
202
|
+
def fault_log(self) -> Optional[FaultLog]:
|
|
203
|
+
return _log_of(self.router.iroh)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class Libp2pRung(Rung):
|
|
207
|
+
"""A stream through the p2p sidecar; the target is derived from the
|
|
208
|
+
descriptor's transport key, so the dial is bound to what it signed."""
|
|
209
|
+
|
|
210
|
+
name = 'libp2p'
|
|
211
|
+
rank = 20
|
|
212
|
+
|
|
213
|
+
def __init__(self, router: Any, seal: Seal) -> None:
|
|
214
|
+
self.router = router
|
|
215
|
+
self.seal = seal
|
|
216
|
+
|
|
217
|
+
def available(self) -> bool:
|
|
218
|
+
p2p = self.router.p2p
|
|
219
|
+
return p2p is not None and bool(getattr(p2p, 'connected', False))
|
|
220
|
+
|
|
221
|
+
def target_of(self, desc: Optional[Mapping[str, Any]], theirs: Mapping[str, int]) -> Optional[str]:
|
|
222
|
+
if 'libp2p' not in theirs:
|
|
223
|
+
return None
|
|
224
|
+
from ..planes.transport.p2p import compartment as p2p_wire
|
|
225
|
+
target, why = p2p_wire.target_of(desc)
|
|
226
|
+
# An advertised but unusable endpoint is still an attempt the ladder
|
|
227
|
+
# should record as this rung's failure, not skip in silence.
|
|
228
|
+
return target if target is not None else 'unusable: %s' % why
|
|
229
|
+
|
|
230
|
+
def deliver(self, message: Mapping[str, Any], to_pubkey: str, target: str, anonymous: bool) -> Verdict:
|
|
231
|
+
if target.startswith('unusable: '):
|
|
232
|
+
raise TransportError('libp2p endpoint ' + target)
|
|
233
|
+
their_agree = self.router.peers.agreement(to_pubkey)
|
|
234
|
+
if their_agree is None:
|
|
235
|
+
raise TransportError('no live agreement key for peer %s' % to_pubkey[:16])
|
|
236
|
+
jwe = self.seal(message, to_pubkey, anonymous)
|
|
237
|
+
sent, note = self.router.p2p.send_direct(target, jwe)
|
|
238
|
+
if not sent:
|
|
239
|
+
raise TransportError(note or 'libp2p stream not acknowledged')
|
|
240
|
+
return {self.name: (True, note)}
|
|
241
|
+
|
|
242
|
+
def health(self) -> Optional[Mapping[str, Any]]:
|
|
243
|
+
p2p = self.router.p2p
|
|
244
|
+
return None if p2p is None else RungHealth(available=self.available())
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class RelayRungAdapter(Rung):
|
|
248
|
+
"""The floor of the ladder: gift-wraps over the relays. Reaches every
|
|
249
|
+
peer, so it never says no; the relay rung itself reports which relays
|
|
250
|
+
took the wrap."""
|
|
251
|
+
|
|
252
|
+
name = 'nostr'
|
|
253
|
+
rank = 100
|
|
254
|
+
|
|
255
|
+
def __init__(self, router: Any) -> None:
|
|
256
|
+
self.router = router
|
|
257
|
+
|
|
258
|
+
def available(self) -> bool:
|
|
259
|
+
return True
|
|
260
|
+
|
|
261
|
+
def target_of(self, desc: Optional[Mapping[str, Any]], theirs: Mapping[str, int]) -> Optional[str]:
|
|
262
|
+
return ''
|
|
263
|
+
|
|
264
|
+
def deliver(self, message: Mapping[str, Any], to_pubkey: str, target: str, anonymous: bool) -> Verdict:
|
|
265
|
+
res: Verdict = self.router.nostr.send(message, to_pubkey, anonymous=anonymous, stored=False)
|
|
266
|
+
return res
|
|
267
|
+
|
|
268
|
+
def health(self) -> Optional[Mapping[str, Any]]:
|
|
269
|
+
return dict(self.router.nostr.health)
|
|
270
|
+
|
|
271
|
+
def fault_log(self) -> Optional[FaultLog]:
|
|
272
|
+
return _log_of(self.router.nostr)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
__all__ = ['FipsRung', 'IrohRung', 'Jwe', 'Libp2pRung', 'RelayRungAdapter', 'RungHealth', 'RungProtocol', 'Seal',
|
|
276
|
+
'Verdict', 'endpoints_of']
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Secure Scuttlebutt (SSB) transport ladder rung for TransportRouter.
|
|
2
|
+
|
|
3
|
+
Integrates SSB peer-to-peer delivery into the Fleetmesh transport ladder:
|
|
4
|
+
- Checks sidecar availability over local Unix domain socket.
|
|
5
|
+
- Resolves peer SSB endpoints from node descriptors (scheme 'ssb').
|
|
6
|
+
- Delivers messages to remote SSB peers via SHS BoxStream.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Any, Callable, Mapping, Optional
|
|
12
|
+
|
|
13
|
+
from node.transport.const import TransportError
|
|
14
|
+
from node.transport.ladder import FaultLog
|
|
15
|
+
from node.transport.rung import Rung, Verdict, endpoints_of
|
|
16
|
+
from sidecar.fleetmesh_ssb import SSBSidecarClient
|
|
17
|
+
|
|
18
|
+
log = logging.getLogger('ssb_rung')
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
'SSBRung',
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SSBRung(Rung):
|
|
26
|
+
"""Transport rung delegating outbound deliveries to the SSB sidecar daemon."""
|
|
27
|
+
|
|
28
|
+
name: str = 'ssb'
|
|
29
|
+
rank: int = 25 # P2P tier between libp2p and relay
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
router: Optional[Any] = None,
|
|
34
|
+
sidecar_client: Optional[SSBSidecarClient] = None,
|
|
35
|
+
socket_path: Optional[str] = None,
|
|
36
|
+
clock: Optional[Callable[[], int]] = None,
|
|
37
|
+
):
|
|
38
|
+
self.router = router
|
|
39
|
+
self._client = sidecar_client
|
|
40
|
+
self._socket_path = socket_path
|
|
41
|
+
clk = clock or (getattr(router, 'clock', None) if router else None)
|
|
42
|
+
self._faults = FaultLog('ssb', clock=clk)
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def faults(self) -> FaultLog:
|
|
46
|
+
return self._faults
|
|
47
|
+
|
|
48
|
+
def fault_log(self) -> Optional[FaultLog]:
|
|
49
|
+
return self._faults
|
|
50
|
+
|
|
51
|
+
def _get_client(self) -> Optional[SSBSidecarClient]:
|
|
52
|
+
if self._client is not None:
|
|
53
|
+
return self._client
|
|
54
|
+
sock_path = self._socket_path
|
|
55
|
+
if sock_path is None and self.router is not None:
|
|
56
|
+
node_dir = getattr(self.router, 'dir', None) or getattr(self.router, 'node_dir', None)
|
|
57
|
+
if node_dir:
|
|
58
|
+
import os
|
|
59
|
+
sock_path = os.path.join(node_dir, 'ssb.sock')
|
|
60
|
+
if sock_path:
|
|
61
|
+
self._client = SSBSidecarClient(sock_path)
|
|
62
|
+
return self._client
|
|
63
|
+
|
|
64
|
+
def available(self) -> bool:
|
|
65
|
+
"""Returns True if the SSB sidecar socket is reachable and healthy."""
|
|
66
|
+
client = self._get_client()
|
|
67
|
+
if client is None:
|
|
68
|
+
return False
|
|
69
|
+
try:
|
|
70
|
+
st = client.status()
|
|
71
|
+
return bool(st.get('ok'))
|
|
72
|
+
except Exception:
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
def target_of(self, desc: Optional[Mapping[str, Any]], theirs: Mapping[str, int]) -> Optional[str]:
|
|
76
|
+
"""Extract SSB endpoint target from descriptor."""
|
|
77
|
+
if desc is None:
|
|
78
|
+
return None
|
|
79
|
+
_, where = endpoints_of(desc)
|
|
80
|
+
if 'ssb' in where:
|
|
81
|
+
return where['ssb']
|
|
82
|
+
|
|
83
|
+
# Check raw tags
|
|
84
|
+
for t in desc.get('tags', []):
|
|
85
|
+
if len(t) > 2 and t[0] in ('endpoint', 'address') and t[1] == 'ssb':
|
|
86
|
+
return t[2]
|
|
87
|
+
if len(t) > 2 and t[0] == 'ssb':
|
|
88
|
+
return t[1]
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
def deliver(
|
|
92
|
+
self,
|
|
93
|
+
message: Mapping[str, Any],
|
|
94
|
+
to_pubkey: str,
|
|
95
|
+
target: str,
|
|
96
|
+
anonymous: bool = False,
|
|
97
|
+
) -> Verdict:
|
|
98
|
+
"""Attempt delivery of message to target SSB endpoint."""
|
|
99
|
+
client = self._get_client()
|
|
100
|
+
if client is None:
|
|
101
|
+
self._faults.record('ssb-client-unavailable', ident=to_pubkey)
|
|
102
|
+
raise TransportError('SSB sidecar client not configured')
|
|
103
|
+
|
|
104
|
+
# Parse target format (e.g. "net:192.168.1.100:8008~shs:@pubkey.ed25519" or "host:port:feed_id")
|
|
105
|
+
host = "127.0.0.1"
|
|
106
|
+
port = 8008
|
|
107
|
+
feed_id = target
|
|
108
|
+
|
|
109
|
+
if '~shs:' in target:
|
|
110
|
+
addr_part, shs_part = target.split('~shs:', 1)
|
|
111
|
+
feed_id = shs_part
|
|
112
|
+
if addr_part.startswith('net:'):
|
|
113
|
+
addr_part = addr_part[4:]
|
|
114
|
+
parts = addr_part.split(':')
|
|
115
|
+
if len(parts) >= 2:
|
|
116
|
+
host = parts[0]
|
|
117
|
+
port = int(parts[1])
|
|
118
|
+
elif ':' in target:
|
|
119
|
+
parts = target.split(':')
|
|
120
|
+
if len(parts) == 3:
|
|
121
|
+
host, port_str, feed_id = parts
|
|
122
|
+
port = int(port_str)
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
# 1. Connect / dial peer if not connected
|
|
126
|
+
dial_res = client.dial(host=host, port=port, feed_id=feed_id)
|
|
127
|
+
if not dial_res.get('ok') and dial_res.get('error') != 'already connected':
|
|
128
|
+
err_msg = dial_res.get('error', 'unknown dial failure')
|
|
129
|
+
self._faults.record(f'dial-failed: {err_msg}', ident=to_pubkey)
|
|
130
|
+
raise TransportError(f"SSB dial failed: {err_msg}")
|
|
131
|
+
|
|
132
|
+
# 2. Publish message payload
|
|
133
|
+
post_res = client.post({
|
|
134
|
+
"type": "fleetmesh-transport",
|
|
135
|
+
"recipient": to_pubkey,
|
|
136
|
+
"payload": dict(message),
|
|
137
|
+
"anonymous": anonymous,
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
if not post_res.get('ok'):
|
|
141
|
+
err_msg = post_res.get('error', 'unknown post failure')
|
|
142
|
+
self._faults.record(f'post-failed: {err_msg}', ident=to_pubkey)
|
|
143
|
+
raise TransportError(f"SSB post failed: {err_msg}")
|
|
144
|
+
|
|
145
|
+
return {self.name: (True, 'delivered via ssb sidecar')}
|
|
146
|
+
|
|
147
|
+
except Exception as e:
|
|
148
|
+
if not isinstance(e, TransportError):
|
|
149
|
+
self._faults.record(str(e), ident=to_pubkey)
|
|
150
|
+
raise TransportError(f"SSB delivery error: {e}") from e
|
|
151
|
+
raise
|
|
152
|
+
|
|
153
|
+
def health(self) -> Optional[Mapping[str, Any]]:
|
|
154
|
+
client = self._get_client()
|
|
155
|
+
if client is None:
|
|
156
|
+
return {"available": False, "status": "no_client"}
|
|
157
|
+
try:
|
|
158
|
+
st = client.status()
|
|
159
|
+
return {
|
|
160
|
+
"available": bool(st.get("ok")),
|
|
161
|
+
"feed_id": st.get("feed_id"),
|
|
162
|
+
"peers_connected": st.get("peers_connected", 0),
|
|
163
|
+
"messages_count": st.get("messages_count", 0),
|
|
164
|
+
}
|
|
165
|
+
except Exception as e:
|
|
166
|
+
return {"available": False, "error": str(e)}
|