Skip to main content

hashtree_cli/
fips_transport.rs

1//! Hashtree daemon integration for the embedded FIPS endpoint API.
2//!
3//! This starts `fips_core::FipsEndpoint` inside the htree process; it does not
4//! depend on or talk to an external FIPS daemon.
5
6use crate::config::Config;
7use crate::storage::{HashtreeStore, StorageRouter};
8use anyhow::{Context, Result};
9use hashtree_fips_transport::{
10    bind_fips_endpoint, FipsEndpointOptions, HashtreeFipsTransport, DEFAULT_FIPS_DISCOVERY_SCOPE,
11};
12use nostr::nips::nip19::ToBech32;
13use std::sync::Arc;
14use std::time::Duration;
15use tokio::task::JoinHandle;
16
17pub type DaemonFipsTransport = HashtreeFipsTransport<StorageRouter>;
18
19pub struct DaemonFipsHandle {
20    pub transport: Arc<DaemonFipsTransport>,
21    pub endpoint_npub: String,
22    pub discovery_scope: String,
23    receiver_task: JoinHandle<()>,
24}
25
26impl DaemonFipsHandle {
27    pub fn shutdown(&self) {
28        self.receiver_task.abort();
29    }
30}
31
32pub async fn start_daemon_fips_transport(
33    config: &Config,
34    keys: &nostr::Keys,
35    store: Arc<HashtreeStore>,
36) -> Result<Option<DaemonFipsHandle>> {
37    if !config.server.enable_fips || !config.server.mode.hash_get_enabled() {
38        return Ok(None);
39    }
40
41    let active_relays = config.nostr.active_relays();
42    let relays = config.server.resolved_fips_relays(&active_relays);
43    let discovery_scope = normalized_discovery_scope(&config.server.fips_discovery_scope);
44    let identity_nsec = keys
45        .secret_key()
46        .to_bech32()
47        .context("Failed to encode daemon identity for FIPS endpoint")?;
48    let mut options = FipsEndpointOptions::new(identity_nsec);
49    options.discovery_scope = discovery_scope.clone();
50    options.relays = relays;
51    options.enable_udp = config.server.enable_fips_udp;
52    options.enable_webrtc = config.server.enable_fips_webrtc;
53    options.udp_bind_addr = config.server.fips_udp_bind_addr.clone();
54    options.udp_public = config.server.fips_udp_public;
55    options.udp_external_addr = config.server.fips_udp_external_addr.clone();
56    options.webrtc_auto_connect = false;
57    options.webrtc_max_connections = hashtree_fips_transport::DEFAULT_FIPS_WEBRTC_MAX_CONNECTIONS;
58    options.open_discovery_max_pending = 0;
59    options.packet_channel_capacity = 1024;
60    let endpoint = bind_fips_endpoint(options)
61        .await
62        .context("Failed to start FIPS endpoint")?;
63
64    let request_timeout = Duration::from_millis(config.server.fips_request_timeout_ms.max(1));
65    let transport = Arc::new(
66        HashtreeFipsTransport::new(endpoint.endpoint, store.store_arc())
67            .with_request_timeout(request_timeout)
68            .with_cache_responses(false),
69    );
70    let receiver_task = transport.start();
71
72    Ok(Some(DaemonFipsHandle {
73        transport,
74        endpoint_npub: endpoint.local_peer_id,
75        discovery_scope: endpoint.discovery_scope,
76        receiver_task,
77    }))
78}
79
80fn normalized_discovery_scope(scope: &str) -> String {
81    let scope = scope.trim();
82    if scope.is_empty() {
83        DEFAULT_FIPS_DISCOVERY_SCOPE.to_string()
84    } else {
85        scope.to_string()
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn empty_discovery_scope_uses_hashtree_default() {
95        assert_eq!(
96            normalized_discovery_scope("  "),
97            DEFAULT_FIPS_DISCOVERY_SCOPE.to_string()
98        );
99    }
100}