From d1a76e00fc92d1550e748fd1b393eaf0f10f7112 Mon Sep 17 00:00:00 2001 From: Stuart Stock Date: Sun, 30 Sep 2018 17:43:27 -0500 Subject: Separate trait objects for file- and environment-based server config --- src/config/environment.rs | 126 +++++++++++++++++++++++++++++++++++++++++++++ src/config/file.rs | 127 ++++++++++++++++++++++++++++++++++++++++++++++ src/config/mod.rs | 90 ++++++++++++++++++++++++++++++++ 3 files changed, 343 insertions(+) create mode 100644 src/config/environment.rs create mode 100644 src/config/file.rs create mode 100644 src/config/mod.rs (limited to 'src/config') diff --git a/src/config/environment.rs b/src/config/environment.rs new file mode 100644 index 0000000..c4d2c06 --- /dev/null +++ b/src/config/environment.rs @@ -0,0 +1,126 @@ +// Copyright 2017-2018 int08h LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate hex; + +use std::env; +use std::net::SocketAddr; +use std::time::Duration; + +use Error; +use config::ServerConfig; +use config::{DEFAULT_BATCH_SIZE, DEFAULT_STATUS_INTERVAL}; + +/// +/// Obtain a Roughenough server configuration ([ServerConfig](trait.ServerConfig.html)) +/// from environment variables. +/// +/// Config parameter | Environment Variable +/// ---------------- | -------------------- +/// port | `ROUGHENOUGH_PORT` +/// interface | `ROUGHENOUGH_INTERFACE` +/// seed | `ROUGHENOUGH_SEED` +/// batch_size | `ROUGHENOUGH_BATCH_SIZE` +/// status_interval | `ROUGHENOUGH_STATUS_INTERVAL` +/// +pub struct EnvironmentConfig { + port: u16, + interface: String, + seed: Vec, + batch_size: u8, + status_interval: Duration, +} + +const ROUGHENOUGH_PORT: &str = "ROUGHENOUGH_PORT"; +const ROUGHENOUGH_INTERFACE: &str = "ROUGHENOUGH_INTERFACE"; +const ROUGHENOUGH_SEED: &str = "ROUGHENOUGH_SEED"; +const ROUGHENOUGH_BATCH_SIZE: &str = "ROUGHENOUGH_BATCH_SIZE"; +const ROUGHENOUGH_STATUS_INTERVAL: &str = "ROUGHENOUGH_STATUS_INTERVAL"; + +impl EnvironmentConfig { + pub fn new() -> Self { + let mut cfg = EnvironmentConfig { + port: 0, + interface: "".to_string(), + seed: Vec::new(), + batch_size: DEFAULT_BATCH_SIZE, + status_interval: DEFAULT_STATUS_INTERVAL, + }; + + if let Ok(port) = env::var(ROUGHENOUGH_PORT) { + cfg.port = port + .parse() + .expect(format!("invalid port: {}", port).as_ref()); + }; + + if let Ok(interface) = env::var(ROUGHENOUGH_INTERFACE) { + cfg.interface = interface.to_string(); + }; + + if let Ok(seed) = env::var(ROUGHENOUGH_SEED) { + cfg.seed = hex::decode(&seed).expect( + format!( + "invalid seed value: {}\n'seed' should be 32 byte hex value", + seed + ).as_ref(), + ); + }; + + if let Ok(batch_size) = env::var(ROUGHENOUGH_BATCH_SIZE) { + cfg.batch_size = batch_size + .parse() + .expect(format!("invalid batch_size: {}", batch_size).as_ref()); + }; + + if let Ok(status_interval) = env::var(ROUGHENOUGH_STATUS_INTERVAL) { + let val: u16 = status_interval + .parse() + .expect(format!("invalid status_interval: {}", status_interval).as_ref()); + + cfg.status_interval = Duration::from_secs(val as u64); + }; + + cfg + } +} + +impl ServerConfig for EnvironmentConfig { + fn interface(&self) -> &str { + self.interface.as_ref() + } + + fn port(&self) -> u16 { + self.port + } + + fn seed(&self) -> &[u8] { + &self.seed + } + + fn batch_size(&self) -> u8 { + self.batch_size + } + + fn status_interval(&self) -> Duration { + self.status_interval + } + + fn socket_addr(&self) -> Result { + let addr = format!("{}:{}", self.interface, self.port); + match addr.parse() { + Ok(v) => Ok(v), + Err(_) => Err(Error::InvalidConfiguration(addr)), + } + } +} diff --git a/src/config/file.rs b/src/config/file.rs new file mode 100644 index 0000000..24011bb --- /dev/null +++ b/src/config/file.rs @@ -0,0 +1,127 @@ +// Copyright 2017-2018 int08h LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate hex; + +use std::fs::File; +use std::io::Read; +use std::net::SocketAddr; +use std::time::Duration; +use yaml_rust::YamlLoader; + +use Error; +use config::ServerConfig; +use config::{DEFAULT_BATCH_SIZE, DEFAULT_STATUS_INTERVAL}; + +/// +/// Read a Roughenough server configuration ([ServerConfig](trait.ServerConfig.html)) +/// from a YAML file. +/// +/// Example config: +/// +/// ```yaml +/// interface: 127.0.0.1 +/// port: 8686 +/// seed: f61075c988feb9cb700a4a6a3291bfbc9cab11b9c9eca8c802468eb38a43d7d3 +/// ``` +/// +pub struct FileConfig { + port: u16, + interface: String, + seed: Vec, + batch_size: u8, + status_interval: Duration, +} + +impl FileConfig { + pub fn from_file(config_file: &str) -> Result { + let mut infile = File::open(config_file).expect("failed to open config file"); + + let mut contents = String::new(); + infile + .read_to_string(&mut contents) + .expect("could not read config file"); + + let cfg = YamlLoader::load_from_str(&contents).expect("could not parse config file"); + + if cfg.len() != 1 { + return Err(Error::InvalidConfiguration( + "Empty or malformed config file".to_string(), + )); + } + + let mut config = FileConfig { + port: 0, + interface: "".to_string(), + seed: Vec::new(), + batch_size: DEFAULT_BATCH_SIZE, + status_interval: DEFAULT_STATUS_INTERVAL, + }; + + for (key, value) in cfg[0].as_hash().unwrap() { + match key.as_str().unwrap() { + "port" => config.port = value.as_i64().unwrap() as u16, + "interface" => config.interface = value.as_str().unwrap().to_string(), + "batch_size" => config.batch_size = value.as_i64().unwrap() as u8, + "seed" => { + let val = value.as_str().unwrap().to_string(); + config.seed = hex::decode(val) + .expect("seed value invalid; 'seed' should be 32 byte hex value"); + } + "status_interval" => { + let val = value.as_i64().expect("status_interval value invalid"); + config.status_interval = Duration::from_secs(val as u64) + } + unknown => { + return Err(Error::InvalidConfiguration(format!( + "unknown config key: {}", + unknown + ))); + } + } + } + + Ok(config) + } +} + +impl ServerConfig for FileConfig { + fn interface(&self) -> &str { + self.interface.as_ref() + } + + fn port(&self) -> u16 { + self.port + } + + fn seed(&self) -> &[u8] { + &self.seed + } + + fn batch_size(&self) -> u8 { + self.batch_size + } + + fn status_interval(&self) -> Duration { + self.status_interval + } + + fn socket_addr(&self) -> Result { + let addr = format!("{}:{}", self.interface, self.port); + match addr.parse() { + Ok(v) => Ok(v), + Err(_) => Err(Error::InvalidConfiguration(addr)), + } + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..0cd8742 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,90 @@ +// Copyright 2017-2018 int08h LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! +//! Ways to configure the Roughenough server. +//! +//! The [ServerConfig](trait.ServerConfig.html) trait specifies the required and optional +//! parameters available for configuring a Roughenoguh server instance. +//! +//! Implementations of `ServerConfig` obtain configurations from different back-end sources. +//! + +extern crate hex; + +use std::net::SocketAddr; +use std::time::Duration; + +mod file; +pub use self::file::FileConfig; + +mod environment; +pub use self::environment::EnvironmentConfig; + +use Error; + +/// Maximum number of requests to process in one batch and include the the Merkle tree. +pub const DEFAULT_BATCH_SIZE: u8 = 64; + +/// Amount of time between each logged status update. +pub const DEFAULT_STATUS_INTERVAL: Duration = Duration::from_secs(600); + +/// +/// Specifies parameters needed to configure a Roughenough server. +/// +/// Parameters labeled "**Required**" must are always be provided and have no default value. +/// Parameters labeled "**Optional**" provide default values that can be overridden. +/// +/// * **`interface`** - [Required] IP address or interface name for listening to client requests +/// * **`port`** - [Required] UDP port to listen for requests +/// * **`seed`** - [Required] A 32-byte hexadecimal value used to generate the server's long-term +/// key pair. **This is a secret value and must be un-guessable**, +/// treat it with care. +/// * **`batch_size`** - [Optional] The maximum number of requests to process in one batch. All +/// nonces in a batch are used to build a Merkle tree, the root of which +/// is signed. +/// Defaults to [DEFAULT_BATCH_SIZE](constant.DEFAULT_BATCH_SIZE.html) +/// * **`status_interval`** - [Optional] Amount of time between each logged status update. +/// Defaults to [DEFAULT_STATUS_INTERVAL](constant.DEFAULT_STATUS_INTERVAL.html) +/// +/// Implementations of this trait obtain a valid configuration from different back- +/// end sources. +/// +/// See: +/// * [FileConfig](struct.FileConfig.html) +/// +pub trait ServerConfig { + /// [Required] IP address or interface name to listen for client requests + fn interface(&self) -> &str; + + /// [Required] UDP port to listen for requests + fn port(&self) -> u16; + + /// [Required] A 32-byte hexadecimal value used to generate the server's + /// long-term key pair. **This is a secret value and must be un-guessable**, + /// treat it with care. + fn seed(&self) -> &[u8]; + + /// [Optional] The maximum number of requests to process in one batch. All + /// nonces in a batch are used to build a Merkle tree, the root of which is signed. + /// Defaults to [DEFAULT_BATCH_SIZE](constant.DEFAULT_BATCH_SIZE.html) + fn batch_size(&self) -> u8; + + /// [Optional] Amount of time between each logged status update. + /// Defaults to [DEFAULT_STATUS_INTERVAL](constant.DEFAULT_STATUS_INTERVAL.html) + fn status_interval(&self) -> Duration; + + /// Convenience function to create a `SocketAddr` from the provided `interface` and `port` + fn socket_addr(&self) -> Result; +} -- cgit v1.2.3