Include documentation in printed config files (#41)

* Start work on printing toml config with comments

* WIP: toml_config: extract default values for fields

* WIP: toml_config: handle single-level nested structs

* WIP: toml_config: improve comment handling, std type trait impls

* WIP: toml_config: add Private trait, improve comment handling, clean up

* toml_config: fix default value bug; improve tests

* Use toml_config in all applicable crates; add toml_config enum support

* toml_config: improve comments

* toml_config_derive: support enum comments

* Improve config comments for udp, cli_helpers, common

* Improve config comments

* Add tests for Config struct TomlConfig implementations

* Improve Config comments

* Improve Config comments

* ws, http: add config comments for tls cert and private key lines

* small fixes to toml_config and toml_config_derive

* Run cargo fmt

* Fix typo in several config comments

* Update README

* Update README
This commit is contained in:
Joakim Frostegård 2021-12-26 11:33:27 +01:00 committed by GitHub
parent d694785244
commit a208775104
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 581 additions and 92 deletions

29
Cargo.lock generated
View file

@ -62,6 +62,7 @@ dependencies = [
"serde", "serde",
"simplelog", "simplelog",
"toml", "toml",
"toml_config",
] ]
[[package]] [[package]]
@ -80,6 +81,7 @@ dependencies = [
"privdrop", "privdrop",
"rand", "rand",
"serde", "serde",
"toml_config",
] ]
[[package]] [[package]]
@ -109,6 +111,7 @@ dependencies = [
"signal-hook", "signal-hook",
"slab", "slab",
"smartstring", "smartstring",
"toml_config",
] ]
[[package]] [[package]]
@ -130,6 +133,7 @@ dependencies = [
"rand_distr", "rand_distr",
"rustls", "rustls",
"serde", "serde",
"toml_config",
] ]
[[package]] [[package]]
@ -179,6 +183,7 @@ dependencies = [
"slab", "slab",
"socket2 0.4.2", "socket2 0.4.2",
"tinytemplate", "tinytemplate",
"toml_config",
] ]
[[package]] [[package]]
@ -196,6 +201,7 @@ dependencies = [
"rand", "rand",
"rand_distr", "rand_distr",
"serde", "serde",
"toml_config",
] ]
[[package]] [[package]]
@ -215,6 +221,7 @@ dependencies = [
"rand_distr", "rand_distr",
"serde", "serde",
"socket2 0.4.2", "socket2 0.4.2",
"toml_config",
] ]
[[package]] [[package]]
@ -259,6 +266,7 @@ dependencies = [
"signal-hook", "signal-hook",
"slab", "slab",
"socket2 0.4.2", "socket2 0.4.2",
"toml_config",
"tungstenite", "tungstenite",
] ]
@ -284,6 +292,7 @@ dependencies = [
"rustls", "rustls",
"serde", "serde",
"serde_json", "serde_json",
"toml_config",
"tungstenite", "tungstenite",
] ]
@ -2066,6 +2075,26 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "toml_config"
version = "0.1.0"
dependencies = [
"quickcheck",
"quickcheck_macros",
"serde",
"toml",
"toml_config_derive",
]
[[package]]
name = "toml_config_derive"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.29" version = "0.1.29"

View file

@ -14,6 +14,8 @@ members = [
"aquatic_ws", "aquatic_ws",
"aquatic_ws_load_test", "aquatic_ws_load_test",
"aquatic_ws_protocol", "aquatic_ws_protocol",
"toml_config",
"toml_config_derive",
] ]
[patch.crates-io] [patch.crates-io]
@ -30,6 +32,8 @@ aquatic_udp_protocol = { path = "aquatic_udp_protocol" }
aquatic_ws = { path = "aquatic_ws" } aquatic_ws = { path = "aquatic_ws" }
aquatic_ws_load_test = { path = "aquatic_ws_load_test" } aquatic_ws_load_test = { path = "aquatic_ws_load_test" }
aquatic_ws_protocol = { path = "aquatic_ws_protocol" } aquatic_ws_protocol = { path = "aquatic_ws_protocol" }
toml_config = { path = "toml_config" }
toml_config_derive = { path = "toml_config_derive" }
[profile.release] [profile.release]
debug = true debug = true

View file

@ -66,8 +66,9 @@ Begin by generating configuration files. They differ between protocols.
Make adjustments to the files. You will likely want to adjust `address` Make adjustments to the files. You will likely want to adjust `address`
(listening address) under the `network` section. (listening address) under the `network` section.
`aquatic_http` and `aquatic_ws` both require configuring a TLS certificate file as well as a Note that both `aquatic_http` and `aquatic_ws` require configuring TLS
private key file to run. More information is available below. certificate and private key files. More details are available in the
respective configuration files.
Once done, run the tracker: Once done, run the tracker:
@ -79,25 +80,12 @@ Once done, run the tracker:
### Configuration values ### Configuration values
Starting more socket workers than request workers is recommended. All Starting more `socket_workers` than `request_workers` is recommended. All
implementations are quite IO-bound and spend a lot of their time reading from implementations are quite IO-bound and spend a lot of their time reading from
and writing to sockets. This is handled by the `socket_workers`, which and writing to sockets. This is handled by the socket workers, which
also do parsing, serialisation and access control. They pass announce and also do parsing, serialisation and access control. They pass announce and
scrape requests to the `request_workers`, which update internal tracker state scrape requests to the request workers, which update internal tracker state
and pass back responses. and pass back responses for sending.
#### TLS
`aquatic_ws` and `aquatic_http` both require access to a TLS certificate file
(DER-encoded X.509) and a corresponding private key file (DER-encoded ASN.1 in
either PKCS#8 or PKCS#1 format) to run. Set their paths in the configuration file, e.g.:
```toml
[network]
address = '0.0.0.0:3000'
tls_certificate_path = './cert.pem'
tls_private_key_path = './key.pem'
```
#### Access control #### Access control
@ -106,8 +94,10 @@ of configuration is:
```toml ```toml
[access_list] [access_list]
mode = 'off' # Change to 'black' (blacklist) or 'white' (whitelist) # Access list mode. Available modes are white, black and off.
path = '' # Path to text file with newline-delimited hex-encoded info hashes mode = "off"
# Path to access list file consisting of newline-separated hex-encoded info hashes.
path = ""
``` ```
The file is read on start and when the program receives `SIGUSR1`. If initial The file is read on start and when the program receives `SIGUSR1`. If initial
@ -115,12 +105,6 @@ parsing fails, the program exits. Later failures result in in emitting of
an error-level log message, while successful updates of the access list result an error-level log message, while successful updates of the access list result
in emitting of an info-level log message. in emitting of an info-level log message.
#### More information
More documentation of the various configuration options might be available
under `src/config.rs` in directories `aquatic_udp`, `aquatic_http` and
`aquatic_ws`.
## Details on implementations ## Details on implementations
### aquatic_udp: UDP BitTorrent tracker ### aquatic_udp: UDP BitTorrent tracker
@ -146,6 +130,7 @@ More details are available [here](./documents/aquatic-udp-load-test-2021-11-28.p
* Using glommio * Using glommio
* Using io-uring * Using io-uring
* Using zerocopy + vectored sends for responses * Using zerocopy + vectored sends for responses
* Using sendmmsg
### aquatic_http: HTTP BitTorrent tracker ### aquatic_http: HTTP BitTorrent tracker

View file

@ -12,3 +12,4 @@ anyhow = "1"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
simplelog = "0.11" simplelog = "0.11"
toml = "0.5" toml = "0.5"
toml_config = "0.1.0"

View file

@ -4,8 +4,10 @@ use std::io::Read;
use anyhow::Context; use anyhow::Context;
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{de::DeserializeOwned, Deserialize, Serialize};
use simplelog::{ColorChoice, ConfigBuilder, LevelFilter, TermLogger, TerminalMode}; use simplelog::{ColorChoice, ConfigBuilder, LevelFilter, TermLogger, TerminalMode};
use toml_config::TomlConfig;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)] /// Log level. Available values are off, error, warn, info, debug and trace.
#[derive(Debug, Clone, Copy, PartialEq, TomlConfig, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum LogLevel { pub enum LogLevel {
Off, Off,
@ -22,7 +24,7 @@ impl Default for LogLevel {
} }
} }
pub trait Config: Default + Serialize + DeserializeOwned { pub trait Config: Default + TomlConfig + DeserializeOwned {
fn get_log_level(&self) -> Option<LogLevel> { fn get_log_level(&self) -> Option<LogLevel> {
None None
} }
@ -169,9 +171,9 @@ where
fn default_config_as_toml<T>() -> String fn default_config_as_toml<T>() -> String
where where
T: Default + Serialize, T: Default + TomlConfig,
{ {
toml::to_string_pretty(&T::default()).expect("Could not serialize default config to toml") <T as TomlConfig>::default_to_string()
} }
fn start_logger(log_level: LogLevel) -> ::anyhow::Result<()> { fn start_logger(log_level: LogLevel) -> ::anyhow::Result<()> {

View file

@ -24,6 +24,7 @@ log = "0.4"
privdrop = "0.5" privdrop = "0.5"
rand = { version = "0.8", features = ["small_rng"] } rand = { version = "0.8", features = ["small_rng"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
toml_config = "0.1.0"
# cpu-pinning # cpu-pinning
hwloc = { version = "0.5", optional = true } hwloc = { version = "0.5", optional = true }

View file

@ -7,8 +7,10 @@ use anyhow::Context;
use arc_swap::{ArcSwap, Cache}; use arc_swap::{ArcSwap, Cache};
use hashbrown::HashSet; use hashbrown::HashSet;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use toml_config::TomlConfig;
#[derive(Clone, Copy, Debug, Serialize, Deserialize)] /// Access list mode. Available modes are white, black and off.
#[derive(Clone, Copy, Debug, PartialEq, TomlConfig, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum AccessListMode { pub enum AccessListMode {
/// Only serve torrents with info hash present in file /// Only serve torrents with info hash present in file
@ -25,7 +27,7 @@ impl AccessListMode {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
pub struct AccessListConfig { pub struct AccessListConfig {
pub mode: AccessListMode, pub mode: AccessListMode,
/// Path to access list file consisting of newline-separated hex-encoded info hashes. /// Path to access list file consisting of newline-separated hex-encoded info hashes.

View file

@ -1,7 +1,8 @@
use hwloc::{CpuSet, ObjectType, Topology, CPUBIND_THREAD}; use hwloc::{CpuSet, ObjectType, Topology, CPUBIND_THREAD};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use toml_config::TomlConfig;
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum CpuPinningMode { pub enum CpuPinningMode {
Ascending, Ascending,
@ -14,7 +15,7 @@ impl Default for CpuPinningMode {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
pub struct CpuPinningConfig { pub struct CpuPinningConfig {
pub active: bool, pub active: bool,
pub mode: CpuPinningMode, pub mode: CpuPinningMode,

View file

@ -7,9 +7,10 @@ use std::{
}; };
use privdrop::PrivDrop; use privdrop::PrivDrop;
use serde::{Deserialize, Serialize}; use serde::Deserialize;
use toml_config::TomlConfig;
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct PrivilegeConfig { pub struct PrivilegeConfig {
/// Chroot and switch user after binding to sockets /// Chroot and switch user after binding to sockets

View file

@ -38,6 +38,7 @@ serde = { version = "1", features = ["derive"] }
signal-hook = { version = "0.3" } signal-hook = { version = "0.3" }
slab = "0.4" slab = "0.4"
smartstring = "0.2" smartstring = "0.2"
toml_config = "0.1.0"
[dev-dependencies] [dev-dependencies]
quickcheck = "1" quickcheck = "1"

View file

@ -1,16 +1,18 @@
use std::{net::SocketAddr, path::PathBuf}; use std::{net::SocketAddr, path::PathBuf};
use aquatic_common::{access_list::AccessListConfig, privileges::PrivilegeConfig}; use aquatic_common::{access_list::AccessListConfig, privileges::PrivilegeConfig};
use serde::{Deserialize, Serialize}; use serde::Deserialize;
use toml_config::TomlConfig;
use aquatic_cli_helpers::LogLevel; use aquatic_cli_helpers::LogLevel;
#[derive(Clone, Debug, Serialize, Deserialize)] /// aquatic_http configuration
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct Config { pub struct Config {
/// Socket workers receive requests from the socket, parse them and send /// Socket workers receive requests from the socket, parse them and send
/// them on to the request handler. They then recieve responses from the /// them on to the request workers. They then receive responses from the
/// request handler, encode them and send them back over the socket. /// request workers, encode them and send them back over the socket.
pub socket_workers: usize, pub socket_workers: usize,
/// Request workers receive a number of requests from socket workers, /// Request workers receive a number of requests from socket workers,
/// generate responses and send them back to the socket workers. /// generate responses and send them back to the socket workers.
@ -31,18 +33,22 @@ impl aquatic_cli_helpers::Config for Config {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct NetworkConfig { pub struct NetworkConfig {
/// Bind to this address /// Bind to this address
pub address: SocketAddr, pub address: SocketAddr,
pub tls_certificate_path: PathBuf, /// Only allow access over IPv6
pub tls_private_key_path: PathBuf,
pub ipv6_only: bool, pub ipv6_only: bool,
/// Path to TLS certificate (DER-encoded X.509)
pub tls_certificate_path: PathBuf,
/// Path to TLS private key (DER-encoded ASN.1 in PKCS#8 or PKCS#1 format)
pub tls_private_key_path: PathBuf,
/// Keep connections alive
pub keep_alive: bool, pub keep_alive: bool,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct ProtocolConfig { pub struct ProtocolConfig {
/// Maximum number of torrents to accept in scrape request /// Maximum number of torrents to accept in scrape request
@ -53,12 +59,12 @@ pub struct ProtocolConfig {
pub peer_announce_interval: usize, pub peer_announce_interval: usize,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct CleaningConfig { pub struct CleaningConfig {
/// Clean peers this often (seconds) /// Clean peers this often (seconds)
pub torrent_cleaning_interval: u64, pub torrent_cleaning_interval: u64,
/// Remove peers that haven't announced for this long (seconds) /// Remove peers that have not announced for this long (seconds)
pub max_peer_age: u64, pub max_peer_age: u64,
} }
@ -109,3 +115,10 @@ impl Default for CleaningConfig {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::Config;
::toml_config::gen_serialize_deserialize_test!(Config);
}

View file

@ -26,6 +26,7 @@ rand = { version = "0.8", features = ["small_rng"] }
rand_distr = "0.4" rand_distr = "0.4"
rustls = { version = "0.20", features = ["dangerous_configuration"] } rustls = { version = "0.20", features = ["dangerous_configuration"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
toml_config = "0.1.0"
[dev-dependencies] [dev-dependencies]
quickcheck = "1" quickcheck = "1"

View file

@ -1,9 +1,11 @@
use std::net::SocketAddr; use std::net::SocketAddr;
use aquatic_cli_helpers::LogLevel; use aquatic_cli_helpers::LogLevel;
use serde::{Deserialize, Serialize}; use serde::Deserialize;
use toml_config::TomlConfig;
#[derive(Clone, Debug, Serialize, Deserialize)] /// aquatic_http_load_test configuration
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct Config { pub struct Config {
pub server_address: SocketAddr, pub server_address: SocketAddr,
@ -14,7 +16,7 @@ pub struct Config {
/// How often to check if num_connections connections are open, and /// How often to check if num_connections connections are open, and
/// open a new one otherwise. A value of 0 means that connections are /// open a new one otherwise. A value of 0 means that connections are
/// opened as quickly as possible, which is useful when the tracker /// opened as quickly as possible, which is useful when the tracker
/// doesn't keep connections alive. /// does not keep connections alive.
pub connection_creation_interval_ms: u64, pub connection_creation_interval_ms: u64,
pub duration: usize, pub duration: usize,
pub torrents: TorrentConfig, pub torrents: TorrentConfig,
@ -28,7 +30,7 @@ impl aquatic_cli_helpers::Config for Config {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct TorrentConfig { pub struct TorrentConfig {
pub number_of_torrents: usize, pub number_of_torrents: usize,
@ -73,3 +75,10 @@ impl Default for TorrentConfig {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::Config;
::toml_config::gen_serialize_deserialize_test!(Config);
}

View file

@ -36,6 +36,7 @@ slab = "0.4"
signal-hook = { version = "0.3" } signal-hook = { version = "0.3" }
socket2 = { version = "0.4", features = ["all"] } socket2 = { version = "0.4", features = ["all"] }
tinytemplate = "1" tinytemplate = "1"
toml_config = "0.1.0"
[dev-dependencies] [dev-dependencies]
quickcheck = "1" quickcheck = "1"

View file

@ -1,15 +1,17 @@
use std::{net::SocketAddr, path::PathBuf}; use std::{net::SocketAddr, path::PathBuf};
use aquatic_common::{access_list::AccessListConfig, privileges::PrivilegeConfig}; use aquatic_common::{access_list::AccessListConfig, privileges::PrivilegeConfig};
use serde::{Deserialize, Serialize}; use serde::Deserialize;
use aquatic_cli_helpers::LogLevel; use aquatic_cli_helpers::LogLevel;
use toml_config::TomlConfig;
#[derive(Clone, Debug, Serialize, Deserialize)] /// aquatic_udp configuration
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct Config { pub struct Config {
/// Socket workers receive requests from the socket, parse them and send /// Socket workers receive requests from the socket, parse them and send
/// them on to the request workers. They then recieve responses from the /// them on to the request workers. They then receive responses from the
/// request workers, encode them and send them back over the socket. /// request workers, encode them and send them back over the socket.
pub socket_workers: usize, pub socket_workers: usize,
/// Request workers receive a number of requests from socket workers, /// Request workers receive a number of requests from socket workers,
@ -62,22 +64,21 @@ impl aquatic_cli_helpers::Config for Config {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct NetworkConfig { pub struct NetworkConfig {
/// Bind to this address /// Bind to this address
pub address: SocketAddr, pub address: SocketAddr,
/// Only allow access over IPv6
pub only_ipv6: bool, pub only_ipv6: bool,
/// Size of socket recv buffer. Use 0 for OS default. /// Size of socket recv buffer. Use 0 for OS default.
/// ///
/// This setting can have a big impact on dropped packages. It might /// This setting can have a big impact on dropped packages. It might
/// require changing system defaults. Some examples of commands to set /// require changing system defaults. Some examples of commands to set
/// recommended values for different operating systems: /// values for different operating systems:
/// ///
/// macOS: /// macOS:
/// $ sudo sysctl net.inet.udp.recvspace=6000000 /// $ sudo sysctl net.inet.udp.recvspace=6000000
/// $ sudo sysctl net.inet.udp.maxdgram=500000 # Not necessary, but recommended
/// $ sudo sysctl kern.ipc.maxsockbuf=8388608 # Not necessary, but recommended
/// ///
/// Linux: /// Linux:
/// $ sudo sysctl -w net.core.rmem_max=104857600 /// $ sudo sysctl -w net.core.rmem_max=104857600
@ -108,7 +109,7 @@ impl Default for NetworkConfig {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct ProtocolConfig { pub struct ProtocolConfig {
/// Maximum number of torrents to accept in scrape request /// Maximum number of torrents to accept in scrape request
@ -129,7 +130,7 @@ impl Default for ProtocolConfig {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct StatisticsConfig { pub struct StatisticsConfig {
/// Collect and print/write statistics this often (seconds) /// Collect and print/write statistics this often (seconds)
@ -138,7 +139,7 @@ pub struct StatisticsConfig {
pub print_to_stdout: bool, pub print_to_stdout: bool,
/// Save statistics as HTML to a file /// Save statistics as HTML to a file
pub write_html_to_file: bool, pub write_html_to_file: bool,
/// Path to save HTML file /// Path to save HTML file to
pub html_file_path: PathBuf, pub html_file_path: PathBuf,
} }
@ -159,7 +160,7 @@ impl Default for StatisticsConfig {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct CleaningConfig { pub struct CleaningConfig {
/// Clean connections this often (seconds) /// Clean connections this often (seconds)
@ -174,9 +175,9 @@ pub struct CleaningConfig {
pub pending_scrape_cleaning_interval: u64, pub pending_scrape_cleaning_interval: u64,
/// Remove connections that are older than this (seconds) /// Remove connections that are older than this (seconds)
pub max_connection_age: u64, pub max_connection_age: u64,
/// Remove peers that haven't announced for this long (seconds) /// Remove peers who have not announced for this long (seconds)
pub max_peer_age: u64, pub max_peer_age: u64,
/// Remove pending scrape responses that haven't been returned from request /// Remove pending scrape responses that have not been returned from request
/// workers for this long (seconds) /// workers for this long (seconds)
pub max_pending_scrape_age: u64, pub max_pending_scrape_age: u64,
} }
@ -193,3 +194,10 @@ impl Default for CleaningConfig {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::Config;
::toml_config::gen_serialize_deserialize_test!(Config);
}

View file

@ -21,3 +21,4 @@ num-format = "0.4"
rand = { version = "0.8", features = ["small_rng"] } rand = { version = "0.8", features = ["small_rng"] }
rand_distr = "0.4" rand_distr = "0.4"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
toml_config = "0.1.0"

View file

@ -1,6 +1,7 @@
use serde::{Deserialize, Serialize}; use serde::Deserialize;
use toml_config::TomlConfig;
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
pub struct BenchConfig { pub struct BenchConfig {
pub num_rounds: usize, pub num_rounds: usize,
pub num_threads: usize, pub num_threads: usize,
@ -24,3 +25,10 @@ impl Default for BenchConfig {
} }
impl aquatic_cli_helpers::Config for BenchConfig {} impl aquatic_cli_helpers::Config for BenchConfig {}
#[cfg(test)]
mod tests {
use super::BenchConfig;
::toml_config::gen_serialize_deserialize_test!(BenchConfig);
}

View file

@ -24,6 +24,7 @@ rand = { version = "0.8", features = ["small_rng"] }
rand_distr = "0.4" rand_distr = "0.4"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
socket2 = { version = "0.4", features = ["all"] } socket2 = { version = "0.4", features = ["all"] }
toml_config = "0.1.0"
[dev-dependencies] [dev-dependencies]
quickcheck = "1" quickcheck = "1"

View file

@ -1,12 +1,14 @@
use std::net::SocketAddr; use std::net::SocketAddr;
use serde::{Deserialize, Serialize}; use serde::Deserialize;
use aquatic_cli_helpers::LogLevel; use aquatic_cli_helpers::LogLevel;
#[cfg(feature = "cpu-pinning")] #[cfg(feature = "cpu-pinning")]
use aquatic_common::cpu_pinning::CpuPinningConfig; use aquatic_common::cpu_pinning::CpuPinningConfig;
use toml_config::TomlConfig;
#[derive(Clone, Debug, Serialize, Deserialize)] /// aquatic_udp_load_test configuration
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct Config { pub struct Config {
/// Server address /// Server address
@ -15,6 +17,7 @@ pub struct Config {
/// address here. /// address here.
pub server_address: SocketAddr, pub server_address: SocketAddr,
pub log_level: LogLevel, pub log_level: LogLevel,
/// Number of workers sending requests
pub workers: u8, pub workers: u8,
/// Run duration (quit and generate report after this many seconds) /// Run duration (quit and generate report after this many seconds)
pub duration: usize, pub duration: usize,
@ -39,7 +42,7 @@ impl Default for Config {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct NetworkConfig { pub struct NetworkConfig {
/// True means bind to one localhost IP per socket. /// True means bind to one localhost IP per socket.
@ -59,12 +62,10 @@ pub struct NetworkConfig {
/// ///
/// This setting can have a big impact on dropped packages. It might /// This setting can have a big impact on dropped packages. It might
/// require changing system defaults. Some examples of commands to set /// require changing system defaults. Some examples of commands to set
/// recommended values for different operating systems: /// values for different operating systems:
/// ///
/// macOS: /// macOS:
/// $ sudo sysctl net.inet.udp.recvspace=6000000 /// $ sudo sysctl net.inet.udp.recvspace=6000000
/// $ sudo sysctl net.inet.udp.maxdgram=500000 # Not necessary, but recommended
/// $ sudo sysctl kern.ipc.maxsockbuf=8388608 # Not necessary, but recommended
/// ///
/// Linux: /// Linux:
/// $ sudo sysctl -w net.core.rmem_max=104857600 /// $ sudo sysctl -w net.core.rmem_max=104857600
@ -84,7 +85,7 @@ impl Default for NetworkConfig {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct RequestConfig { pub struct RequestConfig {
/// Number of torrents to simulate /// Number of torrents to simulate
@ -125,3 +126,10 @@ impl Default for RequestConfig {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::Config;
::toml_config::gen_serialize_deserialize_test!(Config);
}

View file

@ -36,6 +36,7 @@ rustls-pemfile = "0.2"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
signal-hook = { version = "0.3" } signal-hook = { version = "0.3" }
slab = "0.4" slab = "0.4"
toml_config = "0.1.0"
tungstenite = "0.16" tungstenite = "0.16"
# mio # mio

View file

@ -4,16 +4,18 @@ use std::path::PathBuf;
#[cfg(feature = "cpu-pinning")] #[cfg(feature = "cpu-pinning")]
use aquatic_common::cpu_pinning::CpuPinningConfig; use aquatic_common::cpu_pinning::CpuPinningConfig;
use aquatic_common::{access_list::AccessListConfig, privileges::PrivilegeConfig}; use aquatic_common::{access_list::AccessListConfig, privileges::PrivilegeConfig};
use serde::{Deserialize, Serialize}; use serde::Deserialize;
use aquatic_cli_helpers::LogLevel; use aquatic_cli_helpers::LogLevel;
use toml_config::TomlConfig;
#[derive(Clone, Debug, Serialize, Deserialize)] /// aquatic_ws configuration
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct Config { pub struct Config {
/// Socket workers receive requests from the socket, parse them and send /// Socket workers receive requests from the socket, parse them and send
/// them on to the request handler. They then recieve responses from the /// them on to the request workers. They then receive responses from the
/// request handler, encode them and send them back over the socket. /// request workers, encode them and send them back over the socket.
pub socket_workers: usize, pub socket_workers: usize,
/// Request workers receive a number of requests from socket workers, /// Request workers receive a number of requests from socket workers,
/// generate responses and send them back to the socket workers. /// generate responses and send them back to the socket workers.
@ -38,25 +40,29 @@ impl aquatic_cli_helpers::Config for Config {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct NetworkConfig { pub struct NetworkConfig {
/// Bind to this address /// Bind to this address
pub address: SocketAddr, pub address: SocketAddr,
/// Only allow access over IPv6
pub ipv6_only: bool, pub ipv6_only: bool,
/// Path to TLS certificate (DER-encoded X.509)
pub tls_certificate_path: PathBuf,
/// Path to TLS private key (DER-encoded ASN.1 in PKCS#8 or PKCS#1 format)
pub tls_private_key_path: PathBuf,
pub websocket_max_message_size: usize, pub websocket_max_message_size: usize,
pub websocket_max_frame_size: usize, pub websocket_max_frame_size: usize,
pub tls_certificate_path: PathBuf,
pub tls_private_key_path: PathBuf,
#[cfg(feature = "with-mio")] #[cfg(feature = "with-mio")]
pub poll_event_capacity: usize, pub poll_event_capacity: usize,
#[cfg(feature = "with-mio")] #[cfg(feature = "with-mio")]
pub poll_timeout_microseconds: u64, pub poll_timeout_microseconds: u64,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct ProtocolConfig { pub struct ProtocolConfig {
/// Maximum number of torrents to accept in scrape request /// Maximum number of torrents to accept in scrape request
@ -68,7 +74,7 @@ pub struct ProtocolConfig {
} }
#[cfg(feature = "with-mio")] #[cfg(feature = "with-mio")]
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct HandlerConfig { pub struct HandlerConfig {
/// Maximum number of requests to receive from channel before locking /// Maximum number of requests to receive from channel before locking
@ -77,12 +83,12 @@ pub struct HandlerConfig {
pub channel_recv_timeout_microseconds: u64, pub channel_recv_timeout_microseconds: u64,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct CleaningConfig { pub struct CleaningConfig {
/// Clean peers this often (seconds) /// Clean peers this often (seconds)
pub torrent_cleaning_interval: u64, pub torrent_cleaning_interval: u64,
/// Remove peers that haven't announced for this long (seconds) /// Remove peers that have not announced for this long (seconds)
pub max_peer_age: u64, pub max_peer_age: u64,
// Clean connections this often (seconds) // Clean connections this often (seconds)
@ -98,10 +104,10 @@ pub struct CleaningConfig {
} }
#[cfg(feature = "with-mio")] #[cfg(feature = "with-mio")]
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct StatisticsConfig { pub struct StatisticsConfig {
/// Print statistics this often (seconds). Don't print when set to zero. /// Print statistics this often (seconds). Do not print when set to zero.
pub interval: u64, pub interval: u64,
} }
@ -131,12 +137,13 @@ impl Default for NetworkConfig {
Self { Self {
address: SocketAddr::from(([0, 0, 0, 0], 3000)), address: SocketAddr::from(([0, 0, 0, 0], 3000)),
ipv6_only: false, ipv6_only: false,
websocket_max_message_size: 64 * 1024,
websocket_max_frame_size: 16 * 1024,
tls_certificate_path: "".into(), tls_certificate_path: "".into(),
tls_private_key_path: "".into(), tls_private_key_path: "".into(),
websocket_max_message_size: 64 * 1024,
websocket_max_frame_size: 16 * 1024,
#[cfg(feature = "with-mio")] #[cfg(feature = "with-mio")]
poll_event_capacity: 4096, poll_event_capacity: 4096,
#[cfg(feature = "with-mio")] #[cfg(feature = "with-mio")]
@ -187,3 +194,10 @@ impl Default for StatisticsConfig {
Self { interval: 0 } Self { interval: 0 }
} }
} }
#[cfg(test)]
mod tests {
use super::Config;
::toml_config::gen_serialize_deserialize_test!(Config);
}

View file

@ -29,6 +29,7 @@ rand_distr = "0.4"
rustls = { version = "0.20", features = ["dangerous_configuration"] } rustls = { version = "0.20", features = ["dangerous_configuration"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
toml_config = "0.1.0"
tungstenite = "0.16" tungstenite = "0.16"
[dev-dependencies] [dev-dependencies]

View file

@ -3,9 +3,11 @@ use std::net::SocketAddr;
use aquatic_cli_helpers::LogLevel; use aquatic_cli_helpers::LogLevel;
#[cfg(feature = "cpu-pinning")] #[cfg(feature = "cpu-pinning")]
use aquatic_common::cpu_pinning::CpuPinningConfig; use aquatic_common::cpu_pinning::CpuPinningConfig;
use serde::{Deserialize, Serialize}; use serde::Deserialize;
use toml_config::TomlConfig;
#[derive(Clone, Debug, Serialize, Deserialize)] /// aquatic_ws_load_test configuration
#[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct Config { pub struct Config {
pub server_address: SocketAddr, pub server_address: SocketAddr,
@ -41,7 +43,7 @@ impl Default for Config {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, TomlConfig, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct TorrentConfig { pub struct TorrentConfig {
pub offers_per_request: usize, pub offers_per_request: usize,
@ -72,3 +74,10 @@ impl Default for TorrentConfig {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::Config;
::toml_config::gen_serialize_deserialize_test!(Config);
}

21
toml_config/Cargo.toml Normal file
View file

@ -0,0 +1,21 @@
[package]
name = "toml_config"
version = "0.1.0"
authors = ["Joakim Frostegård <joakim.frostegard@gmail.com>"]
edition = "2021"
license = "Apache-2.0"
description = "WebTorrent tracker protocol"
repository = "https://github.com/greatest-ape/aquatic"
exclude = ["target"]
[lib]
name = "toml_config"
[dependencies]
toml = "0.5"
toml_config_derive = { path = "../toml_config_derive" }
[dev-dependencies]
serde = { version = "1.0", features = ["derive"] }
quickcheck = "1"
quickcheck_macros = "1"

126
toml_config/src/lib.rs Normal file
View file

@ -0,0 +1,126 @@
pub use toml;
pub use toml_config_derive::TomlConfig;
/// Run this on your struct implementing TomlConfig to generate a
/// serialization/deserialization test for it.
#[macro_export]
macro_rules! gen_serialize_deserialize_test {
($ident:ident) => {
#[test]
fn test_cargo_toml_serialize_deserialize() {
use ::toml_config::TomlConfig;
let serialized = $ident::default_to_string();
let deserialized = ::toml_config::toml::de::from_str(&serialized).unwrap();
assert_eq!($ident::default(), deserialized);
}
};
}
/// Export structs to toml, converting Rust doc strings to comments.
///
/// Supports one level of nesting. Fields containing structs must come
/// after regular fields.
///
/// Usage:
/// ```
/// use toml_config::TomlConfig;
///
/// #[derive(TomlConfig)]
/// struct SubConfig {
/// /// A
/// a: usize,
/// /// B
/// b: String,
/// }
///
/// impl Default for SubConfig {
/// fn default() -> Self {
/// Self {
/// a: 200,
/// b: "subconfig hello".into(),
/// }
/// }
/// }
///
/// #[derive(TomlConfig)]
/// struct Config {
/// /// A
/// a: usize,
/// /// B
/// b: String,
/// /// C
/// c: SubConfig,
/// }
///
/// impl Default for Config {
/// fn default() -> Self {
/// Self {
/// a: 100,
/// b: "hello".into(),
/// c: Default::default(),
/// }
/// }
/// }
///
/// let expected = "# A\na = 100\n# B\nb = \"hello\"\n\n# C\n[c]\n# A\na = 200\n# B\nb = \"subconfig hello\"\n";
///
/// assert_eq!(
/// Config::default_to_string(),
/// expected,
/// );
/// ```
pub trait TomlConfig: Default {
fn default_to_string() -> String;
}
pub mod __private {
use std::net::SocketAddr;
use std::path::PathBuf;
pub trait Private {
fn __to_string(&self, comment: Option<String>, field_name: String) -> String;
}
macro_rules! impl_trait {
($ident:ident) => {
impl Private for $ident {
fn __to_string(&self, comment: Option<String>, field_name: String) -> String {
let mut output = String::new();
if let Some(comment) = comment {
output.push_str(&comment);
}
let value = crate::toml::ser::to_string(self).unwrap();
output.push_str(&format!("{} = {}\n", field_name, value));
output
}
}
};
}
impl_trait!(isize);
impl_trait!(i8);
impl_trait!(i16);
impl_trait!(i32);
impl_trait!(i64);
impl_trait!(usize);
impl_trait!(u8);
impl_trait!(u16);
impl_trait!(u32);
impl_trait!(u64);
impl_trait!(f32);
impl_trait!(f64);
impl_trait!(bool);
impl_trait!(String);
impl_trait!(PathBuf);
impl_trait!(SocketAddr);
}

46
toml_config/tests/test.rs Normal file
View file

@ -0,0 +1,46 @@
use serde::Deserialize;
use toml_config::{gen_serialize_deserialize_test, TomlConfig};
#[derive(Clone, Debug, PartialEq, Eq, TomlConfig, Deserialize)]
struct TestConfigInnerA {
/// Comment for a
a: String,
/// Comment for b
b: usize,
}
impl Default for TestConfigInnerA {
fn default() -> Self {
Self {
a: "Inner hello world".into(),
b: 100,
}
}
}
/// Comment for TestConfig
#[derive(Clone, Debug, PartialEq, Eq, TomlConfig, Deserialize)]
struct TestConfig {
/// Comment for a that stretches over
/// multiple lines
a: String,
/// Comment for b
b: usize,
c: bool,
/// Comment for TestConfigInnerA
inner_a: TestConfigInnerA,
}
impl Default for TestConfig {
fn default() -> Self {
Self {
a: "Hello, world!".into(),
b: 100,
c: true,
inner_a: Default::default(),
}
}
}
gen_serialize_deserialize_test!(TestConfig);

View file

@ -0,0 +1,17 @@
[package]
name = "toml_config_derive"
version = "0.1.0"
authors = ["Joakim Frostegård <joakim.frostegard@gmail.com>"]
edition = "2021"
license = "Apache-2.0"
description = "WebTorrent tracker protocol"
repository = "https://github.com/greatest-ape/aquatic"
exclude = ["target"]
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1"
quote = "1"
syn = "1"

View file

@ -0,0 +1,177 @@
use proc_macro2::{TokenStream, TokenTree};
use quote::quote;
use syn::{parse_macro_input, Attribute, Data, DataStruct, DeriveInput, Fields, Ident, Type};
#[proc_macro_derive(TomlConfig)]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let comment = extract_comment_string(input.attrs);
let ident = input.ident;
match input.data {
Data::Struct(struct_data) => {
let mut output_stream = quote! {
let mut output = String::new();
};
extract_from_struct(ident.clone(), struct_data, &mut output_stream);
proc_macro::TokenStream::from(quote! {
impl ::toml_config::TomlConfig for #ident {
fn default_to_string() -> String {
let mut output = String::new();
let comment: Option<String> = #comment;
if let Some(comment) = comment {
output.push_str(&comment);
output.push('\n');
}
let body = {
#output_stream
output
};
output.push_str(&body);
output
}
}
impl ::toml_config::__private::Private for #ident {
fn __to_string(&self, comment: Option<String>, field_name: String) -> String {
let mut output = String::new();
output.push('\n');
if let Some(comment) = comment {
output.push_str(&comment);
}
output.push_str(&format!("[{}]\n", field_name));
let body = {
#output_stream
output
};
output.push_str(&body);
output
}
}
})
}
Data::Enum(_) => proc_macro::TokenStream::from(quote! {
impl ::toml_config::__private::Private for #ident {
fn __to_string(&self, comment: Option<String>, field_name: String) -> String {
let mut output = String::new();
let wrapping_comment: Option<String> = #comment;
if let Some(comment) = wrapping_comment {
output.push_str(&comment);
}
if let Some(comment) = comment {
output.push_str(&comment);
}
let value = match ::toml_config::toml::ser::to_string(self) {
Ok(value) => value,
Err(err) => panic!("Couldn't serialize enum to toml: {:#}", err),
};
output.push_str(&format!("{} = {}\n", field_name, value));
output
}
}
}),
Data::Union(_) => panic!("Unions are not supported"),
}
}
fn extract_from_struct(
struct_ty_ident: Ident,
struct_data: DataStruct,
output_stream: &mut TokenStream,
) {
let fields = if let Fields::Named(fields) = struct_data.fields {
fields
} else {
panic!("Fields are not named");
};
output_stream.extend(::std::iter::once(quote! {
let struct_default = #struct_ty_ident::default();
}));
for field in fields.named.into_iter() {
let ident = field.ident.expect("Encountered unnamed field");
let ident_string = format!("{}", ident);
let comment = extract_comment_string(field.attrs);
if let Type::Path(path) = field.ty {
output_stream.extend(::std::iter::once(quote! {
{
let comment: Option<String> = #comment;
let field_default: #path = struct_default.#ident;
let s: String = ::toml_config::__private::Private::__to_string(
&field_default,
comment,
#ident_string.to_string()
);
output.push_str(&s);
}
}));
}
}
}
fn extract_comment_string(attrs: Vec<Attribute>) -> TokenStream {
let mut output = String::new();
for attr in attrs.into_iter() {
let path_ident = if let Some(path_ident) = attr.path.get_ident() {
path_ident
} else {
continue;
};
if format!("{}", path_ident) != "doc" {
continue;
}
for token_tree in attr.tokens {
match token_tree {
TokenTree::Literal(literal) => {
let mut comment = format!("{}", literal);
// Strip leading and trailing quotation marks
comment.remove(comment.len() - 1);
comment.remove(0);
// Add toml comment indicator
comment.insert(0, '#');
output.push_str(&comment);
output.push('\n');
}
_ => {}
}
}
}
if output.is_empty() {
quote! {
None
}
} else {
quote! {
Some(#output.to_string())
}
}
}