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#[derive(Clone, Debug)]
11#[wasm_export]
12pub struct ProcessorConfig {
13 pub(in crate::processor) network_id: u32,
16 pub(in crate::processor) ice_servers: String,
18 pub(in crate::processor) external_address: Option<String>,
20 pub(in crate::processor) webrtc_udp_port_min: Option<u16>,
22 pub(in crate::processor) webrtc_udp_port_max: Option<u16>,
24 pub(in crate::processor) session_sk: SessionSk,
26 pub(in crate::processor) stabilize_interval: Duration,
28 pub(in crate::processor) online_node_heartbeat_interval: Duration,
30 pub(in crate::processor) online_node_ttl: Duration,
32 pub(in crate::processor) online_node_type: OnlineNodeType,
34 pub(in crate::processor) advertise_presence: bool,
36 pub(in crate::processor) dht_virtual_nodes: u16,
38 pub(in crate::processor) advertise_onion_relay: bool,
40 pub(in crate::processor) advertise_onion_exit: bool,
42 pub(in crate::processor) onion_exit_heartbeat_interval: Duration,
44 pub(in crate::processor) onion_exit_ttl: Duration,
46 pub(in crate::processor) onion_exit_services: Vec<OnionExitService>,
48 pub(in crate::processor) onion_exit_policy: OnionExitPolicy,
50}
51
52#[wasm_export]
53impl ProcessorConfig {
54 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 pub fn session_sk(&self) -> SessionSk {
89 self.session_sk.clone()
90 }
91
92 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 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 pub fn advertise_onion_relay(mut self, advertise: bool) -> Self {
108 self.advertise_onion_relay = advertise;
109 self
110 }
111
112 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 pub fn advertise_onion_exit(mut self, advertise: bool) -> Self {
125 self.advertise_onion_exit = advertise;
126 self
127 }
128}
129
130impl ProcessorConfig {
131 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 pub fn onion_exit_policy(mut self, policy: OnionExitPolicy) -> Self {
138 self.onion_exit_policy = policy;
139 self
140 }
141
142 #[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 fn from_str(ser: &str) -> Result<Self> {
158 serde_yaml::from_str::<ProcessorConfig>(ser).map_err(Error::SerdeYamlError)
159 }
160}
161
162#[derive(Serialize, Deserialize, Clone)]
165#[wasm_export]
166pub struct ProcessorConfigSerialized {
167 network_id: u32,
170 ice_servers: String,
172 external_address: Option<String>,
174 webrtc_udp_port_min: Option<u16>,
176 webrtc_udp_port_max: Option<u16>,
178 session_sk: String,
180 stabilize_interval: u64,
182 #[serde(default = "default_online_node_heartbeat_interval_secs")]
184 online_node_heartbeat_interval_secs: u64,
185 #[serde(default = "default_online_node_ttl_secs")]
187 online_node_ttl_secs: u64,
188 #[serde(default = "default_online_node_type")]
190 online_node_type: OnlineNodeType,
191 #[serde(default = "default_advertise_presence")]
193 advertise_presence: bool,
194 #[serde(default = "default_storage_virtual_positions_per_owner")]
196 dht_virtual_nodes: u16,
197 #[serde(default = "default_advertise_onion_relay")]
199 advertise_onion_relay: bool,
200 #[serde(default = "default_advertise_onion_exit")]
202 advertise_onion_exit: bool,
203 #[serde(default = "default_onion_exit_heartbeat_interval_secs")]
205 onion_exit_heartbeat_interval_secs: u64,
206 #[serde(default = "default_onion_exit_ttl_secs")]
208 onion_exit_ttl_secs: u64,
209 #[serde(default = "default_onion_exit_services")]
211 onion_exit_services: Vec<OnionExitService>,
212 #[serde(default = "default_onion_exit_policy")]
214 onion_exit_policy: OnionExitPolicy,
215}
216
217impl ProcessorConfigSerialized {
218 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 pub fn external_address(mut self, external_address: String) -> Self {
250 self.external_address = Some(external_address);
251 self
252 }
253
254 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 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 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 pub fn online_node_type(mut self, node_type: OnlineNodeType) -> Self {
275 self.online_node_type = node_type;
276 self
277 }
278
279 pub fn advertise_presence(mut self, advertise: bool) -> Self {
281 self.advertise_presence = advertise;
282 self
283 }
284
285 pub fn advertise_onion_relay(mut self, advertise: bool) -> Self {
287 self.advertise_onion_relay = advertise;
288 self
289 }
290
291 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 pub fn advertise_onion_exit(mut self, advertise: bool) -> Self {
304 self.advertise_onion_exit = advertise;
305 self
306 }
307
308 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 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 pub fn onion_exit_services(mut self, services: Vec<OnionExitService>) -> Self {
322 self.onion_exit_services = services;
323 self
324 }
325
326 pub fn onion_exit_policy(mut self, policy: OnionExitPolicy) -> Self {
328 self.onion_exit_policy = policy;
329 self
330 }
331
332 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 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}