feat: wire traffic resistance, implement v2 CLI commands, add auth expiry detection

Server:
- Wire traffic resistance decoy generator into main.rs startup behind
  --traffic-resistance flag + --decoy-interval-ms config (feature-gated)

Client:
- Implement v2 CLI one-shot commands: send, recv, dm, group create, group invite
  All previously printed "coming soon" — now fully functional with MLS state
  restoration, peer resolution, KeyPackage fetch, and MLS encryption pipeline

SDK:
- Add SdkError::SessionExpired variant + is_auth_expired() helper for
  detecting expired session tokens (RpcStatus::Unauthorized)
- Add ClientEvent::AuthExpired for UI-layer session expiry notification
This commit is contained in:
2026-04-05 00:03:12 +02:00
parent f58ce2529d
commit a856f9bb53
4 changed files with 175 additions and 21 deletions

View File

@@ -179,6 +179,15 @@ struct Args {
/// Storage/database operation timeout in seconds (default: 10).
#[arg(long, env = "QPQ_STORAGE_TIMEOUT", default_value_t = config::DEFAULT_STORAGE_TIMEOUT_SECS)]
storage_timeout: u64,
/// Enable traffic analysis resistance (decoy traffic + timing jitter).
/// Requires --features traffic-resistance.
#[arg(long, env = "QPQ_TRAFFIC_RESISTANCE", default_value_t = false)]
traffic_resistance: bool,
/// Mean interval in milliseconds between decoy messages (default: 5000).
#[arg(long, env = "QPQ_DECOY_INTERVAL_MS", default_value_t = 5000)]
decoy_interval_ms: u64,
}
// ── In-flight RPC guard ──────────────────────────────────────────────────────
@@ -646,6 +655,40 @@ async fn main() -> anyhow::Result<()> {
"effective timeouts and listeners"
);
// ── Traffic resistance (decoy traffic generator) ──────────────────────────
#[cfg(feature = "traffic-resistance")]
let _decoy_handle = {
if args.traffic_resistance {
let shutdown_notify = Arc::new(tokio::sync::Notify::new());
let delivery_svc = Arc::new(domain::delivery::DeliveryService {
store: Arc::clone(&store),
waiters: Arc::clone(&waiters),
});
let config = domain::traffic_resistance::TrafficResistanceConfig {
decoy_interval_ms: args.decoy_interval_ms,
..Default::default()
};
tracing::info!(
decoy_interval_ms = config.decoy_interval_ms,
jitter_max_ms = config.jitter_max_ms,
padding_boundary = config.padding_boundary,
"traffic resistance enabled — decoy generator started"
);
// Start with an empty recipient list; decoys will be a no-op until
// recipients are populated. A future enhancement can dynamically
// update the list from connected sessions.
Some(domain::traffic_resistance::spawn_decoy_generator(
delivery_svc,
Vec::new(),
b"decoy-channel".to_vec(),
config,
shutdown_notify,
))
} else {
None
}
};
// In-flight RPC counter for graceful drain on shutdown.
let in_flight: Arc<AtomicUsize> = Arc::new(AtomicUsize::new(0));