common/peer/
peer_builder.rs

1use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
2use std::sync::Arc;
3
4use iroh::discovery::pkarr::dht::DhtDiscovery;
5use iroh::Endpoint;
6
7pub use super::blobs_store::BlobsStore;
8
9use crate::bucket_log::BucketLogProvider;
10use crate::crypto::SecretKey;
11
12use super::peer_inner::Peer;
13use super::sync::SyncProvider;
14
15/// Marker type for builder state: needs sync provider to be configured
16pub struct NeedsSyncProvider;
17
18/// Marker type for builder state: ready to build
19pub struct ReadyToBuild;
20
21/// Peer builder with typestate pattern for compile-time enforcement
22///
23/// The builder enforces that a sync provider is configured before building.
24#[derive(Clone)]
25pub struct PeerBuilder<L: BucketLogProvider, State = ReadyToBuild> {
26    /// the socket addr to expose the peer on
27    ///  if not set, an ephemeral port will be used
28    socket_address: Option<SocketAddr>,
29    /// the identity of the peer, as a SecretKey
30    secret_key: Option<SecretKey>,
31    /// pre-loaded blobs store (if provided, blobs_store_path is ignored)
32    blobs_store: Option<BlobsStore>,
33    log_provider: Option<L>,
34    /// Sync provider implementation (trait object for flexibility)
35    sync_provider: Option<Arc<dyn SyncProvider<L>>>,
36    /// State marker (zero-sized type for compile-time guarantees)
37    _state: std::marker::PhantomData<State>,
38}
39
40// Common builder methods available in all states
41impl<L: BucketLogProvider, State> PeerBuilder<L, State> {
42    pub fn socket_address(mut self, socket_addr: SocketAddr) -> Self {
43        self.socket_address = Some(socket_addr);
44        self
45    }
46
47    pub fn secret_key(mut self, secret_key: SecretKey) -> Self {
48        self.secret_key = Some(secret_key);
49        self
50    }
51
52    pub fn blobs_store(mut self, blobs: BlobsStore) -> Self {
53        self.blobs_store = Some(blobs);
54        self
55    }
56
57    pub fn log_provider(mut self, log_provider: L) -> Self {
58        self.log_provider = Some(log_provider);
59        self
60    }
61}
62
63// Initial construction - starts in NeedsSyncProvider state for explicit configuration
64impl<L: BucketLogProvider> Default for PeerBuilder<L, NeedsSyncProvider> {
65    fn default() -> Self {
66        Self::new()
67    }
68}
69
70impl<L: BucketLogProvider> PeerBuilder<L, NeedsSyncProvider> {
71    /// Create a new peer builder
72    ///
73    /// You must call either `with_queued_sync()` or `with_sync_provider()` before building.
74    pub fn new() -> Self {
75        PeerBuilder {
76            socket_address: None,
77            secret_key: None,
78            blobs_store: None,
79            log_provider: None,
80            sync_provider: None,
81            _state: std::marker::PhantomData,
82        }
83    }
84
85    /// Configure with a custom SyncProvider implementation
86    ///
87    /// This allows injecting custom sync providers for testing or alternative implementations.
88    pub fn with_sync_provider(
89        mut self,
90        provider: Arc<dyn SyncProvider<L>>,
91    ) -> PeerBuilder<L, ReadyToBuild> {
92        self.sync_provider = Some(provider);
93
94        PeerBuilder {
95            socket_address: self.socket_address,
96            secret_key: self.secret_key,
97            blobs_store: self.blobs_store,
98            log_provider: self.log_provider,
99            sync_provider: self.sync_provider,
100            _state: std::marker::PhantomData,
101        }
102    }
103}
104
105// Build only available in ReadyToBuild state
106impl<L: BucketLogProvider> PeerBuilder<L, ReadyToBuild> {
107    pub async fn build(self) -> Peer<L> {
108        // set the socket port to unspecified if not set
109        let socket_addr = self
110            .socket_address
111            .unwrap_or_else(|| SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0));
112        // generate a new secret key if not set
113        let secret_key = self.secret_key.unwrap_or_else(SecretKey::generate);
114
115        // get the blobs store, if not set use in memory
116        let blobs_store = match self.blobs_store {
117            Some(blobs) => blobs,
118            None => BlobsStore::memory().await.unwrap(),
119        };
120
121        // setup our discovery mechanism for our peer
122        let mainline_discovery = DhtDiscovery::builder()
123            .secret_key(secret_key.0.clone())
124            .build()
125            .expect("failed to build mainline discovery");
126
127        // Convert the SocketAddr to a SocketAddrV4
128        let addr = SocketAddrV4::new(
129            socket_addr
130                .ip()
131                .to_string()
132                .parse::<Ipv4Addr>()
133                .expect("failed to parse IP address"),
134            socket_addr.port(),
135        );
136
137        // Create the endpoint with our key and discovery
138        let endpoint = Endpoint::builder()
139            .secret_key(secret_key.0.clone())
140            .discovery(mainline_discovery)
141            .bind_addr_v4(addr)
142            .bind()
143            .await
144            .expect("failed to bind ephemeral endpoint");
145
146        // get the log provider, must be set
147        let log_provider = self.log_provider.expect("log_provider is required");
148
149        let sync_provider = self.sync_provider.expect("sync_provider must be set");
150
151        Peer::new(
152            log_provider,
153            socket_addr,
154            blobs_store,
155            secret_key,
156            endpoint,
157            sync_provider,
158        )
159    }
160}