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
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
"""Transport CLI subcommands: FIPS native, Iroh QUIC, and Hardware Enclave."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import time
|
|
8
|
+
from typing import Any, Mapping, TypedDict
|
|
9
|
+
|
|
10
|
+
from node.interfaces import DIDCommMessage
|
|
11
|
+
from node.config.parameter import Parameter
|
|
12
|
+
from .adapter import CommandDispatcher
|
|
13
|
+
from .base import add_command, emit, fail, load_json_arg, resolve_peer_or_raw
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FipsFlowInfo(TypedDict):
|
|
17
|
+
flow: str
|
|
18
|
+
fileno: int
|
|
19
|
+
status: str
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _message_arg(a: argparse.Namespace) -> DIDCommMessage | Mapping[str, Any]:
|
|
23
|
+
"""--message as a DIDComm-ish body: JSON (inline or @file) when it parses,
|
|
24
|
+
otherwise wrapped as a raw string. Default is a ping."""
|
|
25
|
+
msg_str = getattr(a, 'message', None) or '{"type":"fleetmesh/ping"}'
|
|
26
|
+
return load_json_arg(msg_str, '--message', on_error=lambda raw: {'type': 'fleetmesh/raw', 'content': raw})
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ---- 1. Hardware Enclave ---------------------------------------------------
|
|
30
|
+
|
|
31
|
+
def _enclave_status(det: dict) -> None:
|
|
32
|
+
print('Hardware Enclave Status:')
|
|
33
|
+
print(' Platform: %s' % det['platform'])
|
|
34
|
+
print(' Hardware Tier: %s' % det['hardware_tier'])
|
|
35
|
+
print(' Apple Secure Enclave: %s' % ('present' if det['has_secure_enclave'] else 'absent'))
|
|
36
|
+
print(' Biometrics/TouchID: %s' % ('supported' if det['has_biometrics'] else 'absent'))
|
|
37
|
+
print(' Supported Curves: %s' % ', '.join(det['supported_curves']))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _cmd_enclave_status(a: argparse.Namespace) -> int:
|
|
41
|
+
from node.keystore import enclave as enclave_mod
|
|
42
|
+
return emit(a, enclave_mod.detect_hardware_enclave(), _enclave_status)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _cmd_enclave_generate(a: argparse.Namespace) -> int:
|
|
46
|
+
from node.keystore import enclave as enclave_mod
|
|
47
|
+
ktype = getattr(a, 'type', 'p256') or 'p256'
|
|
48
|
+
key = enclave_mod.EnclaveKey.generate(key_type=ktype)
|
|
49
|
+
res = {
|
|
50
|
+
'key_id': key.key_id,
|
|
51
|
+
'key_type': key.key_type,
|
|
52
|
+
'hardware_tier': key.hardware_tier,
|
|
53
|
+
'public_key_hex': key.public_key_hex,
|
|
54
|
+
'multikey': key.multikey,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
def generated(_: dict) -> None:
|
|
58
|
+
print('Generated Enclave Key:')
|
|
59
|
+
print(' ID: %s' % key.key_id)
|
|
60
|
+
print(' Type: %s (%s)' % (key.key_type, key.hardware_tier))
|
|
61
|
+
print(' Multikey: %s' % key.multikey)
|
|
62
|
+
return emit(a, res, generated)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _cmd_enclave_sign(a: argparse.Namespace) -> int:
|
|
66
|
+
from node.keystore import enclave as enclave_mod
|
|
67
|
+
if not getattr(a, 'message', None):
|
|
68
|
+
fail('sign requires --message <text>')
|
|
69
|
+
key = enclave_mod.EnclaveKey.generate(key_type=getattr(a, 'type', 'p256') or 'p256')
|
|
70
|
+
msg = a.message.encode('utf-8')
|
|
71
|
+
sig = key.sign(msg)
|
|
72
|
+
res = {
|
|
73
|
+
'message': a.message,
|
|
74
|
+
'multikey': key.multikey,
|
|
75
|
+
'signature_raw': sig.raw_signature.hex(),
|
|
76
|
+
'attestation': sig.attestation,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
def signed(_: dict) -> None:
|
|
80
|
+
print('Enclave Signature:')
|
|
81
|
+
print(' Multikey: %s' % key.multikey)
|
|
82
|
+
print(' Signature: %s' % sig.raw_signature.hex())
|
|
83
|
+
print(' Tier: %s' % sig.attestation.get('hardware_tier'))
|
|
84
|
+
return emit(a, res, signed)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _cmd_enclave_verify(a: argparse.Namespace) -> int:
|
|
88
|
+
from node.keystore import enclave as enclave_mod
|
|
89
|
+
if not getattr(a, 'message', None) or not getattr(a, 'sig', None) or not getattr(a, 'pub', None):
|
|
90
|
+
fail('verify requires --message, --sig (hex), and --pub (multikey)')
|
|
91
|
+
msg = a.message.encode('utf-8')
|
|
92
|
+
try:
|
|
93
|
+
sig_bytes = bytes.fromhex(a.sig)
|
|
94
|
+
except Exception:
|
|
95
|
+
fail('sig must be a valid hex string')
|
|
96
|
+
ok_v, reason = enclave_mod.verify_signature(msg, a.pub, sig_bytes)
|
|
97
|
+
emit(a, {'valid': ok_v, 'reason': reason},
|
|
98
|
+
lambda _: print('Enclave Verification: %s (%s)' % ('VALID' if ok_v else 'INVALID', reason)), indent=None)
|
|
99
|
+
return 0 if ok_v else 1
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
enclave_dispatcher = CommandDispatcher('enclave', default_action='status')
|
|
103
|
+
enclave_dispatcher.add_custom('status', _cmd_enclave_status)
|
|
104
|
+
enclave_dispatcher.add_custom('generate', _cmd_enclave_generate, parameters=(
|
|
105
|
+
Parameter(key='type', name='type', description='enclave curve', type=str, default='p256', choices=('p256', 'secp256k1')),
|
|
106
|
+
))
|
|
107
|
+
enclave_dispatcher.add_custom('sign', _cmd_enclave_sign, parameters=(
|
|
108
|
+
Parameter(key='type', name='type', description='enclave curve', type=str, default='p256', choices=('p256', 'secp256k1')),
|
|
109
|
+
Parameter(key='message', name='message', description='message string to sign or verify', type=str, default=''),
|
|
110
|
+
))
|
|
111
|
+
enclave_dispatcher.add_custom('verify', _cmd_enclave_verify, parameters=(
|
|
112
|
+
Parameter(key='message', name='message', description='message string to sign or verify', type=str, default=''),
|
|
113
|
+
Parameter(key='sig', name='sig', description='hex signature to verify', type=str, default=''),
|
|
114
|
+
Parameter(key='pub', name='pub', description='public multikey string to verify', type=str, default=''),
|
|
115
|
+
))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def cmd_enclave(a: argparse.Namespace) -> int:
|
|
119
|
+
return enclave_dispatcher.dispatch(a)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# ---- 2. Iroh QUIC Transport -------------------------------------------------
|
|
123
|
+
|
|
124
|
+
def _iroh_status(st: dict) -> None:
|
|
125
|
+
print('Iroh QUIC Engine Status:')
|
|
126
|
+
print(' Node ID: %s' % st['node_id'])
|
|
127
|
+
print(' Endpoint: %s' % st['endpoint'])
|
|
128
|
+
print(' Ticket: %s' % st['ticket'])
|
|
129
|
+
print(' ALPN: %s' % st['alpn'])
|
|
130
|
+
print(' Running: %s' % st['running'])
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _cmd_iroh_status(a: argparse.Namespace) -> int:
|
|
134
|
+
from node import keystore
|
|
135
|
+
from node.transport.iroh_rung import quic as iroh_live
|
|
136
|
+
ks = keystore.Keystore.load(a.dir)
|
|
137
|
+
host = getattr(a, 'host', '127.0.0.1') or '127.0.0.1'
|
|
138
|
+
port = getattr(a, 'port', 0) or 0
|
|
139
|
+
eng = iroh_live.IrohLiveEngine(node_id=ks.node_pk, host=host, port=port)
|
|
140
|
+
eng.start()
|
|
141
|
+
try:
|
|
142
|
+
return emit(a, eng.status(), _iroh_status)
|
|
143
|
+
finally:
|
|
144
|
+
eng.stop()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _cmd_iroh_probe(a: argparse.Namespace) -> int:
|
|
148
|
+
from node import keystore
|
|
149
|
+
from node.transport.iroh_rung import quic as iroh_live
|
|
150
|
+
ks = keystore.Keystore.load(a.dir)
|
|
151
|
+
host = getattr(a, 'host', '127.0.0.1') or '127.0.0.1'
|
|
152
|
+
port = getattr(a, 'port', 9012) or 9012
|
|
153
|
+
eng = iroh_live.IrohLiveEngine(node_id=ks.node_pk, host='127.0.0.1', port=0)
|
|
154
|
+
eng.start()
|
|
155
|
+
try:
|
|
156
|
+
res = eng.probe_nat(host, port, timeout=2.0)
|
|
157
|
+
|
|
158
|
+
def probed(_: dict) -> None:
|
|
159
|
+
if res:
|
|
160
|
+
print('NAT Probe Successful:')
|
|
161
|
+
print(' Target: %s:%d' % (host, port))
|
|
162
|
+
print(' Reflexive: %s:%d' % (res[0], res[1]))
|
|
163
|
+
else:
|
|
164
|
+
print('NAT Probe Timeout: no response from %s:%d' % (host, port))
|
|
165
|
+
emit(a, {'probed': f"{host}:{port}", 'reflexive': f"{res[0]}:{res[1]}" if res else None}, probed, indent=None)
|
|
166
|
+
return 0 if res else 1
|
|
167
|
+
finally:
|
|
168
|
+
eng.stop()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _cmd_iroh_punch(a: argparse.Namespace) -> int:
|
|
172
|
+
from node import keystore
|
|
173
|
+
from node.transport.iroh_rung import quic as iroh_live
|
|
174
|
+
ks = keystore.Keystore.load(a.dir)
|
|
175
|
+
host = getattr(a, 'host', '127.0.0.1') or '127.0.0.1'
|
|
176
|
+
port = getattr(a, 'port', 9012) or 9012
|
|
177
|
+
cnt = getattr(a, 'count', 5) or 5
|
|
178
|
+
eng = iroh_live.IrohLiveEngine(node_id=ks.node_pk, host='127.0.0.1', port=0)
|
|
179
|
+
eng.start()
|
|
180
|
+
try:
|
|
181
|
+
eng.punch_hole(host, port, count=cnt, interval=0.05)
|
|
182
|
+
return emit(a, {'punched': f"{host}:{port}", 'count': cnt},
|
|
183
|
+
lambda _: print('Punched %d UDP pinhole packets to %s:%d' % (cnt, host, port)), indent=None)
|
|
184
|
+
finally:
|
|
185
|
+
eng.stop()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _cmd_iroh_send(a: argparse.Namespace) -> int:
|
|
189
|
+
from node import keystore
|
|
190
|
+
from node.transport.iroh_rung import quic as iroh_live
|
|
191
|
+
ks = keystore.Keystore.load(a.dir)
|
|
192
|
+
target = getattr(a, 'ticket', None) or getattr(a, 'to', None)
|
|
193
|
+
if not target:
|
|
194
|
+
fail('send requires --ticket <iroh_ticket> or --to <ticket/pubkey>')
|
|
195
|
+
msg_obj = _message_arg(a)
|
|
196
|
+
|
|
197
|
+
eng = iroh_live.IrohLiveEngine(node_id=ks.node_pk, host='127.0.0.1', port=0)
|
|
198
|
+
eng.start()
|
|
199
|
+
try:
|
|
200
|
+
conn = eng.get_or_connect(target)
|
|
201
|
+
raw = json.dumps(msg_obj).encode('utf-8')
|
|
202
|
+
sid = eng.send_stream(conn, raw)
|
|
203
|
+
return emit(a, {'ok': True, 'stream_id': sid, 'peer': f"{conn.remote_addr[0]}:{conn.remote_addr[1]}"},
|
|
204
|
+
lambda _: print('Sent QUIC stream %d to %s:%d (%d bytes)' % (sid, conn.remote_addr[0], conn.remote_addr[1], len(raw))),
|
|
205
|
+
indent=None)
|
|
206
|
+
finally:
|
|
207
|
+
eng.stop()
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
iroh_dispatcher = CommandDispatcher('iroh', default_action='status')
|
|
211
|
+
iroh_dispatcher.add_custom('status', _cmd_iroh_status, parameters=(
|
|
212
|
+
Parameter(key='host', name='host', description='remote host for probe/punch', type=str, default='127.0.0.1'),
|
|
213
|
+
Parameter(key='port', name='port', description='remote port for probe/punch', type=int, default=0),
|
|
214
|
+
))
|
|
215
|
+
iroh_dispatcher.add_custom('probe', _cmd_iroh_probe, parameters=(
|
|
216
|
+
Parameter(key='host', name='host', description='remote host for probe/punch', type=str, default='127.0.0.1'),
|
|
217
|
+
Parameter(key='port', name='port', description='remote port for probe/punch', type=int, default=9012),
|
|
218
|
+
))
|
|
219
|
+
iroh_dispatcher.add_custom('punch', _cmd_iroh_punch, parameters=(
|
|
220
|
+
Parameter(key='host', name='host', description='remote host for probe/punch', type=str, default='127.0.0.1'),
|
|
221
|
+
Parameter(key='port', name='port', description='remote port for probe/punch', type=int, default=9012),
|
|
222
|
+
Parameter(key='count', name='count', description='probe/punch packet count', type=int, default=5),
|
|
223
|
+
))
|
|
224
|
+
iroh_dispatcher.add_custom('send', _cmd_iroh_send, parameters=(
|
|
225
|
+
Parameter(key='ticket', name='ticket', description='remote peer iroh ticket', type=str, default=''),
|
|
226
|
+
Parameter(key='to', name='to', description='recipient pubkey or ticket', type=str, default=''),
|
|
227
|
+
Parameter(key='message', name='message', description='message payload to transmit (JSON, @file, or raw text)', type=str, default=''),
|
|
228
|
+
))
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def cmd_iroh(a: argparse.Namespace) -> int:
|
|
232
|
+
return iroh_dispatcher.dispatch(a)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# ---- 3. FIPS Native Datagram Transport --------------------------------------
|
|
236
|
+
|
|
237
|
+
def _fips_status(info: dict) -> None:
|
|
238
|
+
print('FIPS Native Transport Status:')
|
|
239
|
+
print(' Socket Path: %s' % (info['socket_path'] or 'None'))
|
|
240
|
+
print(' Available: %s' % ('YES' if info['available'] else 'NO'))
|
|
241
|
+
print(' Connected: %s' % ('YES' if info.get('connected') else 'NO'))
|
|
242
|
+
print(' Default Port: %d' % info['default_port'])
|
|
243
|
+
if info.get('node_pk'):
|
|
244
|
+
print(' Node ID: %s' % info['node_pk'])
|
|
245
|
+
if info.get('error'):
|
|
246
|
+
print(' Error: %s' % info['error'])
|
|
247
|
+
elif not info['available']:
|
|
248
|
+
print(' Note: %s' % info['note'])
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _fips_flows(r: dict) -> None:
|
|
252
|
+
flows_info = r['flows']
|
|
253
|
+
print('Active FIPS Datagram Flows (%d):' % len(flows_info))
|
|
254
|
+
if not flows_info:
|
|
255
|
+
print(' (no active flows)')
|
|
256
|
+
else:
|
|
257
|
+
for f in flows_info:
|
|
258
|
+
print(' Flow %s [fd %d]: %s' % (f['flow'], f['fileno'], f['status']))
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _fips_context(a: argparse.Namespace) -> tuple[Any, str | None, int, Any]:
|
|
262
|
+
from node import keystore
|
|
263
|
+
from node.transport import fips_rung
|
|
264
|
+
sock_path = getattr(a, 'sock', None) or fips_rung.find_api_socket()
|
|
265
|
+
port = getattr(a, 'port', 11801) or 11801
|
|
266
|
+
ks = keystore.Keystore.load(a.dir) if hasattr(a, 'dir') and os.path.exists(os.path.join(a.dir, 'keys.json')) else None
|
|
267
|
+
return fips_rung, sock_path, port, ks
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _cmd_fips_status(a: argparse.Namespace) -> int:
|
|
271
|
+
fips_rung, sock_path, port, ks = _fips_context(a)
|
|
272
|
+
is_avail = bool(sock_path and os.path.exists(sock_path))
|
|
273
|
+
info = {
|
|
274
|
+
'socket_path': sock_path,
|
|
275
|
+
'available': is_avail,
|
|
276
|
+
'default_port': port,
|
|
277
|
+
'node_pk': ks.node_pk if ks else None,
|
|
278
|
+
'active_flows': 0,
|
|
279
|
+
'listeners': 0,
|
|
280
|
+
}
|
|
281
|
+
if is_avail:
|
|
282
|
+
try:
|
|
283
|
+
client = fips_rung.FipsNativeTransport(sock_path=sock_path)
|
|
284
|
+
client.connect()
|
|
285
|
+
info['connected'] = True
|
|
286
|
+
info['active_flows'] = len(client.open_flows)
|
|
287
|
+
info['listeners'] = len(client.listeners)
|
|
288
|
+
client.close()
|
|
289
|
+
except Exception as e:
|
|
290
|
+
info['connected'] = False
|
|
291
|
+
info['error'] = str(e)
|
|
292
|
+
else:
|
|
293
|
+
info['connected'] = False
|
|
294
|
+
info['note'] = 'FIPS control socket not found or daemon not running'
|
|
295
|
+
emit(a, info, _fips_status)
|
|
296
|
+
return 0 if info.get('connected') or info['available'] else 1
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _cmd_fips_flows(a: argparse.Namespace) -> int:
|
|
300
|
+
fips_rung, sock_path, _port, _ks = _fips_context(a)
|
|
301
|
+
sock_p = sock_path or fips_rung.find_api_socket()
|
|
302
|
+
flows_info: list[FipsFlowInfo] = []
|
|
303
|
+
if sock_p and os.path.exists(sock_p):
|
|
304
|
+
try:
|
|
305
|
+
client = fips_rung.FipsNativeTransport(sock_path=sock_p)
|
|
306
|
+
client.connect()
|
|
307
|
+
for key, sock in client.open_flows.items():
|
|
308
|
+
flows_info.append({'flow': key, 'fileno': sock.fileno(), 'status': 'open'})
|
|
309
|
+
client.close()
|
|
310
|
+
except Exception:
|
|
311
|
+
pass
|
|
312
|
+
return emit(a, {'flows': flows_info, 'count': len(flows_info)}, _fips_flows)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _cmd_fips_connect(a: argparse.Namespace) -> int:
|
|
316
|
+
fips_rung, sock_path, port, _ks = _fips_context(a)
|
|
317
|
+
peer = getattr(a, 'peer', None)
|
|
318
|
+
if not peer:
|
|
319
|
+
fail('connect requires --peer <pubkey|npub|did>')
|
|
320
|
+
peer_pk = resolve_peer_or_raw(a, peer)
|
|
321
|
+
if not sock_path:
|
|
322
|
+
fail('FIPS control socket not found; pass --sock <path>')
|
|
323
|
+
client = fips_rung.FipsNativeTransport(sock_path=sock_path)
|
|
324
|
+
client.connect()
|
|
325
|
+
try:
|
|
326
|
+
flow_sock = client.open_flow(peer_pk, port=port)
|
|
327
|
+
res = {'ok': True, 'peer': peer_pk, 'port': port, 'fileno': flow_sock.fileno()}
|
|
328
|
+
return emit(a, res, lambda _: print('Connected FSP flow to %s:%d (fd %d)' % (peer_pk[:16], port, flow_sock.fileno())))
|
|
329
|
+
finally:
|
|
330
|
+
client.close()
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _cmd_fips_listen(a: argparse.Namespace) -> int:
|
|
334
|
+
fips_rung, sock_path, port, _ks = _fips_context(a)
|
|
335
|
+
if not sock_path:
|
|
336
|
+
fail('FIPS control socket not found; pass --sock <path>')
|
|
337
|
+
client = fips_rung.FipsNativeTransport(sock_path=sock_path)
|
|
338
|
+
client.connect()
|
|
339
|
+
try:
|
|
340
|
+
list_sock = client.listen_port(port=port)
|
|
341
|
+
res = {'ok': True, 'port': port, 'fileno': list_sock.fileno(), 'status': 'listening'}
|
|
342
|
+
return emit(a, res, lambda _: print('Bound FSP listener on port %d (fd %d)' % (port, list_sock.fileno())))
|
|
343
|
+
finally:
|
|
344
|
+
client.close()
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _cmd_fips_send(a: argparse.Namespace) -> int:
|
|
348
|
+
fips_rung, sock_path, port, ks = _fips_context(a)
|
|
349
|
+
peer = getattr(a, 'peer', None)
|
|
350
|
+
if not peer:
|
|
351
|
+
fail('send requires --peer <pubkey|npub|did>')
|
|
352
|
+
peer_pk = resolve_peer_or_raw(a, peer)
|
|
353
|
+
if not sock_path:
|
|
354
|
+
fail('FIPS control socket not found; pass --sock <path>')
|
|
355
|
+
msg_obj = dict(_message_arg(a))
|
|
356
|
+
msg_obj.setdefault('from', ks.did if ks else f'did:nostr:{peer_pk}')
|
|
357
|
+
msg_obj.setdefault('to', [f'did:nostr:{peer_pk}'])
|
|
358
|
+
msg_obj.setdefault('created_time', int(time.time()))
|
|
359
|
+
raw = json.dumps(msg_obj, separators=(',', ':'), sort_keys=True).encode('utf-8')
|
|
360
|
+
|
|
361
|
+
client = fips_rung.FipsNativeTransport(sock_path=sock_path)
|
|
362
|
+
client.connect()
|
|
363
|
+
try:
|
|
364
|
+
flow_sock = client.open_flow(peer_pk, port=port)
|
|
365
|
+
n_sent = client.send_framed(
|
|
366
|
+
flow_sock,
|
|
367
|
+
payload=raw,
|
|
368
|
+
flags=fips_rung.FLAG_DIDCOMM | fips_rung.FLAG_FINAL,
|
|
369
|
+
)
|
|
370
|
+
res = {'ok': True, 'peer': peer_pk, 'port': port, 'bytes_sent': n_sent}
|
|
371
|
+
time.sleep(0.02)
|
|
372
|
+
return emit(a, res, lambda _: print('Sent %d bytes over FSP flow to %s:%d' % (n_sent, peer_pk[:16], port)))
|
|
373
|
+
finally:
|
|
374
|
+
client.close()
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _cmd_fips_probe(a: argparse.Namespace) -> int:
|
|
378
|
+
fips_rung, sock_path, port, _ks = _fips_context(a)
|
|
379
|
+
t0 = time.time()
|
|
380
|
+
if not sock_path or not os.path.exists(sock_path):
|
|
381
|
+
emit(a, {'ok': False, 'error': f'control socket not found at {sock_path}'},
|
|
382
|
+
lambda _: print('FIPS Probe FAILED: control socket not accessible at %s' % sock_path))
|
|
383
|
+
return 1
|
|
384
|
+
try:
|
|
385
|
+
client = fips_rung.FipsNativeTransport(sock_path=sock_path)
|
|
386
|
+
client.connect()
|
|
387
|
+
rtt_ms = round((time.time() - t0) * 1000, 2)
|
|
388
|
+
|
|
389
|
+
def probed(_: dict) -> None:
|
|
390
|
+
print('FIPS Probe SUCCESSFUL:')
|
|
391
|
+
print(' Control Socket: %s' % sock_path)
|
|
392
|
+
print(' Probe RTT: %.2f ms' % rtt_ms)
|
|
393
|
+
print(' Status: ready')
|
|
394
|
+
emit(a, {'ok': True, 'rtt_ms': rtt_ms, 'sock': sock_path, 'port': port}, probed)
|
|
395
|
+
client.close()
|
|
396
|
+
return 0
|
|
397
|
+
except Exception as e:
|
|
398
|
+
err = str(e)
|
|
399
|
+
emit(a, {'ok': False, 'error': err}, lambda _: print('FIPS Probe FAILED: %s' % err))
|
|
400
|
+
return 1
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
fips_dispatcher = CommandDispatcher('fips', default_action='status')
|
|
404
|
+
fips_dispatcher.add_custom('status', _cmd_fips_status, parameters=(
|
|
405
|
+
Parameter(key='sock', name='sock', description='path to FIPS Unix control socket', type=str, default=''),
|
|
406
|
+
Parameter(key='port', name='port', description='FSP service port (default: 11801)', type=int, default=11801),
|
|
407
|
+
))
|
|
408
|
+
fips_dispatcher.add_custom('flows', _cmd_fips_flows, parameters=(
|
|
409
|
+
Parameter(key='sock', name='sock', description='path to FIPS Unix control socket', type=str, default=''),
|
|
410
|
+
))
|
|
411
|
+
fips_dispatcher.add_custom('connect', _cmd_fips_connect, parameters=(
|
|
412
|
+
Parameter(key='sock', name='sock', description='path to FIPS Unix control socket', type=str, default=''),
|
|
413
|
+
Parameter(key='port', name='port', description='FSP service port (default: 11801)', type=int, default=11801),
|
|
414
|
+
Parameter(key='peer', name='peer', description='peer pubkey, npub, or did:nostr', type=str, default=''),
|
|
415
|
+
))
|
|
416
|
+
fips_dispatcher.add_custom('listen', _cmd_fips_listen, parameters=(
|
|
417
|
+
Parameter(key='sock', name='sock', description='path to FIPS Unix control socket', type=str, default=''),
|
|
418
|
+
Parameter(key='port', name='port', description='FSP service port (default: 11801)', type=int, default=11801),
|
|
419
|
+
))
|
|
420
|
+
fips_dispatcher.add_custom('send', _cmd_fips_send, parameters=(
|
|
421
|
+
Parameter(key='sock', name='sock', description='path to FIPS Unix control socket', type=str, default=''),
|
|
422
|
+
Parameter(key='port', name='port', description='FSP service port (default: 11801)', type=int, default=11801),
|
|
423
|
+
Parameter(key='peer', name='peer', description='peer pubkey, npub, or did:nostr', type=str, default=''),
|
|
424
|
+
Parameter(key='message', name='message', description='message payload to transmit (JSON, @file, or raw text)', type=str, default=''),
|
|
425
|
+
))
|
|
426
|
+
fips_dispatcher.add_custom('probe', _cmd_fips_probe, parameters=(
|
|
427
|
+
Parameter(key='sock', name='sock', description='path to FIPS Unix control socket', type=str, default=''),
|
|
428
|
+
Parameter(key='port', name='port', description='FSP service port (default: 11801)', type=int, default=11801),
|
|
429
|
+
Parameter(key='timeout', name='timeout', description='timeout in seconds', type=float, default=5.0),
|
|
430
|
+
))
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def cmd_fips(a: argparse.Namespace) -> int:
|
|
434
|
+
return fips_dispatcher.dispatch(a)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
# ---- 4. Provider Registration -----------------------------------------------
|
|
438
|
+
|
|
439
|
+
class TransportCommandProvider:
|
|
440
|
+
"""Registers transport diagnostic and control commands."""
|
|
441
|
+
|
|
442
|
+
@classmethod
|
|
443
|
+
def register(cls, sub: argparse._SubParsersAction) -> None:
|
|
444
|
+
en = add_command(sub, 'enclave', cmd_enclave, 'inspect and interact with hardware enclaves and passkeys', js=True)
|
|
445
|
+
enclave_dispatcher.register_arguments(en, action_choices=('status', 'generate', 'sign', 'verify'))
|
|
446
|
+
|
|
447
|
+
ir = add_command(sub, 'iroh', cmd_iroh, 'iroh P2P QUIC transport diagnostics and control', js=True)
|
|
448
|
+
iroh_dispatcher.register_arguments(ir, action_choices=('status', 'probe', 'punch', 'send'))
|
|
449
|
+
|
|
450
|
+
fp = add_command(sub, 'fips', cmd_fips, 'FIPS Native Datagram API transport diagnostics and control', js=True)
|
|
451
|
+
fips_dispatcher.register_arguments(fp, action_choices=('status', 'flows', 'connect', 'listen', 'send', 'probe'))
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""video: live CMAF / LL-HLS video streams, Transit Reflector handovers, and barter settlement."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
from typing import Any, Mapping
|
|
6
|
+
|
|
7
|
+
from node.planes.services.video.compartment import VideoTransitCompartment
|
|
8
|
+
from .adapter import CommandDispatcher
|
|
9
|
+
from .base import add_command
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _show_status(r: Mapping[str, Any]) -> None:
|
|
13
|
+
print(
|
|
14
|
+
'video streaming: %d active stream(s), %d reflector session(s)'
|
|
15
|
+
% (int(r.get('active_streams', 0)), len(r.get('reflector_sessions') or {}))
|
|
16
|
+
)
|
|
17
|
+
for s in r.get('streams') or []:
|
|
18
|
+
print(
|
|
19
|
+
' stream %-24s segments=%d handover=%s'
|
|
20
|
+
% (
|
|
21
|
+
s.get('stream_id', ''),
|
|
22
|
+
int(s.get('segments_count', 0)),
|
|
23
|
+
'engaged' if s.get('handover_active') else 'origin',
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
for d in r.get('debts') or []:
|
|
27
|
+
state_str = 'audio-only' if d.get('degraded_audio_only') else 'full'
|
|
28
|
+
print(
|
|
29
|
+
' peer %-18s unsettled=%d B barter=%d B state=%s'
|
|
30
|
+
% (
|
|
31
|
+
str(d.get('debtor') or d.get('peer') or '')[:16],
|
|
32
|
+
int(d.get('unsettled_bytes', 0)),
|
|
33
|
+
int(d.get('bartered_bytes', 0)),
|
|
34
|
+
state_str,
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _show_started(r: Mapping[str, Any]) -> None:
|
|
40
|
+
print('started stream %s (%s)' % (r.get('stream_id', ''), r.get('title', '')))
|
|
41
|
+
print(' genesis op: %s (seq %s)' % (r.get('genesis_op_id', ''), r.get('seq_num', 0)))
|
|
42
|
+
print(' announced: %s' % ', '.join(r.get('announcements') or []))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _show_settled(r: Mapping[str, Any]) -> None:
|
|
46
|
+
debt = r.get('debt_after') or {}
|
|
47
|
+
state_str = 'audio-only' if debt.get('degraded_audio_only') else 'full'
|
|
48
|
+
print(
|
|
49
|
+
'credited %d transit bytes for %s via %s (%s units) -> state=%s'
|
|
50
|
+
% (
|
|
51
|
+
int(r.get('credited_bytes', 0)),
|
|
52
|
+
str(r.get('peer', ''))[:16],
|
|
53
|
+
r.get('utility_kind', ''),
|
|
54
|
+
r.get('units', 0),
|
|
55
|
+
state_str,
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _show_solicited(r: Mapping[str, Any]) -> None:
|
|
61
|
+
sol = r.get('solicitation') or {}
|
|
62
|
+
print(
|
|
63
|
+
'transit solicitation for %s (mode=%s, sent=%s)'
|
|
64
|
+
% (sol.get('stream_id', ''), sol.get('mode', ''), bool(r.get('sent')))
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
_cmd_map = {c.key: c for c in VideoTransitCompartment.COMMANDS}
|
|
69
|
+
dispatcher = CommandDispatcher('video', default_action='status')
|
|
70
|
+
dispatcher.add_command('status', _cmd_map['status'], human=_show_status, timeout=10)
|
|
71
|
+
dispatcher.add_command('start', _cmd_map['start_stream'], human=lambda r, _a: _show_started(r), timeout=15)
|
|
72
|
+
dispatcher.add_command('settle', _cmd_map['settle'], human=lambda r, _a: _show_settled(r), timeout=15)
|
|
73
|
+
dispatcher.add_command('solicit', _cmd_map['solicit_transit'], human=lambda r, _a: _show_solicited(r), timeout=15)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def cmd_video(a: argparse.Namespace) -> int:
|
|
77
|
+
return dispatcher.dispatch(a)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class VideoCommandProvider:
|
|
81
|
+
"""Registers the video streaming and transit reflector CLI commands."""
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def register(cls, sub: argparse._SubParsersAction) -> None:
|
|
85
|
+
p = add_command(
|
|
86
|
+
sub,
|
|
87
|
+
'stream',
|
|
88
|
+
cmd_video,
|
|
89
|
+
'live CMAF/LL-HLS video streams, Transit Reflector handovers, and barter settlement',
|
|
90
|
+
js=True,
|
|
91
|
+
)
|
|
92
|
+
dispatcher.register_arguments(p, action_choices=('status', 'start', 'settle', 'solicit'))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
__all__ = [
|
|
96
|
+
'VideoCommandProvider',
|
|
97
|
+
'cmd_video',
|
|
98
|
+
'dispatcher',
|
|
99
|
+
]
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""The contract every mechanism in this node keeps, so that it can be watched.
|
|
2
|
+
|
|
3
|
+
A mechanism used to be wired in eight places: constructed in `NodeApp.start`,
|
|
4
|
+
hooked into `_tick`, given a key in `health`, a branch in the control dispatch,
|
|
5
|
+
a row in the control table, a function in the CLI, a registration beside it, and
|
|
6
|
+
a line in the README. Eight chances to forget one, and forgetting is silent.
|
|
7
|
+
|
|
8
|
+
There was a ninth place that did not exist, and its absence cost a night. When
|
|
9
|
+
`on_receipt` refused every receipt on a four-node mesh because the two ends
|
|
10
|
+
named different grants, it recorded nothing at all: every other divergent branch
|
|
11
|
+
counted a reason and that one did not, so an operator saw a window with no claim
|
|
12
|
+
and no explanation for it. A refusal that is not recorded is indistinguishable
|
|
13
|
+
from silence, and silence is the one thing this protocol is least able to read.
|
|
14
|
+
|
|
15
|
+
So a subsystem here declares what it is rather than being wired into what it is
|
|
16
|
+
part of:
|
|
17
|
+
|
|
18
|
+
name what it is called, on the wire of the control socket and the CLI
|
|
19
|
+
reads/live the config keys it reads, and which of those it re-reads on a tick
|
|
20
|
+
commands its own control surface, dispatched generically
|
|
21
|
+
tick its periodic work, returning what it did
|
|
22
|
+
state what it is, for monitoring
|
|
23
|
+
trouble what an operator should be told without asking
|
|
24
|
+
refusals every no it said, with the reason and enough detail to act
|
|
25
|
+
|
|
26
|
+
`Refusals` is the part that is new. Everything else already existed by
|
|
27
|
+
convention in one subsystem or another; this makes the convention checkable.
|
|
28
|
+
|
|
29
|
+
A package since the sixth surface (STORES) arrived: the abstract base is in
|
|
30
|
+
base.py where it can be read on its own, and the parts it is assembled from
|
|
31
|
+
each have a file -
|
|
32
|
+
|
|
33
|
+
base.py Compartment, the abstract base, and CompartmentProtocol
|
|
34
|
+
command.py Command, BoundCommand: what a compartment can be asked
|
|
35
|
+
refusals.py Refusals: every no it said
|
|
36
|
+
registry.py Registry: the node's compartments, and the verbs over them
|
|
37
|
+
|
|
38
|
+
Everything is re-exported here, so `from node.compartment import X` and
|
|
39
|
+
`compartment_mod.X` read as they always did.
|
|
40
|
+
"""
|
|
41
|
+
from __future__ import annotations
|
|
42
|
+
|
|
43
|
+
from .base import Compartment, CompartmentProtocol
|
|
44
|
+
from .command import Alias, BoundCommand, Command, CommandError, CommandRecord, declared, every_command
|
|
45
|
+
from .every import Every
|
|
46
|
+
from .handler import Handler
|
|
47
|
+
from .need import Need
|
|
48
|
+
from .problem import (
|
|
49
|
+
DuplicateProblem,
|
|
50
|
+
IncompatibleProblem,
|
|
51
|
+
InvalidIdentifierProblem,
|
|
52
|
+
Note,
|
|
53
|
+
NoteList,
|
|
54
|
+
Problem,
|
|
55
|
+
Problems,
|
|
56
|
+
ReferenceProblem,
|
|
57
|
+
ShadowingProblem,
|
|
58
|
+
Trouble,
|
|
59
|
+
TroubleList,
|
|
60
|
+
TypeMismatchProblem,
|
|
61
|
+
)
|
|
62
|
+
from .refusals import Refusal, Refusals, why_detail
|
|
63
|
+
from .registry import Registry, T
|
|
64
|
+
from .schema import Column, Index, Migration, SchemaEntry, Table
|
|
65
|
+
from .wire import Outbound, WireClient, WireProtocol
|
|
66
|
+
from .worker import Worker
|
|
67
|
+
from .context import NodeContext, NodeStores
|
|
68
|
+
from . import context, lifecycle, sidecar, stores
|
|
69
|
+
from .lifecycle import (
|
|
70
|
+
CONFIGURED,
|
|
71
|
+
DEGRADED,
|
|
72
|
+
DRAINING,
|
|
73
|
+
FAILED,
|
|
74
|
+
QUARANTINED,
|
|
75
|
+
RETIRED,
|
|
76
|
+
RUNNING,
|
|
77
|
+
STATES,
|
|
78
|
+
WIRED,
|
|
79
|
+
Breaker,
|
|
80
|
+
Lifecycle,
|
|
81
|
+
Tracker,
|
|
82
|
+
Transition,
|
|
83
|
+
)
|
|
84
|
+
from .sidecar import Bridge, Sidecar, SidecarError, jsonline_call
|
|
85
|
+
from .stores import (
|
|
86
|
+
COUNTERS,
|
|
87
|
+
LRU,
|
|
88
|
+
MEMORY,
|
|
89
|
+
OLDEST,
|
|
90
|
+
REFUSE,
|
|
91
|
+
RING,
|
|
92
|
+
SET,
|
|
93
|
+
SNAPSHOT,
|
|
94
|
+
TABLE,
|
|
95
|
+
Counters,
|
|
96
|
+
Holdings,
|
|
97
|
+
Members,
|
|
98
|
+
Ring,
|
|
99
|
+
Store,
|
|
100
|
+
StoreFull,
|
|
101
|
+
Table as StoreTable,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
__all__ = [
|
|
105
|
+
'Alias', 'BoundCommand', 'Command', 'CommandError', 'CommandRecord', 'Compartment', 'CompartmentProtocol', 'Every',
|
|
106
|
+
'Handler', 'Need', 'Refusal', 'Refusals', 'Registry', 'T', 'Worker', 'declared', 'every_command', 'why_detail',
|
|
107
|
+
'Column', 'Index', 'Table', 'Migration', 'SchemaEntry',
|
|
108
|
+
'Outbound', 'WireClient', 'WireProtocol',
|
|
109
|
+
'Problem', 'Problems', 'DuplicateProblem', 'InvalidIdentifierProblem', 'ReferenceProblem',
|
|
110
|
+
'TypeMismatchProblem', 'ShadowingProblem', 'IncompatibleProblem', 'Trouble', 'TroubleList',
|
|
111
|
+
'Note', 'NoteList',
|
|
112
|
+
'context', 'NodeContext', 'NodeStores',
|
|
113
|
+
'lifecycle', 'stores', 'sidecar',
|
|
114
|
+
'Breaker', 'Lifecycle', 'Tracker', 'Transition',
|
|
115
|
+
'CONFIGURED', 'DEGRADED', 'DRAINING', 'FAILED', 'QUARANTINED', 'RETIRED', 'RUNNING', 'STATES', 'WIRED',
|
|
116
|
+
'Bridge', 'Sidecar', 'SidecarError', 'jsonline_call',
|
|
117
|
+
'Store', 'Ring', 'Members', 'Counters', 'Holdings', 'StoreFull', 'StoreTable',
|
|
118
|
+
'RING', 'TABLE', 'SET', 'COUNTERS', 'OLDEST', 'LRU', 'REFUSE', 'MEMORY', 'SNAPSHOT',
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|