1use std::{
2 collections::HashSet,
3 fs::{self, File},
4 future::Future,
5 io::{Read, Write},
6 net::{IpAddr, Ipv4Addr, SocketAddr},
7 path::{Path, PathBuf},
8 sync::{atomic::AtomicBool, Arc, LazyLock},
9 time::Duration,
10};
11
12use anyhow::Context;
13use directories::ProjectDirs;
14use either::Either;
15use serde::{Deserialize, Serialize};
16use tokio::runtime::Runtime;
17
18use crate::{
19 dev_tool::PeerId,
20 local_node::OperationMode,
21 tracing::tracer::get_log_dir,
22 transport::{CongestionControlAlgorithm, CongestionControlConfig, TransportKeypair},
23};
24
25mod secret;
26pub use secret::*;
27
28pub const DEFAULT_MAX_CONNECTIONS: usize = 20;
30pub const DEFAULT_MIN_CONNECTIONS: usize = 10;
32pub const DEFAULT_RANDOM_PEER_CONN_THRESHOLD: usize = 7;
39pub const DEFAULT_MAX_HOPS_TO_LIVE: usize = 10;
42
43pub(crate) const OPERATION_TTL: Duration = Duration::from_secs(60);
44
45pub(crate) const PCK_VERSION: &str = env!("CARGO_PKG_VERSION");
47
48static ASYNC_RT: LazyLock<Option<Runtime>> = LazyLock::new(GlobalExecutor::initialize_async_rt);
50
51const DEFAULT_TRANSIENT_BUDGET: usize = 2048;
52const DEFAULT_TRANSIENT_TTL_SECS: u64 = 30;
53
54const QUALIFIER: &str = "";
55const ORGANIZATION: &str = "The Freenet Project Inc";
56const APPLICATION: &str = "Freenet";
57
58const FREENET_GATEWAYS_INDEX: &str = "https://freenet.org/keys/gateways.toml";
59
60#[derive(clap::Parser, Debug, Clone)]
61pub struct ConfigArgs {
62 #[arg(value_enum, env = "MODE")]
64 pub mode: Option<OperationMode>,
65
66 #[command(flatten)]
67 pub ws_api: WebsocketApiArgs,
68
69 #[command(flatten)]
70 pub network_api: NetworkArgs,
71
72 #[command(flatten)]
73 pub secrets: SecretArgs,
74
75 #[arg(long, env = "LOG_LEVEL")]
76 pub log_level: Option<tracing::log::LevelFilter>,
77
78 #[command(flatten)]
79 pub config_paths: ConfigPathsArgs,
80
81 #[arg(long, hide = true)]
83 pub id: Option<String>,
84
85 #[arg(long, short)]
87 pub version: bool,
88
89 #[arg(long, env = "MAX_BLOCKING_THREADS")]
92 pub max_blocking_threads: Option<usize>,
93
94 #[command(flatten)]
95 pub telemetry: TelemetryArgs,
96}
97
98impl Default for ConfigArgs {
99 fn default() -> Self {
100 Self {
101 mode: Some(OperationMode::Network),
102 network_api: NetworkArgs {
103 address: Some(default_listening_address()),
104 network_port: Some(default_network_api_port()),
105 public_address: None,
106 public_port: None,
107 is_gateway: false,
108 skip_load_from_network: true,
109 ignore_protocol_checking: false,
110 gateways: None,
111 location: None,
112 bandwidth_limit: Some(3_000_000), total_bandwidth_limit: None,
114 min_bandwidth_per_connection: None,
115 blocked_addresses: None,
116 transient_budget: Some(DEFAULT_TRANSIENT_BUDGET),
117 transient_ttl_secs: Some(DEFAULT_TRANSIENT_TTL_SECS),
118 min_connections: None,
119 max_connections: None,
120 streaming_threshold: None, ledbat_min_ssthresh: None, congestion_control: None, bbr_startup_rate: None, },
125 ws_api: WebsocketApiArgs {
126 address: Some(default_listening_address()),
127 ws_api_port: Some(default_ws_api_port()),
128 token_ttl_seconds: None,
129 token_cleanup_interval_seconds: None,
130 },
131 secrets: Default::default(),
132 log_level: Some(tracing::log::LevelFilter::Info),
133 config_paths: Default::default(),
134 id: None,
135 version: false,
136 max_blocking_threads: None,
137 telemetry: Default::default(),
138 }
139 }
140}
141
142impl ConfigArgs {
143 pub fn current_version(&self) -> &str {
144 PCK_VERSION
145 }
146
147 fn read_config(dir: &PathBuf) -> std::io::Result<Option<Config>> {
148 if !dir.exists() {
149 return Ok(None);
150 }
151 let mut read_dir = std::fs::read_dir(dir)?;
152 let config_args: Option<(String, String)> = read_dir.find_map(|e| {
153 if let Ok(e) = e {
154 if e.path().is_dir() {
155 return None;
156 }
157 let filename = e.file_name().to_string_lossy().into_owned();
158 let ext = filename.rsplit('.').next().map(|s| s.to_owned());
159 if let Some(ext) = ext {
160 if filename.starts_with("config") {
161 match ext.as_str() {
162 "toml" => {
163 tracing::debug!(filename = %filename, "Found configuration file");
164 return Some((filename, ext));
165 }
166 "json" => {
167 return Some((filename, ext));
168 }
169 _ => {}
170 }
171 }
172 }
173 }
174
175 None
176 });
177
178 match config_args {
179 Some((filename, ext)) => {
180 let path = dir.join(filename).with_extension(&ext);
181 tracing::debug!(path = ?path, "Reading configuration file");
182 match ext.as_str() {
183 "toml" => {
184 let mut file = File::open(&path)?;
185 let mut content = String::new();
186 file.read_to_string(&mut content)?;
187 let mut config = toml::from_str::<Config>(&content).map_err(|e| {
188 std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
189 })?;
190 let secrets = Self::read_secrets(
191 config.secrets.transport_keypair_path,
192 config.secrets.nonce_path,
193 config.secrets.cipher_path,
194 )?;
195 config.secrets = secrets;
196 Ok(Some(config))
197 }
198 "json" => {
199 let mut file = File::open(&path)?;
200 let mut config = serde_json::from_reader::<_, Config>(&mut file)?;
201 let secrets = Self::read_secrets(
202 config.secrets.transport_keypair_path,
203 config.secrets.nonce_path,
204 config.secrets.cipher_path,
205 )?;
206 config.secrets = secrets;
207 Ok(Some(config))
208 }
209 ext => Err(std::io::Error::new(
210 std::io::ErrorKind::InvalidInput,
211 format!("Invalid configuration file extension: {ext}"),
212 )),
213 }
214 }
215 None => Ok(None),
216 }
217 }
218
219 pub async fn build(mut self) -> anyhow::Result<Config> {
221 self.network_api.validate()?;
223
224 let cfg = if let Some(path) = self.config_paths.config_dir.as_ref() {
225 if !path.exists() {
226 return Err(anyhow::Error::new(std::io::Error::new(
227 std::io::ErrorKind::NotFound,
228 "Configuration directory not found",
229 )));
230 }
231
232 Self::read_config(path)?
233 } else {
234 let (config, data, is_temp_dir) = {
236 match ConfigPathsArgs::default_dirs(self.id.as_deref())? {
237 Either::Left(defaults) => (
238 defaults.config_local_dir().to_path_buf(),
239 defaults.data_local_dir().to_path_buf(),
240 false,
241 ),
242 Either::Right(dir) => (dir.clone(), dir, true),
243 }
244 };
245 self.config_paths.config_dir = Some(config.clone());
246 if self.config_paths.data_dir.is_none() {
247 self.config_paths.data_dir = Some(data);
248 }
249 if is_temp_dir {
252 None
253 } else {
254 Self::read_config(&config)?.inspect(|_| {
255 tracing::debug!("Found configuration file in default directory");
256 })
257 }
258 };
259
260 let should_persist = cfg.is_none();
261
262 if let Some(cfg) = cfg {
264 self.secrets.merge(cfg.secrets);
265 self.mode.get_or_insert(cfg.mode);
266 self.ws_api.address.get_or_insert(cfg.ws_api.address);
267 self.ws_api.ws_api_port.get_or_insert(cfg.ws_api.port);
268 self.ws_api
269 .token_ttl_seconds
270 .get_or_insert(cfg.ws_api.token_ttl_seconds);
271 self.ws_api
272 .token_cleanup_interval_seconds
273 .get_or_insert(cfg.ws_api.token_cleanup_interval_seconds);
274 self.network_api
275 .address
276 .get_or_insert(cfg.network_api.address);
277 self.network_api
278 .network_port
279 .get_or_insert(cfg.network_api.port);
280 if let Some(addr) = cfg.network_api.public_address {
281 self.network_api.public_address.get_or_insert(addr);
282 }
283 if let Some(port) = cfg.network_api.public_port {
284 self.network_api.public_port.get_or_insert(port);
285 }
286 if let Some(limit) = cfg.network_api.bandwidth_limit {
287 self.network_api.bandwidth_limit.get_or_insert(limit);
288 }
289 if let Some(addrs) = cfg.network_api.blocked_addresses {
290 self.network_api
291 .blocked_addresses
292 .get_or_insert_with(|| addrs.into_iter().collect());
293 }
294 self.network_api
295 .transient_budget
296 .get_or_insert(cfg.network_api.transient_budget);
297 self.network_api
298 .transient_ttl_secs
299 .get_or_insert(cfg.network_api.transient_ttl_secs);
300 self.network_api
301 .min_connections
302 .get_or_insert(cfg.network_api.min_connections);
303 self.network_api
304 .max_connections
305 .get_or_insert(cfg.network_api.max_connections);
306 if cfg.network_api.streaming_threshold != default_streaming_threshold() {
307 self.network_api
308 .streaming_threshold
309 .get_or_insert(cfg.network_api.streaming_threshold);
310 }
311 if self.network_api.ledbat_min_ssthresh.is_none() {
313 self.network_api.ledbat_min_ssthresh = cfg.network_api.ledbat_min_ssthresh;
314 }
315 if self.network_api.congestion_control.is_none()
317 && cfg.network_api.congestion_control != default_congestion_control()
318 {
319 self.network_api
320 .congestion_control
321 .get_or_insert(cfg.network_api.congestion_control);
322 }
323 if self.network_api.bbr_startup_rate.is_none() {
324 self.network_api.bbr_startup_rate = cfg.network_api.bbr_startup_rate;
325 }
326 self.log_level.get_or_insert(cfg.log_level);
327 self.config_paths.merge(cfg.config_paths.as_ref().clone());
328 if !cfg.telemetry.enabled {
332 self.telemetry.enabled = false;
333 }
334 if self.telemetry.endpoint.is_none() {
335 self.telemetry
336 .endpoint
337 .get_or_insert(cfg.telemetry.endpoint);
338 }
339 }
340
341 let mode = self.mode.unwrap_or(OperationMode::Network);
342 let config_paths = self.config_paths.build(self.id.as_deref())?;
343
344 let secrets = self.secrets.build(Some(&config_paths.secrets_dir(mode)))?;
345
346 let peer_id = self
347 .network_api
348 .public_address
349 .zip(self.network_api.public_port)
350 .map(|(addr, port)| {
351 PeerId::new(
352 (addr, port).into(),
353 secrets.transport_keypair.public().clone(),
354 )
355 });
356 let gateways_file = config_paths.config_dir.join("gateways.toml");
357
358 let remotely_loaded_gateways = if mode == OperationMode::Local {
360 Gateways::default()
361 } else if !self.network_api.skip_load_from_network {
362 load_gateways_from_index(FREENET_GATEWAYS_INDEX, &config_paths.secrets_dir)
363 .await
364 .inspect_err(|error| {
365 tracing::error!(
366 error = %error,
367 index = FREENET_GATEWAYS_INDEX,
368 "Failed to load gateways from index"
369 );
370 })
371 .unwrap_or_default()
372 } else if let Some(gateways) = self.network_api.gateways {
373 let gateways = gateways
374 .into_iter()
375 .map(|cfg| {
376 let cfg = serde_json::from_str::<InlineGwConfig>(&cfg)?;
377 Ok::<_, anyhow::Error>(GatewayConfig {
378 address: Address::HostAddress(cfg.address),
379 public_key_path: cfg.public_key_path,
380 location: cfg.location,
381 })
382 })
383 .collect::<Result<Vec<_>, _>>()?;
384 Gateways { gateways }
385 } else {
386 Gateways::default()
387 };
388
389 let gateways = if mode == OperationMode::Local {
391 Gateways { gateways: vec![] }
393 } else if !self.network_api.skip_load_from_network
394 && !remotely_loaded_gateways.gateways.is_empty()
395 {
396 tracing::info!(
400 gateway_count = remotely_loaded_gateways.gateways.len(),
401 "Replacing local gateways with gateways from remote index"
402 );
403
404 if let Err(e) = remotely_loaded_gateways.save_to_file(&gateways_file) {
406 tracing::warn!(
407 error = %e,
408 file = ?gateways_file,
409 "Failed to save updated gateways to file"
410 );
411 }
412
413 remotely_loaded_gateways
414 } else if self.network_api.skip_load_from_network && self.network_api.is_gateway {
415 if remotely_loaded_gateways.gateways.is_empty() {
419 tracing::info!(
420 "Gateway running in isolated mode (skip_load_from_network), not connecting to other gateways"
421 );
422 Gateways { gateways: vec![] }
423 } else {
424 remotely_loaded_gateways
426 }
427 } else {
428 let mut gateways = match File::open(&*gateways_file) {
430 Ok(mut file) => {
431 let mut content = String::new();
432 file.read_to_string(&mut content)?;
433 toml::from_str::<Gateways>(&content).map_err(|e| {
434 std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
435 })?
436 }
437 Err(err) => {
438 if peer_id.is_none()
439 && mode == OperationMode::Network
440 && remotely_loaded_gateways.gateways.is_empty()
441 {
442 tracing::error!(
443 file = ?gateways_file,
444 error = %err,
445 "Failed to read gateways file"
446 );
447
448 return Err(anyhow::Error::new(std::io::Error::new(
449 std::io::ErrorKind::NotFound,
450 "Cannot initialize node without gateways",
451 )));
452 }
453 if remotely_loaded_gateways.gateways.is_empty() {
454 tracing::warn!("No gateways file found, initializing disjoint gateway");
455 }
456 Gateways { gateways: vec![] }
457 }
458 };
459
460 if !remotely_loaded_gateways.gateways.is_empty() {
463 gateways.merge_and_deduplicate(remotely_loaded_gateways);
464 }
465
466 gateways
467 };
468
469 let this = Config {
470 mode,
471 peer_id,
472 network_api: NetworkApiConfig {
473 address: self.network_api.address.unwrap_or_else(|| match mode {
474 OperationMode::Local => default_local_address(),
475 OperationMode::Network => default_listening_address(),
476 }),
477 port: self
478 .network_api
479 .network_port
480 .unwrap_or_else(default_network_api_port),
481 public_address: self.network_api.public_address,
482 public_port: self.network_api.public_port,
483 ignore_protocol_version: self.network_api.ignore_protocol_checking,
484 bandwidth_limit: self.network_api.bandwidth_limit,
485 total_bandwidth_limit: self.network_api.total_bandwidth_limit,
486 min_bandwidth_per_connection: self.network_api.min_bandwidth_per_connection,
487 blocked_addresses: self
488 .network_api
489 .blocked_addresses
490 .map(|addrs| addrs.into_iter().collect()),
491 transient_budget: self
492 .network_api
493 .transient_budget
494 .unwrap_or(DEFAULT_TRANSIENT_BUDGET),
495 transient_ttl_secs: self
496 .network_api
497 .transient_ttl_secs
498 .unwrap_or(DEFAULT_TRANSIENT_TTL_SECS),
499 min_connections: self
500 .network_api
501 .min_connections
502 .unwrap_or(DEFAULT_MIN_CONNECTIONS),
503 max_connections: self
504 .network_api
505 .max_connections
506 .unwrap_or(DEFAULT_MAX_CONNECTIONS),
507 streaming_threshold: self
508 .network_api
509 .streaming_threshold
510 .unwrap_or_else(default_streaming_threshold),
511 ledbat_min_ssthresh: self
512 .network_api
513 .ledbat_min_ssthresh
514 .or_else(default_ledbat_min_ssthresh),
515 congestion_control: self
516 .network_api
517 .congestion_control
518 .clone()
519 .unwrap_or_else(default_congestion_control),
520 bbr_startup_rate: self.network_api.bbr_startup_rate,
521 },
522 ws_api: WebsocketApiConfig {
523 address: {
524 let addr = self.ws_api.address.unwrap_or_else(|| match mode {
525 OperationMode::Local => default_local_address(),
526 OperationMode::Network => default_listening_address(),
527 });
528 match (mode, addr) {
530 (OperationMode::Local, IpAddr::V4(ip)) if ip == Ipv4Addr::UNSPECIFIED => {
531 default_local_address()
532 }
533 (OperationMode::Local, IpAddr::V6(ip)) if ip.is_unspecified() => {
534 default_local_address()
535 }
536 _ => addr,
537 }
538 },
539 port: self.ws_api.ws_api_port.unwrap_or(default_ws_api_port()),
540 token_ttl_seconds: self
541 .ws_api
542 .token_ttl_seconds
543 .unwrap_or(default_token_ttl_seconds()),
544 token_cleanup_interval_seconds: self
545 .ws_api
546 .token_cleanup_interval_seconds
547 .unwrap_or(default_token_cleanup_interval_seconds()),
548 },
549 secrets,
550 log_level: self.log_level.unwrap_or(tracing::log::LevelFilter::Info),
551 config_paths: Arc::new(config_paths),
552 gateways: gateways.gateways.clone(),
553 is_gateway: self.network_api.is_gateway,
554 location: self.network_api.location,
555 max_blocking_threads: self
556 .max_blocking_threads
557 .unwrap_or_else(default_max_blocking_threads),
558 telemetry: TelemetryConfig {
559 enabled: self.telemetry.enabled,
560 endpoint: self
561 .telemetry
562 .endpoint
563 .unwrap_or_else(|| DEFAULT_TELEMETRY_ENDPOINT.to_string()),
564 transport_snapshot_interval_secs: self
565 .telemetry
566 .transport_snapshot_interval_secs
567 .unwrap_or_else(default_transport_snapshot_interval_secs),
568 is_test_environment: self.id.is_some(),
572 },
573 };
574
575 fs::create_dir_all(this.config_dir())?;
576 if !self.network_api.skip_load_from_network {
580 gateways.save_to_file(&gateways_file)?;
581 }
582
583 if should_persist {
584 let path = this.config_dir().join("config.toml");
585 tracing::info!(path = ?path, "Persisting configuration");
586 let mut file = File::create(path)?;
587 file.write_all(
588 toml::to_string(&this)
589 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?
590 .as_bytes(),
591 )?;
592 }
593
594 Ok(this)
595 }
596}
597
598mod serde_log_level_filter {
599 use serde::{Deserialize, Deserializer, Serializer};
600 use tracing::log::LevelFilter;
601
602 pub fn parse_log_level_str<'a, D>(level: &str) -> Result<LevelFilter, D::Error>
603 where
604 D: serde::Deserializer<'a>,
605 {
606 Ok(match level.trim() {
607 "off" | "Off" | "OFF" => LevelFilter::Off,
608 "error" | "Error" | "ERROR" => LevelFilter::Error,
609 "warn" | "Warn" | "WARN" => LevelFilter::Warn,
610 "info" | "Info" | "INFO" => LevelFilter::Info,
611 "debug" | "Debug" | "DEBUG" => LevelFilter::Debug,
612 "trace" | "Trace" | "TRACE" => LevelFilter::Trace,
613 s => return Err(serde::de::Error::custom(format!("unknown log level: {s}"))),
614 })
615 }
616
617 pub fn serialize<S>(level: &LevelFilter, serializer: S) -> Result<S::Ok, S::Error>
618 where
619 S: Serializer,
620 {
621 let level = match level {
622 LevelFilter::Off => "off",
623 LevelFilter::Error => "error",
624 LevelFilter::Warn => "warn",
625 LevelFilter::Info => "info",
626 LevelFilter::Debug => "debug",
627 LevelFilter::Trace => "trace",
628 };
629 serializer.serialize_str(level)
630 }
631
632 pub fn deserialize<'de, D>(deserializer: D) -> Result<LevelFilter, D::Error>
633 where
634 D: Deserializer<'de>,
635 {
636 let level = String::deserialize(deserializer)?;
637 parse_log_level_str::<D>(level.as_str())
638 }
639}
640
641#[derive(Debug, Serialize, Deserialize, Clone)]
642pub struct Config {
643 pub mode: OperationMode,
645 #[serde(flatten)]
646 pub network_api: NetworkApiConfig,
647 #[serde(flatten)]
648 pub ws_api: WebsocketApiConfig,
649 #[serde(flatten)]
650 pub secrets: Secrets,
651 #[serde(with = "serde_log_level_filter")]
652 pub log_level: tracing::log::LevelFilter,
653 #[serde(flatten)]
654 config_paths: Arc<ConfigPaths>,
655 #[serde(skip)]
656 pub(crate) peer_id: Option<PeerId>,
657 #[serde(skip)]
658 pub(crate) gateways: Vec<GatewayConfig>,
659 pub(crate) is_gateway: bool,
660 pub(crate) location: Option<f64>,
661 #[serde(default = "default_max_blocking_threads")]
663 pub max_blocking_threads: usize,
664 #[serde(flatten)]
666 pub telemetry: TelemetryConfig,
667}
668
669fn default_max_blocking_threads() -> usize {
671 std::thread::available_parallelism()
672 .map(|n| (n.get() * 2).clamp(4, 32))
673 .unwrap_or(8)
674}
675
676impl Config {
677 pub fn transport_keypair(&self) -> &TransportKeypair {
678 self.secrets.transport_keypair()
679 }
680
681 pub fn paths(&self) -> Arc<ConfigPaths> {
682 self.config_paths.clone()
683 }
684}
685
686#[derive(clap::Parser, Debug, Default, Clone, Serialize, Deserialize)]
687pub struct NetworkArgs {
688 #[arg(
690 name = "network_address",
691 long = "network-address",
692 env = "NETWORK_ADDRESS"
693 )]
694 #[serde(rename = "network-address", skip_serializing_if = "Option::is_none")]
695 pub address: Option<IpAddr>,
696
697 #[arg(long, env = "NETWORK_PORT")]
699 #[serde(rename = "network-port", skip_serializing_if = "Option::is_none")]
700 pub network_port: Option<u16>,
701
702 #[arg(long = "public-network-address", env = "PUBLIC_NETWORK_ADDRESS")]
704 #[serde(
705 rename = "public-network-address",
706 skip_serializing_if = "Option::is_none"
707 )]
708 pub public_address: Option<IpAddr>,
709
710 #[arg(long = "public-network-port", env = "PUBLIC_NETWORK_PORT")]
712 #[serde(
713 rename = "public-network-port",
714 skip_serializing_if = "Option::is_none"
715 )]
716 pub public_port: Option<u16>,
717
718 #[arg(long)]
721 pub is_gateway: bool,
722
723 #[arg(long)]
725 pub skip_load_from_network: bool,
726
727 #[arg(long, hide = true)]
729 pub gateways: Option<Vec<String>>,
730
731 #[arg(long, hide = true, env = "LOCATION")]
733 pub location: Option<f64>,
734
735 #[arg(long)]
737 pub ignore_protocol_checking: bool,
738
739 #[arg(long)]
744 pub bandwidth_limit: Option<usize>,
745
746 #[arg(long)]
750 #[serde(
751 rename = "total-bandwidth-limit",
752 skip_serializing_if = "Option::is_none"
753 )]
754 pub total_bandwidth_limit: Option<usize>,
755
756 #[arg(long)]
760 #[serde(
761 rename = "min-bandwidth-per-connection",
762 skip_serializing_if = "Option::is_none"
763 )]
764 pub min_bandwidth_per_connection: Option<usize>,
765
766 #[arg(long, num_args = 0..)]
768 pub blocked_addresses: Option<Vec<SocketAddr>>,
769
770 #[arg(long, env = "TRANSIENT_BUDGET")]
772 #[serde(rename = "transient-budget", skip_serializing_if = "Option::is_none")]
773 pub transient_budget: Option<usize>,
774
775 #[arg(long, env = "TRANSIENT_TTL_SECS")]
777 #[serde(rename = "transient-ttl-secs", skip_serializing_if = "Option::is_none")]
778 pub transient_ttl_secs: Option<u64>,
779
780 #[arg(long = "min-number-of-connections", env = "MIN_NUMBER_OF_CONNECTIONS")]
782 #[serde(
783 rename = "min-number-of-connections",
784 skip_serializing_if = "Option::is_none"
785 )]
786 pub min_connections: Option<usize>,
787
788 #[arg(long = "max-number-of-connections", env = "MAX_NUMBER_OF_CONNECTIONS")]
790 #[serde(
791 rename = "max-number-of-connections",
792 skip_serializing_if = "Option::is_none"
793 )]
794 pub max_connections: Option<usize>,
795
796 #[arg(long, env = "STREAMING_THRESHOLD")]
799 #[serde(
800 rename = "streaming-threshold",
801 skip_serializing_if = "Option::is_none"
802 )]
803 pub streaming_threshold: Option<usize>,
804
805 #[arg(long, env = "LEDBAT_MIN_SSTHRESH")]
820 #[serde(
821 rename = "ledbat-min-ssthresh",
822 skip_serializing_if = "Option::is_none"
823 )]
824 pub ledbat_min_ssthresh: Option<usize>,
825
826 #[arg(long, env = "FREENET_CONGESTION_CONTROL")]
835 #[serde(rename = "congestion-control", skip_serializing_if = "Option::is_none")]
836 pub congestion_control: Option<String>,
837
838 #[arg(long, env = "FREENET_BBR_STARTUP_RATE")]
845 #[serde(rename = "bbr-startup-rate", skip_serializing_if = "Option::is_none")]
846 pub bbr_startup_rate: Option<u64>,
847}
848
849#[derive(Debug, Clone, Serialize, Deserialize)]
850pub struct InlineGwConfig {
851 pub address: SocketAddr,
853
854 #[serde(rename = "public_key")]
856 pub public_key_path: PathBuf,
857
858 pub location: Option<f64>,
860}
861
862impl NetworkArgs {
863 pub(crate) fn validate(&self) -> anyhow::Result<()> {
864 if self.is_gateway {
865 if self.public_address.is_none() {
867 return Err(anyhow::anyhow!(
868 "Gateway nodes must specify a public network address"
869 ));
870 }
871 if self.public_port.is_none() && self.network_port.is_none() {
872 return Err(anyhow::anyhow!("Gateway nodes must specify a network port"));
873 }
874 }
875 Ok(())
876 }
877}
878
879#[derive(Debug, Clone, Serialize, Deserialize)]
880pub struct NetworkApiConfig {
881 #[serde(default = "default_listening_address", rename = "network-address")]
883 pub address: IpAddr,
884
885 #[serde(default = "default_network_api_port", rename = "network-port")]
887 pub port: u16,
888
889 #[serde(
891 rename = "public_network_address",
892 skip_serializing_if = "Option::is_none"
893 )]
894 pub public_address: Option<IpAddr>,
895
896 #[serde(rename = "public_port", skip_serializing_if = "Option::is_none")]
898 pub public_port: Option<u16>,
899
900 #[serde(skip)]
902 pub ignore_protocol_version: bool,
903
904 #[serde(skip_serializing_if = "Option::is_none")]
912 pub bandwidth_limit: Option<usize>,
913
914 #[serde(skip_serializing_if = "Option::is_none")]
921 pub total_bandwidth_limit: Option<usize>,
922
923 #[serde(skip_serializing_if = "Option::is_none")]
929 pub min_bandwidth_per_connection: Option<usize>,
930
931 #[serde(skip_serializing_if = "Option::is_none")]
933 pub blocked_addresses: Option<HashSet<SocketAddr>>,
934
935 #[serde(default = "default_transient_budget", rename = "transient-budget")]
937 pub transient_budget: usize,
938
939 #[serde(default = "default_transient_ttl_secs", rename = "transient-ttl-secs")]
941 pub transient_ttl_secs: u64,
942
943 #[serde(
945 default = "default_min_connections",
946 rename = "min-number-of-connections"
947 )]
948 pub min_connections: usize,
949
950 #[serde(
952 default = "default_max_connections",
953 rename = "max-number-of-connections"
954 )]
955 pub max_connections: usize,
956
957 #[serde(
960 default = "default_streaming_threshold",
961 rename = "streaming-threshold"
962 )]
963 pub streaming_threshold: usize,
964
965 #[serde(
974 default = "default_ledbat_min_ssthresh",
975 rename = "ledbat-min-ssthresh",
976 skip_serializing_if = "Option::is_none"
977 )]
978 pub ledbat_min_ssthresh: Option<usize>,
979
980 #[serde(default = "default_congestion_control", rename = "congestion-control")]
987 pub congestion_control: String,
988
989 #[serde(
993 default = "default_bbr_startup_rate",
994 rename = "bbr-startup-rate",
995 skip_serializing_if = "Option::is_none"
996 )]
997 pub bbr_startup_rate: Option<u64>,
998}
999
1000impl NetworkApiConfig {
1001 pub fn build_congestion_config(&self) -> CongestionControlConfig {
1006 let algo = match self.congestion_control.to_lowercase().as_str() {
1007 "bbr" => CongestionControlAlgorithm::Bbr,
1008 "ledbat" => CongestionControlAlgorithm::Ledbat,
1009 _ => CongestionControlAlgorithm::FixedRate, };
1011
1012 let mut config = CongestionControlConfig::new(algo);
1013
1014 if algo == CongestionControlAlgorithm::Bbr {
1016 if let Some(rate) = self.bbr_startup_rate {
1017 tracing::debug!("Using custom BBR startup pacing rate: {} bytes/sec", rate);
1018 config = config.with_startup_min_pacing_rate(rate);
1019 }
1020 }
1021
1022 config
1023 }
1024}
1025
1026mod port_allocation;
1027use port_allocation::find_available_port;
1028
1029pub fn default_network_api_port() -> u16 {
1030 find_available_port().unwrap_or(31337) }
1032
1033fn default_transient_budget() -> usize {
1034 DEFAULT_TRANSIENT_BUDGET
1035}
1036
1037fn default_transient_ttl_secs() -> u64 {
1038 DEFAULT_TRANSIENT_TTL_SECS
1039}
1040
1041fn default_min_connections() -> usize {
1042 DEFAULT_MIN_CONNECTIONS
1043}
1044
1045fn default_max_connections() -> usize {
1046 DEFAULT_MAX_CONNECTIONS
1047}
1048
1049fn default_streaming_threshold() -> usize {
1051 64 * 1024
1052}
1053
1054fn default_ledbat_min_ssthresh() -> Option<usize> {
1061 Some(100 * 1024) }
1063
1064fn default_congestion_control() -> String {
1068 "fixedrate".to_string()
1069}
1070
1071fn default_bbr_startup_rate() -> Option<u64> {
1075 None
1076}
1077
1078#[derive(clap::Parser, Debug, Default, Copy, Clone, Serialize, Deserialize)]
1079pub struct WebsocketApiArgs {
1080 #[arg(
1082 name = "ws_api_address",
1083 long = "ws-api-address",
1084 env = "WS_API_ADDRESS"
1085 )]
1086 #[serde(rename = "ws-api-address", skip_serializing_if = "Option::is_none")]
1087 pub address: Option<IpAddr>,
1088
1089 #[arg(long, env = "WS_API_PORT")]
1091 #[serde(rename = "ws-api-port", skip_serializing_if = "Option::is_none")]
1092 pub ws_api_port: Option<u16>,
1093
1094 #[arg(long, env = "TOKEN_TTL_SECONDS")]
1096 #[serde(rename = "token-ttl-seconds", skip_serializing_if = "Option::is_none")]
1097 pub token_ttl_seconds: Option<u64>,
1098
1099 #[arg(long, env = "TOKEN_CLEANUP_INTERVAL_SECONDS")]
1101 #[serde(
1102 rename = "token-cleanup-interval-seconds",
1103 skip_serializing_if = "Option::is_none"
1104 )]
1105 pub token_cleanup_interval_seconds: Option<u64>,
1106}
1107
1108pub const DEFAULT_TELEMETRY_ENDPOINT: &str = "http://nova.locut.us:4318";
1111
1112#[derive(clap::Parser, Debug, Clone, Serialize, Deserialize)]
1113pub struct TelemetryArgs {
1114 #[arg(
1117 long = "telemetry-enabled",
1118 env = "FREENET_TELEMETRY_ENABLED",
1119 default_value = "true"
1120 )]
1121 #[serde(rename = "telemetry-enabled", default = "default_telemetry_enabled")]
1122 pub enabled: bool,
1123
1124 #[arg(long = "telemetry-endpoint", env = "FREENET_TELEMETRY_ENDPOINT")]
1126 #[serde(rename = "telemetry-endpoint", skip_serializing_if = "Option::is_none")]
1127 pub endpoint: Option<String>,
1128
1129 #[arg(
1132 long = "transport-snapshot-interval-secs",
1133 env = "FREENET_TRANSPORT_SNAPSHOT_INTERVAL_SECS"
1134 )]
1135 #[serde(
1136 rename = "transport-snapshot-interval-secs",
1137 skip_serializing_if = "Option::is_none"
1138 )]
1139 pub transport_snapshot_interval_secs: Option<u64>,
1140}
1141
1142impl Default for TelemetryArgs {
1143 fn default() -> Self {
1144 Self {
1145 enabled: true,
1146 endpoint: None,
1147 transport_snapshot_interval_secs: None,
1148 }
1149 }
1150}
1151
1152fn default_telemetry_enabled() -> bool {
1153 true
1154}
1155
1156#[derive(Debug, Clone, Serialize, Deserialize)]
1157pub struct TelemetryConfig {
1158 #[serde(default = "default_telemetry_enabled", rename = "telemetry-enabled")]
1160 pub enabled: bool,
1161
1162 #[serde(default = "default_telemetry_endpoint", rename = "telemetry-endpoint")]
1164 pub endpoint: String,
1165
1166 #[serde(
1170 default = "default_transport_snapshot_interval_secs",
1171 rename = "transport-snapshot-interval-secs"
1172 )]
1173 pub transport_snapshot_interval_secs: u64,
1174
1175 #[serde(skip)]
1178 pub is_test_environment: bool,
1179}
1180
1181fn default_transport_snapshot_interval_secs() -> u64 {
1182 30
1183}
1184
1185fn default_telemetry_endpoint() -> String {
1186 DEFAULT_TELEMETRY_ENDPOINT.to_string()
1187}
1188
1189impl Default for TelemetryConfig {
1190 fn default() -> Self {
1191 Self {
1192 enabled: true,
1193 endpoint: DEFAULT_TELEMETRY_ENDPOINT.to_string(),
1194 transport_snapshot_interval_secs: default_transport_snapshot_interval_secs(),
1195 is_test_environment: false,
1196 }
1197 }
1198}
1199
1200#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
1201pub struct WebsocketApiConfig {
1202 #[serde(default = "default_listening_address", rename = "ws-api-address")]
1204 pub address: IpAddr,
1205
1206 #[serde(default = "default_ws_api_port", rename = "ws-api-port")]
1208 pub port: u16,
1209
1210 #[serde(default = "default_token_ttl_seconds", rename = "token-ttl-seconds")]
1212 pub token_ttl_seconds: u64,
1213
1214 #[serde(
1216 default = "default_token_cleanup_interval_seconds",
1217 rename = "token-cleanup-interval-seconds"
1218 )]
1219 pub token_cleanup_interval_seconds: u64,
1220}
1221
1222#[inline]
1223const fn default_token_ttl_seconds() -> u64 {
1224 86400 }
1226
1227#[inline]
1228const fn default_token_cleanup_interval_seconds() -> u64 {
1229 300 }
1231
1232impl From<SocketAddr> for WebsocketApiConfig {
1233 fn from(addr: SocketAddr) -> Self {
1234 Self {
1235 address: addr.ip(),
1236 port: addr.port(),
1237 token_ttl_seconds: default_token_ttl_seconds(),
1238 token_cleanup_interval_seconds: default_token_cleanup_interval_seconds(),
1239 }
1240 }
1241}
1242
1243impl Default for WebsocketApiConfig {
1244 #[inline]
1245 fn default() -> Self {
1246 Self {
1247 address: default_listening_address(),
1248 port: default_ws_api_port(),
1249 token_ttl_seconds: default_token_ttl_seconds(),
1250 token_cleanup_interval_seconds: default_token_cleanup_interval_seconds(),
1251 }
1252 }
1253}
1254
1255#[inline]
1256const fn default_listening_address() -> IpAddr {
1257 IpAddr::V4(Ipv4Addr::UNSPECIFIED)
1258}
1259
1260#[inline]
1261const fn default_local_address() -> IpAddr {
1262 IpAddr::V4(Ipv4Addr::LOCALHOST)
1263}
1264
1265#[inline]
1266const fn default_ws_api_port() -> u16 {
1267 7509
1268}
1269
1270#[derive(clap::Parser, Default, Debug, Clone, Serialize, Deserialize)]
1271pub struct ConfigPathsArgs {
1272 #[arg(long, default_value = None, env = "CONFIG_DIR")]
1274 pub config_dir: Option<PathBuf>,
1275 #[arg(long, default_value = None, env = "DATA_DIR")]
1277 pub data_dir: Option<PathBuf>,
1278 #[arg(long, default_value = None, env = "LOG_DIR")]
1280 pub log_dir: Option<PathBuf>,
1281}
1282
1283impl ConfigPathsArgs {
1284 fn merge(&mut self, other: ConfigPaths) {
1285 self.config_dir.get_or_insert(other.config_dir);
1286 self.data_dir.get_or_insert(other.data_dir);
1287 self.log_dir = self.log_dir.take().or(other.log_dir);
1288 }
1289
1290 fn default_dirs(id: Option<&str>) -> std::io::Result<Either<ProjectDirs, PathBuf>> {
1291 let default_dir: Either<_, _> = if cfg!(any(test, debug_assertions)) || id.is_some() {
1293 let base_name = if let Some(id) = id {
1294 format!("freenet-{id}")
1295 } else {
1296 "freenet".into()
1297 };
1298 let temp_path = std::env::temp_dir().join(&base_name);
1299
1300 if temp_path.exists() && fs::remove_dir_all(&temp_path).is_err() {
1305 let unique_path =
1306 std::env::temp_dir().join(format!("{}-{}", base_name, std::process::id()));
1307 let _cleanup = fs::remove_dir_all(&unique_path);
1309 return Ok(Either::Right(unique_path));
1310 }
1311 Either::Right(temp_path)
1312 } else {
1313 Either::Left(
1314 ProjectDirs::from(QUALIFIER, ORGANIZATION, APPLICATION)
1315 .ok_or(std::io::ErrorKind::NotFound)?,
1316 )
1317 };
1318 Ok(default_dir)
1319 }
1320
1321 pub fn build(self, id: Option<&str>) -> std::io::Result<ConfigPaths> {
1322 let app_data_dir = self
1323 .data_dir
1324 .map(Ok::<_, std::io::Error>)
1325 .unwrap_or_else(|| {
1326 let default_dirs = Self::default_dirs(id)?;
1327 let Either::Left(defaults) = default_dirs else {
1328 unreachable!("default_dirs should return Left if data_dir is None and id is not set for temp dir")
1329 };
1330 Ok(defaults.data_dir().to_path_buf())
1331 })?;
1332 let contracts_dir = app_data_dir.join("contracts");
1333 let delegates_dir = app_data_dir.join("delegates");
1334 let secrets_dir = app_data_dir.join("secrets");
1335 let db_dir = app_data_dir.join("db");
1336
1337 if !contracts_dir.exists() {
1338 fs::create_dir_all(&contracts_dir)?;
1339 fs::create_dir_all(contracts_dir.join("local"))?;
1340 }
1341
1342 if !delegates_dir.exists() {
1343 fs::create_dir_all(&delegates_dir)?;
1344 fs::create_dir_all(delegates_dir.join("local"))?;
1345 }
1346
1347 if !secrets_dir.exists() {
1348 fs::create_dir_all(&secrets_dir)?;
1349 fs::create_dir_all(secrets_dir.join("local"))?;
1350 }
1351
1352 if !db_dir.exists() {
1353 fs::create_dir_all(&db_dir)?;
1354 fs::create_dir_all(db_dir.join("local"))?;
1355 }
1356
1357 let event_log = app_data_dir.join("_EVENT_LOG");
1358 if !event_log.exists() {
1359 fs::write(&event_log, [])?;
1360 let mut local_file = event_log.clone();
1361 local_file.set_file_name("_EVENT_LOG_LOCAL");
1362 fs::write(local_file, [])?;
1363 }
1364
1365 let config_dir = self
1366 .config_dir
1367 .map(Ok::<_, std::io::Error>)
1368 .unwrap_or_else(|| {
1369 let default_dirs = Self::default_dirs(id)?;
1370 let Either::Left(defaults) = default_dirs else {
1371 unreachable!("default_dirs should return Left if config_dir is None and id is not set for temp dir")
1372 };
1373 Ok(defaults.config_dir().to_path_buf())
1374 })?;
1375
1376 let log_dir = self.log_dir.or_else(get_log_dir);
1377
1378 Ok(ConfigPaths {
1379 config_dir,
1380 data_dir: app_data_dir,
1381 contracts_dir,
1382 delegates_dir,
1383 secrets_dir,
1384 db_dir,
1385 event_log,
1386 log_dir,
1387 })
1388 }
1389}
1390
1391#[derive(Debug, Clone, Serialize, Deserialize)]
1392pub struct ConfigPaths {
1393 contracts_dir: PathBuf,
1394 delegates_dir: PathBuf,
1395 secrets_dir: PathBuf,
1396 db_dir: PathBuf,
1397 event_log: PathBuf,
1398 data_dir: PathBuf,
1399 config_dir: PathBuf,
1400 #[serde(default = "get_log_dir")]
1401 log_dir: Option<PathBuf>,
1402}
1403
1404impl ConfigPaths {
1405 pub fn db_dir(&self, mode: OperationMode) -> PathBuf {
1406 match mode {
1407 OperationMode::Local => self.db_dir.join("local"),
1408 OperationMode::Network => self.db_dir.to_owned(),
1409 }
1410 }
1411
1412 pub fn with_db_dir(mut self, db_dir: PathBuf) -> Self {
1413 self.db_dir = db_dir;
1414 self
1415 }
1416
1417 pub fn contracts_dir(&self, mode: OperationMode) -> PathBuf {
1418 match mode {
1419 OperationMode::Local => self.contracts_dir.join("local"),
1420 OperationMode::Network => self.contracts_dir.to_owned(),
1421 }
1422 }
1423
1424 pub fn with_contract_dir(mut self, contracts_dir: PathBuf) -> Self {
1425 self.contracts_dir = contracts_dir;
1426 self
1427 }
1428
1429 pub fn delegates_dir(&self, mode: OperationMode) -> PathBuf {
1430 match mode {
1431 OperationMode::Local => self.delegates_dir.join("local"),
1432 OperationMode::Network => self.delegates_dir.to_owned(),
1433 }
1434 }
1435
1436 pub fn with_delegates_dir(mut self, delegates_dir: PathBuf) -> Self {
1437 self.delegates_dir = delegates_dir;
1438 self
1439 }
1440
1441 pub fn config_dir(&self) -> PathBuf {
1442 self.config_dir.clone()
1443 }
1444
1445 pub fn secrets_dir(&self, mode: OperationMode) -> PathBuf {
1446 match mode {
1447 OperationMode::Local => self.secrets_dir.join("local"),
1448 OperationMode::Network => self.secrets_dir.to_owned(),
1449 }
1450 }
1451
1452 pub fn with_secrets_dir(mut self, secrets_dir: PathBuf) -> Self {
1453 self.secrets_dir = secrets_dir;
1454 self
1455 }
1456
1457 pub fn event_log(&self, mode: OperationMode) -> PathBuf {
1458 match mode {
1459 OperationMode::Local => {
1460 let mut local_file = self.event_log.clone();
1461 local_file.set_file_name("_EVENT_LOG_LOCAL");
1462 local_file
1463 }
1464 OperationMode::Network => self.event_log.to_owned(),
1465 }
1466 }
1467
1468 pub fn log_dir(&self) -> Option<&Path> {
1469 self.log_dir.as_deref()
1470 }
1471
1472 pub fn with_event_log(mut self, event_log: PathBuf) -> Self {
1473 self.event_log = event_log;
1474 self
1475 }
1476
1477 pub fn iter(&self) -> ConfigPathsIter<'_> {
1478 ConfigPathsIter {
1479 curr: 0,
1480 config_paths: self,
1481 }
1482 }
1483
1484 fn path_by_index(&self, index: usize) -> (bool, &PathBuf) {
1485 match index {
1486 0 => (true, &self.contracts_dir),
1487 1 => (true, &self.delegates_dir),
1488 2 => (true, &self.secrets_dir),
1489 3 => (true, &self.db_dir),
1490 4 => (true, &self.data_dir),
1491 5 => (false, &self.event_log),
1492 6 => (true, &self.config_dir),
1493 _ => panic!("invalid path index"),
1494 }
1495 }
1496
1497 const MAX_PATH_INDEX: usize = 6;
1498}
1499
1500pub struct ConfigPathsIter<'a> {
1501 curr: usize,
1502 config_paths: &'a ConfigPaths,
1503}
1504
1505impl<'a> Iterator for ConfigPathsIter<'a> {
1506 type Item = (bool, &'a PathBuf);
1508
1509 fn next(&mut self) -> Option<Self::Item> {
1510 if self.curr > ConfigPaths::MAX_PATH_INDEX {
1511 None
1512 } else {
1513 let path = self.config_paths.path_by_index(self.curr);
1514 self.curr += 1;
1515 Some(path)
1516 }
1517 }
1518
1519 fn size_hint(&self) -> (usize, Option<usize>) {
1520 (0, Some(ConfigPaths::MAX_PATH_INDEX))
1521 }
1522}
1523
1524impl core::iter::FusedIterator for ConfigPathsIter<'_> {}
1525
1526impl Config {
1527 pub fn db_dir(&self) -> PathBuf {
1528 self.config_paths.db_dir(self.mode)
1529 }
1530
1531 pub fn contracts_dir(&self) -> PathBuf {
1532 self.config_paths.contracts_dir(self.mode)
1533 }
1534
1535 pub fn delegates_dir(&self) -> PathBuf {
1536 self.config_paths.delegates_dir(self.mode)
1537 }
1538
1539 pub fn secrets_dir(&self) -> PathBuf {
1540 self.config_paths.secrets_dir(self.mode)
1541 }
1542
1543 pub fn event_log(&self) -> PathBuf {
1544 self.config_paths.event_log(self.mode)
1545 }
1546
1547 pub fn config_dir(&self) -> PathBuf {
1548 self.config_paths.config_dir()
1549 }
1550}
1551
1552#[derive(Debug, Serialize, Deserialize, Default)]
1553struct Gateways {
1554 pub gateways: Vec<GatewayConfig>,
1555}
1556
1557impl Gateways {
1558 pub fn merge_and_deduplicate(&mut self, other: Gateways) {
1559 let mut existing_gateways: HashSet<_> = self.gateways.drain(..).collect();
1560 for gateway in other.gateways {
1561 existing_gateways.insert(gateway);
1562 }
1563 self.gateways = existing_gateways.into_iter().collect();
1564 }
1565
1566 pub fn save_to_file(&self, path: &Path) -> anyhow::Result<()> {
1567 if let Some(parent) = path.parent() {
1569 fs::create_dir_all(parent)?;
1570 }
1571 let content = toml::to_string(self)?;
1572 fs::write(path, content)?;
1573 Ok(())
1574 }
1575}
1576
1577#[derive(Debug, Serialize, Deserialize, Clone)]
1578pub struct GatewayConfig {
1579 pub address: Address,
1581
1582 #[serde(rename = "public_key")]
1584 pub public_key_path: PathBuf,
1585
1586 #[serde(skip_serializing_if = "Option::is_none")]
1588 pub location: Option<f64>,
1589}
1590
1591impl PartialEq for GatewayConfig {
1592 fn eq(&self, other: &Self) -> bool {
1593 self.address == other.address
1594 }
1595}
1596
1597impl Eq for GatewayConfig {}
1598
1599impl std::hash::Hash for GatewayConfig {
1600 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1601 self.address.hash(state);
1602 }
1603}
1604
1605#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)]
1606pub enum Address {
1607 #[serde(rename = "hostname")]
1608 Hostname(String),
1609 #[serde(rename = "host_address")]
1610 HostAddress(SocketAddr),
1611}
1612
1613pub struct GlobalExecutor;
1625
1626impl GlobalExecutor {
1627 pub(crate) fn initialize_async_rt() -> Option<Runtime> {
1630 if tokio::runtime::Handle::try_current().is_ok() {
1631 tracing::debug!(target: "freenet::diagnostics::thread_explosion", "GlobalExecutor: runtime exists");
1632 None
1633 } else {
1634 tracing::warn!(target: "freenet::diagnostics::thread_explosion", "GlobalExecutor: Creating fallback runtime");
1635 let mut builder = tokio::runtime::Builder::new_multi_thread();
1636 builder.enable_all().thread_name("freenet-node");
1637 if cfg!(debug_assertions) {
1638 builder.worker_threads(2).max_blocking_threads(2);
1639 }
1640 Some(builder.build().expect("failed to build tokio runtime"))
1641 }
1642 }
1643
1644 #[inline]
1645 pub fn spawn<R: Send + 'static>(
1646 f: impl Future<Output = R> + Send + 'static,
1647 ) -> tokio::task::JoinHandle<R> {
1648 if let Ok(handle) = tokio::runtime::Handle::try_current() {
1649 handle.spawn(f)
1650 } else if let Some(rt) = &*ASYNC_RT {
1651 tracing::warn!(target: "freenet::diagnostics::thread_explosion", "GlobalExecutor::spawn using fallback");
1652 rt.spawn(f)
1653 } else {
1654 unreachable!("ASYNC_RT should be initialized if Handle::try_current fails")
1655 }
1656 }
1657}
1658
1659use rand::rngs::SmallRng;
1664use rand::{Rng, RngCore, SeedableRng};
1665
1666static THREAD_INDEX_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1667
1668std::thread_local! {
1669 static THREAD_RNG: std::cell::RefCell<Option<SmallRng>> = const { std::cell::RefCell::new(None) };
1670 static THREAD_INDEX: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
1671 static THREAD_SEED: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
1672}
1673
1674pub struct GlobalRng;
1706
1707pub struct SeedGuard {
1725 _private: (),
1727}
1728
1729impl Drop for SeedGuard {
1730 fn drop(&mut self) {
1731 GlobalRng::clear_seed();
1732 }
1733}
1734
1735impl GlobalRng {
1736 pub fn set_seed(seed: u64) {
1746 THREAD_SEED.with(|s| s.set(Some(seed)));
1747 THREAD_RNG.with(|rng| {
1748 *rng.borrow_mut() = None;
1749 });
1750 THREAD_INDEX.with(|idx| idx.set(Some(0)));
1753 }
1754
1755 pub fn clear_seed() {
1757 THREAD_SEED.with(|s| s.set(None));
1758 THREAD_RNG.with(|rng| {
1759 *rng.borrow_mut() = None;
1760 });
1761 THREAD_INDEX.with(|idx| idx.set(None));
1762 }
1763
1764 pub fn thread_index() -> u64 {
1769 THREAD_INDEX.with(|c| match c.get() {
1770 Some(idx) => idx,
1771 None => {
1772 let idx = THREAD_INDEX_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
1773 c.set(Some(idx));
1774 idx
1775 }
1776 })
1777 }
1778
1779 pub fn is_seeded() -> bool {
1781 THREAD_SEED.with(|s| s.get()).is_some()
1782 }
1783
1784 pub fn seed_guard(seed: u64) -> SeedGuard {
1797 Self::set_seed(seed);
1798 SeedGuard { _private: () }
1799 }
1800
1801 pub fn scoped_seed<F, R>(seed: u64, f: F) -> R
1816 where
1817 F: FnOnce() -> R,
1818 {
1819 let _guard = Self::seed_guard(seed);
1820 f()
1821 }
1822
1823 #[inline]
1826 pub fn with_rng<F, R>(f: F) -> R
1827 where
1828 F: FnOnce(&mut dyn RngCore) -> R,
1829 {
1830 let seed = THREAD_SEED.with(|s| s.get());
1833
1834 if let Some(seed) = seed {
1835 THREAD_RNG.with(|rng_cell| {
1837 let mut rng_ref = rng_cell.borrow_mut();
1838 if rng_ref.is_none() {
1839 let thread_seed =
1840 seed.wrapping_add(Self::thread_index().wrapping_mul(0x9E3779B97F4A7C15));
1841 *rng_ref = Some(SmallRng::seed_from_u64(thread_seed));
1842 }
1843 f(rng_ref.as_mut().unwrap())
1844 })
1845 } else {
1846 f(&mut rand::rng())
1848 }
1849 }
1850
1851 #[inline]
1853 pub fn random_range<T, R>(range: R) -> T
1854 where
1855 T: rand::distr::uniform::SampleUniform,
1856 R: rand::distr::uniform::SampleRange<T>,
1857 {
1858 Self::with_rng(|rng| rng.random_range(range))
1859 }
1860
1861 #[inline]
1863 pub fn random_bool(probability: f64) -> bool {
1864 Self::with_rng(|rng| rng.random_bool(probability))
1865 }
1866
1867 #[inline]
1869 pub fn choose<T>(slice: &[T]) -> Option<&T> {
1870 if slice.is_empty() {
1871 None
1872 } else {
1873 let idx = Self::random_range(0..slice.len());
1874 Some(&slice[idx])
1875 }
1876 }
1877
1878 #[inline]
1880 pub fn shuffle<T>(slice: &mut [T]) {
1881 Self::with_rng(|rng| {
1882 use rand::seq::SliceRandom;
1883 slice.shuffle(rng);
1884 })
1885 }
1886
1887 #[inline]
1889 pub fn fill_bytes(dest: &mut [u8]) {
1890 Self::with_rng(|rng| rng.fill_bytes(dest))
1891 }
1892
1893 #[inline]
1895 pub fn random_u64() -> u64 {
1896 Self::with_rng(|rng| rng.random())
1897 }
1898
1899 #[inline]
1901 pub fn random_u32() -> u32 {
1902 Self::with_rng(|rng| rng.random())
1903 }
1904}
1905
1906std::thread_local! {
1912 static SIMULATION_TIME_MS: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
1913 static SIMULATION_TIME_COUNTER: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
1914}
1915
1916pub struct GlobalSimulationTime;
1936
1937impl GlobalSimulationTime {
1938 pub fn set_time_ms(time_ms: u64) {
1942 SIMULATION_TIME_MS.with(|t| t.set(Some(time_ms)));
1943 SIMULATION_TIME_COUNTER.with(|c| c.set(0));
1944 }
1945
1946 pub fn clear_time() {
1948 SIMULATION_TIME_MS.with(|t| t.set(None));
1949 SIMULATION_TIME_COUNTER.with(|c| c.set(0));
1950 }
1951
1952 pub fn current_time_ms() -> u64 {
1957 SIMULATION_TIME_MS.with(|t| {
1958 if let Some(base_time) = t.get() {
1959 let counter = SIMULATION_TIME_COUNTER.with(|c| {
1960 let val = c.get();
1961 c.set(val + 1);
1962 val
1963 });
1964 base_time.saturating_add(counter)
1965 } else {
1966 use std::time::{SystemTime, UNIX_EPOCH};
1967 SystemTime::now()
1968 .duration_since(UNIX_EPOCH)
1969 .expect("system time before unix epoch")
1970 .as_millis() as u64
1971 }
1972 })
1973 }
1974
1975 pub fn read_time_ms() -> u64 {
1980 SIMULATION_TIME_MS.with(|t| {
1981 if let Some(base_time) = t.get() {
1982 let counter = SIMULATION_TIME_COUNTER.with(|c| c.get());
1983 base_time.saturating_add(counter)
1984 } else {
1985 use std::time::{SystemTime, UNIX_EPOCH};
1986 SystemTime::now()
1987 .duration_since(UNIX_EPOCH)
1988 .expect("system time before unix epoch")
1989 .as_millis() as u64
1990 }
1991 })
1992 }
1993
1994 pub fn is_simulation_time() -> bool {
1996 SIMULATION_TIME_MS.with(|t| t.get().is_some())
1997 }
1998
1999 pub fn new_ulid() -> ulid::Ulid {
2007 use ulid::Ulid;
2008
2009 if GlobalRng::is_seeded() || Self::is_simulation_time() {
2010 let timestamp_ms = Self::current_time_ms();
2012
2013 let mut random_bytes = [0u8; 10];
2015 GlobalRng::fill_bytes(&mut random_bytes);
2016
2017 let ts = (timestamp_ms as u128) << 80;
2020 let rand_high = (random_bytes[0] as u128) << 72;
2021 let rand_mid = u64::from_be_bytes([
2022 random_bytes[1],
2023 random_bytes[2],
2024 random_bytes[3],
2025 random_bytes[4],
2026 random_bytes[5],
2027 random_bytes[6],
2028 random_bytes[7],
2029 random_bytes[8],
2030 ]) as u128;
2031 let rand_low = (random_bytes[9] as u128) << 56;
2032 let ulid_value = ts | rand_high | (rand_mid << 8) | rand_low;
2033
2034 Ulid(ulid_value)
2035 } else {
2036 Ulid::new()
2038 }
2039 }
2040}
2041
2042std::thread_local! {
2047 static SIMULATION_TRANSPORT_OPT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
2048}
2049
2050pub struct SimulationTransportOpt;
2067
2068impl SimulationTransportOpt {
2069 pub fn enable() {
2071 SIMULATION_TRANSPORT_OPT.with(|f| f.set(true));
2072 }
2073
2074 pub fn disable() {
2076 SIMULATION_TRANSPORT_OPT.with(|f| f.set(false));
2077 }
2078
2079 pub fn is_enabled() -> bool {
2081 SIMULATION_TRANSPORT_OPT.with(|f| f.get())
2082 }
2083}
2084
2085std::thread_local! {
2091 static GLOBAL_RESYNC_REQUESTS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
2092 static GLOBAL_DELTA_SENDS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
2093 static GLOBAL_FULL_STATE_SENDS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
2094 static GLOBAL_PENDING_OP_INSERTS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
2095 static GLOBAL_PENDING_OP_REMOVES: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
2096 static GLOBAL_PENDING_OP_HWM: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
2097 static GLOBAL_NEIGHBOR_CACHE_UPDATES: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
2098 static GLOBAL_ANTI_STARVATION_TRIGGERS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
2099}
2100
2101pub struct GlobalTestMetrics;
2121
2122impl GlobalTestMetrics {
2123 pub fn reset() {
2125 GLOBAL_RESYNC_REQUESTS.with(|c| c.set(0));
2126 GLOBAL_DELTA_SENDS.with(|c| c.set(0));
2127 GLOBAL_FULL_STATE_SENDS.with(|c| c.set(0));
2128 GLOBAL_PENDING_OP_INSERTS.with(|c| c.set(0));
2129 GLOBAL_PENDING_OP_REMOVES.with(|c| c.set(0));
2130 GLOBAL_PENDING_OP_HWM.with(|c| c.set(0));
2131 GLOBAL_NEIGHBOR_CACHE_UPDATES.with(|c| c.set(0));
2132 GLOBAL_ANTI_STARVATION_TRIGGERS.with(|c| c.set(0));
2133 }
2134
2135 pub fn record_resync_request() {
2138 GLOBAL_RESYNC_REQUESTS.with(|c| c.set(c.get() + 1));
2139 }
2140
2141 pub fn resync_requests() -> u64 {
2143 GLOBAL_RESYNC_REQUESTS.with(|c| c.get())
2144 }
2145
2146 pub fn record_delta_send() {
2149 GLOBAL_DELTA_SENDS.with(|c| c.set(c.get() + 1));
2150 }
2151
2152 pub fn delta_sends() -> u64 {
2154 GLOBAL_DELTA_SENDS.with(|c| c.get())
2155 }
2156
2157 pub fn record_full_state_send() {
2160 GLOBAL_FULL_STATE_SENDS.with(|c| c.set(c.get() + 1));
2161 }
2162
2163 pub fn full_state_sends() -> u64 {
2165 GLOBAL_FULL_STATE_SENDS.with(|c| c.get())
2166 }
2167
2168 pub fn record_pending_op_insert() {
2169 GLOBAL_PENDING_OP_INSERTS.with(|c| c.set(c.get() + 1));
2170 }
2171
2172 pub fn pending_op_inserts() -> u64 {
2173 GLOBAL_PENDING_OP_INSERTS.with(|c| c.get())
2174 }
2175
2176 pub fn record_pending_op_remove() {
2177 GLOBAL_PENDING_OP_REMOVES.with(|c| c.set(c.get() + 1));
2178 }
2179
2180 pub fn pending_op_removes() -> u64 {
2181 GLOBAL_PENDING_OP_REMOVES.with(|c| c.get())
2182 }
2183
2184 pub fn record_pending_op_size(len: u64) {
2186 GLOBAL_PENDING_OP_HWM.with(|c| c.set(c.get().max(len)));
2187 }
2188
2189 pub fn pending_op_high_water_mark() -> u64 {
2190 GLOBAL_PENDING_OP_HWM.with(|c| c.get())
2191 }
2192
2193 pub fn record_neighbor_cache_update() {
2194 GLOBAL_NEIGHBOR_CACHE_UPDATES.with(|c| c.set(c.get() + 1));
2195 }
2196
2197 pub fn neighbor_cache_updates() -> u64 {
2198 GLOBAL_NEIGHBOR_CACHE_UPDATES.with(|c| c.get())
2199 }
2200
2201 pub fn record_anti_starvation_trigger() {
2202 GLOBAL_ANTI_STARVATION_TRIGGERS.with(|c| c.set(c.get() + 1));
2203 }
2204
2205 pub fn anti_starvation_triggers() -> u64 {
2206 GLOBAL_ANTI_STARVATION_TRIGGERS.with(|c| c.get())
2207 }
2208}
2209
2210pub fn set_logger(
2211 level: Option<tracing::level_filters::LevelFilter>,
2212 endpoint: Option<String>,
2213 log_dir: Option<&Path>,
2214) {
2215 #[cfg(feature = "trace")]
2216 {
2217 static LOGGER_SET: AtomicBool = AtomicBool::new(false);
2218 if LOGGER_SET
2219 .compare_exchange(
2220 false,
2221 true,
2222 std::sync::atomic::Ordering::Release,
2223 std::sync::atomic::Ordering::SeqCst,
2224 )
2225 .is_err()
2226 {
2227 return;
2228 }
2229
2230 crate::tracing::tracer::init_tracer(level, endpoint, log_dir)
2231 .expect("failed tracing initialization")
2232 }
2233}
2234
2235async fn load_gateways_from_index(url: &str, pub_keys_dir: &Path) -> anyhow::Result<Gateways> {
2236 let response = reqwest::get(url).await?.error_for_status()?.text().await?;
2237 let mut gateways: Gateways = toml::from_str(&response)?;
2238 let mut base_url = reqwest::Url::parse(url)?;
2239 base_url.set_path("");
2240 let mut valid_gateways = Vec::new();
2241
2242 for gateway in &mut gateways.gateways {
2243 gateway.location = None; let public_key_url = base_url.join(&gateway.public_key_path.to_string_lossy())?;
2245 let public_key_response = reqwest::get(public_key_url).await?.error_for_status()?;
2246 let file_name = gateway
2247 .public_key_path
2248 .file_name()
2249 .ok_or_else(|| anyhow::anyhow!("Invalid public key path"))?;
2250 let local_path = pub_keys_dir.join(file_name);
2251 let mut public_key_file = File::create(&local_path)?;
2252 let content = public_key_response.bytes().await?;
2253 std::io::copy(&mut content.as_ref(), &mut public_key_file)?;
2254
2255 let mut key_file = File::open(&local_path).with_context(|| {
2258 format!(
2259 "failed loading gateway pubkey from {:?}",
2260 gateway.public_key_path
2261 )
2262 })?;
2263 let mut buf = String::new();
2264 key_file.read_to_string(&mut buf)?;
2265 let buf = buf.trim();
2266
2267 if buf.starts_with("-----BEGIN") {
2269 tracing::warn!(
2270 public_key_path = ?gateway.public_key_path,
2271 "Gateway uses legacy RSA PEM public key format. \
2272 Gateway needs to be updated to X25519 format. Skipping."
2273 );
2274 continue;
2275 }
2276
2277 if let Ok(key_bytes) = hex::decode(buf) {
2278 if key_bytes.len() == 32 {
2279 gateway.public_key_path = local_path;
2280 valid_gateways.push(gateway.clone());
2281 } else {
2282 tracing::warn!(
2283 public_key_path = ?gateway.public_key_path,
2284 "Invalid public key length {} (expected 32), ignoring",
2285 key_bytes.len()
2286 );
2287 }
2288 } else {
2289 tracing::warn!(
2290 public_key_path = ?gateway.public_key_path,
2291 "Invalid public key hex encoding in remote gateway file, ignoring"
2292 );
2293 }
2294 }
2295
2296 gateways.gateways = valid_gateways;
2297 Ok(gateways)
2298}
2299
2300#[cfg(test)]
2301mod tests {
2302 use httptest::{matchers::*, responders::*, Expectation, Server};
2303
2304 use crate::node::NodeConfig;
2305 use crate::transport::TransportKeypair;
2306
2307 use super::*;
2308
2309 #[tokio::test]
2310 async fn test_serde_config_args() {
2311 let temp_dir = tempfile::tempdir().unwrap();
2313 let args = ConfigArgs {
2314 mode: Some(OperationMode::Local),
2315 config_paths: ConfigPathsArgs {
2316 config_dir: Some(temp_dir.path().to_path_buf()),
2317 data_dir: Some(temp_dir.path().to_path_buf()),
2318 log_dir: Some(temp_dir.path().to_path_buf()),
2319 },
2320 ..Default::default()
2321 };
2322 let cfg = args.build().await.unwrap();
2323 let serialized = toml::to_string(&cfg).unwrap();
2324 let _: Config = toml::from_str(&serialized).unwrap();
2325 }
2326
2327 #[tokio::test]
2328 async fn test_load_gateways_from_index() {
2329 let server = Server::run();
2330 server.expect(
2331 Expectation::matching(all_of!(request::method("GET"), request::path("/gateways")))
2332 .respond_with(status_code(200).body(
2333 r#"
2334 [[gateways]]
2335 address = { hostname = "example.com" }
2336 public_key = "/path/to/public_key.pem"
2337 "#,
2338 )),
2339 );
2340
2341 let url = server.url_str("/gateways");
2342
2343 let keypair = TransportKeypair::new();
2345 let key_hex = hex::encode(keypair.public().as_bytes());
2346 server.expect(
2347 Expectation::matching(request::path("/path/to/public_key.pem"))
2348 .respond_with(status_code(200).body(key_hex)),
2349 );
2350
2351 let pub_keys_dir = tempfile::tempdir().unwrap();
2352 let gateways = load_gateways_from_index(&url, pub_keys_dir.path())
2353 .await
2354 .unwrap();
2355
2356 assert_eq!(gateways.gateways.len(), 1);
2357 assert_eq!(
2358 gateways.gateways[0].address,
2359 Address::Hostname("example.com".to_string())
2360 );
2361 assert_eq!(
2362 gateways.gateways[0].public_key_path,
2363 pub_keys_dir.path().join("public_key.pem")
2364 );
2365 assert!(pub_keys_dir.path().join("public_key.pem").exists());
2366 }
2367
2368 #[test]
2369 fn test_gateways() {
2370 let gateways = Gateways {
2371 gateways: vec![
2372 GatewayConfig {
2373 address: Address::HostAddress(
2374 ([127, 0, 0, 1], default_network_api_port()).into(),
2375 ),
2376 public_key_path: PathBuf::from("path/to/key"),
2377 location: None,
2378 },
2379 GatewayConfig {
2380 address: Address::Hostname("technic.locut.us".to_string()),
2381 public_key_path: PathBuf::from("path/to/key"),
2382 location: None,
2383 },
2384 ],
2385 };
2386
2387 let serialized = toml::to_string(&gateways).unwrap();
2388 let _: Gateways = toml::from_str(&serialized).unwrap();
2389 }
2390
2391 #[tokio::test]
2392 #[ignore = "Requires gateway keys to be updated to X25519 format (issue #2531)"]
2393 async fn test_remote_freenet_gateways() {
2394 let tmp_dir = tempfile::tempdir().unwrap();
2395 let gateways = load_gateways_from_index(FREENET_GATEWAYS_INDEX, tmp_dir.path())
2396 .await
2397 .unwrap();
2398 assert!(!gateways.gateways.is_empty());
2399
2400 for gw in gateways.gateways {
2401 assert!(gw.public_key_path.exists());
2402 let key_contents = std::fs::read_to_string(&gw.public_key_path).unwrap();
2404 let key_bytes =
2405 hex::decode(key_contents.trim()).expect("Gateway public key should be valid hex");
2406 assert_eq!(
2407 key_bytes.len(),
2408 32,
2409 "Gateway public key should be 32 bytes (X25519)"
2410 );
2411 let socket = NodeConfig::parse_socket_addr(&gw.address).await.unwrap();
2412 assert!(socket.port() > 1024); }
2415 }
2416
2417 #[test]
2418 fn test_streaming_config_defaults_via_serde() {
2419 let minimal_config = r#"
2420 network-address = "127.0.0.1"
2421 network-port = 8080
2422 "#;
2423 let network_api: NetworkApiConfig = toml::from_str(minimal_config).unwrap();
2424 assert_eq!(
2425 network_api.streaming_threshold,
2426 64 * 1024,
2427 "Default streaming threshold should be 64KB"
2428 );
2429 }
2430
2431 #[test]
2432 fn test_streaming_config_serde() {
2433 let config_str = r#"
2434 network-address = "127.0.0.1"
2435 network-port = 8080
2436 streaming-threshold = 131072
2437 "#;
2438
2439 let config: NetworkApiConfig = toml::from_str(config_str).unwrap();
2440 assert_eq!(config.streaming_threshold, 128 * 1024);
2441
2442 let serialized = toml::to_string(&config).unwrap();
2443 assert!(serialized.contains("streaming-threshold = 131072"));
2444 }
2445
2446 #[test]
2447 fn test_network_args_streaming_defaults() {
2448 let args = NetworkArgs::default();
2449 assert!(
2450 args.streaming_threshold.is_none(),
2451 "NetworkArgs.streaming_threshold should be None by default"
2452 );
2453 }
2454
2455 #[test]
2456 fn test_congestion_control_config_defaults() {
2457 let config_str = r#"
2459 network-address = "127.0.0.1"
2460 network-port = 8080
2461 "#;
2462 let network_api: NetworkApiConfig = toml::from_str(config_str).unwrap();
2463 assert_eq!(
2464 network_api.congestion_control, "fixedrate",
2465 "Default congestion control should be fixedrate"
2466 );
2467 assert!(
2468 network_api.bbr_startup_rate.is_none(),
2469 "Default BBR startup rate should be None"
2470 );
2471
2472 let cc_config = network_api.build_congestion_config();
2474 assert_eq!(cc_config.algorithm, CongestionControlAlgorithm::FixedRate);
2475 }
2476
2477 #[test]
2478 fn test_congestion_control_config_bbr() {
2479 let config_str = r#"
2481 network-address = "127.0.0.1"
2482 network-port = 8080
2483 congestion-control = "bbr"
2484 bbr-startup-rate = 10000000
2485 "#;
2486
2487 let config: NetworkApiConfig = toml::from_str(config_str).unwrap();
2488 assert_eq!(config.congestion_control, "bbr");
2489 assert_eq!(config.bbr_startup_rate, Some(10_000_000));
2490
2491 let cc_config = config.build_congestion_config();
2493 assert_eq!(cc_config.algorithm, CongestionControlAlgorithm::Bbr);
2494 }
2495
2496 #[test]
2497 fn test_congestion_control_config_ledbat() {
2498 let config_str = r#"
2500 network-address = "127.0.0.1"
2501 network-port = 8080
2502 congestion-control = "ledbat"
2503 "#;
2504
2505 let config: NetworkApiConfig = toml::from_str(config_str).unwrap();
2506 assert_eq!(config.congestion_control, "ledbat");
2507
2508 let cc_config = config.build_congestion_config();
2509 assert_eq!(cc_config.algorithm, CongestionControlAlgorithm::Ledbat);
2510 }
2511
2512 #[test]
2513 fn test_congestion_control_config_serde_roundtrip() {
2514 let config_str = r#"
2516 network-address = "127.0.0.1"
2517 network-port = 8080
2518 congestion-control = "bbr"
2519 bbr-startup-rate = 5000000
2520 "#;
2521
2522 let config: NetworkApiConfig = toml::from_str(config_str).unwrap();
2523
2524 let serialized = toml::to_string(&config).unwrap();
2526 assert!(serialized.contains("congestion-control = \"bbr\""));
2527 assert!(serialized.contains("bbr-startup-rate = 5000000"));
2528
2529 let config2: NetworkApiConfig = toml::from_str(&serialized).unwrap();
2531 assert_eq!(config2.congestion_control, "bbr");
2532 assert_eq!(config2.bbr_startup_rate, Some(5_000_000));
2533 }
2534
2535 #[test]
2536 fn test_set_seed_pins_thread_index_to_zero() {
2537 GlobalRng::clear_seed();
2538
2539 GlobalRng::set_seed(0xDEAD_BEEF);
2540 assert_eq!(GlobalRng::thread_index(), 0);
2541
2542 let val1 = GlobalRng::random_u64();
2544 GlobalRng::set_seed(0xDEAD_BEEF);
2545 let val2 = GlobalRng::random_u64();
2546 assert_eq!(val1, val2);
2547
2548 GlobalRng::clear_seed();
2549 }
2550}