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.
Files changed (338) hide show
  1. fleetmesh_node-0.2.0.dist-info/METADATA +57 -0
  2. fleetmesh_node-0.2.0.dist-info/RECORD +338 -0
  3. fleetmesh_node-0.2.0.dist-info/WHEEL +5 -0
  4. fleetmesh_node-0.2.0.dist-info/entry_points.txt +2 -0
  5. fleetmesh_node-0.2.0.dist-info/licenses/LICENSE +121 -0
  6. fleetmesh_node-0.2.0.dist-info/top_level.txt +2 -0
  7. node/RELEASE +1 -0
  8. node/__init__.py +35 -0
  9. node/app/__init__.py +49 -0
  10. node/app/commands.py +265 -0
  11. node/app/config.py +131 -0
  12. node/app/control.py +670 -0
  13. node/app/core.py +867 -0
  14. node/app/plugins.py +76 -0
  15. node/app/router.py +83 -0
  16. node/app/socket.py +431 -0
  17. node/app/systemd.py +36 -0
  18. node/app/views.py +204 -0
  19. node/channels/__init__.py +378 -0
  20. node/channels/bus.py +331 -0
  21. node/channels/journal.py +46 -0
  22. node/commands/__init__.py +140 -0
  23. node/commands/adapter.py +210 -0
  24. node/commands/base.py +278 -0
  25. node/commands/cluster_cmds.py +183 -0
  26. node/commands/compute_cmds.py +401 -0
  27. node/commands/core.py +474 -0
  28. node/commands/gossip_cmds.py +106 -0
  29. node/commands/nsite_cmds.py +141 -0
  30. node/commands/peering.py +402 -0
  31. node/commands/plugin_cmds.py +135 -0
  32. node/commands/sidecar_cmds.py +120 -0
  33. node/commands/sync.py +388 -0
  34. node/commands/transport_cmds.py +451 -0
  35. node/commands/video_cmds.py +99 -0
  36. node/compartment/__init__.py +124 -0
  37. node/compartment/base.py +868 -0
  38. node/compartment/command.py +458 -0
  39. node/compartment/context.py +163 -0
  40. node/compartment/every.py +108 -0
  41. node/compartment/handler.py +131 -0
  42. node/compartment/lifecycle.py +460 -0
  43. node/compartment/lint.py +388 -0
  44. node/compartment/need.py +158 -0
  45. node/compartment/problem.py +521 -0
  46. node/compartment/refusals.py +120 -0
  47. node/compartment/registry.py +790 -0
  48. node/compartment/schema.py +290 -0
  49. node/compartment/sidecar.py +499 -0
  50. node/compartment/stores.py +662 -0
  51. node/compartment/wire.py +225 -0
  52. node/compartment/worker.py +61 -0
  53. node/config/__init__.py +43 -0
  54. node/config/base.py +116 -0
  55. node/config/change.py +441 -0
  56. node/config/changestore.py +201 -0
  57. node/config/compartments.py +1144 -0
  58. node/config/node.py +318 -0
  59. node/config/parameter.py +563 -0
  60. node/config/protocols.py +274 -0
  61. node/config/records.py +171 -0
  62. node/examples/__init__.py +1 -0
  63. node/examples/demo.py +226 -0
  64. node/examples/follower.py +46 -0
  65. node/interfaces.py +235 -0
  66. node/keystore/__init__.py +27 -0
  67. node/keystore/core.py +103 -0
  68. node/keystore/enclave.py +688 -0
  69. node/meshnode.py +111 -0
  70. node/metrics/__init__.py +396 -0
  71. node/metrics/adapters.py +102 -0
  72. node/metrics/face.py +110 -0
  73. node/planes/__init__.py +21 -0
  74. node/planes/identity/__init__.py +20 -0
  75. node/planes/identity/announce/__init__.py +9 -0
  76. node/planes/identity/announce/compartment.py +447 -0
  77. node/planes/identity/announce/types.py +26 -0
  78. node/planes/identity/discovery/__init__.py +9 -0
  79. node/planes/identity/discovery/compartment.py +522 -0
  80. node/planes/identity/discovery/types.py +13 -0
  81. node/planes/identity/linked/__init__.py +1 -0
  82. node/planes/identity/linked/compartment.py +270 -0
  83. node/planes/identity/linked/types.py +25 -0
  84. node/planes/identity/presence/__init__.py +7 -0
  85. node/planes/identity/presence/compartment.py +409 -0
  86. node/planes/identity/presence/types.py +14 -0
  87. node/planes/operations/__init__.py +13 -0
  88. node/planes/operations/scheduler/__init__.py +9 -0
  89. node/planes/operations/scheduler/compartment.py +353 -0
  90. node/planes/operations/scheduler/types.py +21 -0
  91. node/planes/operations/updates/__init__.py +17 -0
  92. node/planes/operations/updates/core.py +310 -0
  93. node/planes/operations/updates/store.py +79 -0
  94. node/planes/operations/updates/types.py +20 -0
  95. node/planes/peering/__init__.py +28 -0
  96. node/planes/peering/budget/__init__.py +43 -0
  97. node/planes/peering/budget/chain.py +43 -0
  98. node/planes/peering/budget/core.py +537 -0
  99. node/planes/peering/budget/evaluation.py +80 -0
  100. node/planes/peering/budget/meter.py +100 -0
  101. node/planes/peering/budget/receipts.py +439 -0
  102. node/planes/peering/budget/shape.py +101 -0
  103. node/planes/peering/budget/tick.py +110 -0
  104. node/planes/peering/budget/types.py +67 -0
  105. node/planes/peering/budget/windows.py +346 -0
  106. node/planes/peering/capacity/__init__.py +7 -0
  107. node/planes/peering/capacity/compartment.py +342 -0
  108. node/planes/peering/capacity/types.py +24 -0
  109. node/planes/peering/peering/__init__.py +25 -0
  110. node/planes/peering/peering/core.py +464 -0
  111. node/planes/peering/peering/envelope.py +142 -0
  112. node/planes/peering/peering/handshake.py +198 -0
  113. node/planes/peering/peering/shape.py +80 -0
  114. node/planes/peering/peering/types.py +39 -0
  115. node/planes/peering/reporter/__init__.py +42 -0
  116. node/planes/peering/reporter/constants.py +29 -0
  117. node/planes/peering/reporter/core.py +299 -0
  118. node/planes/peering/reporter/finding.py +74 -0
  119. node/planes/peering/reporter/findings.py +171 -0
  120. node/planes/peering/reporter/publish.py +285 -0
  121. node/planes/peering/reporter/reading.py +193 -0
  122. node/planes/peering/reporter/shape.py +86 -0
  123. node/planes/peering/reporter/types.py +50 -0
  124. node/planes/peering/strain/__init__.py +9 -0
  125. node/planes/peering/strain/compartment.py +204 -0
  126. node/planes/peering/strain/types.py +21 -0
  127. node/planes/peering/trust/__init__.py +9 -0
  128. node/planes/peering/trust/compartment.py +239 -0
  129. node/planes/peering/trust/types.py +30 -0
  130. node/planes/services/__init__.py +33 -0
  131. node/planes/services/compute/__init__.py +160 -0
  132. node/planes/services/compute/common.py +134 -0
  133. node/planes/services/compute/compartment.py +681 -0
  134. node/planes/services/compute/executors/__init__.py +121 -0
  135. node/planes/services/compute/executors/agent.py +840 -0
  136. node/planes/services/compute/executors/bacalhau.py +605 -0
  137. node/planes/services/compute/executors/base.py +127 -0
  138. node/planes/services/compute/executors/harness.py +439 -0
  139. node/planes/services/compute/executors/mcp.py +454 -0
  140. node/planes/services/compute/executors/media.py +898 -0
  141. node/planes/services/compute/executors/nondet.py +283 -0
  142. node/planes/services/compute/executors/providers.py +499 -0
  143. node/planes/services/compute/executors/registry.py +140 -0
  144. node/planes/services/compute/executors/wasm.py +244 -0
  145. node/planes/services/compute/executors/zk.py +604 -0
  146. node/planes/services/compute/jobs.py +227 -0
  147. node/planes/services/compute/load.py +180 -0
  148. node/planes/services/compute/market.py +433 -0
  149. node/planes/services/compute/sandbox.py +282 -0
  150. node/planes/services/compute/types.py +84 -0
  151. node/planes/services/compute_wire/__init__.py +58 -0
  152. node/planes/services/compute_wire/compartment.py +256 -0
  153. node/planes/services/compute_wire/messages.py +320 -0
  154. node/planes/services/compute_wire/sealing.py +147 -0
  155. node/planes/services/compute_wire/session.py +572 -0
  156. node/planes/services/compute_wire/types.py +49 -0
  157. node/planes/services/compute_wire/verify.py +331 -0
  158. node/planes/services/dvm/__init__.py +24 -0
  159. node/planes/services/dvm/gateway.py +526 -0
  160. node/planes/services/dvm/protocol.py +35 -0
  161. node/planes/services/dvm/translate.py +15 -0
  162. node/planes/services/dvm/types.py +25 -0
  163. node/planes/services/gateway/__init__.py +40 -0
  164. node/planes/services/gateway/settle.py +279 -0
  165. node/planes/services/gateway/translate.py +644 -0
  166. node/planes/services/gossip/__init__.py +22 -0
  167. node/planes/services/gossip/core.py +929 -0
  168. node/planes/services/gossip/envelope.py +154 -0
  169. node/planes/services/gossip/mesh.py +127 -0
  170. node/planes/services/gossip/types.py +50 -0
  171. node/planes/services/nsite/__init__.py +7 -0
  172. node/planes/services/nsite/client.py +62 -0
  173. node/planes/services/nsite/compartment.py +752 -0
  174. node/planes/services/nsite/types.py +44 -0
  175. node/planes/services/paidfetch/__init__.py +7 -0
  176. node/planes/services/paidfetch/compartment.py +862 -0
  177. node/planes/services/paidfetch/types.py +99 -0
  178. node/planes/services/settle/__init__.py +27 -0
  179. node/planes/services/settle/compartment.py +384 -0
  180. node/planes/services/settle/engine.py +456 -0
  181. node/planes/services/settle/gateways.py +541 -0
  182. node/planes/services/settle/payment.py +512 -0
  183. node/planes/services/settle/store.py +74 -0
  184. node/planes/services/settle/types.py +58 -0
  185. node/planes/services/sidecars/__init__.py +8 -0
  186. node/planes/services/sidecars/compartment.py +461 -0
  187. node/planes/services/sidecars/manifest.py +304 -0
  188. node/planes/services/sidecars/manifests/10-grasp.json +37 -0
  189. node/planes/services/sidecars/manifests/20-ncontainer.json +31 -0
  190. node/planes/services/sidecars/manifests/30-magpie.json +28 -0
  191. node/planes/services/sidecars/types.py +43 -0
  192. node/planes/services/video/__init__.py +106 -0
  193. node/planes/services/video/barter.py +407 -0
  194. node/planes/services/video/cli.py +158 -0
  195. node/planes/services/video/compartment.py +932 -0
  196. node/planes/services/video/ingest.py +524 -0
  197. node/planes/services/video/ledger.py +705 -0
  198. node/planes/services/video/packager.py +847 -0
  199. node/planes/services/video/player.py +456 -0
  200. node/planes/services/video/timed_segments.py +124 -0
  201. node/planes/services/video/transit.py +478 -0
  202. node/planes/store/__init__.py +24 -0
  203. node/planes/store/blob_replication/__init__.py +7 -0
  204. node/planes/store/blob_replication/compartment.py +496 -0
  205. node/planes/store/blob_replication/types.py +35 -0
  206. node/planes/store/blob_wire/__init__.py +48 -0
  207. node/planes/store/blob_wire/client.py +57 -0
  208. node/planes/store/blob_wire/core.py +359 -0
  209. node/planes/store/blob_wire/intake.py +74 -0
  210. node/planes/store/blob_wire/messages.py +85 -0
  211. node/planes/store/projected/__init__.py +7 -0
  212. node/planes/store/projected/compartment.py +258 -0
  213. node/planes/store/projected/types.py +29 -0
  214. node/planes/store/retention/__init__.py +7 -0
  215. node/planes/store/retention/compartment.py +214 -0
  216. node/planes/store/retention/types.py +12 -0
  217. node/planes/store/sync/__init__.py +35 -0
  218. node/planes/store/sync/core.py +311 -0
  219. node/planes/store/sync/envelope.py +106 -0
  220. node/planes/store/sync/handover.py +206 -0
  221. node/planes/store/sync/initiator.py +128 -0
  222. node/planes/store/sync/responder.py +114 -0
  223. node/planes/store/sync/rules.py +179 -0
  224. node/planes/store/sync/shape.py +80 -0
  225. node/planes/store/sync/types.py +51 -0
  226. node/planes/transport/__init__.py +23 -0
  227. node/planes/transport/bridge/__init__.py +13 -0
  228. node/planes/transport/bridge/compartment.py +785 -0
  229. node/planes/transport/bridge/ratelimit.py +44 -0
  230. node/planes/transport/bridge/types.py +43 -0
  231. node/planes/transport/lan/__init__.py +9 -0
  232. node/planes/transport/lan/compartment.py +446 -0
  233. node/planes/transport/lan/types.py +25 -0
  234. node/planes/transport/link/__init__.py +7 -0
  235. node/planes/transport/link/compartment.py +257 -0
  236. node/planes/transport/link/types.py +27 -0
  237. node/planes/transport/mediation/__init__.py +45 -0
  238. node/planes/transport/mediation/compartment.py +238 -0
  239. node/planes/transport/mediation/leaf.py +107 -0
  240. node/planes/transport/mediation/messages.py +302 -0
  241. node/planes/transport/mediation/service.py +233 -0
  242. node/planes/transport/mediation/store.py +37 -0
  243. node/planes/transport/mediation/types.py +20 -0
  244. node/planes/transport/p2p/__init__.py +7 -0
  245. node/planes/transport/p2p/compartment.py +349 -0
  246. node/planes/transport/p2p/types.py +22 -0
  247. node/protocols/__init__.py +277 -0
  248. node/protocols/activitypub/__init__.py +56 -0
  249. node/protocols/activitypub/actor.py +230 -0
  250. node/protocols/activitypub/federation.py +490 -0
  251. node/protocols/activitypub/media.py +145 -0
  252. node/protocols/activitypub/models.py +286 -0
  253. node/protocols/activitypub/package.py +271 -0
  254. node/protocols/activitypub/store.py +564 -0
  255. node/protocols/arcade/__init__.py +26 -0
  256. node/protocols/atproto/__init__.py +13 -0
  257. node/protocols/atproto/firehose.py +126 -0
  258. node/protocols/atproto/metering.py +165 -0
  259. node/protocols/atproto/package.py +155 -0
  260. node/protocols/atproto/repo.py +250 -0
  261. node/protocols/atproto/xrpc.py +285 -0
  262. node/protocols/base.py +109 -0
  263. node/protocols/circles/__init__.py +29 -0
  264. node/protocols/dm/__init__.py +50 -0
  265. node/protocols/grasp/__init__.py +22 -0
  266. node/protocols/isotime.py +15 -0
  267. node/protocols/nostr/__init__.py +51 -0
  268. node/protocols/nostr/blobstore.py +383 -0
  269. node/protocols/nostr/blossom.py +627 -0
  270. node/protocols/nostr/client.py +377 -0
  271. node/protocols/nostr/descriptor.py +211 -0
  272. node/protocols/nostr/eventstore.py +304 -0
  273. node/protocols/nostr/package.py +182 -0
  274. node/protocols/nostr/peers.py +109 -0
  275. node/protocols/nostr/relayserver.py +656 -0
  276. node/protocols/p2panda/__init__.py +22 -0
  277. node/protocols/p2panda/identity.py +19 -0
  278. node/protocols/p2panda/package.py +236 -0
  279. node/protocols/p2panda/rung.py +152 -0
  280. node/protocols/p2panda/store.py +273 -0
  281. node/protocols/rhio/__init__.py +33 -0
  282. node/protocols/rhio/package.py +275 -0
  283. node/protocols/rhio/rung.py +136 -0
  284. node/protocols/rhio/store.py +1423 -0
  285. node/protocols/ssb/muxrpc.py +256 -0
  286. node/protocols/ssb/package.py +267 -0
  287. node/protocols/textile/__init__.py +111 -0
  288. node/protocols/textile/transducers.py +259 -0
  289. node/release.py +119 -0
  290. node/storage/__init__.py +88 -0
  291. node/storage/accounts.py +305 -0
  292. node/storage/adapter.py +161 -0
  293. node/storage/base.py +385 -0
  294. node/storage/dialect.py +209 -0
  295. node/storage/grants.py +317 -0
  296. node/storage/locator.py +150 -0
  297. node/storage/postgres.py +294 -0
  298. node/storage/projected.py +953 -0
  299. node/storage/sharding.py +448 -0
  300. node/storage/standing.py +568 -0
  301. node/testnet/__init__.py +9 -0
  302. node/testnet/cluster.py +731 -0
  303. node/testnet/fips_daemon.py +230 -0
  304. node/transport/__init__.py +94 -0
  305. node/transport/const.py +110 -0
  306. node/transport/didcomm_inbound.py +251 -0
  307. node/transport/dnstxt.py +169 -0
  308. node/transport/fips_rung/__init__.py +67 -0
  309. node/transport/fips_rung/core.py +666 -0
  310. node/transport/fips_rung/listener.py +96 -0
  311. node/transport/fips_rung/types.py +12 -0
  312. node/transport/host.py +334 -0
  313. node/transport/iroh_rung/__init__.py +29 -0
  314. node/transport/iroh_rung/core.py +477 -0
  315. node/transport/iroh_rung/quic.py +942 -0
  316. node/transport/iroh_rung/types.py +22 -0
  317. node/transport/ladder.py +145 -0
  318. node/transport/lineproto.py +49 -0
  319. node/transport/mdns.py +303 -0
  320. node/transport/netfetch.py +283 -0
  321. node/transport/relay_rung/__init__.py +23 -0
  322. node/transport/relay_rung/cooldown.py +87 -0
  323. node/transport/relay_rung/core.py +138 -0
  324. node/transport/relay_rung/inbound.py +161 -0
  325. node/transport/relay_rung/ledger.py +104 -0
  326. node/transport/relay_rung/listen.py +121 -0
  327. node/transport/relay_rung/outbound.py +181 -0
  328. node/transport/relay_rung/shape.py +71 -0
  329. node/transport/relay_rung/types.py +58 -0
  330. node/transport/relay_rung/wire.py +63 -0
  331. node/transport/router.py +761 -0
  332. node/transport/rung.py +276 -0
  333. node/transport/ssb_rung.py +166 -0
  334. node/transport/ws.py +502 -0
  335. sidecar/fleetmesh_announce.py +338 -0
  336. sidecar/fleetmesh_p2p.py +373 -0
  337. sidecar/fleetmesh_rhio.py +352 -0
  338. sidecar/fleetmesh_ssb.py +603 -0
@@ -0,0 +1,230 @@
1
+ """A FIPS daemon stand-in for the local testnet.
2
+
3
+ Speaks enough of the fipsd control socket protocol for node/fips_transport.py
4
+ to bind, send and receive datagrams between the cluster's nodes on one
5
+ machine, with no real fipsd installed. It is a test double, and it lives
6
+ apart from node/testnet/cluster.py because two suites (test_cluster_fips,
7
+ test_didcomm_inbound) drive it on its own, with no cluster around it.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import array
12
+ import json
13
+ import os
14
+ import socket
15
+ import threading
16
+ from typing import Optional
17
+
18
+
19
+ class ClusterFipsDaemon:
20
+ """In-process FIPS Native Control daemon for LocalCluster simulation.
21
+
22
+ Manages bilateral datagram flows between cluster nodes without requiring
23
+ an external fipsd daemon:
24
+ - Handles 'listen' and 'connect' commands over Unix domain control socket.
25
+ - Passes connected datagram sockets to clients via SCM_RIGHTS.
26
+ - Routes bilateral datagrams directly between peer sockets.
27
+ """
28
+
29
+ def __init__(self, sock_path: str) -> None:
30
+ self.sock_path = os.path.abspath(sock_path)
31
+ if os.path.exists(self.sock_path):
32
+ try:
33
+ os.unlink(self.sock_path)
34
+ except OSError:
35
+ pass
36
+ os.makedirs(os.path.dirname(self.sock_path), exist_ok=True)
37
+ self.srv_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
38
+ self.srv_sock.bind(self.sock_path)
39
+ self.srv_sock.listen(32)
40
+ self.running = True
41
+ self._lock = threading.Lock()
42
+ self.peer_listeners: dict[tuple[str, int], socket.socket] = {}
43
+ self.listeners: dict[int, list[socket.socket]] = {}
44
+ self.listener_conns: dict[socket.socket, socket.socket] = {}
45
+ self.active_pairs: list[tuple[socket.socket, socket.socket]] = []
46
+ self.threads: list[threading.Thread] = []
47
+ self.thread = threading.Thread(target=self._run, daemon=True)
48
+ self.thread.start()
49
+
50
+ def _run(self) -> None:
51
+ while self.running:
52
+ try:
53
+ conn, _ = self.srv_sock.accept()
54
+ except Exception:
55
+ break
56
+ t = threading.Thread(target=self._handle_client, args=(conn,), daemon=True)
57
+ t.start()
58
+ self.threads.append(t)
59
+
60
+ def _handle_client(self, conn: socket.socket) -> None:
61
+ buf = b""
62
+ client_node_pk: Optional[str] = None
63
+ registered_listeners: list[tuple[Optional[str], int, socket.socket]] = []
64
+ try:
65
+ while self.running:
66
+ try:
67
+ chunk = conn.recv(4096)
68
+ if not chunk:
69
+ break
70
+ buf += chunk
71
+ while b"\n" in buf:
72
+ line, buf = buf.split(b"\n", 1)
73
+ if not line.strip():
74
+ continue
75
+ req = json.loads(line.decode("utf-8"))
76
+ cmd = req.get("command") or req.get("cmd")
77
+ params = req.get("params") if isinstance(req.get("params"), dict) else req
78
+ port = int(params.get("local_port") or params.get("remote_port") or params.get("port") or 11801)
79
+
80
+ # The socket protocol has three verbs; a request naming none of them
81
+ # is ignored and the connection stays open, as before.
82
+ match cmd:
83
+ case "listen":
84
+ # Client wishes to listen on port
85
+ client_node_pk = params.get("node_pk") or req.get("node_pk") or client_node_pk
86
+ client_sock, daemon_sock = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM)
87
+ with self._lock:
88
+ self.active_pairs.append((client_sock, daemon_sock))
89
+ self.listeners.setdefault(port, []).append(daemon_sock)
90
+ if client_node_pk:
91
+ self.peer_listeners[(client_node_pk, port)] = daemon_sock
92
+ self.listener_conns[daemon_sock] = conn
93
+ registered_listeners.append((client_node_pk, port, daemon_sock))
94
+
95
+ resp_dict = {
96
+ "status": "ok",
97
+ "port": port,
98
+ "state": "listening",
99
+ "data": {
100
+ "backlog": 16,
101
+ "local_port": port,
102
+ "node": client_node_pk or "local",
103
+ },
104
+ }
105
+ resp = json.dumps(resp_dict).encode("utf-8") + b"\n"
106
+ fds = array.array("i", [client_sock.fileno()])
107
+ cmsg = [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)]
108
+ conn.sendmsg([resp], cmsg)
109
+ client_sock.close()
110
+
111
+ case "connect":
112
+ peer = str(params.get("peer") or req.get("peer") or "")
113
+ if not client_node_pk and (params.get("caller") or req.get("caller")):
114
+ client_node_pk = params.get("caller") or req.get("caller")
115
+
116
+ with self._lock:
117
+ target_sock = self.peer_listeners.get((peer, port))
118
+ if not target_sock:
119
+ for (p_k, p_port), s_sock in self.peer_listeners.items():
120
+ if p_port == port and (p_k.startswith(peer) or peer.startswith(p_k)):
121
+ target_sock = s_sock
122
+ break
123
+ if not target_sock and not peer and port in self.listeners and self.listeners[port]:
124
+ target_sock = self.listeners[port][0]
125
+
126
+ if target_sock is not None:
127
+ target_conn = self.listener_conns.get(target_sock)
128
+ if target_conn is not None:
129
+ try:
130
+ peek = target_conn.recv(1, socket.MSG_PEEK | socket.MSG_DONTWAIT)
131
+ if peek == b"":
132
+ target_sock = None
133
+ except (BlockingIOError, socket.error):
134
+ pass
135
+ except Exception:
136
+ target_sock = None
137
+
138
+ if target_sock is None or getattr(target_sock, 'fileno', lambda: -1)() == -1:
139
+ resp = json.dumps({"status": "error", "error": f"peer {peer}:{port} not listening or unreachable"}).encode("utf-8") + b"\n"
140
+ conn.sendall(resp)
141
+ continue
142
+
143
+ client_sock, daemon_sock = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM)
144
+ with self._lock:
145
+ self.active_pairs.append((client_sock, daemon_sock))
146
+
147
+ def forward(src: socket.socket, dst: socket.socket):
148
+ while self.running:
149
+ try:
150
+ data = src.recv(65536)
151
+ if not data:
152
+ break
153
+ dst.send(data)
154
+ except Exception:
155
+ break
156
+ t = threading.Thread(target=forward, args=(daemon_sock, target_sock), daemon=True)
157
+ t.start()
158
+ self.threads.append(t)
159
+
160
+ resp_dict = {
161
+ "status": "connected",
162
+ "port": port,
163
+ "peer": peer,
164
+ "flow": f"{peer}:{port}",
165
+ "data": {
166
+ "flow_id": len(self.active_pairs),
167
+ "local_port": 49152,
168
+ "max_payload": 1170,
169
+ "node": client_node_pk or "local",
170
+ "peer": peer,
171
+ "remote_port": port,
172
+ },
173
+ }
174
+ resp = json.dumps(resp_dict).encode("utf-8") + b"\n"
175
+ fds = array.array("i", [client_sock.fileno()])
176
+ cmsg = [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)]
177
+ conn.sendmsg([resp], cmsg)
178
+ client_sock.close()
179
+
180
+ case "status":
181
+ resp = json.dumps({
182
+ "status": "ok",
183
+ "protocol": "fleetmesh-fips/1",
184
+ "listeners": len(self.listeners),
185
+ "active_flows": len(self.active_pairs),
186
+ }).encode("utf-8") + b"\n"
187
+ conn.sendall(resp)
188
+ except Exception:
189
+ break
190
+ finally:
191
+ with self._lock:
192
+ for pk, p, ds in registered_listeners:
193
+ if pk and (pk, p) in self.peer_listeners and self.peer_listeners[(pk, p)] is ds:
194
+ del self.peer_listeners[(pk, p)]
195
+ if p in self.listeners and ds in self.listeners[p]:
196
+ try:
197
+ self.listeners[p].remove(ds)
198
+ except ValueError:
199
+ pass
200
+ self.listener_conns.pop(ds, None)
201
+ try:
202
+ conn.close()
203
+ except Exception:
204
+ pass
205
+
206
+ def stop(self) -> None:
207
+ self.running = False
208
+ try:
209
+ self.srv_sock.close()
210
+ except Exception:
211
+ pass
212
+ with self._lock:
213
+ for c_sock, d_sock in self.active_pairs:
214
+ try:
215
+ c_sock.close()
216
+ except Exception:
217
+ pass
218
+ try:
219
+ d_sock.close()
220
+ except Exception:
221
+ pass
222
+ self.active_pairs.clear()
223
+ self.listeners.clear()
224
+ self.peer_listeners.clear()
225
+ self.listener_conns.clear()
226
+ if os.path.exists(self.sock_path):
227
+ try:
228
+ os.unlink(self.sock_path)
229
+ except OSError:
230
+ pass
@@ -0,0 +1,94 @@
1
+ """The transport adapter, and its first implementation: gift wraps over relays.
2
+
3
+ A DIDComm plaintext message goes out as a JWE (didcomm.py) inside a NIP-59
4
+ wrap (giftwrap.py) published as kind 21059 to every relay this node uses,
5
+ addressed to the peer's node key. Inbound, the node holds one subscription
6
+ per relay for kind 21059 with #p equal to its own key, unwraps, decrypts,
7
+ and checks that the three names agree: the seal's signer, the envelope's
8
+ skid, and the plaintext's `from` must all be the same node. Any disagreement
9
+ is dropped, not surfaced, because a message that lies about who sent it is
10
+ not a message.
11
+
12
+ Every message carries a thread. A reply's `thid` is the id of the message it
13
+ answers, so request() can wait for exactly its answer while unrelated
14
+ traffic goes to the inbox. This is the interface the direct rungs (fips-native,
15
+ iroh, libp2p) implement behind TransportRouter; the NIP says this one always works.
16
+
17
+ It does not. A relay can accept a stored wrap, answer OK, and never serve it -
18
+ relay.pocketstr.com did exactly that to every receipt for seventeen hours on
19
+ 2026-09-07, and four of the largest public relays refuse the ephemeral kind
20
+ outright. The publish result is the relay's word about itself, not about
21
+ delivery, and nothing in this node could tell the two apart. So every stored
22
+ message now asks for an acknowledgment and is held until one arrives, using
23
+ the two headers DIDComm v2.1 defines for it: `please_ack` on the message and
24
+ `ack` on the answer, carried by the empty message when nothing else is due.
25
+ A peer that has spoken since and still has not acknowledged is the proof
26
+ that the stored path failed, and that is what `sweep()` records. The ack is
27
+ a live wrap, because the peer is online by construction when it sends one.
28
+ """
29
+ from __future__ import annotations
30
+
31
+ import os
32
+ import sys
33
+
34
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..')))
35
+ # nostr and router do `from . import const` while this file is still running;
36
+ # that resolves as a submodule import against the half-built package, so the
37
+ # order here is not load-bearing. const is a leaf and stays first anyway, so
38
+ # the dependency reads the way it runs. node/tests/test_imports.py imports
39
+ # the package and each submodule cold to hold that.
40
+ from . import const, fips_rung, iroh_rung, ladder, netfetch, relay_rung, router, ws # noqa: E402
41
+ from .const import ( # noqa: E402
42
+ WRAP_KIND,
43
+ RELAY_CONTENT_MAX,
44
+ FRAGMENT_BYTES,
45
+ MAX_FRAGMENTS,
46
+ ENVELOPE_MAX,
47
+ SEEN_MAX,
48
+ PARTIALS_PER_SENDER,
49
+ PARTIALS_MAX,
50
+ INBOX_MAX,
51
+ DROPPED_KEPT,
52
+ BACKOFF_MAX,
53
+ EMPTY_TYPE,
54
+ ACK_SLACK,
55
+ ACK_GRACE,
56
+ ACK_EXPIRE,
57
+ TransportError,
58
+ Inbound,
59
+ _foreign,
60
+ )
61
+ from .relay_rung import NostrTransport, RelayRung # noqa: E402
62
+ from .router import TransportRouter # noqa: E402
63
+
64
+ __all__ = [
65
+ 'WRAP_KIND',
66
+ 'RELAY_CONTENT_MAX',
67
+ 'FRAGMENT_BYTES',
68
+ 'MAX_FRAGMENTS',
69
+ 'ENVELOPE_MAX',
70
+ 'SEEN_MAX',
71
+ 'PARTIALS_PER_SENDER',
72
+ 'PARTIALS_MAX',
73
+ 'INBOX_MAX',
74
+ 'DROPPED_KEPT',
75
+ 'BACKOFF_MAX',
76
+ 'EMPTY_TYPE',
77
+ 'ACK_SLACK',
78
+ 'ACK_GRACE',
79
+ 'ACK_EXPIRE',
80
+ 'TransportError',
81
+ 'Inbound',
82
+ '_foreign',
83
+ 'NostrTransport',
84
+ 'RelayRung',
85
+ 'TransportRouter',
86
+ 'ladder',
87
+ 'const',
88
+ 'fips_rung',
89
+ 'iroh_rung',
90
+ 'netfetch',
91
+ 'relay_rung',
92
+ 'router',
93
+ 'ws',
94
+ ]
@@ -0,0 +1,110 @@
1
+ """Limits, kinds and timeouts shared by every transport rung, plus TransportError and Inbound.
2
+
3
+ Separate so that the rungs and the router can read each value through this module at call time,
4
+ which is what lets a test (or an operator) reassign one and have every reader see the new value.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from typing import Any
9
+
10
+ from . import didcomm_inbound
11
+ from fleetmesh_core.crypto import giftwrap
12
+
13
+
14
+ WRAP_KIND: int = giftwrap.WRAP_LIVE
15
+ # A wrap's seal is one nip44/2 payload, and nip44 caps plaintext at 65535
16
+ # bytes. A deliver of a few dozen events with their envelopes is bigger, so
17
+ # an envelope larger than this rides in numbered fragments, each its own wrap,
18
+ # reassembled by the receiver before anything is decrypted.
19
+ # A fragment is plaintext; what reaches the relay is that text sealed and then
20
+ # wrapped, two NIP-44 layers, each padded and base64. A 40000-byte fragment
21
+ # became a 76548-byte wrap content, and relay.pocketstr.com caps content at
22
+ # 65536, so every deliver over one fragment was refused and the peer waited on
23
+ # an envelope that never finished. 24000 wraps to 54704: under the cap with
24
+ # room for a relay that pads differently. test_transport.py holds it there.
25
+ RELAY_CONTENT_MAX: int = 65536
26
+ FRAGMENT_BYTES: int = 24000
27
+ # The receiver refuses an envelope claiming more than this many fragments, so
28
+ # a sender that splits into more is publishing wraps nobody will ever
29
+ # reassemble. It recorded nothing and the peer simply timed out - the same
30
+ # shape as the fragment that did not fit: the sender never looked at whether
31
+ # what it sent could arrive. Both ends read this number.
32
+ MAX_FRAGMENTS: int = 64
33
+ ENVELOPE_MAX: int = FRAGMENT_BYTES * MAX_FRAGMENTS
34
+ # How long a peer's kind 10050 relay list (NIP-17) is trusted before it is read again.
35
+ DM_RELAYS_TTL: int = 3600
36
+ # What a peer, or a relay, may make this node hold on its behalf. Every one of
37
+ # these was unbounded: the set of wrap ids seen, the fragments of envelopes
38
+ # nobody finished, the inbox, and the list of drops. A node that runs for a
39
+ # month must not remember every wrap it ever saw, and a peer that sends the
40
+ # first fragment of ten thousand envelopes must not own the node's memory.
41
+ SEEN_MAX: int = 20000
42
+ PARTIALS_PER_SENDER: int = 4
43
+ PARTIALS_MAX: int = 64
44
+ INBOX_MAX: int = 10000
45
+ DROPPED_KEPT: int = 200
46
+ BACKOFF_MAX: float = 30.0
47
+ # The empty message: DIDComm v2.1's carrier for headers with no body, which
48
+ # is what a pure acknowledgment is. It is handled here, below the protocols,
49
+ # and never reaches a handler.
50
+ EMPTY_TYPE: str = 'https://didcomm.org/empty/1.0/empty'
51
+ # A stored message is judged undelivered only against the peer's own
52
+ # behaviour, never against a clock alone: the peer may be offline for a day,
53
+ # which is the reason the message was stored. ACK_SLACK is how long after the
54
+ # send a message from the peer counts as "heard since" - a relay serves a
55
+ # stored wrap to a live subscription within seconds, so a peer that is
56
+ # talking two minutes later has had it if it was ever going to. ACK_GRACE is
57
+ # how long after that first hearing the verdict waits, because a node coming
58
+ # back may send a receipt before it opens the replayed wrap. ACK_EXPIRE is the
59
+ # relay's own horizon: past it nothing can arrive and the entry is dropped
60
+ # without a verdict.
61
+ ACK_SLACK: int = 120
62
+ ACK_GRACE: int = 60
63
+ ACK_EXPIRE: int = giftwrap.TWO_DAYS
64
+
65
+
66
+ class TransportError(Exception):
67
+ pass
68
+
69
+
70
+ # Anoncrypt on a direct rung. A JWE with no skid proves nothing about who
71
+ # wrote it, and on a fips flow, an iroh stream or a libp2p stream there is
72
+ # no seal either, so Inbound.sender is whatever `from` claimed. Every
73
+ # handler in this node identifies the peer by inbound.sender, and the one
74
+ # that checks `authenticated` for itself (mediation) is the exception, not
75
+ # the rule. So a direct rung delivers an unauthenticated message only when
76
+ # its type is one that is anonymous by design: a protocol whose handler
77
+ # already treats the sender as a stranger. On the relay rung the seal
78
+ # signer is verified (giftwrap.unwrap), so this gate does not apply there.
79
+ #
80
+ # - routing/2.0/forward: anyone may post to a leaf through its mediator;
81
+ # handle_forward checks body.next against the keylist, never the sender.
82
+ # - trust-ping/2.0: reachability, anonymous by the DIDComm spec; no handler
83
+ # in this node acts on it.
84
+ # - discover-features/2.0/queries: answered from the public descriptor, so a
85
+ # stranger learns nothing a relay would not tell it; the handler writes
86
+ # nothing against the sender.
87
+ # Peering propose is NOT here: the offer handler writes a grant to `sender`.
88
+ ANONYMOUS_OK: tuple[str, ...] = (
89
+ 'https://didcomm.org/routing/2.0/forward',
90
+ 'https://didcomm.org/trust-ping/2.0/',
91
+ 'https://didcomm.org/discover-features/2.0/queries',
92
+ )
93
+
94
+
95
+ def anonymous_allowed(msg_type: Any) -> bool:
96
+ """True if an unauthenticated (anoncrypt) message of this type may be
97
+ delivered off a direct rung. Exact match or prefix with a trailing slash."""
98
+ if not isinstance(msg_type, str):
99
+ return False
100
+ for allowed in ANONYMOUS_OK:
101
+ if msg_type == allowed or (allowed.endswith('/') and msg_type.startswith(allowed)):
102
+ return True
103
+ return False
104
+
105
+
106
+ # Inbound and the envelope-opening rule live in didcomm_inbound so that every
107
+ # rung imports one definition; the name is re-exported here because the rest
108
+ # of the node has always said transport.Inbound.
109
+ Inbound = didcomm_inbound.Inbound
110
+ _foreign = didcomm_inbound.foreign