diff options
Diffstat (limited to 'src/datatype/config.rs')
-rw-r--r-- | src/datatype/config.rs | 389 |
1 files changed, 389 insertions, 0 deletions
diff --git a/src/datatype/config.rs b/src/datatype/config.rs new file mode 100644 index 0000000..5db0f12 --- /dev/null +++ b/src/datatype/config.rs @@ -0,0 +1,389 @@ +use rustc_serialize::Decodable; +use std::fs; +use std::fs::File; +use std::io::ErrorKind; +use std::os::unix::fs::PermissionsExt; +use std::io::prelude::*; +use std::path::Path; +use toml; +use toml::{Decoder, Parser, Table, Value}; + +use datatype::{Error, SystemInfo, Url}; +use package_manager::PackageManager; + + +/// An aggregation of all the configuration options parsed at startup. +#[derive(Default, PartialEq, Eq, Debug, Clone)] +pub struct Config { + pub auth: Option<AuthConfig>, + pub core: CoreConfig, + pub dbus: Option<DBusConfig>, + pub device: DeviceConfig, + pub gateway: GatewayConfig, + pub network: NetworkConfig, + pub rvi: Option<RviConfig>, +} + +impl Config { + pub fn load(path: &str) -> Result<Config, Error> { + info!("Loading config file: {}", path); + let mut file = try!(File::open(path).map_err(Error::Io)); + let mut toml = String::new(); + try!(file.read_to_string(&mut toml)); + Config::parse(&toml) + } + + pub fn parse(toml: &str) -> Result<Config, Error> { + let table = try!(parse_table(&toml)); + + let auth_cfg = if let Some(auth) = table.get("auth") { + let parsed = try!(decode_section(auth.clone())); + Some(try!(bootstrap_credentials(parsed))) + } else { + None + }; + + let dbus_cfg = if let Some(dbus) = table.get("dbus") { + Some(try!(decode_section(dbus.clone()))) + } else { + None + }; + + let rvi_cfg = if let Some(rvi) = table.get("rvi") { + Some(try!(decode_section(rvi.clone()))) + } else { + None + }; + + Ok(Config { + auth: auth_cfg, + core: try!(read_section(&table, "core")), + dbus: dbus_cfg, + device: try!(read_section(&table, "device")), + gateway: try!(read_section(&table, "gateway")), + network: try!(read_section(&table, "network")), + rvi: rvi_cfg, + }) + } +} + +fn parse_table(toml: &str) -> Result<Table, Error> { + let mut parser = Parser::new(toml); + Ok(try!(parser.parse().ok_or_else(move || parser.errors))) +} + +fn read_section<T: Decodable>(table: &Table, section: &str) -> Result<T, Error> { + let part = try!(table.get(section) + .ok_or_else(|| Error::Parse(format!("invalid section: {}", section)))); + decode_section(part.clone()) +} + +fn decode_section<T: Decodable>(section: Value) -> Result<T, Error> { + let mut decoder = Decoder::new(section); + Ok(try!(T::decode(&mut decoder))) +} + + +#[derive(RustcEncodable, RustcDecodable)] +struct CredentialsFile { + pub client_id: String, + pub client_secret: String, +} + +// Read AuthConfig values from the credentials file if it exists, or write the +// current AuthConfig values to a new credentials file otherwise. +fn bootstrap_credentials(auth_cfg: AuthConfig) -> Result<AuthConfig, Error> { + let creds = auth_cfg.credentials_file.clone(); + let path = Path::new(&creds); + debug!("bootstrap_credentials: {:?}", path); + + let credentials = match File::open(path) { + Ok(mut file) => { + let mut text = String::new(); + try!(file.read_to_string(&mut text)); + let table = try!(parse_table(&text)); + try!(read_section::<CredentialsFile>(&table, "auth")) + } + + Err(ref err) if err.kind() == ErrorKind::NotFound => { + let mut table = Table::new(); + let credentials = CredentialsFile { + client_id: auth_cfg.client_id, + client_secret: auth_cfg.client_secret + }; + table.insert("auth".to_string(), toml::encode(&credentials)); + + let dir = try!(path.parent().ok_or(Error::Parse("Invalid credentials file path".to_string()))); + try!(fs::create_dir_all(&dir)); + let mut file = try!(File::create(path)); + let mut perms = try!(file.metadata()).permissions(); + perms.set_mode(0o600); + try!(fs::set_permissions(path, perms)); + try!(file.write_all(&toml::encode_str(&table).into_bytes())); + + credentials + } + + Err(err) => return Err(Error::Io(err)) + }; + + Ok(AuthConfig { + server: auth_cfg.server, + client_id: credentials.client_id, + client_secret: credentials.client_secret, + credentials_file: auth_cfg.credentials_file, + }) +} + + +/// A parsed representation of the [auth] configuration section. +#[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] +pub struct AuthConfig { + pub server: Url, + pub client_id: String, + pub client_secret: String, + pub credentials_file: String, +} + +impl Default for AuthConfig { + fn default() -> AuthConfig { + AuthConfig { + server: "http://127.0.0.1:9001".parse().unwrap(), + client_id: "client-id".to_string(), + client_secret: "client-secret".to_string(), + credentials_file: "/tmp/sota_credentials.toml".to_string(), + } + } +} + + +/// A parsed representation of the [core] configuration section. +#[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] +pub struct CoreConfig { + pub server: Url +} + +impl Default for CoreConfig { + fn default() -> CoreConfig { + CoreConfig { + server: "http://127.0.0.1:8080".parse().unwrap() + } + } +} + + +/// A parsed representation of the [dbus] configuration section. +#[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] +pub struct DBusConfig { + pub name: String, + pub path: String, + pub interface: String, + pub software_manager: String, + pub software_manager_path: String, + pub timeout: i32, // dbus-rs expects a signed int +} + +impl Default for DBusConfig { + fn default() -> DBusConfig { + DBusConfig { + name: "org.genivi.SotaClient".to_string(), + path: "/org/genivi/SotaClient".to_string(), + interface: "org.genivi.SotaClient".to_string(), + software_manager: "org.genivi.SoftwareLoadingManager".to_string(), + software_manager_path: "/org/genivi/SoftwareLoadingManager".to_string(), + timeout: 60 + } + } +} + + +/// A parsed representation of the [device] configuration section. +#[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] +pub struct DeviceConfig { + pub uuid: String, + pub vin: String, + pub packages_dir: String, + pub package_manager: PackageManager, + pub system_info: SystemInfo, + pub polling_interval: u64, + pub certificates_path: String, +} + +impl Default for DeviceConfig { + fn default() -> DeviceConfig { + DeviceConfig { + uuid: "123e4567-e89b-12d3-a456-426655440000".to_string(), + vin: "V1234567890123456".to_string(), + packages_dir: "/tmp/".to_string(), + package_manager: PackageManager::Deb, + system_info: SystemInfo::default(), + polling_interval: 10, + certificates_path: "/tmp/sota_certificates".to_string() + } + } +} + + +/// A parsed representation of the [gateway] configuration section. +#[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] +pub struct GatewayConfig { + pub console: bool, + pub dbus: bool, + pub http: bool, + pub rvi: bool, + pub socket: bool, + pub websocket: bool, +} + +impl Default for GatewayConfig { + fn default() -> GatewayConfig { + GatewayConfig { + console: false, + dbus: false, + http: false, + rvi: false, + socket: false, + websocket: true, + } + } +} + + +/// A parsed representation of the [network] configuration section. +#[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] +pub struct NetworkConfig { + pub http_server: String, + pub rvi_edge_server: String, + pub socket_commands_path: String, + pub socket_events_path: String, + pub websocket_server: String +} + +impl Default for NetworkConfig { + fn default() -> NetworkConfig { + NetworkConfig { + http_server: "http://127.0.0.1:8888".to_string(), + rvi_edge_server: "http://127.0.0.1:9080".to_string(), + socket_commands_path: "/tmp/sota-commands.socket".to_string(), + socket_events_path: "/tmp/sota-events.socket".to_string(), + websocket_server: "ws://127.0.0.1:3012".to_string() + } + } +} + + +/// A parsed representation of the [rvi] configuration section. +#[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] +pub struct RviConfig { + pub client: Url, + pub storage_dir: String, + pub timeout: Option<i64>, +} + +impl Default for RviConfig { + fn default() -> RviConfig { + RviConfig { + client: "http://127.0.0.1:8901".parse().unwrap(), + storage_dir: "/var/sota".to_string(), + timeout: Some(20), + } + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + + const AUTH_CONFIG: &'static str = + r#" + [auth] + server = "http://127.0.0.1:9001" + client_id = "client-id" + client_secret = "client-secret" + credentials_file = "/tmp/sota_credentials.toml" + "#; + + const CORE_CONFIG: &'static str = + r#" + [core] + server = "http://127.0.0.1:8080" + "#; + + const DBUS_CONFIG: &'static str = + r#" + [dbus] + name = "org.genivi.SotaClient" + path = "/org/genivi/SotaClient" + interface = "org.genivi.SotaClient" + software_manager = "org.genivi.SoftwareLoadingManager" + software_manager_path = "/org/genivi/SoftwareLoadingManager" + timeout = 60 + "#; + + const DEVICE_CONFIG: &'static str = + r#" + [device] + uuid = "123e4567-e89b-12d3-a456-426655440000" + vin = "V1234567890123456" + system_info = "system_info.sh" + polling_interval = 10 + packages_dir = "/tmp/" + package_manager = "deb" + certificates_path = "/tmp/sota_certificates" + "#; + + const GATEWAY_CONFIG: &'static str = + r#" + [gateway] + console = false + dbus = false + http = false + rvi = false + socket = false + websocket = true + "#; + + const NETWORK_CONFIG: &'static str = + r#" + [network] + http_server = "http://127.0.0.1:8888" + rvi_edge_server = "http://127.0.0.1:9080" + socket_commands_path = "/tmp/sota-commands.socket" + socket_events_path = "/tmp/sota-events.socket" + websocket_server = "ws://127.0.0.1:3012" + "#; + + const RVI_CONFIG: &'static str = + r#" + [rvi] + client = "http://127.0.0.1:8901" + storage_dir = "/var/sota" + timeout = 20 + "#; + + + #[test] + fn parse_default_config() { + let config = String::new() + + CORE_CONFIG + + DEVICE_CONFIG + + GATEWAY_CONFIG + + NETWORK_CONFIG; + assert_eq!(Config::parse(&config).unwrap(), Config::default()); + } + + #[test] + fn parse_example_config() { + let config = String::new() + + AUTH_CONFIG + + CORE_CONFIG + + DBUS_CONFIG + + DEVICE_CONFIG + + GATEWAY_CONFIG + + NETWORK_CONFIG + + RVI_CONFIG; + assert_eq!(Config::load("tests/sota.toml").unwrap(), Config::parse(&config).unwrap()); + } +} |