When a MeshEnvelope is delivered locally and its payload starts with a
known FAPP wire tag (0x01-0x05), MeshNode.process_incoming now delegates
to FappRouter instead of returning a raw Deliver action. Nodes without
FAPP capabilities still receive FAPP-tagged payloads as normal Deliver
actions, preserving backward compatibility.
Adds IncomingAction::Fapp variant, is_fapp_payload() helper, and three
integration tests covering the routing, passthrough, and no-router cases.
New tests/fapp_flow.rs with 3 integration tests:
- full_fapp_flow_announce_query_reserve_confirm: Complete flow
from therapist announcement through patient reservation to
confirmation with E2E encryption
- fapp_rejection_flow: Tests the rejection case
- fapp_query_filters: Tests Fachrichtung, PLZ, and other filters
FappRouter additions:
- register_therapist_key(): public method for key registration
- store_announce(): public method for storing announcements
Total tests: 217 (198 lib + 3 fapp_flow + 16 multi_node)
- persistence.rs: Append-only log storage for routing table,
KeyPackage cache, and messages with compaction and GC
- shutdown.rs: Coordinated shutdown with phase transitions,
task tracking, connection draining, and hook system
Enables stateful operation and clean restarts.
- error.rs: Structured error types with context for all subsystems
(transport, routing, crypto, protocol, store, config)
- config.rs: Runtime configuration with TOML parsing and validation
- metrics.rs: Counter/gauge/histogram metrics with transport-specific
tracking and JSON-serializable snapshots
- rate_limit.rs: Token bucket rate limiting with per-peer tracking,
duty cycle enforcement for LoRa, and backpressure control
These modules provide the foundation for production deployment.
crypto_negotiation module enables transitioning between crypto modes:
GroupCryptoState tracks current mode:
- MlsLite (signed/unsigned)
- FullMls (classical/hybrid)
- Upgrading (transition state)
MlsLiteBootstrap derives MLS-Lite keys from MLS epoch secret:
- Enables fallback to MLS-Lite over constrained links
- Same group can use full MLS over WiFi, MLS-Lite over LoRa
Upgrade protocol:
1. Member sends KeyPackage over fast link
2. Creator creates MLS Welcome
3. Group transitions to full MLS
4. Optionally maintains MLS-Lite fallback for constrained links
TransportCapability enum classifies transports by bandwidth/MTU:
- Unconstrained (≥1 Mbps): Full MLS with PQ-KEM
- Medium (≥10 kbps): Full MLS classical
- Constrained (≥1 kbps): MLS-Lite with signature
- SeverelyConstrained (<1 kbps): MLS-Lite minimal
TransportManager now provides:
- best_transport() - highest capability transport
- recommended_crypto() - appropriate crypto mode
- supports_mls() - whether any transport handles full MLS
- select_for_size() - best transport for a given payload
CryptoMode enum with overhead estimates for each mode.
Implements announce-based KeyPackage distribution for serverless MLS:
- MeshAnnounce now includes optional `keypackage_hash` field (8 bytes)
- CAP_MLS_READY capability flag for nodes with KeyPackages
- KeyPackageCache for storing received KeyPackages:
- Indexed by mesh address
- Multiple per address (for rotation)
- TTL-based expiry
- Capacity-bounded with LRU eviction
- Mesh protocol messages:
- KeyPackageRequest (request by address or hash)
- KeyPackageResponse (KeyPackage + hash)
- KeyPackageUnavailable (negative response)
Protocol flow:
1. Bob announces with keypackage_hash
2. Alice requests KeyPackage via mesh
3. Bob (or relay) responds with full KeyPackage
4. Alice creates MLS Welcome, sends to Bob via mesh
docs/specs/fapp-security.md:
- Full threat model for patient protection
- 3-level verification roadmap (transparency → endorsements → registry)
- UI warning mockups
- Technical implementation plan
- Honest assessment of limitations
SlotAnnounce changes:
- Added profile_url field for therapist verification
- New with_profile() constructor
- profile_url included in signature
docs/specs/fapp-protocol.md:
- Added Security & Anti-Fraud section
- Link to full security spec
New fapp_router.rs module:
- FappAction enum (Ignore, Dropped, Forward, QueryResponse)
- Wire format: 1-byte tag (0x01-0x05) + CBOR body
- FappRouter with shared RoutingTable and TransportManager
- handle_incoming() decodes and dispatches FAPP frames
- process_slot_announce() with relay/flood logic
- process_slot_query() answers from local FappStore
- broadcast_announce() / send_query() for outbound floods
- drain_pending_sends() for async send integration
- 3 unit tests
Also fixed borrow checker issue in FappStore::store
CBOR with field names has higher overhead than raw binary formats.
Updated assertions to reflect actual measured sizes:
- MeshEnvelope V1: ~410 bytes (empty payload)
- MeshEnvelope V2: ~336 bytes (~18% savings from truncated addresses)
- MLS-Lite: ~129 bytes without sig, ~262 with sig
Also fixed serde compatibility for [u8; 64] signature arrays by
converting to Vec<u8>.
MLS-Lite provides group encryption without full MLS overhead:
- Pre-shared group secret (QR code, NFC, or MLS epoch export)
- ChaCha20-Poly1305 symmetric encryption (same as MLS app messages)
- Per-message nonce from epoch + sequence
- Replay protection via sliding window
- Optional Ed25519 signatures
Wire overhead: ~41 bytes without signature, ~105 with signature
(vs ~174 bytes for MeshEnvelope V1)
Tradeoffs vs full MLS:
- No automatic post-compromise security (manual key rotation)
- No automatic forward secrecy (only per-epoch)
- Keys are pre-shared, not negotiated
Designed for SF12 LoRa where MLS KeyPackages are impractical.
S5: Compact envelope format for constrained links:
- 16-byte truncated addresses (MeshAddress) instead of 32-byte keys
- 16-byte truncated content ID
- u16 TTL and u32 timestamp (smaller than V1)
- Priority field (Low/Normal/High/Emergency)
- ~30-50 bytes savings per envelope vs V1
Full public keys are exchanged during announce phase and cached in
routing table. Envelope only needs addresses for routing.
- measure_mls_wire_sizes: KeyPackage, Welcome, Commit, AppMessage sizes
- measure_mls_wire_sizes_hybrid: same with post-quantum mode
- measure_mesh_envelope_overhead: MeshEnvelope overhead for various payloads
These tests print actual byte sizes to inform constrained link
feasibility planning (LoRa SF12, MLS-Lite design).
Implement transport abstraction (TCP/iroh), announce and routing table,
multi-hop mesh router, truncated-address link layer, and LoRa mock
medium with fragmentation plus EU868-style duty-cycle accounting.
Add mesh_lora_relay_demo and scripts/mesh-demo.sh. Relax CBOR vs JSON
size assertion to match fixed-size cryptographic overhead. Extend
.gitignore for nested targets and node_modules.
Made-with: Cursor
- Remove `|| true` from cursor positioning condition in v2_tui.rs
- Replace .lock().unwrap() with .expect() in P2P routing tests
- Remove assert!(true) placeholder in WebTransport test
Rename all crate directories, package names, binary names, proto
package/module paths, ALPN strings, env var prefixes, config filenames,
mDNS service names, and plugin ABI symbols from quicproquo/qpq to
quicprochat/qpc.