use rings_core::dht::default_storage_virtual_positions_per_owner;
use rings_core::dht::VirtualNodeConfig;
use rings_core::dht::DEFAULT_STORAGE_VIRTUAL_POSITIONS_PER_OWNER;
use rings_core::dht::MAX_STORAGE_VIRTUAL_POSITIONS_PER_OWNER;
use super::*;
#[derive(Clone, Debug)]
#[wasm_export]
pub struct ProcessorConfig {
pub(in crate::processor) network_id: u32,
pub(in crate::processor) ice_servers: String,
pub(in crate::processor) external_address: Option<String>,
pub(in crate::processor) webrtc_udp_port_min: Option<u16>,
pub(in crate::processor) webrtc_udp_port_max: Option<u16>,
pub(in crate::processor) session_sk: SessionSk,
pub(in crate::processor) stabilize_interval: Duration,
pub(in crate::processor) online_node_heartbeat_interval: Duration,
pub(in crate::processor) online_node_ttl: Duration,
pub(in crate::processor) online_node_type: OnlineNodeType,
pub(in crate::processor) advertise_presence: bool,
pub(in crate::processor) dht_virtual_nodes: u16,
pub(in crate::processor) advertise_onion_relay: bool,
pub(in crate::processor) advertise_onion_exit: bool,
pub(in crate::processor) onion_exit_heartbeat_interval: Duration,
pub(in crate::processor) onion_exit_ttl: Duration,
pub(in crate::processor) onion_exit_services: Vec<OnionExitService>,
pub(in crate::processor) onion_exit_policy: OnionExitPolicy,
}
#[wasm_export]
impl ProcessorConfig {
pub fn new(
network_id: u32,
ice_servers: String,
session_sk: SessionSk,
stabilize_interval: u64,
) -> Self {
Self {
network_id,
ice_servers,
external_address: None,
webrtc_udp_port_min: None,
webrtc_udp_port_max: None,
session_sk,
stabilize_interval: Duration::from_secs(stabilize_interval),
online_node_heartbeat_interval: Duration::from_secs(
default_online_node_heartbeat_interval_secs(),
),
online_node_ttl: Duration::from_secs(default_online_node_ttl_secs()),
online_node_type: default_online_node_type(),
advertise_presence: default_advertise_presence(),
dht_virtual_nodes: DEFAULT_STORAGE_VIRTUAL_POSITIONS_PER_OWNER,
advertise_onion_relay: default_advertise_onion_relay(),
advertise_onion_exit: default_advertise_onion_exit(),
onion_exit_heartbeat_interval: Duration::from_secs(
default_onion_exit_heartbeat_interval_secs(),
),
onion_exit_ttl: Duration::from_secs(default_onion_exit_ttl_secs()),
onion_exit_services: default_onion_exit_services(),
onion_exit_policy: default_onion_exit_policy(),
}
}
pub fn session_sk(&self) -> SessionSk {
self.session_sk.clone()
}
pub fn enable_https_onion_exit(mut self) -> Self {
self.advertise_onion_exit = true;
self.onion_exit_services = https_onion_exit_services();
self
}
pub fn enable_default_onion_exit(mut self) -> Self {
self.advertise_onion_exit = true;
self.onion_exit_services = default_onion_exit_services();
self
}
pub fn advertise_onion_relay(mut self, advertise: bool) -> Self {
self.advertise_onion_relay = advertise;
self
}
pub fn dht_virtual_nodes(mut self, positions_per_peer: u16) -> Self {
self.dht_virtual_nodes = positions_per_peer;
self
}
pub fn advertise_onion_exit(mut self, advertise: bool) -> Self {
self.advertise_onion_exit = advertise;
self
}
}
impl ProcessorConfig {
pub fn webrtc_udp_port_range(&self) -> Result<Option<WebrtcUdpPortRange>> {
parse_webrtc_udp_port_range(self.webrtc_udp_port_min, self.webrtc_udp_port_max)
}
pub fn onion_exit_policy(mut self, policy: OnionExitPolicy) -> Self {
self.onion_exit_policy = policy;
self
}
#[cfg(all(feature = "browser", target_family = "wasm"))]
pub fn onion_https_exit_policy(&self) -> Option<OnionExitPolicy> {
(self.advertise_onion_exit
&& self
.onion_exit_services
.iter()
.any(|service| service.matches_route_service(ONION_PROXY_HTTPS_SERVICE)))
.then(|| self.onion_exit_policy.clone())
}
}
impl FromStr for ProcessorConfig {
type Err = Error;
fn from_str(ser: &str) -> Result<Self> {
serde_yaml::from_str::<ProcessorConfig>(ser).map_err(Error::SerdeYamlError)
}
}
#[derive(Serialize, Deserialize, Clone)]
#[wasm_export]
pub struct ProcessorConfigSerialized {
network_id: u32,
ice_servers: String,
external_address: Option<String>,
webrtc_udp_port_min: Option<u16>,
webrtc_udp_port_max: Option<u16>,
session_sk: String,
stabilize_interval: u64,
#[serde(default = "default_online_node_heartbeat_interval_secs")]
online_node_heartbeat_interval_secs: u64,
#[serde(default = "default_online_node_ttl_secs")]
online_node_ttl_secs: u64,
#[serde(default = "default_online_node_type")]
online_node_type: OnlineNodeType,
#[serde(default = "default_advertise_presence")]
advertise_presence: bool,
#[serde(default = "default_storage_virtual_positions_per_owner")]
dht_virtual_nodes: u16,
#[serde(default = "default_advertise_onion_relay")]
advertise_onion_relay: bool,
#[serde(default = "default_advertise_onion_exit")]
advertise_onion_exit: bool,
#[serde(default = "default_onion_exit_heartbeat_interval_secs")]
onion_exit_heartbeat_interval_secs: u64,
#[serde(default = "default_onion_exit_ttl_secs")]
onion_exit_ttl_secs: u64,
#[serde(default = "default_onion_exit_services")]
onion_exit_services: Vec<OnionExitService>,
#[serde(default = "default_onion_exit_policy")]
onion_exit_policy: OnionExitPolicy,
}
impl ProcessorConfigSerialized {
pub fn new(
network_id: u32,
ice_servers: String,
session_sk: String,
stabilize_interval: u64,
) -> Self {
Self {
network_id,
ice_servers,
external_address: None,
webrtc_udp_port_min: None,
webrtc_udp_port_max: None,
session_sk,
stabilize_interval,
online_node_heartbeat_interval_secs: default_online_node_heartbeat_interval_secs(),
online_node_ttl_secs: default_online_node_ttl_secs(),
online_node_type: default_online_node_type(),
advertise_presence: default_advertise_presence(),
dht_virtual_nodes: DEFAULT_STORAGE_VIRTUAL_POSITIONS_PER_OWNER,
advertise_onion_relay: default_advertise_onion_relay(),
advertise_onion_exit: default_advertise_onion_exit(),
onion_exit_heartbeat_interval_secs: default_onion_exit_heartbeat_interval_secs(),
onion_exit_ttl_secs: default_onion_exit_ttl_secs(),
onion_exit_services: default_onion_exit_services(),
onion_exit_policy: default_onion_exit_policy(),
}
}
pub fn external_address(mut self, external_address: String) -> Self {
self.external_address = Some(external_address);
self
}
pub fn webrtc_udp_port_range(mut self, range: WebrtcUdpPortRange) -> Self {
self.webrtc_udp_port_min = Some(range.min());
self.webrtc_udp_port_max = Some(range.max());
self
}
pub fn online_node_heartbeat_interval_secs(mut self, interval_secs: u64) -> Self {
self.online_node_heartbeat_interval_secs = interval_secs;
self
}
pub fn online_node_ttl_secs(mut self, ttl_secs: u64) -> Self {
self.online_node_ttl_secs = ttl_secs;
self
}
pub fn online_node_type(mut self, node_type: OnlineNodeType) -> Self {
self.online_node_type = node_type;
self
}
pub fn advertise_presence(mut self, advertise: bool) -> Self {
self.advertise_presence = advertise;
self
}
pub fn advertise_onion_relay(mut self, advertise: bool) -> Self {
self.advertise_onion_relay = advertise;
self
}
pub fn dht_virtual_nodes(mut self, positions_per_peer: u16) -> Self {
self.dht_virtual_nodes = positions_per_peer;
self
}
pub fn advertise_onion_exit(mut self, advertise: bool) -> Self {
self.advertise_onion_exit = advertise;
self
}
pub fn onion_exit_heartbeat_interval_secs(mut self, interval_secs: u64) -> Self {
self.onion_exit_heartbeat_interval_secs = interval_secs;
self
}
pub fn onion_exit_ttl_secs(mut self, ttl_secs: u64) -> Self {
self.onion_exit_ttl_secs = ttl_secs;
self
}
pub fn onion_exit_services(mut self, services: Vec<OnionExitService>) -> Self {
self.onion_exit_services = services;
self
}
pub fn onion_exit_policy(mut self, policy: OnionExitPolicy) -> Self {
self.onion_exit_policy = policy;
self
}
pub fn enable_https_onion_exit(mut self) -> Self {
self.advertise_onion_exit = true;
self.onion_exit_services = https_onion_exit_services();
self
}
pub fn enable_default_onion_exit(mut self) -> Self {
self.advertise_onion_exit = true;
self.onion_exit_services = default_onion_exit_services();
self
}
}
pub(crate) fn parse_webrtc_udp_port_range(
min: Option<u16>,
max: Option<u16>,
) -> Result<Option<WebrtcUdpPortRange>> {
match (min, max) {
(None, None) => Ok(None),
(Some(min), Some(max)) => WebrtcUdpPortRange::new(min, max)
.map(Some)
.map_err(Error::from),
(min, max) => Err(Error::IncompleteWebrtcUdpPortRange { min, max }),
}
}
fn validate_dht_virtual_nodes(positions_per_peer: u16) -> Result<()> {
if VirtualNodeConfig::positions_per_owner_within_limit(positions_per_peer) {
return Ok(());
}
Err(Error::InvalidConfig(format!(
"dht_virtual_nodes {positions_per_peer} exceeds maximum {MAX_STORAGE_VIRTUAL_POSITIONS_PER_OWNER}"
)))
}
pub(in crate::processor) fn validate_onion_role_config(
advertise_presence: bool,
advertise_onion_relay: bool,
advertise_onion_exit: bool,
onion_exit_services: &[OnionExitService],
onion_exit_policy: &OnionExitPolicy,
) -> Result<()> {
if advertise_onion_relay && !advertise_presence {
return Err(Error::InvalidConfig(
"advertise_onion_relay requires advertise_presence because relay capability is published in online-node descriptors"
.to_string(),
));
}
if advertise_onion_exit && onion_exit_services.is_empty() {
return Err(Error::InvalidConfig(
"advertise_onion_exit requires at least one onion_exit_services entry".to_string(),
));
}
if advertise_onion_exit {
for service in onion_exit_services {
if let Some(expected) = OnionExitService::reserved_transport(service.name.as_str()) {
if service.transport == expected {
continue;
}
return Err(Error::InvalidConfig(format!(
"onion exit service {:?} must use {:?} transport, got {:?}",
service.name, expected, service.transport
)));
}
}
onion_exit_policy.validate_targets()?;
}
Ok(())
}
impl TryFrom<ProcessorConfig> for ProcessorConfigSerialized {
type Error = Error;
fn try_from(ins: ProcessorConfig) -> Result<Self> {
Ok(Self {
network_id: ins.network_id,
ice_servers: ins.ice_servers.clone(),
external_address: ins.external_address.clone(),
webrtc_udp_port_min: ins.webrtc_udp_port_min,
webrtc_udp_port_max: ins.webrtc_udp_port_max,
session_sk: ins.session_sk.dump()?,
stabilize_interval: ins.stabilize_interval.as_secs(),
online_node_heartbeat_interval_secs: ins.online_node_heartbeat_interval.as_secs(),
online_node_ttl_secs: ins.online_node_ttl.as_secs(),
online_node_type: ins.online_node_type,
advertise_presence: ins.advertise_presence,
dht_virtual_nodes: ins.dht_virtual_nodes,
advertise_onion_relay: ins.advertise_onion_relay,
advertise_onion_exit: ins.advertise_onion_exit,
onion_exit_heartbeat_interval_secs: ins.onion_exit_heartbeat_interval.as_secs(),
onion_exit_ttl_secs: ins.onion_exit_ttl.as_secs(),
onion_exit_services: ins.onion_exit_services,
onion_exit_policy: ins.onion_exit_policy,
})
}
}
impl TryFrom<ProcessorConfigSerialized> for ProcessorConfig {
type Error = Error;
fn try_from(ins: ProcessorConfigSerialized) -> Result<Self> {
let webrtc_udp_port_range =
parse_webrtc_udp_port_range(ins.webrtc_udp_port_min, ins.webrtc_udp_port_max)?;
validate_dht_virtual_nodes(ins.dht_virtual_nodes)?;
let online_node_heartbeat_interval =
Duration::from_secs(ins.online_node_heartbeat_interval_secs);
let online_node_ttl = Duration::from_secs(ins.online_node_ttl_secs);
let onion_exit_heartbeat_interval =
Duration::from_secs(ins.onion_exit_heartbeat_interval_secs);
let onion_exit_ttl = Duration::from_secs(ins.onion_exit_ttl_secs);
validate_online_node_registration_timing(
ins.advertise_presence,
online_node_heartbeat_interval,
online_node_ttl,
)?;
validate_onion_exit_registration_timing(
ins.advertise_onion_exit,
onion_exit_heartbeat_interval,
onion_exit_ttl,
)?;
validate_onion_role_config(
ins.advertise_presence,
ins.advertise_onion_relay,
ins.advertise_onion_exit,
&ins.onion_exit_services,
&ins.onion_exit_policy,
)?;
Ok(Self {
network_id: ins.network_id,
ice_servers: ins.ice_servers.clone(),
external_address: ins.external_address.clone(),
webrtc_udp_port_min: webrtc_udp_port_range.map(WebrtcUdpPortRange::min),
webrtc_udp_port_max: webrtc_udp_port_range.map(WebrtcUdpPortRange::max),
session_sk: SessionSk::from_str(&ins.session_sk)?,
stabilize_interval: Duration::from_secs(ins.stabilize_interval),
online_node_heartbeat_interval,
online_node_ttl,
online_node_type: ins.online_node_type,
advertise_presence: ins.advertise_presence,
dht_virtual_nodes: ins.dht_virtual_nodes,
advertise_onion_relay: ins.advertise_onion_relay,
advertise_onion_exit: ins.advertise_onion_exit,
onion_exit_heartbeat_interval,
onion_exit_ttl,
onion_exit_services: ins.onion_exit_services,
onion_exit_policy: ins.onion_exit_policy,
})
}
}
impl Serialize for ProcessorConfig {
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> core::result::Result<S::Ok, S::Error> {
let ins: ProcessorConfigSerialized = self
.clone()
.try_into()
.map_err(|e: Error| serde::ser::Error::custom(e.to_string()))?;
ProcessorConfigSerialized::serialize(&ins, serializer)
}
}
impl<'de> serde::de::Deserialize<'de> for ProcessorConfig {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where D: serde::Deserializer<'de> {
match ProcessorConfigSerialized::deserialize(deserializer) {
Ok(ins) => {
let cfg: ProcessorConfig = ins
.try_into()
.map_err(|e: Error| serde::de::Error::custom(e.to_string()))?;
Ok(cfg)
}
Err(e) => Err(e),
}
}
}