Skip to main content

rings_node/processor/
config.rs

1use rings_core::dht::default_storage_virtual_positions_per_owner;
2use rings_core::dht::VirtualNodeConfig;
3use rings_core::dht::DEFAULT_STORAGE_VIRTUAL_POSITIONS_PER_OWNER;
4use rings_core::dht::MAX_STORAGE_VIRTUAL_POSITIONS_PER_OWNER;
5
6use super::*;
7
8/// ProcessorConfig is usually serialized as json or yaml.
9/// There is a `from_config` method in [ProcessorBuilder] used to initialize the Builder with a serialized ProcessorConfig.
10#[derive(Clone, Debug)]
11#[wasm_export]
12pub struct ProcessorConfig {
13    /// The network_id is used to distinguish different networks.
14    /// Use 1 for main network.
15    pub(in crate::processor) network_id: u32,
16    /// ICE servers for webrtc
17    pub(in crate::processor) ice_servers: String,
18    /// External address for webrtc
19    pub(in crate::processor) external_address: Option<String>,
20    /// Inclusive lower native WebRTC UDP port bound.
21    pub(in crate::processor) webrtc_udp_port_min: Option<u16>,
22    /// Inclusive upper native WebRTC UDP port bound.
23    pub(in crate::processor) webrtc_udp_port_max: Option<u16>,
24    /// [SessionSk].
25    pub(in crate::processor) session_sk: SessionSk,
26    /// Stabilization interval.
27    pub(in crate::processor) stabilize_interval: Duration,
28    /// Online-node registry heartbeat interval.
29    pub(in crate::processor) online_node_heartbeat_interval: Duration,
30    /// Online-node registry descriptor TTL.
31    pub(in crate::processor) online_node_ttl: Duration,
32    /// Runtime family advertised in the online-node registry.
33    pub(in crate::processor) online_node_type: OnlineNodeType,
34    /// Whether listen() advertises this node's presence.
35    pub(in crate::processor) advertise_presence: bool,
36    /// Storage-only virtual positions derived per physical peer.
37    pub(in crate::processor) dht_virtual_nodes: u16,
38    /// Whether this node advertises onion relay capability in the online-node registry.
39    pub(in crate::processor) advertise_onion_relay: bool,
40    /// Whether this node publishes an onion-exit descriptor.
41    pub(in crate::processor) advertise_onion_exit: bool,
42    /// Onion-exit registry heartbeat interval.
43    pub(in crate::processor) onion_exit_heartbeat_interval: Duration,
44    /// Onion-exit registry descriptor TTL.
45    pub(in crate::processor) onion_exit_ttl: Duration,
46    /// Services this node publishes when onion exit advertisement is enabled.
47    pub(in crate::processor) onion_exit_services: Vec<OnionExitService>,
48    /// Exit policy this node publishes when onion exit advertisement is enabled.
49    pub(in crate::processor) onion_exit_policy: OnionExitPolicy,
50}
51
52#[wasm_export]
53impl ProcessorConfig {
54    /// Creates a new `ProcessorConfig` instance without an external address.
55    pub fn new(
56        network_id: u32,
57        ice_servers: String,
58        session_sk: SessionSk,
59        stabilize_interval: u64,
60    ) -> Self {
61        Self {
62            network_id,
63            ice_servers,
64            external_address: None,
65            webrtc_udp_port_min: None,
66            webrtc_udp_port_max: None,
67            session_sk,
68            stabilize_interval: Duration::from_secs(stabilize_interval),
69            online_node_heartbeat_interval: Duration::from_secs(
70                default_online_node_heartbeat_interval_secs(),
71            ),
72            online_node_ttl: Duration::from_secs(default_online_node_ttl_secs()),
73            online_node_type: default_online_node_type(),
74            advertise_presence: default_advertise_presence(),
75            dht_virtual_nodes: DEFAULT_STORAGE_VIRTUAL_POSITIONS_PER_OWNER,
76            advertise_onion_relay: default_advertise_onion_relay(),
77            advertise_onion_exit: default_advertise_onion_exit(),
78            onion_exit_heartbeat_interval: Duration::from_secs(
79                default_onion_exit_heartbeat_interval_secs(),
80            ),
81            onion_exit_ttl: Duration::from_secs(default_onion_exit_ttl_secs()),
82            onion_exit_services: default_onion_exit_services(),
83            onion_exit_policy: default_onion_exit_policy(),
84        }
85    }
86
87    /// Return associated [SessionSk].
88    pub fn session_sk(&self) -> SessionSk {
89        self.session_sk.clone()
90    }
91
92    /// Enables only the standard HTTPS-over-TCP onion exit service.
93    pub fn enable_https_onion_exit(mut self) -> Self {
94        self.advertise_onion_exit = true;
95        self.onion_exit_services = https_onion_exit_services();
96        self
97    }
98
99    /// Enables default native onion exit advertisement.
100    pub fn enable_default_onion_exit(mut self) -> Self {
101        self.advertise_onion_exit = true;
102        self.onion_exit_services = default_onion_exit_services();
103        self
104    }
105
106    /// Sets whether listen() advertises this node as an onion relay.
107    pub fn advertise_onion_relay(mut self, advertise: bool) -> Self {
108        self.advertise_onion_relay = advertise;
109        self
110    }
111
112    /// Sets storage-only virtual positions derived per physical peer.
113    ///
114    /// Serialized configs reject values above
115    /// [`MAX_STORAGE_VIRTUAL_POSITIONS_PER_OWNER`]. This setter is infallible
116    /// for direct programmatic use; the core swarm builder normalizes the value
117    /// once before storage ownership and protocol advertisement are created.
118    pub fn dht_virtual_nodes(mut self, positions_per_peer: u16) -> Self {
119        self.dht_virtual_nodes = positions_per_peer;
120        self
121    }
122
123    /// Sets whether listen() publishes this node as an onion exit.
124    pub fn advertise_onion_exit(mut self, advertise: bool) -> Self {
125        self.advertise_onion_exit = advertise;
126        self
127    }
128}
129
130impl ProcessorConfig {
131    /// Returns the validated native WebRTC UDP port range, when configured.
132    pub fn webrtc_udp_port_range(&self) -> Result<Option<WebrtcUdpPortRange>> {
133        parse_webrtc_udp_port_range(self.webrtc_udp_port_min, self.webrtc_udp_port_max)
134    }
135
136    /// Sets the onion-exit policy.
137    pub fn onion_exit_policy(mut self, policy: OnionExitPolicy) -> Self {
138        self.onion_exit_policy = policy;
139        self
140    }
141
142    /// Return the HTTPS onion-exit policy when this config advertises that service.
143    #[cfg(all(feature = "browser", target_family = "wasm"))]
144    pub fn onion_https_exit_policy(&self) -> Option<OnionExitPolicy> {
145        (self.advertise_onion_exit
146            && self
147                .onion_exit_services
148                .iter()
149                .any(|service| service.matches_route_service(ONION_PROXY_HTTPS_SERVICE)))
150        .then(|| self.onion_exit_policy.clone())
151    }
152}
153
154impl FromStr for ProcessorConfig {
155    type Err = Error;
156    /// Reveal config from serialized string.
157    fn from_str(ser: &str) -> Result<Self> {
158        serde_yaml::from_str::<ProcessorConfig>(ser).map_err(Error::SerdeYamlError)
159    }
160}
161
162/// `ProcessorConfigSerialized` is a serialized version of `ProcessorConfig`.
163/// Instead of storing the `SessionSk` instance, it stores the dumped string representation of the session secret key.
164#[derive(Serialize, Deserialize, Clone)]
165#[wasm_export]
166pub struct ProcessorConfigSerialized {
167    /// The network_id is used to distinguish different networks.
168    /// Use 1 for main network.
169    network_id: u32,
170    /// A string representing ICE servers for WebRTC
171    ice_servers: String,
172    /// An optional string representing the external address for WebRTC
173    external_address: Option<String>,
174    /// Inclusive lower native WebRTC UDP port bound.
175    webrtc_udp_port_min: Option<u16>,
176    /// Inclusive upper native WebRTC UDP port bound.
177    webrtc_udp_port_max: Option<u16>,
178    /// A string representing the dumped `SessionSk`.
179    session_sk: String,
180    /// An unsigned integer representing the stabilization interval in seconds.
181    stabilize_interval: u64,
182    /// Online-node registry heartbeat interval in seconds.
183    #[serde(default = "default_online_node_heartbeat_interval_secs")]
184    online_node_heartbeat_interval_secs: u64,
185    /// Online-node registry descriptor TTL in seconds.
186    #[serde(default = "default_online_node_ttl_secs")]
187    online_node_ttl_secs: u64,
188    /// Runtime family advertised in the online-node registry.
189    #[serde(default = "default_online_node_type")]
190    online_node_type: OnlineNodeType,
191    /// Whether listen() advertises this node's presence.
192    #[serde(default = "default_advertise_presence")]
193    advertise_presence: bool,
194    /// Storage-only virtual positions derived per physical peer.
195    #[serde(default = "default_storage_virtual_positions_per_owner")]
196    dht_virtual_nodes: u16,
197    /// Whether listen() advertises onion relay capability.
198    #[serde(default = "default_advertise_onion_relay")]
199    advertise_onion_relay: bool,
200    /// Whether listen() publishes an onion-exit descriptor.
201    #[serde(default = "default_advertise_onion_exit")]
202    advertise_onion_exit: bool,
203    /// Onion-exit registry heartbeat interval in seconds.
204    #[serde(default = "default_onion_exit_heartbeat_interval_secs")]
205    onion_exit_heartbeat_interval_secs: u64,
206    /// Onion-exit registry descriptor TTL in seconds.
207    #[serde(default = "default_onion_exit_ttl_secs")]
208    onion_exit_ttl_secs: u64,
209    /// Exit services advertised by this node.
210    #[serde(default = "default_onion_exit_services")]
211    onion_exit_services: Vec<OnionExitService>,
212    /// Exit policy advertised by this node.
213    #[serde(default = "default_onion_exit_policy")]
214    onion_exit_policy: OnionExitPolicy,
215}
216
217impl ProcessorConfigSerialized {
218    /// Creates a new `ProcessorConfigSerialized` instance without an external address.
219    pub fn new(
220        network_id: u32,
221        ice_servers: String,
222        session_sk: String,
223        stabilize_interval: u64,
224    ) -> Self {
225        Self {
226            network_id,
227            ice_servers,
228            external_address: None,
229            webrtc_udp_port_min: None,
230            webrtc_udp_port_max: None,
231            session_sk,
232            stabilize_interval,
233            online_node_heartbeat_interval_secs: default_online_node_heartbeat_interval_secs(),
234            online_node_ttl_secs: default_online_node_ttl_secs(),
235            online_node_type: default_online_node_type(),
236            advertise_presence: default_advertise_presence(),
237            dht_virtual_nodes: DEFAULT_STORAGE_VIRTUAL_POSITIONS_PER_OWNER,
238            advertise_onion_relay: default_advertise_onion_relay(),
239            advertise_onion_exit: default_advertise_onion_exit(),
240            onion_exit_heartbeat_interval_secs: default_onion_exit_heartbeat_interval_secs(),
241            onion_exit_ttl_secs: default_onion_exit_ttl_secs(),
242            onion_exit_services: default_onion_exit_services(),
243            onion_exit_policy: default_onion_exit_policy(),
244        }
245    }
246
247    /// Sets up the external address for WebRTC.
248    /// This will be used to configure the transport to listen for WebRTC connections in "HOST" mode.
249    pub fn external_address(mut self, external_address: String) -> Self {
250        self.external_address = Some(external_address);
251        self
252    }
253
254    /// Sets the native WebRTC UDP port range bounds.
255    pub fn webrtc_udp_port_range(mut self, range: WebrtcUdpPortRange) -> Self {
256        self.webrtc_udp_port_min = Some(range.min());
257        self.webrtc_udp_port_max = Some(range.max());
258        self
259    }
260
261    /// Sets the online-node registry heartbeat interval in seconds.
262    pub fn online_node_heartbeat_interval_secs(mut self, interval_secs: u64) -> Self {
263        self.online_node_heartbeat_interval_secs = interval_secs;
264        self
265    }
266
267    /// Sets the online-node registry descriptor TTL in seconds.
268    pub fn online_node_ttl_secs(mut self, ttl_secs: u64) -> Self {
269        self.online_node_ttl_secs = ttl_secs;
270        self
271    }
272
273    /// Sets the runtime family advertised in the online-node registry.
274    pub fn online_node_type(mut self, node_type: OnlineNodeType) -> Self {
275        self.online_node_type = node_type;
276        self
277    }
278
279    /// Sets whether listen() advertises this node's presence.
280    pub fn advertise_presence(mut self, advertise: bool) -> Self {
281        self.advertise_presence = advertise;
282        self
283    }
284
285    /// Sets whether listen() advertises onion relay capability.
286    pub fn advertise_onion_relay(mut self, advertise: bool) -> Self {
287        self.advertise_onion_relay = advertise;
288        self
289    }
290
291    /// Sets storage-only virtual positions derived per physical peer.
292    ///
293    /// Serialized configs reject values above
294    /// [`MAX_STORAGE_VIRTUAL_POSITIONS_PER_OWNER`]. This setter is infallible
295    /// for direct programmatic use; the core swarm builder normalizes the value
296    /// once before storage ownership and protocol advertisement are created.
297    pub fn dht_virtual_nodes(mut self, positions_per_peer: u16) -> Self {
298        self.dht_virtual_nodes = positions_per_peer;
299        self
300    }
301
302    /// Sets whether listen() publishes an onion-exit descriptor.
303    pub fn advertise_onion_exit(mut self, advertise: bool) -> Self {
304        self.advertise_onion_exit = advertise;
305        self
306    }
307
308    /// Sets the onion-exit registry heartbeat interval in seconds.
309    pub fn onion_exit_heartbeat_interval_secs(mut self, interval_secs: u64) -> Self {
310        self.onion_exit_heartbeat_interval_secs = interval_secs;
311        self
312    }
313
314    /// Sets the onion-exit registry descriptor TTL in seconds.
315    pub fn onion_exit_ttl_secs(mut self, ttl_secs: u64) -> Self {
316        self.onion_exit_ttl_secs = ttl_secs;
317        self
318    }
319
320    /// Sets the onion-exit services advertised by this node.
321    pub fn onion_exit_services(mut self, services: Vec<OnionExitService>) -> Self {
322        self.onion_exit_services = services;
323        self
324    }
325
326    /// Sets the onion-exit policy advertised by this node.
327    pub fn onion_exit_policy(mut self, policy: OnionExitPolicy) -> Self {
328        self.onion_exit_policy = policy;
329        self
330    }
331
332    /// Enables only the standard HTTPS-over-TCP onion exit service.
333    pub fn enable_https_onion_exit(mut self) -> Self {
334        self.advertise_onion_exit = true;
335        self.onion_exit_services = https_onion_exit_services();
336        self
337    }
338
339    /// Enables the default native onion exit services.
340    pub fn enable_default_onion_exit(mut self) -> Self {
341        self.advertise_onion_exit = true;
342        self.onion_exit_services = default_onion_exit_services();
343        self
344    }
345}
346
347pub(crate) fn parse_webrtc_udp_port_range(
348    min: Option<u16>,
349    max: Option<u16>,
350) -> Result<Option<WebrtcUdpPortRange>> {
351    match (min, max) {
352        (None, None) => Ok(None),
353        (Some(min), Some(max)) => WebrtcUdpPortRange::new(min, max)
354            .map(Some)
355            .map_err(Error::from),
356        (min, max) => Err(Error::IncompleteWebrtcUdpPortRange { min, max }),
357    }
358}
359
360fn validate_dht_virtual_nodes(positions_per_peer: u16) -> Result<()> {
361    if VirtualNodeConfig::positions_per_owner_within_limit(positions_per_peer) {
362        return Ok(());
363    }
364
365    Err(Error::InvalidConfig(format!(
366        "dht_virtual_nodes {positions_per_peer} exceeds maximum {MAX_STORAGE_VIRTUAL_POSITIONS_PER_OWNER}"
367    )))
368}
369
370pub(in crate::processor) fn validate_onion_role_config(
371    advertise_presence: bool,
372    advertise_onion_relay: bool,
373    advertise_onion_exit: bool,
374    onion_exit_services: &[OnionExitService],
375    onion_exit_policy: &OnionExitPolicy,
376) -> Result<()> {
377    if advertise_onion_relay && !advertise_presence {
378        return Err(Error::InvalidConfig(
379            "advertise_onion_relay requires advertise_presence because relay capability is published in online-node descriptors"
380                .to_string(),
381        ));
382    }
383    if advertise_onion_exit && onion_exit_services.is_empty() {
384        return Err(Error::InvalidConfig(
385            "advertise_onion_exit requires at least one onion_exit_services entry".to_string(),
386        ));
387    }
388    if advertise_onion_exit {
389        for service in onion_exit_services {
390            if let Some(expected) = OnionExitService::reserved_transport(service.name.as_str()) {
391                if service.transport == expected {
392                    continue;
393                }
394                return Err(Error::InvalidConfig(format!(
395                    "onion exit service {:?} must use {:?} transport, got {:?}",
396                    service.name, expected, service.transport
397                )));
398            }
399        }
400        onion_exit_policy.validate_targets()?;
401    }
402    Ok(())
403}
404
405impl TryFrom<ProcessorConfig> for ProcessorConfigSerialized {
406    type Error = Error;
407    fn try_from(ins: ProcessorConfig) -> Result<Self> {
408        Ok(Self {
409            network_id: ins.network_id,
410            ice_servers: ins.ice_servers.clone(),
411            external_address: ins.external_address.clone(),
412            webrtc_udp_port_min: ins.webrtc_udp_port_min,
413            webrtc_udp_port_max: ins.webrtc_udp_port_max,
414            session_sk: ins.session_sk.dump()?,
415            stabilize_interval: ins.stabilize_interval.as_secs(),
416            online_node_heartbeat_interval_secs: ins.online_node_heartbeat_interval.as_secs(),
417            online_node_ttl_secs: ins.online_node_ttl.as_secs(),
418            online_node_type: ins.online_node_type,
419            advertise_presence: ins.advertise_presence,
420            dht_virtual_nodes: ins.dht_virtual_nodes,
421            advertise_onion_relay: ins.advertise_onion_relay,
422            advertise_onion_exit: ins.advertise_onion_exit,
423            onion_exit_heartbeat_interval_secs: ins.onion_exit_heartbeat_interval.as_secs(),
424            onion_exit_ttl_secs: ins.onion_exit_ttl.as_secs(),
425            onion_exit_services: ins.onion_exit_services,
426            onion_exit_policy: ins.onion_exit_policy,
427        })
428    }
429}
430
431impl TryFrom<ProcessorConfigSerialized> for ProcessorConfig {
432    type Error = Error;
433    fn try_from(ins: ProcessorConfigSerialized) -> Result<Self> {
434        let webrtc_udp_port_range =
435            parse_webrtc_udp_port_range(ins.webrtc_udp_port_min, ins.webrtc_udp_port_max)?;
436        validate_dht_virtual_nodes(ins.dht_virtual_nodes)?;
437        let online_node_heartbeat_interval =
438            Duration::from_secs(ins.online_node_heartbeat_interval_secs);
439        let online_node_ttl = Duration::from_secs(ins.online_node_ttl_secs);
440        let onion_exit_heartbeat_interval =
441            Duration::from_secs(ins.onion_exit_heartbeat_interval_secs);
442        let onion_exit_ttl = Duration::from_secs(ins.onion_exit_ttl_secs);
443        validate_online_node_registration_timing(
444            ins.advertise_presence,
445            online_node_heartbeat_interval,
446            online_node_ttl,
447        )?;
448        validate_onion_exit_registration_timing(
449            ins.advertise_onion_exit,
450            onion_exit_heartbeat_interval,
451            onion_exit_ttl,
452        )?;
453        validate_onion_role_config(
454            ins.advertise_presence,
455            ins.advertise_onion_relay,
456            ins.advertise_onion_exit,
457            &ins.onion_exit_services,
458            &ins.onion_exit_policy,
459        )?;
460        Ok(Self {
461            network_id: ins.network_id,
462            ice_servers: ins.ice_servers.clone(),
463            external_address: ins.external_address.clone(),
464            webrtc_udp_port_min: webrtc_udp_port_range.map(WebrtcUdpPortRange::min),
465            webrtc_udp_port_max: webrtc_udp_port_range.map(WebrtcUdpPortRange::max),
466            session_sk: SessionSk::from_str(&ins.session_sk)?,
467            stabilize_interval: Duration::from_secs(ins.stabilize_interval),
468            online_node_heartbeat_interval,
469            online_node_ttl,
470            online_node_type: ins.online_node_type,
471            advertise_presence: ins.advertise_presence,
472            dht_virtual_nodes: ins.dht_virtual_nodes,
473            advertise_onion_relay: ins.advertise_onion_relay,
474            advertise_onion_exit: ins.advertise_onion_exit,
475            onion_exit_heartbeat_interval,
476            onion_exit_ttl,
477            onion_exit_services: ins.onion_exit_services,
478            onion_exit_policy: ins.onion_exit_policy,
479        })
480    }
481}
482
483impl Serialize for ProcessorConfig {
484    fn serialize<S: serde::Serializer>(
485        &self,
486        serializer: S,
487    ) -> core::result::Result<S::Ok, S::Error> {
488        let ins: ProcessorConfigSerialized = self
489            .clone()
490            .try_into()
491            .map_err(|e: Error| serde::ser::Error::custom(e.to_string()))?;
492        ProcessorConfigSerialized::serialize(&ins, serializer)
493    }
494}
495
496impl<'de> serde::de::Deserialize<'de> for ProcessorConfig {
497    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
498    where D: serde::Deserializer<'de> {
499        match ProcessorConfigSerialized::deserialize(deserializer) {
500            Ok(ins) => {
501                let cfg: ProcessorConfig = ins
502                    .try_into()
503                    .map_err(|e: Error| serde::de::Error::custom(e.to_string()))?;
504                Ok(cfg)
505            }
506            Err(e) => Err(e),
507        }
508    }
509}