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
use serde_derive::{Deserialize, Serialize};
use std::env;
use std::fs;
use std::net::{IpAddr, Ipv4Addr};

/// Config holds the configuration for local and remote UDP addresses
#[derive(Debug, Deserialize, Serialize)]
pub struct Config {
    pub bind_addresses: Vec<Address>,
    pub destination_address: Option<Address>,
}

/// Address is a struct that holds an IP address and a port
/// It is used to configure the local and remote addresses for UDP
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Address {
    pub ip: IpAddr,
    pub port: u16,
}

impl Default for Config {
    fn default() -> Self {
        Config {
            bind_addresses: vec![Address {
                ip: IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
                port: 5060,
            }],
            destination_address: Some(Address {
                ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
                port: 5061,
            }),
        }
    }
}

/// Configuration through TOML
pub trait FromToml {
    fn from_toml(filename: String) -> Self;
}

/// Configuration through arguments
pub trait FromArguments {
    fn from_arguments(local: Vec<Address>, remote: Option<Address>) -> Self;
}

/// Default configuration
pub trait FromDefault {
    fn from_default() -> Self;
}

/// Configuration through environment variables
pub trait FromEnv {
    fn from_env() -> Self;
}

/// ## Info
/// FromToml is used when the user wants to specify
/// the addresses and ports via a config file
///
/// ## Example
/// ``` Toml
/// [[bind_addresses]]
/// ip = "127.0.0.1"
/// port = 5061
/// [destination_address]
/// ip = "127.0.0.1"
/// port = 5060
/// ```
/// ``` rust
/// let config = Config::from_toml("config.toml".to_string());
/// ```
impl FromToml for Config {
    fn from_toml(filename: String) -> Self {
        let contents = fs::read_to_string(filename);

        match contents {
            Ok(cf) => toml::from_str(&cf).unwrap(),
            Err(_) => Config::default(),
        }
    }
}

/// ## Info
/// FromArguments is used for when the user prefers to
/// pass the addresses and ports via their own means
///
/// ## Example
/// ``` rust
/// let config = Config::from_arguments(
///        vec![(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 5061)],
///        Some((IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 5060)),
///    );
/// ```
impl FromArguments for Config {
    fn from_arguments(local: Vec<Address>, remote: Option<Address>) -> Self {
        Config {
            bind_addresses: local,
            destination_address: remote,
        }
    }
}

/// FromDefault is used when no config file is specified
///
///for the time I using 5060/5061 as the default ports
impl FromDefault for Config {
    fn from_default() -> Self {
        Config::default()
    }
}

/// ## Info
/// FromEnv only supports a single address/port pair
/// for both the local and remote addresses
///
/// THE ENVIRONMENT VARIABLES ARE:
/// * BIND_ADDRS: the local address to bind to
/// * BIND_PORT: the local port to bind to
/// * DEST_ADDRS: the remote address to send to
/// * DEST_PORT: the remote port to send to
///
/// ## Example
///``` rust
/// let config = Config::from_env();
/// ```
impl FromEnv for Config {
    fn from_env() -> Self {
        println!("BIND_ADDRS: {:?}", env::var("BIND_ADDRS"));
        println!("BIND_PORT: {:?}", env::var("BIND_PORT"));

        let bind_address = match env::var("BIND_ADDRS") {
            Ok(addr) => addr
                .parse::<IpAddr>()
                .unwrap_or_else(|_| panic!("Invalid address")),
            Err(err) => panic!("{}", err),
        };

        let bind_port = match env::var("BIND_PORT") {
            Ok(port) => port
                .parse::<u16>()
                .unwrap_or_else(|_| panic!("Invalid port")),
            Err(err) => panic!("{}", err),
        };

        let dest_address = match env::var("DEST_ADDRS") {
            Ok(addr) => Some(
                addr.parse::<IpAddr>()
                    .unwrap_or_else(|_| panic!("Invalid address")),
            ),
            Err(_err) => None,
        };

        let dest_port = match env::var("DEST_PORT") {
            Ok(port) => Some(
                port.parse::<u16>()
                    .unwrap_or_else(|_| panic!("Invalid port")),
            ),
            Err(_err) => None,
        };

        Config {
            bind_addresses: vec![Address {
                ip: bind_address,
                port: bind_port,
            }],
            destination_address: match (dest_address, dest_port) {
                (Some(ip), Some(port)) => Some(Address { ip, port }),
                _ => None,
            },
        }
    }
}

#[cfg(test)]
mod tests {
    use super::FromEnv;
    use serial_test::serial;
    use std::env;

    #[test]
    fn default_config() {
        let config = super::Config::default();
        assert_eq!(config.bind_addresses.len(), 1);
        assert_eq!(config.destination_address.is_some(), true);
    }

    #[test]
    #[serial]
    fn env_config_v4() {
        env::set_var("BIND_ADDRS", "192.168.1.0");
        env::set_var("BIND_PORT", "5060");
        env::set_var("DEST_ADDRS", "192.168.1.0");
        env::set_var("DEST_PORT", "5061");

        let config = super::Config::from_env();
        assert_eq!(config.bind_addresses.len(), 1);
        assert_eq!(config.destination_address.is_some(), true);
    }

    #[test]
    #[serial]
    #[should_panic]
    fn bind_address_env_not_there() {
        env::remove_var("BIND_ADDRS");
        env::remove_var("BIND_PORT");
        super::Config::from_env();
    }

    #[test]
    #[serial]
    fn dest_address_env_not_there() {
        env::set_var("BIND_ADDRS", "192.168.1.0");
        env::set_var("BIND_PORT", "5060");
        env::remove_var("DEST_ADDRS");
        env::remove_var("DEST_PORT");
        let config = super::Config::from_env();
        assert_eq!(config.bind_addresses.len(), 1);
        assert_eq!(config.destination_address.is_none(), true);
    }
}