diff --git a/Cargo.toml b/Cargo.toml index 525ba58..8782fec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,4 +30,5 @@ serde_json = "1.0.116" serde_libconfig = "0.0.2" serde_yaml = "0.9.34" strum_macros = "0.26.2" +tempfile = "3.14.0" tokio = { version = "1.37.0", features = ["full", "sync"] } diff --git a/src/logger.rs b/src/logger.rs index a34fefe..e1345f3 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -279,7 +279,7 @@ impl Logger { return sender; } } - return self.tx_vec.last().unwrap(); + self.tx_vec.last().unwrap() } pub fn set_base_dir(new_base_dir: String) { diff --git a/src/logic/model_handler.rs b/src/logic/model_handler.rs index 6f5b9d1..4eb1ccc 100644 --- a/src/logic/model_handler.rs +++ b/src/logic/model_handler.rs @@ -576,7 +576,7 @@ fn calculate_pbe_cc_capacity( /* * Determine the fair share badnwidth c_p (physical layer) and c_t (transport layer) * */ - let p_alloc_rnti_suggested: u64 = p_alloc_rnti + ((p_idle + nof_rnti_shared - 1) / nof_rnti_shared); + let p_alloc_rnti_suggested: u64 = p_alloc_rnti + p_idle.div_ceil(nof_rnti_shared); let c_p: u64 = (((r_w * p_alloc_rnti_suggested) as f64) / (nof_dci as f64)) as u64; let c_t = translate_physcial_to_transport_simple(c_p); diff --git a/src/logic/ngscope_controller.rs b/src/logic/ngscope_controller.rs index 2cc728f..b8f2848 100644 --- a/src/logic/ngscope_controller.rs +++ b/src/logic/ngscope_controller.rs @@ -15,13 +15,13 @@ use crate::logic::{ CHANNEL_SYNC_SIZE, DEFAULT_WORKER_SLEEP_MS, DEFAULT_WORKER_SLEEP_US, }; use crate::ngscope; -use crate::ngscope::config::NgScopeConfig; +use crate::ngscope::config::{NgScopeConfig, NgScopeConfigRfDev}; use crate::ngscope::types::{Message, NgScopeCellDci}; use crate::ngscope::{ ngscope_validate_server_check, ngscope_validate_server_send_initial, start_ngscope, stop_ngscope, }; -use crate::parse::{Arguments, FlattenedNgScopeArgs}; +use crate::parse::{Arguments, FlattenedNgScopeArgs, FlattenedNgScopeSdrConfigArgs}; use crate::util::{determine_process_id, is_debug, print_debug, print_info}; const WAIT_FOR_TRIGGER_NGSCOPE_RESPONE_MS: u64 = 500; @@ -94,9 +94,6 @@ fn run(run_args: &mut RunArgs, run_args_mov: RunArgsMovables) -> Result<()> { let ng_args = FlattenedNgScopeArgs::from_unflattened(app_args.clone().ngscope.unwrap())?; let mut ng_process_option: Option = None; - let ngscope_config = NgScopeConfig { - ..Default::default() - }; let (tx_dci_thread, rx_main_thread) = sync_channel::(CHANNEL_SYNC_SIZE); let (tx_main_thread, rx_dci_thread) = sync_channel::(CHANNEL_SYNC_SIZE); @@ -123,7 +120,7 @@ fn run(run_args: &mut RunArgs, run_args_mov: RunArgsMovables) -> Result<()> { match ngcontrol_state { NgControlState::CheckingCellInfo => { - ngcontrol_state = handle_cell_update(rx_cell_info, &ngscope_config)?; + ngcontrol_state = handle_cell_update(rx_cell_info, &ng_args.ng_sdr_config)?; } NgControlState::TriggerListenDci => { tx_dci_thread.send(LocalDciState::SendInitial)?; @@ -233,7 +230,7 @@ fn handle_start_ngscope( fn handle_cell_update( rx_cell_info: &mut BusReader, - ng_conf: &NgScopeConfig, + ng_sdr_config: &FlattenedNgScopeSdrConfigArgs, ) -> Result { match check_cell_update(rx_cell_info)? { Some(cell_info) => { @@ -241,10 +238,7 @@ fn handle_cell_update( if cell_info.cells.is_empty() { return Ok(NgControlState::StopNgScope); } - // TODO: Handle multi cell - let mut new_conf = ng_conf.clone(); - new_conf.rf_config0.as_mut().unwrap().rf_freq = - cell_info.cells.first().unwrap().frequency as i64; + let new_conf = NgScopeConfig::from_cell(ng_sdr_config, &cell_info)?; Ok(NgControlState::StartNgScope(Box::new(new_conf))) } _ => Ok(NgControlState::CheckingCellInfo), @@ -476,3 +470,51 @@ fn check_cell_update(rx_cell_info: &mut BusReader) -> Result Ok(None), } } + +impl NgScopeConfig { + pub fn from_cell(ng_sdr_config: &FlattenedNgScopeSdrConfigArgs, cell_info: &CellInfo) -> Result { + let mut ng_conf: NgScopeConfig = NgScopeConfig { + rf_config0: Some(NgScopeConfigRfDev { + rf_freq: cell_info.cells.first().ok_or_else(|| anyhow!("[ngcontrol] CellInfo.cells are empty while building NgScopeConfig"))? + .frequency as i64, + N_id_2: ng_sdr_config.ng_sdr_a.ng_sdr_a_n_id, + rf_args: format!("serial={}", ng_sdr_config.ng_sdr_a.ng_sdr_a_serial), + ..Default::default() + }), + nof_rf_dev: 1, + ..Default::default() + }; + + if cell_info.cells.len() >= 2 { + if let Some(ng_sdr_b) = &ng_sdr_config.ng_sdr_b { + ng_conf.rf_config1 = Some(NgScopeConfigRfDev { + rf_freq: cell_info.cells[1].frequency as i64, + N_id_2: ng_sdr_b.ng_sdr_b_n_id, + rf_args: format!("serial={}", ng_sdr_b.ng_sdr_b_serial), + ..Default::default() + }); + ng_conf.nof_rf_dev = 2; + } + else { + print_info("[ngcontrol] two cells identified, but second SDR not configured! (continuing with one)"); + return Ok(ng_conf) + } + } + + if cell_info.cells.len() >= 3 { + if let Some(ng_sdr_c) = &ng_sdr_config.ng_sdr_c { + ng_conf.rf_config2 = Some(NgScopeConfigRfDev { + rf_freq: cell_info.cells[2].frequency as i64, + N_id_2: ng_sdr_c.ng_sdr_c_n_id, + rf_args: format!("serial={}", ng_sdr_c.ng_sdr_c_serial), + ..Default::default() + }); + ng_conf.nof_rf_dev = 3; + } else { + print_info("[ngcontrol] three cells identified, but third SDR not configured! (continuing with two)"); + return Ok(ng_conf) + } + } + Ok(ng_conf) + } +} diff --git a/src/ngscope/config.rs b/src/ngscope/config.rs index 1cb97e8..a7c6918 100644 --- a/src/ngscope/config.rs +++ b/src/ngscope/config.rs @@ -116,7 +116,6 @@ pub fn write_config(config: &NgScopeConfig, file_path: &str) -> Result<()> { // https://crates.io/crates/libconfig-rs #[cfg(test)] - mod tests { use super::*; diff --git a/src/parse.rs b/src/parse.rs index 1064487..fd2de7d 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,35 +1,72 @@ /// Credits: https://stackoverflow.com/questions/55133351/is-there-a-way-to-get-clap-to-use-default-values-from-a-file -use anyhow::Result; +use anyhow::{anyhow, Result}; use clap::{Args, Command, CommandFactory, Parser, ValueEnum}; use serde::{Deserialize, Serialize}; use std::{default, error::Error, path::PathBuf}; -use crate::{logic::traffic_patterns::RntiMatchingTrafficPatternType, util::print_debug}; +use crate::{ + logic::traffic_patterns::RntiMatchingTrafficPatternType, + util::{print_info,UpdateIfSome} +}; pub const DEFAULT_SCENARIO: Scenario = Scenario::TrackUeAndEstimateTransportCapacity; +pub const DEFAULT_VERBOSE: bool = true; +pub const DEFAULT_CELL_API: CellApiConfig = CellApiConfig::Milesight; + +pub const DEFAULT_MILESIGHT_ADDRESS: &str = "http://127.0.0.1:8080"; +pub const DEFAULT_MILESIGHT_USER: &str = "root"; +pub const DEFAULT_MILESIGHT_AUTH: &str = "root-password"; + +//port is implicitly always 7573 or something like that; might make sense to make it modifiable.. +pub const DEFAULT_DEVPUB_ADDRESS: &str = "127.0.0.1"; +pub const DEFAULT_DEVPUB_AUTH: &str = "some_auth"; + +pub const DEFAULT_NG_PATH: &str = "/dev_ws/dependencies/ng-scope/build_x86/ngscope/src/ngscope"; +pub const DEFAULT_NG_LOCAL_ADDR: &str = "0.0.0.0:9191"; +pub const DEFAULT_NG_SERVER_ADDR: &str = "0.0.0.0:6767"; +pub const DEFAULT_NG_LOG_FILE: &str = "./.ng_scope_log.txt"; +pub const DEFAULT_NG_START_PROCESS: bool = true; +pub const DEFAULT_NG_LOG_DCI: bool = true; +pub const DEFAULT_NG_LOG_DCI_BATCH_SIZE: u64 = 60000; +pub const DEFAULT_NG_SDR_A_SERIAL: &str = "3295B62"; +pub const DEFAULT_NG_SDR_A_N_ID: i16 = -1; + +pub const DEFAULT_MATCHING_LOCAL_ADDR: &str = "0.0.0.0:9292"; +pub const DEFAULT_MATCHING_TRAFFIC_PATTERN: &[RntiMatchingTrafficPatternType] = &[RntiMatchingTrafficPatternType::A]; +pub const DEFAULT_MATCHING_TRAFFIC_DEST: &str = "127.0.0.1:9494"; +pub const DEFAULT_MATCHING_LOG_TRAFFIC: bool = true; + +pub const DEFAULT_MODEL_INTERVAL_VALUE: f64 = 1.0; +pub const DEFAULT_MODEL_INTERVAL_TYPE: DynamicValue = DynamicValue::RttFactor; +pub const DEFAULT_MODEL_SMOOTHING_VALUE: f64 = 1.0; +pub const DEFAULT_MODEL_SMOOTHING_TYPE: DynamicValue = DynamicValue::RttFactor; +pub const DEFAULT_MODEL_LOG_METRIC: bool = true; + pub const DEFAULT_LOG_BASE_DIR: &str = "./.logs.ue/"; -pub const DEFAULT_DOWNLOAD_BASE_ADDR: &str = "http://some.addr"; +pub const DEFAULT_DOWNLOAD_BASE_ADDR: &str = "127.0.0.1:9393"; pub const DEFAULT_DOWNLOAD_PATHS: &[&str] = &[ "/10s/cubic", "/10s/bbr", - "/10s/pbe/fair0/init", - "/10s/pbe/fair0/upper", - "/10s/pbe/fair0/init_and_upper", - "/10s/pbe/fair0/direct", - "/10s/pbe/fair1/init", - "/10s/pbe/fair1/upper", - "/10s/pbe/fair1/init_and_upper", - "/10s/pbe/fair1/direct", + "/10s/reno", + "/10s/l2b/fair0/init", + "/10s/l2b/fair0/upper", + "/10s/l2b/fair0/init_and_upper", + "/10s/l2b/fair0/direct", + "/10s/l2b/fair1/init", + "/10s/l2b/fair1/upper", + "/10s/l2b/fair1/init_and_upper", + "/10s/l2b/fair1/direct", "/60s/cubic", "/60s/bbr", - "/60s/pbe/fair0/init", - "/60s/pbe/fair0/upper", - "/60s/pbe/fair0/init_and_upper", - "/60s/pbe/fair0/direct", - "/60s/pbe/fair1/init", - "/60s/pbe/fair1/upper", - "/60s/pbe/fair1/init_and_upper", - "/60s/pbe/fair1/direct", + "/60s/reno", + "/60s/l2b/fair0/init", + "/60s/l2b/fair0/upper", + "/60s/l2b/fair0/init_and_upper", + "/60s/l2b/fair0/direct", + "/60s/l2b/fair1/init", + "/60s/l2b/fair1/upper", + "/60s/l2b/fair1/init_and_upper", + "/60s/l2b/fair1/direct", ]; #[derive(Debug, Clone, PartialEq, Parser, Serialize, Deserialize)] @@ -104,7 +141,7 @@ pub struct MilesightArgs { /// username for login #[arg(long, required = false)] pub milesight_user: Option, - /// authentication: Base64 encoded password + /// authentication: Base64 encoded string (NOT the password base64 encoded, you need to get this through wireshark) #[arg(long, required = false)] pub milesight_auth: Option, } @@ -147,6 +184,10 @@ pub struct NgScopeArgs { #[arg(long, required = false)] pub ng_server_addr: Option, + /// SDR configuration + #[command(flatten)] + pub ng_sdr_config: Option, + /// Filepath for stdout + stderr logging of the NG-Scope process #[arg(long, required = false)] pub ng_log_file: Option, @@ -164,17 +205,92 @@ pub struct NgScopeArgs { pub ng_log_dci_batch_size: Option, } +#[derive(Args, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct NgScopeSdrConfigArgs { + /// SDR A + #[command(flatten)] + pub ng_sdr_a: Option, + + /// SDR B + #[command(flatten)] + pub ng_sdr_b: Option, + + /// SDR C + #[command(flatten)] + pub ng_sdr_c: Option, +} + + +#[derive(Args, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct NgScopeSdrConfigArgsA { + /// SDR USB serial identifier + #[arg(long, required = false)] + ng_sdr_a_serial: Option, + + /// NG-Scope cell selection parameter + #[arg(long, required = false)] + ng_sdr_a_n_id: Option, +} + +#[derive(Args, Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +pub struct NgScopeSdrConfigArgsB { + /// SDR USB serial identifier + #[arg(long, required = false)] + ng_sdr_b_serial: Option, + + /// NG-Scope cell selection parameter + #[arg(long, required = false)] + ng_sdr_b_n_id: Option, +} + +#[derive(Args, Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +pub struct NgScopeSdrConfigArgsC { + /// SDR USB serial identifier + #[arg(long, required = false)] + ng_sdr_c_serial: Option, + + /// NG-Scope cell selection parameter + #[arg(long, required = false)] + ng_sdr_c_n_id: Option, +} + #[derive(Clone, Debug)] pub struct FlattenedNgScopeArgs { pub ng_path: String, pub ng_local_addr: String, pub ng_server_addr: String, + pub ng_sdr_config: FlattenedNgScopeSdrConfigArgs, pub ng_log_file: Option, pub ng_start_process: bool, pub ng_log_dci: bool, pub ng_log_dci_batch_size: u64, } +#[derive(Clone, Debug)] +pub struct FlattenedNgScopeSdrConfigArgs { + pub ng_sdr_a: FlattenedNgScopeSdrConfigArgsA, + pub ng_sdr_b: Option, + pub ng_sdr_c: Option, +} + +#[derive(Clone, Debug)] +pub struct FlattenedNgScopeSdrConfigArgsA { + pub ng_sdr_a_serial: String, + pub ng_sdr_a_n_id: i16, +} + +#[derive(Clone, Debug)] +pub struct FlattenedNgScopeSdrConfigArgsB { + pub ng_sdr_b_serial: String, + pub ng_sdr_b_n_id: i16, +} + +#[derive(Clone, Debug)] +pub struct FlattenedNgScopeSdrConfigArgsC { + pub ng_sdr_c_serial: String, + pub ng_sdr_c_n_id: i16, +} + #[derive(Args, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RntiMatchingArgs { /// Local UE Cell Tracker address to generate RNTI matching traffic (addr:port) @@ -270,51 +386,15 @@ impl default::Default for Arguments { fn default() -> Self { Arguments { scenario: Some(DEFAULT_SCENARIO), - verbose: Some(true), - cellapi: Some(CellApiConfig::Milesight), - milesight: Some(MilesightArgs { - milesight_address: Some("http://127.0.0.1".to_string()), - milesight_user: Some("root".to_string()), - milesight_auth: Some("root-password".to_string()), - }), - devicepublisher: Some(DevicePublisherArgs { - devpub_address: Some("https://some.address".to_string()), - devpub_auth: Some("some_auth".to_string()), - }), - ngscope: Some(NgScopeArgs { - ng_path: Some("/dev_ws/dependencies/ng-scope/build_x86/ngscope/src/".to_string()), - ng_local_addr: Some("0.0.0.0:9191".to_string()), - ng_server_addr: Some("0.0.0.0:6767".to_string()), - ng_log_file: Some("./.ng_scope_log.txt".to_string()), - ng_start_process: Some(true), - ng_log_dci: Some(true), - ng_log_dci_batch_size: Some(60000), - }), - rntimatching: Some(RntiMatchingArgs { - matching_local_addr: Some("0.0.0.0:9292".to_string()), - matching_traffic_pattern: Some(vec![RntiMatchingTrafficPatternType::A]), - matching_traffic_destination: Some("1.1.1.1:53".to_string()), - matching_log_traffic: Some(true), - }), - model: Some(ModelArgs { - model_send_metric_interval_value: Some(1.0), - model_send_metric_interval_type: Some(DynamicValue::RttFactor), - model_metric_smoothing_size_value: Some(1.0), - model_metric_smoothing_size_type: Some(DynamicValue::RttFactor), - model_log_metric: Some(true), - }), - log: Some(LogArgs { - log_base_dir: Some(DEFAULT_LOG_BASE_DIR.to_string()), - }), - download: Some(DownloadArgs { - download_base_addr: Some(DEFAULT_DOWNLOAD_BASE_ADDR.to_string()), - download_paths: Some( - DEFAULT_DOWNLOAD_PATHS - .iter() - .map(|path| path.to_string()) - .collect(), - ), - }), + verbose: Some(DEFAULT_VERBOSE), + cellapi: Some(DEFAULT_CELL_API), + milesight: Some(MilesightArgs::default()), + devicepublisher: Some(DevicePublisherArgs::default()), + ngscope: Some(NgScopeArgs::default()), + rntimatching: Some(RntiMatchingArgs::default()), + model: Some(ModelArgs::default()), + log: Some(LogArgs::default()), + download: Some(DownloadArgs::default()), } } } @@ -324,42 +404,84 @@ impl Arguments { pub fn build() -> Result> { let app: Command = Arguments::command(); let app_name: &str = app.get_name(); + let user_args = Arguments::parse(); + let config_args: Arguments = confy::load(app_name, None)?; + let combined_args = user_args.merge_with(config_args); + let printed_args = combined_args.print_config_file(app_name)?; + Ok(printed_args) + } - let parsed_args = Arguments::parse(); - match parsed_args.clone().get_config_file(app_name) { - Ok(parsed_config_args) => { - let printed_args = parsed_config_args.print_config_file(app_name)?; - Ok(printed_args) - } - Err(_) => { - let printed_args = parsed_args - .set_config_file(app_name)? - .print_config_file(app_name)?; - Ok(printed_args) - } - } + /// Merge Arguments with another Arguments struct. + /// !!! This fills None values with their ::defaults !!! + /// other > self > defaults + fn merge_with(mut self, other: Arguments) -> Self { + // Simple types + self.scenario = self.scenario.or(other.scenario).or(Some(DEFAULT_SCENARIO)); + self.cellapi = self.cellapi.or(other.cellapi).or(Some(DEFAULT_CELL_API)); + self.verbose = self.verbose.or(other.verbose).or(Some(DEFAULT_VERBOSE)); + // Struct types + self.fill_milesight(&other); + self.fill_devicepublisher(&other); + self.fill_ngscope(&other); + self.fill_rntimatching(&other); + self.fill_download(&other); + self.fill_model(&other); + self.fill_log(&other); + + self } - /// Get configuration file. - /// A new configuration file is created with default values if none exists. - fn get_config_file(mut self, app_name: &str) -> Result> { - let config_file: Arguments = confy::load(app_name, None)?; + fn fill_milesight(&mut self, other: &Arguments) { + let defaults = MilesightArgs::default(); + let defaults_with_self = defaults.merge_with(self.milesight.clone()); + let combined = defaults_with_self.merge_with(other.milesight.clone()); + self.milesight = Some(combined); + } - self.cellapi = self.cellapi.or(config_file.cellapi); - self.milesight = self.milesight.or(config_file.milesight); - self.devicepublisher = self.devicepublisher.or(config_file.devicepublisher); - self.ngscope = self.ngscope.or(config_file.ngscope); - self.rntimatching = self.rntimatching.or(config_file.rntimatching); - self.model = self.model.or(config_file.model); - self.log = self.log.or(config_file.log); - self.download = self.download.or(config_file.download); - self.verbose = self.verbose.or(config_file.verbose); - self.scenario = self.scenario.or(config_file.scenario); + fn fill_devicepublisher(&mut self, other: &Arguments) { + let defaults = DevicePublisherArgs::default(); + let defaults_with_self = defaults.merge_with(self.devicepublisher.clone()); + let combined = defaults_with_self.merge_with(other.devicepublisher.clone()); + self.devicepublisher = Some(combined); + } - Ok(self) + fn fill_ngscope(&mut self, other: &Arguments) { + let defaults = NgScopeArgs::default(); + let defaults_with_self = defaults.merge_with(self.ngscope.clone()); + let combined = defaults_with_self.merge_with(other.ngscope.clone()); + self.ngscope = Some(combined); + } + + fn fill_rntimatching(&mut self, other: &Arguments) { + let defaults = RntiMatchingArgs::default(); + let defaults_with_self = defaults.merge_with(self.rntimatching.clone()); + let combined = defaults_with_self.merge_with(other.rntimatching.clone()); + self.rntimatching = Some(combined); + } + + fn fill_model(&mut self, other: &Arguments) { + let defaults = ModelArgs::default(); + let defaults_with_self = defaults.merge_with(self.model.clone()); + let combined = defaults_with_self.merge_with(other.model.clone()); + self.model = Some(combined); + } + + fn fill_download(&mut self, other: &Arguments) { + let defaults = DownloadArgs::default(); + let defaults_with_self = defaults.merge_with(self.download.clone()); + let combined = defaults_with_self.merge_with(other.download.clone()); + self.download = Some(combined); + } + + fn fill_log(&mut self, other: &Arguments) { + let defaults = LogArgs::default(); + let defaults_with_self = defaults.merge_with(self.log.clone()); + let combined = defaults_with_self.merge_with(other.log.clone()); + self.log = Some(combined); } /// Save changes made to a configuration object + #[allow(dead_code)] fn set_config_file(self, app_name: &str) -> Result> { let default_args: Arguments = Default::default(); confy::store(app_name, None, default_args)?; @@ -370,19 +492,262 @@ impl Arguments { fn print_config_file(self, app_name: &str) -> Result> { if self.verbose.unwrap_or(true) { let file_path: PathBuf = confy::get_configuration_file_path(app_name, None)?; - print_debug(&format!( + print_info(&format!( "DEBUG [parse] Configuration file: '{}'", file_path.display() )); let yaml: String = serde_yaml::to_string(&self)?; - print_debug(&format!("\t{}", yaml.replace('\n', "\n\t"))); + print_info(&format!("\t{}", yaml.replace('\n', "\n\t"))); } Ok(self) } } +/* -------------- */ +/* Defaults */ +/* -------------- */ +impl default::Default for MilesightArgs { + fn default() -> Self { + MilesightArgs { + milesight_address: Some(DEFAULT_MILESIGHT_ADDRESS.to_string()), + milesight_user: Some(DEFAULT_MILESIGHT_USER.to_string()), + milesight_auth: Some(DEFAULT_MILESIGHT_AUTH.to_string()), + } + } +} + +impl default::Default for DevicePublisherArgs { + fn default() -> Self { + DevicePublisherArgs { + devpub_address: Some(DEFAULT_DEVPUB_ADDRESS.to_string()), + devpub_auth: Some(DEFAULT_DEVPUB_AUTH.to_string()), + } + } +} + +impl default::Default for NgScopeArgs { + fn default() -> Self { + NgScopeArgs { + ng_path: Some(DEFAULT_NG_PATH.to_string()), + ng_local_addr: Some(DEFAULT_NG_LOCAL_ADDR.to_string()), + ng_server_addr: Some(DEFAULT_NG_SERVER_ADDR.to_string()), + ng_log_file: Some(DEFAULT_NG_LOG_FILE.to_string()), + ng_start_process: Some(DEFAULT_NG_START_PROCESS), + ng_log_dci: Some(DEFAULT_NG_LOG_DCI), + ng_log_dci_batch_size: Some(DEFAULT_NG_LOG_DCI_BATCH_SIZE), + ng_sdr_config: Some(NgScopeSdrConfigArgs::default()), + } + } +} + +impl default::Default for NgScopeSdrConfigArgs { + fn default() -> Self { + NgScopeSdrConfigArgs { + ng_sdr_a: Some(NgScopeSdrConfigArgsA::default()), + ng_sdr_b: None, + ng_sdr_c: None, + } + } +} + +impl default::Default for NgScopeSdrConfigArgsA { + fn default() -> Self { + NgScopeSdrConfigArgsA { + ng_sdr_a_serial: Some(DEFAULT_NG_SDR_A_SERIAL.to_string()), + ng_sdr_a_n_id: Some(DEFAULT_NG_SDR_A_N_ID), + } + } +} + +impl default::Default for RntiMatchingArgs { + fn default() -> Self { + RntiMatchingArgs { + matching_local_addr: Some(DEFAULT_MATCHING_LOCAL_ADDR.to_string()), + matching_traffic_pattern: Some(DEFAULT_MATCHING_TRAFFIC_PATTERN.to_vec()), + matching_traffic_destination: Some(DEFAULT_MATCHING_TRAFFIC_DEST.to_string()), + matching_log_traffic: Some(DEFAULT_MATCHING_LOG_TRAFFIC), + } + } +} + +impl default::Default for ModelArgs { + fn default() -> Self { + ModelArgs { + model_send_metric_interval_value: Some(DEFAULT_MODEL_INTERVAL_VALUE), + model_send_metric_interval_type: Some(DEFAULT_MODEL_INTERVAL_TYPE), + model_metric_smoothing_size_value: Some(DEFAULT_MODEL_SMOOTHING_VALUE), + model_metric_smoothing_size_type: Some(DEFAULT_MODEL_SMOOTHING_TYPE), + model_log_metric: Some(DEFAULT_MODEL_LOG_METRIC), + } + } +} + +impl default::Default for DownloadArgs { + fn default() -> Self { + DownloadArgs { + download_base_addr: Some(DEFAULT_DOWNLOAD_BASE_ADDR.to_string()), + download_paths: Some(DEFAULT_DOWNLOAD_PATHS + .iter() + .map(|path| path.to_string()) + .collect()), + } + } +} + +impl default::Default for LogArgs { + fn default() -> Self { + LogArgs { + log_base_dir: Some(DEFAULT_LOG_BASE_DIR.to_string()), + } + } +} + +/* -------------- */ +/* Merge Helpers */ +/* -------------- */ +impl MilesightArgs { + fn merge_with(mut self, other: Option) -> Self { + if let Some(other) = other { + self.milesight_address.update_if_some(other.milesight_address); + self.milesight_user.update_if_some(other.milesight_user); + self.milesight_auth.update_if_some(other.milesight_auth); + } + self + } +} + +impl DevicePublisherArgs { + fn merge_with(mut self, other: Option) -> Self { + if let Some(other) = other { + self.devpub_address.update_if_some(other.devpub_address); + self.devpub_auth.update_if_some(other.devpub_auth); + } + self + } +} + +impl NgScopeArgs { + fn merge_with(mut self, other: Option) -> Self { + if let Some(other) = other { + self.ng_path.update_if_some(other.ng_path); + self.ng_local_addr.update_if_some(other.ng_local_addr); + self.ng_server_addr.update_if_some(other.ng_server_addr); + self.ng_log_file.update_if_some(other.ng_log_file); + self.ng_start_process.update_if_some(other.ng_start_process); + self.ng_log_dci.update_if_some(other.ng_log_dci); + self.ng_log_dci_batch_size.update_if_some(other.ng_log_dci_batch_size); + self.ng_sdr_config = if let Some(ng_sdr_config) = self.ng_sdr_config { + Some(ng_sdr_config.merge_with(other.ng_sdr_config)) + } else { + other.ng_sdr_config + }; + } + self + } +} + +impl NgScopeSdrConfigArgs { + fn merge_with(mut self, other: Option) -> Self { + if let Some(other) = other { + self.ng_sdr_a = match (self.ng_sdr_a, other.ng_sdr_a) { + (Some(sdr_a), Some(other_a)) => { Some(sdr_a.merge_with(Some(other_a))) } + (None, Some(other_a)) => Some(other_a), + (some_a, None) => some_a, + }; + self.ng_sdr_b = match (self.ng_sdr_b, other.ng_sdr_b) { + (Some(sdr_b), Some(other_b)) => { Some(sdr_b.merge_with(Some(other_b))) } + (None, Some(other_b)) => Some(other_b), + (some_b, None) => some_b, + }; + self.ng_sdr_c = match (self.ng_sdr_c, other.ng_sdr_c) { + (Some(sdr_c), Some(other_c)) => { Some(sdr_c.merge_with(Some(other_c))) } + (None, Some(other_c)) => Some(other_c), + (some_c, None) => some_c, + }; + } + self + } +} + +impl NgScopeSdrConfigArgsA { + fn merge_with(mut self, other: Option) -> Self { + if let Some(other) = other { + self.ng_sdr_a_serial.update_if_some(other.ng_sdr_a_serial); + self.ng_sdr_a_n_id.update_if_some(other.ng_sdr_a_n_id); + } + self + } +} + +impl NgScopeSdrConfigArgsB { + fn merge_with(mut self, other: Option) -> Self { + if let Some(other) = other { + self.ng_sdr_b_serial.update_if_some(other.ng_sdr_b_serial); + self.ng_sdr_b_n_id.update_if_some(other.ng_sdr_b_n_id); + } + self + } +} + +impl NgScopeSdrConfigArgsC { + fn merge_with(mut self, other: Option) -> Self { + if let Some(other) = other { + self.ng_sdr_c_serial.update_if_some(other.ng_sdr_c_serial); + self.ng_sdr_c_n_id.update_if_some(other.ng_sdr_c_n_id); + } + self + } +} + +impl RntiMatchingArgs { + fn merge_with(mut self, other: Option) -> Self { + if let Some(other) = other { + self.matching_local_addr.update_if_some(other.matching_local_addr); + self.matching_traffic_pattern.update_if_some(other.matching_traffic_pattern); + self.matching_traffic_destination.update_if_some(other.matching_traffic_destination); + self.matching_log_traffic.update_if_some(other.matching_log_traffic); + } + self + } +} + +impl ModelArgs { + fn merge_with(mut self, other: Option) -> Self { + if let Some(other) = other { + self.model_send_metric_interval_value.update_if_some(other.model_send_metric_interval_value); + self.model_send_metric_interval_type.update_if_some(other.model_send_metric_interval_type); + self.model_metric_smoothing_size_value.update_if_some(other.model_metric_smoothing_size_value); + self.model_metric_smoothing_size_type.update_if_some(other.model_metric_smoothing_size_type); + self.model_log_metric.update_if_some(other.model_log_metric); + } + self + } +} + +impl DownloadArgs { + fn merge_with(mut self, other: Option) -> Self { + if let Some(other) = other { + self.download_base_addr.update_if_some(other.download_base_addr); + self.download_paths.update_if_some(other.download_paths); + } + self + } +} + +impl LogArgs { + fn merge_with(mut self, other: Option) -> Self { + if let Some(other) = other { + self.log_base_dir.update_if_some(other.log_base_dir); + } + self + } +} + +/* -------------- */ +/* Unpacking (non-optional) helpers */ +/* -------------- */ impl FlattenedCellApiConfig { pub fn from_unflattened( cell_api: CellApiConfig, @@ -417,10 +782,58 @@ impl FlattenedNgScopeArgs { ng_log_file: ng_args.ng_log_file, ng_log_dci: ng_args.ng_log_dci.unwrap(), ng_log_dci_batch_size: ng_args.ng_log_dci_batch_size.unwrap(), + ng_sdr_config: FlattenedNgScopeSdrConfigArgs::from_unflattened(ng_args.ng_sdr_config.unwrap())?, }) } } +impl FlattenedNgScopeSdrConfigArgs { + pub fn from_unflattened(ng_sdr_config: NgScopeSdrConfigArgs) -> Result { + Ok(FlattenedNgScopeSdrConfigArgs { + ng_sdr_a: FlattenedNgScopeSdrConfigArgsA::from_unflattened(ng_sdr_config.ng_sdr_a.unwrap())?, + ng_sdr_b: FlattenedNgScopeSdrConfigArgsB::from_some_unflattened(ng_sdr_config.ng_sdr_b).ok(), + ng_sdr_c: FlattenedNgScopeSdrConfigArgsC::from_some_unflattened(ng_sdr_config.ng_sdr_c).ok(), + }) + } +} + +impl FlattenedNgScopeSdrConfigArgsA { + pub fn from_unflattened(ng_sdr_a: NgScopeSdrConfigArgsA) -> Result { + Ok(FlattenedNgScopeSdrConfigArgsA { + ng_sdr_a_serial: ng_sdr_a.ng_sdr_a_serial.expect("ng_sdr_a_serial missing"), + ng_sdr_a_n_id: ng_sdr_a.ng_sdr_a_n_id.unwrap_or(-1), + }) + } +} + +impl FlattenedNgScopeSdrConfigArgsB { + pub fn from_some_unflattened(ng_sdr_b_option: Option) -> Result { + if let Some(ng_sdr_b) = ng_sdr_b_option { + Ok(FlattenedNgScopeSdrConfigArgsB { + ng_sdr_b_serial: ng_sdr_b.ng_sdr_b_serial.expect("ng_sdr_b_serial missing"), + ng_sdr_b_n_id: ng_sdr_b.ng_sdr_b_n_id.unwrap_or(-1), + }) + } + else { + Err(anyhow!("")) // ok, none should've been parsed + } + } +} + +impl FlattenedNgScopeSdrConfigArgsC { + pub fn from_some_unflattened(ng_sdr_c_option: Option) -> Result { + if let Some(ng_sdr_c) = ng_sdr_c_option { + Ok(FlattenedNgScopeSdrConfigArgsC { + ng_sdr_c_serial: ng_sdr_c.ng_sdr_c_serial.expect("ng_sdr_c_serial missing"), + ng_sdr_c_n_id: ng_sdr_c.ng_sdr_c_n_id.unwrap_or(-1), + }) + } + else { + Err(anyhow!("")) // ok, none should've been parsed + } + } +} + impl FlattenedRntiMatchingArgs { pub fn from_unflattened(rnti_args: RntiMatchingArgs) -> Result { Ok(FlattenedRntiMatchingArgs { @@ -462,3 +875,352 @@ impl FlattenedDownloadArgs { }) } } + + + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use tempfile::tempdir; + + + #[test] + fn test_load_confy_default() { + let temp_dir = tempdir().expect("Failed to create temp dir"); + let config_path = temp_dir.path().join("ue-cell-tracker.yaml"); + fs::write(&config_path, DEFAULT_CONFIG_STR).expect("Failed to write config"); + + let parsed_args: Arguments = confy::load_path(&config_path) + .expect("Error loading ue-cell-tracker config"); + + let default_args = Arguments::default(); + assert_eq!(parsed_args, default_args); + } + + + #[test] + fn test_load_confy_partial() { + let temp_dir = tempdir().expect("Failed to create temp dir"); + let config_path = temp_dir.path().join("ue-cell-tracker.yaml"); + fs::write(&config_path, PARTIAL_CONFIG_STR).expect("Failed to write config"); + + let parsed_args: Arguments = confy::load_path(&config_path) + .expect("Error loading ue-cell-tracker config"); + + let partial_args = Arguments { + cellapi: Some(CellApiConfig::DevicePublisher), + log: Some(LogArgs { + log_base_dir: Some("./.logs.ue/".to_string()), + }), + scenario: Some(Scenario::TrackUeAndEstimateTransportCapacity), + milesight: None, + devicepublisher: None, + ngscope: None, + rntimatching: None, + model: None, + download: None, + verbose: None, + }; + assert_eq!(parsed_args, partial_args); + } + + #[test] + fn test_load_confy_partial_ng_sdr() { + let temp_dir = tempdir().expect("Failed to create temp dir"); + let config_path = temp_dir.path().join("ue-cell-tracker.yaml"); + fs::write(&config_path, PARTIAL_CONFIG_NG_SDR_STR).expect("Failed to write config"); + + let parsed_args: Arguments = confy::load_path(&config_path) + .expect("Error loading ue-cell-tracker config"); + + let partial_args = Arguments { + cellapi: None, + log: None, + scenario: None, + milesight: None, + devicepublisher: None, + ngscope: Some(NgScopeArgs { + ng_path: None, + ng_local_addr: None, + ng_server_addr: None, + ng_sdr_config: Some(NgScopeSdrConfigArgs { + ng_sdr_a: Some(NgScopeSdrConfigArgsA { + ng_sdr_a_serial: Some("A2C5B62".to_string()), + ng_sdr_a_n_id: Some(0), + }), + ng_sdr_b: Some(NgScopeSdrConfigArgsB { + ng_sdr_b_serial: Some("C2B5513".to_string()), + ng_sdr_b_n_id: Some(-1), + }), + ng_sdr_c: Some(NgScopeSdrConfigArgsC { + ng_sdr_c_serial: Some("D2D0F61".to_string()), + ng_sdr_c_n_id: Some(1), + }), + }), + ng_log_file: None, + ng_start_process: None, + ng_log_dci: None, + ng_log_dci_batch_size: None, + }), + rntimatching: None, + model: None, + download: None, + verbose: None, + }; + assert_eq!(parsed_args, partial_args); + } + + #[test] + fn test_load_confy_partial_ng_sdr_default_n_id() { + let temp_dir = tempdir().expect("Failed to create temp dir"); + let config_path = temp_dir.path().join("ue-cell-tracker.yaml"); + fs::write(&config_path, PARTIAL_CONFIG_NG_SDR_TWO_STR).expect("Failed to write config"); + + let parsed_args: Arguments = confy::load_path(&config_path) + .expect("Error loading ue-cell-tracker config"); + + let partial_args = Arguments { + cellapi: None, + log: None, + scenario: None, + milesight: None, + devicepublisher: None, + ngscope: Some(NgScopeArgs { + ng_path: None, + ng_local_addr: None, + ng_server_addr: None, + ng_sdr_config: Some(NgScopeSdrConfigArgs { + ng_sdr_a: Some(NgScopeSdrConfigArgsA { + ng_sdr_a_serial: Some("A2C5B62".to_string()), + ng_sdr_a_n_id: None, + }), + ng_sdr_b: Some(NgScopeSdrConfigArgsB { + ng_sdr_b_serial: Some("C2B5513".to_string()), + ng_sdr_b_n_id: None, + }), + ng_sdr_c: None, + }), + ng_log_file: None, + ng_start_process: None, + ng_log_dci: None, + ng_log_dci_batch_size: None, + }), + rntimatching: None, + model: None, + download: None, + verbose: None, + }; + assert_eq!(parsed_args, partial_args); + } + + #[test] + fn test_parse_default() { + let temp_dir = tempdir().expect("Failed to create temp dir"); + let config_path = temp_dir.path().join("ue-cell-tracker.yaml"); + fs::write(&config_path, DEFAULT_CONFIG_STR).expect("Failed to write config"); + + let loaded_args: Arguments = confy::load_path(&config_path) + .expect("Error loading ue-cell-tracker config"); + let parsed_args: Arguments = loaded_args.clone().merge_with(loaded_args); // merging with + // itself applies + // default parameters + let default_args = Arguments::default(); + assert_eq!(parsed_args, default_args); + } + + + #[test] + fn test_parse_partial() { + let temp_dir = tempdir().expect("Failed to create temp dir"); + let config_path = temp_dir.path().join("ue-cell-tracker.yaml"); + fs::write(&config_path, PARTIAL_CONFIG_STR).expect("Failed to write config"); + + let loaded_args: Arguments = confy::load_path(&config_path) + .expect("Error loading ue-cell-tracker config"); + let parsed_args: Arguments = loaded_args.clone().merge_with(loaded_args); // merging with + // itself applies + // default parameters + + let partial_args = Arguments { + cellapi: Some(CellApiConfig::DevicePublisher), + log: Some(LogArgs { + log_base_dir: Some("./.logs.ue/".to_string()), + }), + scenario: Some(Scenario::TrackUeAndEstimateTransportCapacity), + ..Default::default() + }; + assert_eq!(parsed_args, partial_args); + } + + #[test] + fn test_parse_partial_ng_sdr() { + let temp_dir = tempdir().expect("Failed to create temp dir"); + let config_path = temp_dir.path().join("ue-cell-tracker.yaml"); + fs::write(&config_path, PARTIAL_CONFIG_NG_SDR_STR).expect("Failed to write config"); + + let loaded_args: Arguments = confy::load_path(&config_path) + .expect("Error loading ue-cell-tracker config"); + let parsed_args: Arguments = loaded_args.clone().merge_with(loaded_args); // merging with + // itself applies + // default parameters + + let partial_args = Arguments { + ngscope: Some(NgScopeArgs { + ng_sdr_config: Some(NgScopeSdrConfigArgs { + ng_sdr_a: Some(NgScopeSdrConfigArgsA { + ng_sdr_a_serial: Some("A2C5B62".to_string()), + ng_sdr_a_n_id: Some(0), + }), + ng_sdr_b: Some(NgScopeSdrConfigArgsB { + ng_sdr_b_serial: Some("C2B5513".to_string()), + ng_sdr_b_n_id: Some(-1), + }), + ng_sdr_c: Some(NgScopeSdrConfigArgsC { + ng_sdr_c_serial: Some("D2D0F61".to_string()), + ng_sdr_c_n_id: Some(1), + }), + }), + ..Default::default() + }), + ..Default::default() + }; + assert_eq!(parsed_args, partial_args); + } + + #[test] + fn test_parse_partial_ng_sdr_default_n_id() { + let temp_dir = tempdir().expect("Failed to create temp dir"); + let config_path = temp_dir.path().join("ue-cell-tracker.yaml"); + fs::write(&config_path, PARTIAL_CONFIG_NG_SDR_TWO_STR).expect("Failed to write config"); + + let loaded_args: Arguments = confy::load_path(&config_path) + .expect("Error loading ue-cell-tracker config"); + let parsed_args: Arguments = loaded_args.clone().merge_with(loaded_args); // merging with + // itself applies + // default parameters + + let partial_args = Arguments { + ngscope: Some(NgScopeArgs { + ng_sdr_config: Some(NgScopeSdrConfigArgs { + ng_sdr_a: Some(NgScopeSdrConfigArgsA { + ng_sdr_a_serial: Some("A2C5B62".to_string()), + ..Default::default() + }), + ng_sdr_b: Some(NgScopeSdrConfigArgsB { + ng_sdr_b_serial: Some("C2B5513".to_string()), + ..Default::default() + }), + ..Default::default() + }), + ..Default::default() + }), + ..Default::default() + }; + assert_eq!(parsed_args, partial_args); + } + + #[allow(dead_code)] + const DEFAULT_CONFIG_STR: &str = +r#" +scenario: TrackUeAndEstimateTransportCapacity +cellapi: Milesight +milesight: + milesight_address: http://127.0.0.1:8080 + milesight_user: root + milesight_auth: root-password +devicepublisher: + devpub_address: 127.0.0.1 + devpub_auth: some_auth +ngscope: + ng_path: /dev_ws/dependencies/ng-scope/build_x86/ngscope/src/ngscope + ng_local_addr: 0.0.0.0:9191 + ng_server_addr: 0.0.0.0:6767 + ng_sdr_config: + ng_sdr_a: + ng_sdr_a_serial: 3295B62 + ng_sdr_a_n_id: -1 + ng_log_file: ./.ng_scope_log.txt + ng_start_process: true + ng_log_dci: true + ng_log_dci_batch_size: 60000 +rntimatching: + matching_local_addr: 0.0.0.0:9292 + matching_traffic_pattern: + - A + matching_traffic_destination: 127.0.0.1:9494 + matching_log_traffic: true +model: + model_send_metric_interval_value: 1.0 + model_send_metric_interval_type: RttFactor + model_metric_smoothing_size_value: 1.0 + model_metric_smoothing_size_type: RttFactor + model_log_metric: true +log: + log_base_dir: ./.logs.ue/ +download: + download_base_addr: 127.0.0.1:9393 + download_paths: + - /10s/cubic + - /10s/bbr + - /10s/reno + - /10s/l2b/fair0/init + - /10s/l2b/fair0/upper + - /10s/l2b/fair0/init_and_upper + - /10s/l2b/fair0/direct + - /10s/l2b/fair1/init + - /10s/l2b/fair1/upper + - /10s/l2b/fair1/init_and_upper + - /10s/l2b/fair1/direct + - /60s/cubic + - /60s/bbr + - /60s/reno + - /60s/l2b/fair0/init + - /60s/l2b/fair0/upper + - /60s/l2b/fair0/init_and_upper + - /60s/l2b/fair0/direct + - /60s/l2b/fair1/init + - /60s/l2b/fair1/upper + - /60s/l2b/fair1/init_and_upper + - /60s/l2b/fair1/direct +verbose: true +"#; + + #[allow(dead_code)] + const PARTIAL_CONFIG_STR: &str = +r#" +scenario: TrackUeAndEstimateTransportCapacity +cellapi: DevicePublisher +log: + log_base_dir: ./.logs.ue/ +"#; + + #[allow(dead_code)] + const PARTIAL_CONFIG_NG_SDR_STR: &str = +r#" +ngscope: + ng_sdr_config: + ng_sdr_a: + ng_sdr_a_serial: A2C5B62 + ng_sdr_a_n_id: 0 + ng_sdr_b: + ng_sdr_b_serial: C2B5513 + ng_sdr_b_n_id: -1 + ng_sdr_c: + ng_sdr_c_serial: D2D0F61 + ng_sdr_c_n_id: 1 +"#; + + #[allow(dead_code)] + const PARTIAL_CONFIG_NG_SDR_TWO_STR: &str = +r#" +ngscope: + ng_sdr_config: + ng_sdr_a: + ng_sdr_a_serial: A2C5B62 + ng_sdr_b: + ng_sdr_b_serial: C2B5513 +"#; + + +} diff --git a/src/util.rs b/src/util.rs index 134e196..9ff7202 100644 --- a/src/util.rs +++ b/src/util.rs @@ -229,3 +229,15 @@ pub fn init_heap_buffer(size: usize) -> Box<[u8]> { vec.resize_with(size, || 0); vec.into_boxed_slice() } + +pub trait UpdateIfSome { + fn update_if_some(&mut self, other: Option); +} + +impl UpdateIfSome for Option { + fn update_if_some(&mut self, other: Option) { + if let Some(value) = other { + *self = Some(value); + } + } +}