From 2c035d4218d1572a1a6a96a36dac01e05e0f0277 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 16 Nov 2024 20:42:44 -0500 Subject: [PATCH] fix parsing --- veilid-tools/src/bin/virtual_router/main.rs | 5 +- veilid-tools/src/lib.rs | 4 +- veilid-tools/src/virtual_network/commands.rs | 6 + .../src/virtual_network/router_client.rs | 9 + .../virtual_network/router_server/config.rs | 131 +++---- .../router_server/default_config.yml | 329 +++++++++--------- .../src/virtual_network/router_server/mod.rs | 13 +- .../router_server/predefined_config.yml | 118 +++++++ 8 files changed, 377 insertions(+), 238 deletions(-) create mode 100644 veilid-tools/src/virtual_network/router_server/predefined_config.yml diff --git a/veilid-tools/src/bin/virtual_router/main.rs b/veilid-tools/src/bin/virtual_router/main.rs index 5e5d9aa8..f8c07517 100644 --- a/veilid-tools/src/bin/virtual_router/main.rs +++ b/veilid-tools/src/bin/virtual_router/main.rs @@ -61,7 +61,10 @@ fn main() -> Result<(), String> { let args = CmdlineArgs::parse(); - let router_server = virtual_network::RouterServer::new(); + let config = + Config::new(args.config_file).map_err(|e| format!("Error loading config: {}", e))?; + + let router_server = virtual_network::RouterServer::new(config); let _ss_tcp = if !args.no_tcp { Some( router_server diff --git a/veilid-tools/src/lib.rs b/veilid-tools/src/lib.rs index 5d2d470e..37bb62b6 100644 --- a/veilid-tools/src/lib.rs +++ b/veilid-tools/src/lib.rs @@ -57,6 +57,7 @@ pub mod timeout; pub mod timeout_or; pub mod timestamp; pub mod tools; +#[cfg(feature = "virtual-network")] pub mod virtual_network; #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] pub mod wasm; @@ -249,7 +250,8 @@ pub use timeout_or::*; pub use timestamp::*; #[doc(inline)] pub use tools::*; - +#[cfg(feature = "virtual-network")] +pub use virtual_network::*; #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] pub use wasm::*; diff --git a/veilid-tools/src/virtual_network/commands.rs b/veilid-tools/src/virtual_network/commands.rs index f66b1b05..da3e7eb0 100644 --- a/veilid-tools/src/virtual_network/commands.rs +++ b/veilid-tools/src/virtual_network/commands.rs @@ -124,6 +124,9 @@ pub enum ServerProcessorRequest { protocol: VirtualProtocolType, external_port: u16, }, + TXTQuery { + name: String, + }, } #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] @@ -193,6 +196,9 @@ pub enum ServerProcessorReplyValue { external_port: u16, }, RemovePort, + TXTQuery { + result: Vec, + }, } #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] diff --git a/veilid-tools/src/virtual_network/router_client.rs b/veilid-tools/src/virtual_network/router_client.rs index 45add081..680359d9 100644 --- a/veilid-tools/src/virtual_network/router_client.rs +++ b/veilid-tools/src/virtual_network/router_client.rs @@ -466,6 +466,15 @@ impl RouterClient { Ok(()) } + pub async fn txt_query(self, name: String) -> VirtualNetworkResult> { + let request = ServerProcessorRequest::TXTQuery { name }; + let ServerProcessorReplyValue::TXTQuery { result } = self.perform_request(request).await? + else { + return Err(VirtualNetworkError::ResponseMismatch); + }; + Ok(result) + } + ////////////////////////////////////////////////////////////////////////// // Private implementation diff --git a/veilid-tools/src/virtual_network/router_server/config.rs b/veilid-tools/src/virtual_network/router_server/config.rs index a0d71807..493c3c78 100644 --- a/veilid-tools/src/virtual_network/router_server/config.rs +++ b/veilid-tools/src/virtual_network/router_server/config.rs @@ -1,72 +1,32 @@ use super::*; use ipnet::*; use serde::*; -use std::ffi::OsStr; use std::path::Path; pub use ::config::ConfigError; -fn load_default_config() -> Result<::config::Config, ConfigError> { - let default_config = include_str!("default_config.yml"); - - ::config::Config::builder() - .add_source(::config::File::from_str( - default_config, - ::config::FileFormat::Yaml, - )) - .build() -} - -fn load_config(cfg: ::config::Config, config_file: &Path) -> Result<::config::Config, ConfigError> { - if let Some(config_file_str) = config_file.to_str() { - ::config::Config::builder() - .add_source(cfg) - .add_source(::config::File::new( - config_file_str, - ::config::FileFormat::Yaml, - )) - .build() - } else { - Err(ConfigError::Message( - "config file path is not valid UTF-8".to_owned(), - )) - } -} - -const fn default_weight() -> f32 { - 1.0f32 -} +const PREDEFINED_CONFIG: &str = include_str!("predefined_config.yml"); +const DEFAULT_CONFIG: &str = include_str!("default_config.yml"); #[derive(Debug, Clone, Deserialize)] #[serde(untagged)] pub enum WeightedList { - List { - #[serde(flatten)] - value: Vec>, - }, - Single { - #[serde(flatten)] - value: T, - }, + Single(T), + List(Vec>), +} +impl Default for WeightedList { + fn default() -> Self { + Self::List(Vec::new()) + } } -#[derive(Debug, Clone, Default, Deserialize)] -pub struct Probability { - #[serde(flatten)] - probability: f32, -} +pub type Probability = f32; #[derive(Debug, Clone, Deserialize)] #[serde(untagged)] pub enum Weighted { - Weighted { - item: T, - weight: f32, - }, - Unweighted { - #[serde(flatten)] - value: T, - }, + Weighted(T, f32), + Unweighted(T), } #[derive(Debug, Clone, Deserialize)] @@ -121,7 +81,8 @@ pub enum Location { #[derive(Debug, Clone, Deserialize)] pub struct Network { - model: String, + #[serde(default)] + model: Option, #[serde(default)] ipv4: Option, #[serde(default)] @@ -152,7 +113,8 @@ pub struct NetworkGateway { #[derive(Debug, Clone, Deserialize)] pub struct Blueprint { - models: WeightedList, + #[serde(default)] + model: WeightedList, #[serde(default)] ipv4: Option, #[serde(default)] @@ -187,7 +149,9 @@ pub struct BlueprintGateway { #[derive(Debug, Clone, Deserialize)] pub struct Subnets { + #[serde(default)] subnet4: Vec, + #[serde(default)] subnet6: Vec, } @@ -240,28 +204,65 @@ pub struct Allocation { pub struct Config { seed: Option, default_network: String, + default_model: String, + #[serde(default)] profiles: HashMap, + #[serde(default)] machines: HashMap, + #[serde(default)] templates: HashMap, + #[serde(default)] networks: HashMap, - blueprint: HashMap, + #[serde(default)] + blueprints: HashMap, + #[serde(default)] allocations: HashMap, + #[serde(default)] models: HashMap, } impl Config { - pub fn new(config_file: Option<&OsStr>) -> Result { - // Load the default config - let mut cfg = load_default_config()?; - - // Merge in the config file if we have one - if let Some(config_file) = config_file { - let config_file_path = Path::new(config_file); - // If the user specifies a config file on the command line then it must exist - cfg = load_config(cfg, config_file_path)?; - } + pub fn new>(config_file: Option

) -> Result { + let cfg = load_config(config_file)?; // Generate config cfg.try_deserialize() } } + +fn load_default_config() -> Result<::config::Config, ConfigError> { + ::config::Config::builder() + .add_source(::config::File::from_str( + PREDEFINED_CONFIG, + ::config::FileFormat::Yaml, + )) + .add_source(::config::File::from_str( + DEFAULT_CONFIG, + ::config::FileFormat::Yaml, + )) + .build() +} + +fn load_config>( + opt_config_file: Option

, +) -> Result<::config::Config, ConfigError> { + let Some(config_file) = opt_config_file else { + return load_default_config(); + }; + let config_path = config_file.as_ref(); + let Some(config_file_str) = config_path.to_str() else { + return Err(ConfigError::Message( + "config file path is not valid UTF-8".to_owned(), + )); + }; + ::config::Config::builder() + .add_source(::config::File::from_str( + PREDEFINED_CONFIG, + ::config::FileFormat::Yaml, + )) + .add_source(::config::File::new( + config_file_str, + ::config::FileFormat::Yaml, + )) + .build() +} diff --git a/veilid-tools/src/virtual_network/router_server/default_config.yml b/veilid-tools/src/virtual_network/router_server/default_config.yml index 148dcc24..b39d9d1a 100644 --- a/veilid-tools/src/virtual_network/router_server/default_config.yml +++ b/veilid-tools/src/virtual_network/router_server/default_config.yml @@ -4,11 +4,15 @@ # line to choose a different test scenario. The same seed will # generate the same configuration on all machines given the same # configuration file. -seed: 0 +# seed: 0 # The name of the predefined network to use by default (typically # this is '$internet') -default_network: "$internet" +# default_network: "$internet" + +# The name of the predefined performance model to use by default (typically +# this is '$lan') +# default_model: "$lan" ################################################################# # Profiles @@ -18,22 +22,22 @@ default_network: "$internet" # the VirtualRouter profiles: - - bootstrap: - instances: - # two bootstrap machines - - machine: "bootstrap-1.veilid.net" - - machine: "bootstrap-2.veilid.net" - # pool of up to 4 relay-capable servers - - template: "bootrelay" + bootstrap: + instances: + # two bootstrap machines + - machine: "bootstrap-1.veilid.net" + - machine: "bootstrap-2.veilid.net" + # pool of up to 4 relay-capable servers + - template: "bootrelay" # geographically disperse servers of various configurations - - server: - instances: - - template: - - "relayserver" - - "ipv4server" - - "ipv6server" - - "nat4home" - - "nat4+6home" + server: + instances: + - template: + - "relayserver" + - "ipv4server" + - "ipv6server" + - "nat4home" + - "nat4+6home" ################################################################# # Machines @@ -42,20 +46,20 @@ profiles: # can only be allocated one time machines: - - bootstrap-1.veilid.net: - network: "boot" - address4: "170.64.128.16" - address6: "2a03:b0c0:2:dd::ddd:0010" - disable_capabilities: - ["ROUT", "TUNL", "SGNL", "RLAY", "DIAL", "DHTV", "DHTW", "APPM"] - bootstrap: true - - bootstrap-2.veilid.net: - network: "boot" - address4: "170.64.128.17" - address6: "2a03:b0c0:2:dd::ddd:0011" - disable_capabilities: - ["ROUT", "TUNL", "SGNL", "RLAY", "DIAL", "DHTV", "DHTW", "APPM"] - bootstrap: true + bootstrap-1.veilid.net: + network: "boot" + address4: "170.64.128.16" + address6: "2a03:b0c0:2:dd::ddd:0010" + disable_capabilities: + ["ROUT", "TUNL", "SGNL", "RLAY", "DIAL", "DHTV", "DHTW", "APPM"] + bootstrap: true + bootstrap-2.veilid.net: + network: "boot" + address4: "170.64.128.17" + address6: "2a03:b0c0:2:dd::ddd:0011" + disable_capabilities: + ["ROUT", "TUNL", "SGNL", "RLAY", "DIAL", "DHTV", "DHTW", "APPM"] + bootstrap: true ################################################################# # Templates @@ -70,43 +74,46 @@ templates: # - will have no capabilities disabled # - will not use NAT, and be directly connected # - limited to 4 machines - - bootrelay: - network: "boot" - machine_count: 4 + bootrelay: + network: "boot" + machine_count: 4 # Servers on subnets within the 'internet' network - - relayserver: - blueprint: "direct" - machine_count: [1, 2, 3] - - ipv4server: - blueprint: "direct_ipv4_no_ipv6" - machine_count: [1, 2, 3] - - ipv6server: - blueprint: "direct_ipv6_no_ipv4" - machine_count: [1, 2, 3] - - nat4home: - blueprint: "nat_ipv4_no_ipv6" - machine_count: [1, 2, 3] - - nat4+6home: - blueprint: "nat_ipv4_direct_ipv6" - machine_count: [1, 2, 3] + relayserver: + blueprint: "direct" + machine_count: [1, 2, 3] + ipv4server: + blueprint: "direct_ipv4_no_ipv6" + machine_count: [1, 2, 3] + ipv6server: + blueprint: "direct_ipv6_no_ipv4" + machine_count: [1, 2, 3] + nat4home: + blueprint: "nat_ipv4_no_ipv6" + machine_count: [1, 2, 3] + nat4+6home: + blueprint: "nat_ipv4_direct_ipv6" + machine_count: [1, 2, 3] ################################################################# # Networks # -# Networks are +# Networks are a location where Machines can be allocated and represent +# a network segment with address allocations per address type +# and a gateway to another network. The performance characteristics of +# a network are defined by a performance Model networks: # Custom networks - - boot: - model: "$lan" - ipv4: - allocation: "boot" - ipv6: - allocation: "boot" + boot: + ipv4: + allocation: "boot" + ipv6: + allocation: "boot" - # Predefined networks - - $internet: - model: "$internet" + # # Predefined networks + # $internet: + # allocation: "$internet" + # model: "$internet" ################################################################# # Blueprints @@ -116,43 +123,38 @@ networks: blueprints: # A subnet of the internet directly attached with no translation # with both ipv4 and ipv6 networking - - direct: - models: "$lan" - ipv4: - prefix: 24 - ipv6: - prefix: 64 + direct: + ipv4: + prefix: 24 + ipv6: + prefix: 64 # An ipv4-only subnet of the internet directly attached with no translation - - direct_ipv4_no_ipv6: - models: "$lan" - ipv4: - prefix: 24 + direct_ipv4_no_ipv6: + ipv4: + prefix: 24 # An ipv6-only subnet of the internet directly attached with no translation - - direct_ipv6_no_ipv4: - models: "$lan" - ipv6: - prefix: 64 + direct_ipv6_no_ipv4: + ipv6: + prefix: 64 # An ipv4-only subnet of the internet attached via NAT - - nat_ipv4_no_ipv6: - models: "$lan" - ipv4: - allocation: "$private" - prefix: 0 - gateway: - translation: "PortRestricted" - upnp: 0.25 + nat_ipv4_no_ipv6: + ipv4: + allocation: "$private" + prefix: 0 + gateway: + translation: "port_restricted" + upnp: 0.25 # An ipv4 subnet of the internet attached via NAT and # an ipv6 subnet of the internet directly attached with no translation - - nat_ipv4_direct_ipv6: - models: "$lan" - ipv4: - allocation: "$private" - prefix: 0 - gateway: - translation: "PortRestricted" - upnp: 0.25 - ipv6: - prefix: 56 + nat_ipv4_direct_ipv6: + ipv4: + allocation: "$private" + prefix: 0 + gateway: + translation: "port_restricted" + upnp: 0.25 + ipv6: + prefix: 56 ################################################################# # Allocations @@ -165,58 +167,57 @@ blueprints: allocations: # Custom network allocations - - boot: - subnet4: ["170.64.128.0/24"] - subnet6: ["2a03:b0c0:2::/48"] - - # Predefined networks - - $private: - subnet4: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] - subnet6: ["fc00::/7"] - - $cgnat: - subnet4: ["100.64.0.0/10"] - - $linklocal: - subnet4: ["169.254.0.0/16"] - subnet6: ["fe80::/10"] - - $localhost: - subnet4: ["127.0.0.0/8"] - subnet6: ["::1/128"] - - $ietf: - subnet4: ["192.0.0.0/24"] - - $cellnat: - subnet4: ["192.0.0.0/29"] - - $documentation: - subnet4: ["192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24"] - subnet6: ["2001:db8::/32", "3fff::/20"] - - $benchmark: - subnet4: ["198.18.0.0/15"] - - $mulitcast: - subnet4: ["224.0.0.0/4"] - - $mulitcasttest: - subnet4: ["233.252.0.0/24"] - subnet6: ["ff00::/8"] - - $unspecified: - subnet4: ["0.0.0.0/8"] - subnet6: ["::/128"] - - $reserved: - subnet4: ["192.88.99.0/24", "240.0.0.0/4"] - - $broadcast: - subnet4: ["255.255.255.255/32"] - - $mapped: - subnet6: ["::ffff:0:0/96", "::ffff:0:0:0/96"] - - $translation: - subnet6: ["64:ff9b::/96", "64:ff9b:1::/48"] - - $discard: - subnet6: ["100::/64"] - - $teredo: - subnet6: ["2001::/32"] - - $orchidv2: - subnet6: ["2001:20::/28"] - - $6to4: - subnet6: ["2002::/16"] - - $srv6: - subnet6: ["5f00::/16"] - + boot: + subnet4: ["170.64.128.0/24"] + subnet6: ["2a03:b0c0:2::/48"] + # # Predefined networks + # $internet: {} + # $private: + # subnet4: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] + # subnet6: ["fc00::/7"] + # $cgnat: + # subnet4: ["100.64.0.0/10"] + # $linklocal: + # subnet4: ["169.254.0.0/16"] + # subnet6: ["fe80::/10"] + # $localhost: + # subnet4: ["127.0.0.0/8"] + # subnet6: ["::1/128"] + # $ietf: + # subnet4: ["192.0.0.0/24"] + # $cellnat: + # subnet4: ["192.0.0.0/29"] + # $documentation: + # subnet4: ["192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24"] + # subnet6: ["2001:db8::/32", "3fff::/20"] + # $benchmark: + # subnet4: ["198.18.0.0/15"] + # $mulitcast: + # subnet4: ["224.0.0.0/4"] + # $mulitcasttest: + # subnet4: ["233.252.0.0/24"] + # subnet6: ["ff00::/8"] + # $unspecified: + # subnet4: ["0.0.0.0/8"] + # subnet6: ["::/128"] + # $reserved: + # subnet4: ["192.88.99.0/24", "240.0.0.0/4"] + # $broadcast: + # subnet4: ["255.255.255.255/32"] + # $mapped: + # subnet6: ["::ffff:0:0/96", "::ffff:0:0:0/96"] + # $translation: + # subnet6: ["64:ff9b::/96", "64:ff9b:1::/48"] + # $discard: + # subnet6: ["100::/64"] + # $teredo: + # subnet6: ["2001::/32"] + # $orchidv2: + # subnet6: ["2001:20::/28"] + # $6to4: + # subnet6: ["2002::/16"] + # $srv6: + # subnet6: ["5f00::/16"] ################################################################# # Models # @@ -225,24 +226,24 @@ allocations: # Distance is assigned over a circular probability and then # mapped linearly as a multiplier to latency and loss -models: - # Predefined models - $lan: - latency: - mean: 0.0038 - sigma: 0.001416 - skew: 0.0009 - min: 0.0015 - max: 0.0075 - loss: 0.0 - $internet: - distance: - min: 0.04 - max: 2.0 - latency: - mean: 0.200 - sigma: 0.080 - skew: 0 - min: 0.030 - max: 0.400 - loss: 0.01 +# models: +# # Predefined models +# $lan: +# latency: +# mean: 0.0038 +# sigma: 0.001416 +# skew: 0.0009 +# min: 0.0015 +# max: 0.0075 +# loss: 0.0 +# $internet: +# distance: +# min: 0.04 +# max: 2.0 +# latency: +# mean: 0.200 +# sigma: 0.080 +# skew: 0 +# min: 0.030 +# max: 0.400 +# loss: 0.01 diff --git a/veilid-tools/src/virtual_network/router_server/mod.rs b/veilid-tools/src/virtual_network/router_server/mod.rs index fa846a74..970e0c8e 100644 --- a/veilid-tools/src/virtual_network/router_server/mod.rs +++ b/veilid-tools/src/virtual_network/router_server/mod.rs @@ -1,5 +1,7 @@ mod config; +pub use config::*; + use super::*; use async_tungstenite::accept_async; use futures_codec::{Bytes, BytesCodec, FramedRead, FramedWrite}; @@ -29,6 +31,7 @@ enum RunLoopEvent { #[derive(Debug)] struct RouterServerUnlockedInner { + config: Config, new_client_sender: flume::Sender>, new_client_receiver: flume::Receiver>, } @@ -54,12 +57,13 @@ impl RouterServer { // Public Interface /// Create a router server for virtual networking - pub fn new() -> Self { + pub fn new(config: Config) -> Self { // Make a channel to receive new clients let (new_client_sender, new_client_receiver) = flume::unbounded(); Self { unlocked_inner: Arc::new(RouterServerUnlockedInner { + config, new_client_sender, new_client_receiver, }), @@ -416,12 +420,7 @@ impl RouterServer { protocol, external_port, } => todo!(), + ServerProcessorRequest::TXTQuery { name } => todo!(), } } } - -impl Default for RouterServer { - fn default() -> Self { - Self::new() - } -} diff --git a/veilid-tools/src/virtual_network/router_server/predefined_config.yml b/veilid-tools/src/virtual_network/router_server/predefined_config.yml new file mode 100644 index 00000000..3deb0b9c --- /dev/null +++ b/veilid-tools/src/virtual_network/router_server/predefined_config.yml @@ -0,0 +1,118 @@ +--- +# Random number seed used to generate all profile configurations +# for a test. The seed can be overriden on the VirtualRouter command +# line to choose a different test scenario. The same seed will +# generate the same configuration on all machines given the same +# configuration file. +seed: 0 + +# The name of the predefined network to use by default (typically +# this is '$internet') +default_network: "$internet" + +# The name of the predefined performance model to use by default (typically +# this is '$') +default_model: "$lan" + +################################################################# +# Networks +# +# Networks are a location where Machines can be allocated and represent +# a network segment with address allocations per address type +# and a gateway to another network. The performance characteristics of +# a network are defined by a performance Model + +networks: + # Predefined networks + $internet: + allocation: "$internet" + model: "$internet" + +################################################################# +# Allocations +# +# Allocations are partitions of the address space that networks +# can be assigned to. Machines on the networks will be given +# addresses within these ranges. If an allocation +# is not specified, an address -outside- any of the allocation +# will be used (on the 'public internet'). + +allocations: + # Predefined networks + $internet: {} + $private: + subnet4: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] + subnet6: ["fc00::/7"] + $cgnat: + subnet4: ["100.64.0.0/10"] + $linklocal: + subnet4: ["169.254.0.0/16"] + subnet6: ["fe80::/10"] + $localhost: + subnet4: ["127.0.0.0/8"] + subnet6: ["::1/128"] + $ietf: + subnet4: ["192.0.0.0/24"] + $cellnat: + subnet4: ["192.0.0.0/29"] + $documentation: + subnet4: ["192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24"] + subnet6: ["2001:db8::/32", "3fff::/20"] + $benchmark: + subnet4: ["198.18.0.0/15"] + $mulitcast: + subnet4: ["224.0.0.0/4"] + $mulitcasttest: + subnet4: ["233.252.0.0/24"] + subnet6: ["ff00::/8"] + $unspecified: + subnet4: ["0.0.0.0/8"] + subnet6: ["::/128"] + $reserved: + subnet4: ["192.88.99.0/24", "240.0.0.0/4"] + $broadcast: + subnet4: ["255.255.255.255/32"] + $mapped: + subnet6: ["::ffff:0:0/96", "::ffff:0:0:0/96"] + $translation: + subnet6: ["64:ff9b::/96", "64:ff9b:1::/48"] + $discard: + subnet6: ["100::/64"] + $teredo: + subnet6: ["2001::/32"] + $orchidv2: + subnet6: ["2001:20::/28"] + $6to4: + subnet6: ["2002::/16"] + $srv6: + subnet6: ["5f00::/16"] + +################################################################# +# Models +# +# Performance models representing how a network behaves +# Latency models are a skewed normal distribution +# Distance is assigned over a circular probability and then +# mapped linearly as a multiplier to latency and loss + +models: + # Predefined models + $lan: + latency: + mean: 0.0038 + sigma: 0.001416 + skew: 0.0009 + min: 0.0015 + max: 0.0075 + loss: 0.0 + $internet: + distance: + min: 0.04 + max: 2.0 + latency: + mean: 0.200 + sigma: 0.080 + skew: 0 + min: 0.030 + max: 0.400 + loss: 0.01