| // Copyright 2020 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! Encapsulates functionality to support the command line interface with the |
| //! Trichechus and Dugong daemons. |
| |
| use std::env::current_exe; |
| use std::fmt::{self, Display}; |
| use std::process::exit; |
| |
| use getopts::{self, Options}; |
| use libchromeos::vsock::{SocketAddr as VSocketAddr, VsockCid}; |
| use libsirenia::transport::{self, TransportType, DEFAULT_SERVER_PORT, LOOPBACK_DEFAULT}; |
| |
| use super::build_info::BUILD_TIMESTAMP; |
| |
| #[derive(Debug)] |
| pub enum Error { |
| /// Error parsing command line options. |
| CLIParse(getopts::Fail), |
| TransportParse(transport::Error), |
| } |
| |
| impl Display for Error { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| use self::Error::*; |
| |
| match self { |
| CLIParse(e) => write!(f, "failed to parse the command line options: {}", e), |
| TransportParse(e) => write!(f, "failed to parse transport type: {}", e), |
| } |
| } |
| } |
| |
| /// The result of an operation in this crate. |
| pub type Result<T> = std::result::Result<T, Error>; |
| |
| /// The configuration options that can be configured by command line arguments, |
| /// flags, and options. |
| #[derive(Debug, PartialEq)] |
| pub struct CommonConfig { |
| pub connection_type: TransportType, |
| } |
| |
| pub fn get_name_and_version_string() -> String { |
| let program_name = match current_exe() { |
| Ok(exe_path) => exe_path |
| .file_name() |
| .map(|f| f.to_str().map(|f| f.to_string())) |
| .flatten(), |
| _ => None, |
| }; |
| |
| if let Some(program_name) = program_name { |
| format!("{}: {}", program_name, BUILD_TIMESTAMP) |
| } else { |
| BUILD_TIMESTAMP.to_string() |
| } |
| } |
| |
| /// Sets up command line argument parsing and generates a CommonConfig based on |
| /// the command line entry. |
| pub fn initialize_common_arguments(args: &[String]) -> Result<CommonConfig> { |
| // Vsock is used as the default because it is the transport used in production. |
| // IP is provided for testing and development. |
| // Not sure yet what cid default makes sense or if a default makes sense at |
| // all. |
| let default_connection = TransportType::VsockConnection(VSocketAddr { |
| cid: VsockCid::Any, |
| port: DEFAULT_SERVER_PORT, |
| }); |
| let mut config = CommonConfig { |
| connection_type: default_connection, |
| }; |
| |
| let url_name = "U"; |
| |
| let mut opts = Options::new(); |
| opts.optflagopt( |
| url_name, |
| "server-url", |
| "URL to the server", |
| LOOPBACK_DEFAULT, |
| ); |
| opts.optflag("h", "help", "Show this help string."); |
| let matches = opts.parse(&args[..]).map_err(|e| { |
| println!("{}", opts.usage(&get_name_and_version_string())); |
| Error::CLIParse(e) |
| })?; |
| |
| if matches.opt_present("h") { |
| println!("{}", opts.usage(&get_name_and_version_string())); |
| exit(0); |
| } |
| |
| if let Some(value) = matches.opt_str(url_name) { |
| config.connection_type = value |
| .parse::<TransportType>() |
| .map_err(Error::TransportParse)? |
| }; |
| Ok(config) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use libsirenia::transport::{get_test_ip_uri, get_test_vsock_uri}; |
| use std::net::{IpAddr, Ipv4Addr, SocketAddr}; |
| |
| #[test] |
| fn initialize_common_arguments_invalid_args() { |
| let value: [String; 1] = ["-foo".to_string()]; |
| let act_result = initialize_common_arguments(&value); |
| match &act_result { |
| Err(Error::CLIParse(_)) => (), |
| _ => panic!("Got unexpected result: {:?}", &act_result), |
| } |
| } |
| |
| #[test] |
| fn initialize_common_arguments_ip_valid() { |
| let exp_socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)), 1234); |
| let exp_result = CommonConfig { |
| connection_type: TransportType::IpConnection(exp_socket), |
| }; |
| let value: [String; 2] = ["-U".to_string(), get_test_ip_uri().to_string()]; |
| let act_result = initialize_common_arguments(&value).unwrap(); |
| assert_eq!(act_result, exp_result); |
| } |
| |
| #[test] |
| fn initialize_common_arguments_vsock_valid() { |
| let vsock = TransportType::VsockConnection(VSocketAddr { |
| cid: VsockCid::Local, |
| port: 1, |
| }); |
| let exp_result = CommonConfig { |
| connection_type: vsock, |
| }; |
| let value: [String; 2] = ["-U".to_string(), get_test_vsock_uri()]; |
| let act_result = initialize_common_arguments(&value).unwrap(); |
| assert_eq!(act_result, exp_result); |
| } |
| |
| #[test] |
| fn initialize_common_arguments_no_args() { |
| let default_connection = TransportType::VsockConnection(VSocketAddr { |
| cid: VsockCid::Any, |
| port: DEFAULT_SERVER_PORT, |
| }); |
| let exp_result = CommonConfig { |
| connection_type: default_connection, |
| }; |
| let value: [String; 0] = []; |
| let act_result = initialize_common_arguments(&value).unwrap(); |
| assert_eq!(act_result, exp_result); |
| } |
| } |