1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
//! Structures to represent configuration used by `aether_lib`
//!
//! - All time values are in milliseconds unless specified otherwise
//! - `_US` is used as suffix for time values in microseconds
//!
//! ## Configuration file
//! The default configuration file is to be stored in `$HOME/.config/aether/config.yaml` and must
//! be in [YAML](https://yaml.org/) format
//!
//! Note that any missing values will be replaced with default values. It is not recommended to
//! leave any missing values in the configuration file as the values need to follow certain
//! constaints. For example, `handshake_timeout` cannot be smaller than `peer_poll_time` because in
//! such a case, the handshake would timeout before even a single poll is complete.
use log::{info, warn};
use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, default::Default, fs, path::Path};

use crate::error::AetherError;

/// Structure to represent configuration options for `aether_lib`
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Default)]
#[serde(default)]
pub struct Config {
    /// Configuration for [`peer`][crate::peer] module
    pub aether: AetherConfig,
    /// Configuration for [`handshake`][crate::peer::handshake] module
    pub handshake: HandshakeConfig,
    /// Configuration for [`link`][crate::link] module
    pub link: LinkConfig,
}

/// Structure to represent configuration for [`peer`][crate::peer] module
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
#[serde(default)]
pub struct AetherConfig {
    /// Duration to wait for Tracker server to respond (in ms)
    pub server_retry_delay: u64,
    /// How often to poll server for new connections
    pub server_poll_time: u64,
    /// How long to wait to retry handshake after a failed attempt
    /// Also used as duration to wait to receive nonce from other peer during
    /// authentication
    pub handshake_retry_delay: u64,
    /// Poll time to check if connection has been established
    pub connection_check_delay: u64,
    /// Magnitude by which to randomize retry delay
    pub delta_time: u64,
    /// General poll time to be used to check for updates to lists shared by threads
    /// (in us)
    pub poll_time_us: u64,
}

/// Structure to represent configuration for [`handshake`][crate::peer::handshake] module
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
#[serde(default)]
pub struct HandshakeConfig {
    /// Poll time to send sequence or sequence+acknowledgement to the other peer
    /// Also, the timeout for receiving sequence or sequence+acknowledgment from the other peer (in
    /// ms)
    pub peer_poll_time: u64,
    /// Timeout after which handshake can be declared failed if not complete (in ms)
    pub handshake_timeout: u64,
}

/// Structure to represent configuration for [`link`][crate::link] module
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
#[serde(default)]
pub struct LinkConfig {
    /// Window size for the link. Determines how many packets are sent in a single burst
    pub window_size: u16,
    /// Time to wait for acknowledgement to be received
    pub ack_wait_time: u64,
    /// Poll time for shared memory structures
    pub poll_time_us: u64,
    /// Timeout or time of inactivity after which link is declared as broken
    pub timeout: u64,
    /// Time to wait for acknowledgment before sending packets again
    pub retry_delay: u64,
    /// Time to wait before sending another acknowledgment only packet when primary queue is empty
    /// i.e. no more packets to be sent
    pub ack_only_time: u64,
    /// Number of times a packet can be retried before link is declared as broken
    pub max_retries: i16,
}

impl Config {
    /// Returns configuration read from `file_path`
    /// Configuration file must be in [YAML](https://yaml.org/) format
    /// This may return an [`AetherError`] if the file is not present or if the file
    /// is not correctly formated as yaml
    ///
    /// # Examples
    ///
    /// ```
    /// use aether_lib::config::Config;
    /// use std::path::Path;
    ///
    /// // For a file located inside /home/user/aether_config.yaml we can construct
    /// // a path
    /// let path = Path::new("/home/user/aether_config.yaml");
    ///
    /// let config = Config::from_file(&path);
    /// ```
    pub fn from_file(file_path: &Path) -> Result<Config, AetherError> {
        match fs::read_to_string(file_path) {
            Ok(data) => match Config::try_from(data) {
                Ok(config) => Ok(config),
                Err(err) => Err(AetherError::YamlParse(err)),
            },
            Err(err) => Err(AetherError::FileRead(err)),
        }
    }

    /// Returns configuration read from the default configuration file
    /// If default configuration file is not found, the default internal configuration
    /// is returned
    ///
    /// # Examples
    ///
    /// ```
    /// use aether_lib::config::Config;
    /// let config = Config::get_config();
    /// ```
    pub fn get_config() -> Result<Config, AetherError> {
        match home::home_dir() {
            Some(mut path_buf) => {
                path_buf.push(".config");
                path_buf.push("aether");
                path_buf.push("config.yaml");

                let path = path_buf.as_path();

                info!(
                    "Reading configuration from {}",
                    path.to_str().unwrap_or("Cannot parse path")
                );

                match Config::from_file(path) {
                    Ok(config) => Ok(config),
                    Err(err) => match err {
                        AetherError::FileRead(file_err) => {
                            warn!("{:?}", file_err);
                            Ok(Config::default())
                        }
                        _ => Err(err),
                    },
                }
            }
            None => Ok(Config::default()),
        }
    }
}

impl TryFrom<String> for Config {
    type Error = serde_yaml::Error;
    fn try_from(string: String) -> Result<Self, Self::Error> {
        serde_yaml::from_str(&string)
    }
}

impl TryFrom<Config> for String {
    type Error = serde_yaml::Error;
    fn try_from(value: Config) -> Result<Self, Self::Error> {
        serde_yaml::to_string(&value)
    }
}

/// Default values for [`AetherConfig`]
impl Default for AetherConfig {
    fn default() -> Self {
        Self {
            server_retry_delay: 1_000,
            server_poll_time: 1_000,
            handshake_retry_delay: 1_500,
            connection_check_delay: 1_000,
            delta_time: 1000,
            poll_time_us: 100,
        }
    }
}

/// Default values for [`HandshakeConfig`]
impl Default for HandshakeConfig {
    fn default() -> Self {
        Self {
            peer_poll_time: 100,
            handshake_timeout: 2_500,
        }
    }
}

/// Default values for ['LinkConfig`]
impl Default for LinkConfig {
    fn default() -> Self {
        Self {
            window_size: 20,
            ack_wait_time: 1_000,
            poll_time_us: 100,
            timeout: 10_000,
            retry_delay: 100,
            ack_only_time: 50,
            max_retries: 10,
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::config::Config;
    use std::{convert::TryFrom, fs, path::Path};

    #[test]
    fn read_test() {
        let default = Config::default();

        let path = "./tmp/config.yaml";

        fs::create_dir_all("./tmp").unwrap();

        fs::write(path, String::try_from(default).unwrap()).unwrap();

        let config = Config::from_file(Path::new(path)).unwrap();

        assert_eq!(config, default);
    }
}