diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 9ad26da0cf..f532fc36ea 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -20,5 +20,5 @@ add_subdirectory(examples) add_subdirectory(gnb) -add_subdirectory(modules) +add_subdirectory(units) add_subdirectory(services) diff --git a/apps/gnb/CMakeLists.txt b/apps/gnb/CMakeLists.txt index 36d5fc4b16..74811dd3d5 100644 --- a/apps/gnb/CMakeLists.txt +++ b/apps/gnb/CMakeLists.txt @@ -48,6 +48,7 @@ target_link_libraries(gnb srsran_version srsran_build_info srsran_flexible_du_ru_dynamic + srsran_cu_cp_app_unit ) if (DPDK_FOUND) diff --git a/apps/gnb/gnb.cpp b/apps/gnb/gnb.cpp index daabd858d9..ae5064cb10 100644 --- a/apps/gnb/gnb.cpp +++ b/apps/gnb/gnb.cpp @@ -56,7 +56,7 @@ #include "apps/services/metrics_hub.h" #include "apps/services/rlc_metrics_plotter_json.h" -#include "apps/modules/flexible_du/split_ru_dynamic/dynamic_du_factory.h" +#include "apps/units/flexible_du/split_ru_dynamic/dynamic_du_factory.h" #include "srsran/phy/upper/upper_phy_timing_notifier.h" #include "srsran/ru/ru_adapters.h" @@ -67,16 +67,18 @@ #include "apps/gnb/adapters/e2_gateway_remote_connector.h" #include "apps/services/e2_metric_connector_manager.h" +#include "apps/units/cu_cp/cu_cp_logger_registrator.h" +#include "apps/units/cu_cp/cu_cp_unit_config_cli11_schema.h" +#include "apps/units/cu_cp/cu_cp_unit_config_validator.h" #include "srsran/support/sysinfo.h" #include -#include "../modules/cu_cp/logger_registrator.h" -#include "../modules/cu_cp/pcap_factory.h" -#include "../modules/cu_up/logger_registrator.h" -#include "../modules/cu_up/pcap_factory.h" -#include "../modules/flexible_du/du_high/pcap_factory.h" -#include "../modules/flexible_du/split_ru_dynamic/logger_registrator.h" +#include "../units/cu_cp/pcap_factory.h" +#include "../units/cu_up/logger_registrator.h" +#include "../units/cu_up/pcap_factory.h" +#include "../units/flexible_du/du_high/pcap_factory.h" +#include "../units/flexible_du/split_ru_dynamic/logger_registrator.h" #ifdef DPDK_FOUND #include "srsran/hal/dpdk/dpdk_eal_factory.h" @@ -202,7 +204,6 @@ static void register_app_logs(const log_appconfig& log_cfg) e2ap_logger.set_hex_dump_max_size(log_cfg.hex_max_size); // Register the loggers of the modules. - modules::cu_cp::register_logs(log_cfg); modules::cu_up::register_logs(log_cfg); modules::flexible_du::split_ru_dynamic::register_logs(log_cfg); } @@ -226,6 +227,9 @@ int main(int argc, char** argv) // Configure CLI11 with the gNB application configuration schema. configure_cli11_with_gnb_appconfig_schema(app, gnb_parsed_cfg); + cu_cp_unit_config cu_cp_config; + configure_cli11_with_cu_cp_unit_config_schema(app, cu_cp_config); + // Parse arguments. CLI11_PARSE(app, argc, argv); @@ -235,13 +239,14 @@ int main(int argc, char** argv) derive_auto_params(gnb_cfg); // Check the modified configuration. - if (!validate_appconfig(gnb_cfg)) { + if (!validate_appconfig(gnb_cfg) || !validate_cu_cp_unit_config(cu_cp_config)) { report_error("Invalid configuration detected.\n"); } // Set up logging. initialize_log(gnb_cfg.log_cfg.filename); register_app_logs(gnb_cfg.log_cfg); + register_loggers(cu_cp_config.loggers); srslog::basic_logger& gnb_logger = srslog::fetch_basic_logger("GNB"); if (not gnb_cfg.log_cfg.tracing_filename.empty()) { @@ -357,7 +362,7 @@ int main(int argc, char** argv) e2_gateway_remote_connector e2_gw{*epoll_broker, e2_du_nw_config, *e2ap_p}; // Create CU-CP config. - srs_cu_cp::cu_cp_configuration cu_cp_cfg = generate_cu_cp_config(gnb_cfg); + srs_cu_cp::cu_cp_configuration cu_cp_cfg = generate_cu_cp_config(gnb_cfg, cu_cp_config); cu_cp_cfg.cu_cp_executor = workers.cu_cp_exec; cu_cp_cfg.cu_cp_e2_exec = workers.cu_cp_e2_exec; cu_cp_cfg.ngap_notifier = ngap_adapter.get(); diff --git a/apps/gnb/gnb_appconfig.h b/apps/gnb/gnb_appconfig.h index 939ed26452..db48d6edf5 100644 --- a/apps/gnb/gnb_appconfig.h +++ b/apps/gnb/gnb_appconfig.h @@ -41,7 +41,6 @@ #include "srsran/ran/pucch/pucch_configuration.h" #include "srsran/ran/pusch/pusch_mcs.h" #include "srsran/ran/rnti.h" -#include "srsran/ran/s_nssai.h" #include "srsran/ran/sib/system_info_config.h" #include "srsran/ran/slot_pdu_capacity_constants.h" #include "srsran/ran/subcarrier_spacing.h" @@ -185,10 +184,14 @@ struct pdsch_appconfig { std::vector rv_sequence = {0, 2, 3, 1}; /// MCS table to use for PDSCH pdsch_mcs_table mcs_table = pdsch_mcs_table::qam64; - /// Minimum number of RBs for Resource Allocation of UE PDSCHs. + /// Minimum number of RBs for resource allocation of UE PDSCHs. unsigned min_rb_size = 1; - /// Maximum number of RBs for Resource Allocation of UE PDSCHs. + /// Maximum number of RBs for resource allocation of UE PDSCHs. unsigned max_rb_size = MAX_NOF_PRBS; + /// Start RB for resource allocation of UE PDSCHs. + unsigned start_rb = 0; + /// End RB for resource allocation of UE PDSCHs. + unsigned end_rb = MAX_NOF_PRBS; /// Maximum number of PDSCH grants per slot. unsigned max_pdschs_per_slot = MAX_PDSCH_PDUS_PER_SLOT; /// Maximum number of DL or UL PDCCH allocation attempts per slot. @@ -271,6 +274,14 @@ struct pusch_appconfig { float olla_max_snr_offset{5.0}; /// Position for additional DM-RS in UL (see TS 38.211, clause 6.4.1.1.3). unsigned dmrs_add_pos{2}; + /// Minimum number of RBs for resource allocation of UE PUSCHs. + unsigned min_rb_size = 1; + /// Maximum number of RBs for resource allocation of UE PUSCHs. + unsigned max_rb_size = MAX_NOF_PRBS; + /// Start RB for resource allocation of UE PUSCHs. + unsigned start_rb = 0; + /// End RB for resource allocation of UE PUSCHs. + unsigned end_rb = MAX_NOF_PRBS; }; struct pucch_appconfig { @@ -650,6 +661,7 @@ struct amf_appconfig { std::string n2_bind_interface = "auto"; std::string n3_bind_addr = "auto"; std::string n3_bind_interface = "auto"; + std::string n3_ext_addr = "auto"; int sctp_rto_initial = 120; int sctp_rto_min = 120; int sctp_rto_max = 500; @@ -675,81 +687,6 @@ struct e2_appconfig { bool e2sm_rc_enabled = false; ///< Whether to enable RC service module }; -struct cu_cp_neighbor_cell_appconfig_item { - uint64_t nr_cell_id; ///< Cell id. - std::vector report_cfg_ids; ///< Report config ids -}; - -/// \brief Each item describes the relationship between one cell to all other cells. -struct cu_cp_cell_appconfig_item { - uint64_t nr_cell_id; ///< Cell id. - optional periodic_report_cfg_id; - - // These parameters must only be set for external cells - // TODO: Add optional SSB parameters. - optional gnb_id_bit_length; ///< gNodeB identifier bit length. - optional pci; ///< PCI. - optional band; ///< NR band. - optional ssb_arfcn; ///< SSB ARFCN. - optional ssb_scs; ///< SSB subcarrier spacing. - optional ssb_period; ///< SSB period. - optional ssb_offset; ///< SSB offset. - optional ssb_duration; ///< SSB duration. - - std::vector ncells; ///< Vector of cells that are a neighbor of this cell. -}; - -/// \brief Report configuration, for now only supporting the A3 event. -struct cu_cp_report_appconfig { - unsigned report_cfg_id; - std::string report_type; - optional report_interval_ms; - std::string a3_report_type; - optional a3_offset_db; ///< [-30..30] Note the actual value is field value * 0.5 dB. E.g. putting a value of -6 - ///< here results in -3dB offset. - optional a3_hysteresis_db; - optional a3_time_to_trigger_ms; -}; - -/// \brief All mobility related configuration parameters. -struct mobility_appconfig { - std::vector cells; ///< List of all cells known to the CU-CP. - std::vector report_configs; ///< Report config. - bool trigger_handover_from_measurements = false; ///< Whether to start HO if neighbor cell measurements arrive. -}; - -/// \brief RRC specific configuration parameters. -struct rrc_appconfig { - bool force_reestablishment_fallback = false; - unsigned rrc_procedure_timeout_ms = 720; ///< Timeout for RRC procedures (2 * default SRB maxRetxThreshold * - ///< t-PollRetransmit = 2 * 8 * 45ms = 720ms, see TS 38.331 Sec 9.2.1). -}; - -/// \brief Security configuration parameters. -struct security_appconfig { - std::string integrity_protection = "not_needed"; - std::string confidentiality_protection = "required"; - std::string nea_preference_list = "nea0,nea2,nea1,nea3"; - std::string nia_preference_list = "nia2,nia1,nia3"; -}; - -/// \brief F1AP-CU configuration parameters. -struct f1ap_cu_appconfig { - /// Timeout for the UE context setup procedure in milliseconds. - unsigned ue_context_setup_timeout = 1000; -}; - -struct cu_cp_appconfig { - uint16_t max_nof_dus = 6; - uint16_t max_nof_cu_ups = 6; - int inactivity_timer = 120; // in seconds - unsigned pdu_session_setup_timeout = 3; // in seconds (must be larger than T310) - mobility_appconfig mobility_config; - rrc_appconfig rrc_config; - security_appconfig security_config; - f1ap_cu_appconfig f1ap_config; -}; - struct cu_up_appconfig { unsigned gtpu_queue_size = 2048; unsigned gtpu_reordering_timer_ms = 0; @@ -777,11 +714,9 @@ struct log_appconfig { std::string f1ap_level = "warning"; std::string f1u_level = "warning"; std::string pdcp_level = "warning"; - std::string rrc_level = "warning"; std::string ngap_level = "warning"; std::string sdap_level = "warning"; std::string gtpu_level = "warning"; - std::string sec_level = "warning"; std::string fapi_level = "warning"; std::string ofh_level = "warning"; std::string e2ap_level = "warning"; @@ -843,7 +778,6 @@ struct metrics_appconfig { struct pdcp_metrics { unsigned report_period = 0; // PDCP report period in ms } pdcp; - unsigned cu_cp_statistics_report_period = 1; // Statistics report period in seconds unsigned cu_up_statistics_report_period = 1; // Statistics report period in seconds /// JSON metrics reporting. bool enable_json_metrics = false; @@ -1258,14 +1192,10 @@ struct gnb_appconfig { std::string ran_node_name = "srsgnb01"; /// AMF configuration. amf_appconfig amf_cfg; - /// CU-CP configuration. - cu_cp_appconfig cu_cp_cfg; /// CU-UP configuration. cu_up_appconfig cu_up_cfg; /// DU configuration. du_appconfig du_cfg; - /// F1AP configuration. - f1ap_cu_appconfig f1ap_cfg; /// \brief E2 configuration. e2_appconfig e2_cfg; /// Radio Unit configuration. @@ -1283,9 +1213,6 @@ struct gnb_appconfig { /// SRB configuration. std::map srb_cfg; - /// Network slice configuration. - std::vector slice_cfg = {s_nssai_t{1}}; - /// Expert physical layer configuration. expert_upper_phy_appconfig expert_phy_cfg; diff --git a/apps/gnb/gnb_appconfig_cli11_schema.cpp b/apps/gnb/gnb_appconfig_cli11_schema.cpp index 9dcd099b07..a8b9f247d5 100644 --- a/apps/gnb/gnb_appconfig_cli11_schema.cpp +++ b/apps/gnb/gnb_appconfig_cli11_schema.cpp @@ -62,7 +62,6 @@ static void configure_cli11_log_args(CLI::App& app, log_appconfig& log_params) app.add_option("--mac_level", log_params.mac_level, "MAC log level")->capture_default_str()->check(level_check); app.add_option("--rlc_level", log_params.rlc_level, "RLC log level")->capture_default_str()->check(level_check); app.add_option("--pdcp_level", log_params.pdcp_level, "PDCP log level")->capture_default_str()->check(level_check); - app.add_option("--rrc_level", log_params.rrc_level, "RRC log level")->capture_default_str()->check(level_check); app.add_option("--sdap_level", log_params.sdap_level, "SDAP log level")->capture_default_str()->check(level_check); app.add_option("--ngap_level", log_params.ngap_level, "NGAP log level")->capture_default_str()->check(level_check); app.add_option("--gtpu_level", log_params.gtpu_level, "GTPU log level")->capture_default_str()->check(level_check); @@ -71,13 +70,10 @@ static void configure_cli11_log_args(CLI::App& app, log_appconfig& log_params) app.add_option("--ofh_level", log_params.ofh_level, "Open Fronthaul log level") ->capture_default_str() ->check(level_check); - app.add_option("--f1ap_level", log_params.f1ap_level, "F1AP log level")->capture_default_str()->check(level_check); + add_option(app, "--f1ap_level", log_params.f1ap_level, "F1AP log level")->capture_default_str()->check(level_check); app.add_option("--f1u_level", log_params.f1u_level, "F1-U log level")->capture_default_str()->check(level_check); app.add_option("--du_level", log_params.du_level, "Log level for the DU")->capture_default_str()->check(level_check); - app.add_option("--cu_level", log_params.cu_level, "Log level for the CU")->capture_default_str()->check(level_check); - app.add_option("--sec_level", log_params.sec_level, "Security functions log level") - ->capture_default_str() - ->check(level_check); + add_option(app, "--cu_level", log_params.cu_level, "Log level for the CU")->capture_default_str()->check(level_check); app.add_option("--lib_level", log_params.lib_level, "Generic log level")->capture_default_str()->check(level_check); app.add_option( "--hex_max_size", log_params.hex_max_size, "Maximum number of bytes to print in hex (zero for no hex dumps)") @@ -116,60 +112,24 @@ static void configure_cli11_log_args(CLI::App& app, log_appconfig& log_params) // Post-parsing callback. This allows us to set the log level to "all" level, if no level is provided. app.callback([&]() { - if (app.count("--phy_level") == 0) { - log_params.phy_level = log_params.all_level; - } - if (app.count("--mac_level") == 0) { - log_params.mac_level = log_params.all_level; - } - if (app.count("--rlc_level") == 0) { - log_params.rlc_level = log_params.all_level; - } - if (app.count("--f1ap_level") == 0) { - log_params.f1ap_level = log_params.all_level; - } - if (app.count("--pdcp_level") == 0) { - log_params.pdcp_level = log_params.all_level; - } - if (app.count("--rrc_level") == 0) { - log_params.rrc_level = log_params.all_level; - } - if (app.count("--sdap_level") == 0) { - log_params.sdap_level = log_params.all_level; - } - if (app.count("--ngap_level") == 0) { - log_params.ngap_level = log_params.all_level; - } - if (app.count("--gtpu_level") == 0) { - log_params.gtpu_level = log_params.all_level; - } - // Update the radio log level to all levels when radio level was not found and all level is not warning. - if (app.count("--radio_level") == 0 && log_params.all_level != "warning") { - log_params.radio_level = log_params.all_level; - } - if (app.count("--fapi_level") == 0) { - log_params.fapi_level = log_params.all_level; - } - if (app.count("--ofh_level") == 0) { - log_params.ofh_level = log_params.all_level; - } - if (app.count("--f1ap_level") == 0) { - log_params.f1ap_level = log_params.all_level; - } - if (app.count("--f1u_level") == 0) { - log_params.f1u_level = log_params.all_level; - } - if (app.count("--du_level") == 0) { - log_params.du_level = log_params.all_level; - } - if (app.count("--cu_level") == 0) { - log_params.cu_level = log_params.all_level; - } - if (app.count("--sec_level") == 0) { - log_params.sec_level = log_params.all_level; + // Do nothing when all_level is not defined or it is defined as warning. + if (app.count("--all_level") == 0 || log_params.all_level == "warning") { + return; } - if (app.count("--lib_level") == 0) { - log_params.lib_level = log_params.all_level; + + const auto options = app.get_options(); + for (auto* option : options) { + // Skip all_level option and unrelated options to log level. + if (option->check_name("--all_level") || option->get_name().find("level") == std::string::npos) { + continue; + } + + // Do nothing if option is present. + if (option->count()) { + continue; + } + + option->default_val(log_params.all_level); } }); } @@ -204,11 +164,6 @@ static void configure_cli11_metrics_args(CLI::App& app, metrics_appconfig& metri app.add_option("--pdcp_report_period", metrics_params.pdcp.report_period, "PDCP metrics report period") ->capture_default_str(); - app.add_option("--cu_cp_statistics_report_period", - metrics_params.cu_cp_statistics_report_period, - "CU-CP statistics report period in seconds. Set this value to 0 to disable this feature") - ->capture_default_str(); - app.add_option("--cu_up_statistics_report_period", metrics_params.cu_up_statistics_report_period, "CU-UP statistics report period in seconds. Set this value to 0 to disable this feature") @@ -232,14 +187,6 @@ static void configure_cli11_metrics_args(CLI::App& app, metrics_appconfig& metri ->capture_default_str(); } -static void configure_cli11_slicing_args(CLI::App& app, s_nssai_t& slice_params) -{ - app.add_option("--sst", slice_params.sst, "Slice Service Type")->capture_default_str()->check(CLI::Range(0, 255)); - app.add_option("--sd", slice_params.sd, "Service Differentiator") - ->capture_default_str() - ->check(CLI::Range(0, 0xffffff)); -} - static void configure_cli11_amf_args(CLI::App& app, amf_appconfig& amf_params) { app.add_option("--addr", amf_params.ip_addr, "AMF IP address"); @@ -256,6 +203,10 @@ static void configure_cli11_amf_args(CLI::App& app, amf_appconfig& amf_params) ->capture_default_str(); app.add_option("--n2_bind_interface", amf_params.n2_bind_interface, "Network device to bind for N2 interface") ->capture_default_str(); + app.add_option("--n3_ext_addr", + amf_params.n3_ext_addr, + "External IP address that is advertised to receive GTP-U packets from UPF via N3 interface") + ->check(CLI::ValidIPV4); app.add_option("--sctp_rto_initial", amf_params.sctp_rto_initial, "SCTP initial RTO value"); app.add_option("--sctp_rto_min", amf_params.sctp_rto_min, "SCTP RTO min"); app.add_option("--sctp_rto_max", amf_params.sctp_rto_max, "SCTP RTO max"); @@ -284,198 +235,6 @@ static void configure_cli11_e2_args(CLI::App& app, e2_appconfig& e2_params) app.add_option("--e2sm_rc_enabled", e2_params.e2sm_rc_enabled, "Enable RC service module"); } -static void configure_cli11_ncell_args(CLI::App& app, cu_cp_neighbor_cell_appconfig_item& config) -{ - app.add_option("--nr_cell_id", config.nr_cell_id, "Neighbor cell id"); - app.add_option( - "--report_configs", config.report_cfg_ids, "Report configurations to configure for this neighbor cell"); -} - -static void configure_cli11_cells_args(CLI::App& app, cu_cp_cell_appconfig_item& config) -{ - app.add_option("--nr_cell_id", config.nr_cell_id, "Cell id to be configured"); - app.add_option("--periodic_report_cfg_id", - config.periodic_report_cfg_id, - "Periodical report configuration for the serving cell") - ->check(CLI::Range(1, 64)); - add_auto_enum_option(app, "--band", config.band, "NR frequency band"); - app.add_option("--gnb_id_bit_length", config.gnb_id_bit_length, "gNodeB identifier bit length") - ->check(CLI::Range(22, 32)); - app.add_option("--pci", config.pci, "Physical Cell Id")->check(CLI::Range(0, 1007)); - app.add_option("--ssb_arfcn", config.ssb_arfcn, "SSB ARFCN"); - app.add_option("--ssb_scs", config.ssb_scs, "SSB subcarrier spacing")->check(CLI::IsMember({15, 30, 60, 120, 240})); - app.add_option("--ssb_period", config.ssb_period, "SSB period in ms")->check(CLI::IsMember({5, 10, 20, 40, 80, 160})); - app.add_option("--ssb_offset", config.ssb_offset, "SSB offset"); - app.add_option("--ssb_duration", config.ssb_duration, "SSB duration")->check(CLI::IsMember({1, 2, 3, 4, 5})); - - // report configuration parameters. - app.add_option_function>( - "--ncells", - [&config](const std::vector& values) { - config.ncells.resize(values.size()); - - for (unsigned i = 0, e = values.size(); i != e; ++i) { - CLI::App subapp("CU-CP neighbor cell list"); - subapp.config_formatter(create_yaml_config_parser()); - subapp.allow_config_extras(CLI::config_extras_mode::error); - configure_cli11_ncell_args(subapp, config.ncells[i]); - std::istringstream ss(values[i]); - subapp.parse_from_stream(ss); - } - }, - "Sets the list of neighbor cells known to the CU-CP"); -} - -static void configure_cli11_report_args(CLI::App& app, cu_cp_report_appconfig& report_params) -{ - app.add_option("--report_cfg_id", report_params.report_cfg_id, "Report configuration id to be configured") - ->check(CLI::Range(1, 64)); - app.add_option("--report_type", report_params.report_type, "Type of the report configuration") - ->check(CLI::IsMember({"periodical", "event_triggered"})); - app.add_option("--report_interval_ms", report_params.report_interval_ms, "Report interval in ms") - ->check( - CLI::IsMember({120, 240, 480, 640, 1024, 2048, 5120, 10240, 20480, 40960, 60000, 360000, 720000, 1800000})); - app.add_option("--a3_report_type", report_params.a3_report_type, "A3 report type") - ->check(CLI::IsMember({"rsrp", "rsrq", "sinr"})); - app.add_option("--a3_offset_db", - report_params.a3_offset_db, - "A3 offset in dB used for measurement report trigger. Note the actual value is field value * 0.5 dB") - ->check(CLI::Range(-30, 30)); - app.add_option( - "--a3_hysteresis_db", - report_params.a3_hysteresis_db, - "A3 hysteresis in dB used for measurement report trigger. Note the actual value is field value * 0.5 dB") - ->check(CLI::Range(0, 30)); - app.add_option("--a3_time_to_trigger_ms", - report_params.a3_time_to_trigger_ms, - "Time in ms during which A3 condition must be met before measurement report trigger") - ->check(CLI::IsMember({0, 40, 64, 80, 100, 128, 160, 256, 320, 480, 512, 640, 1024, 1280, 2560, 5120})); -} - -static void configure_cli11_mobility_args(CLI::App& app, mobility_appconfig& config) -{ - app.add_option("--trigger_handover_from_measurements", - config.trigger_handover_from_measurements, - "Whether to start HO if neighbor cells become stronger") - ->capture_default_str(); - - // Cell map parameters. - app.add_option_function>( - "--cells", - [&config](const std::vector& values) { - config.cells.resize(values.size()); - - for (unsigned i = 0, e = values.size(); i != e; ++i) { - CLI::App subapp("CU-CP cell list"); - subapp.config_formatter(create_yaml_config_parser()); - subapp.allow_config_extras(CLI::config_extras_mode::error); - configure_cli11_cells_args(subapp, config.cells[i]); - std::istringstream ss(values[i]); - subapp.parse_from_stream(ss); - } - }, - "Sets the list of cells known to the CU-CP"); - - // report configuration parameters. - app.add_option_function>( - "--report_configs", - [&config](const std::vector& values) { - config.report_configs.resize(values.size()); - - for (unsigned i = 0, e = values.size(); i != e; ++i) { - CLI::App subapp("CU-CP measurement report config list"); - subapp.config_formatter(create_yaml_config_parser()); - subapp.allow_config_extras(CLI::config_extras_mode::error); - configure_cli11_report_args(subapp, config.report_configs[i]); - std::istringstream ss(values[i]); - subapp.parse_from_stream(ss); - } - }, - "Sets report configurations"); -} - -static void configure_cli11_rrc_args(CLI::App& app, rrc_appconfig& config) -{ - app.add_option("--force_reestablishment_fallback", - config.force_reestablishment_fallback, - "Force RRC re-establishment fallback to RRC setup") - ->capture_default_str(); - - app.add_option( - "--rrc_procedure_timeout_ms", - config.rrc_procedure_timeout_ms, - "Timeout in ms used for RRC message exchange with UE. It needs to suit the expected communication delay and " - "account for potential retransmissions UE processing delays, SR delays, etc.") - ->capture_default_str(); -} - -static void configure_cli11_security_args(CLI::App& app, security_appconfig& config) -{ - auto sec_check = [](const std::string& value) -> std::string { - if (value == "required" || value == "preferred" || value == "not_needed") { - return {}; - } - return "Security indication value not supported. Accepted values [required,preferred,not_needed]"; - }; - - app.add_option("--integrity", config.integrity_protection, "Default integrity protection indication for DRBs") - ->capture_default_str() - ->check(sec_check); - - app.add_option("--confidentiality", - config.confidentiality_protection, - "Default confidentiality protection indication for DRBs") - ->capture_default_str() - ->check(sec_check); - - app.add_option("--nea_pref_list", - config.nea_preference_list, - "Ordered preference list for the selection of encryption algorithm (NEA) (default: NEA0, NEA2, NEA1)"); - - app.add_option("--nia_pref_list", - config.nia_preference_list, - "Ordered preference list for the selection of encryption algorithm (NIA) (default: NIA2, NIA1)") - ->capture_default_str(); -} - -static void configure_cli11_f1ap_args(CLI::App& app, f1ap_cu_appconfig& f1ap_params) -{ - app.add_option( - "--ue_context_setup_timeout", f1ap_params.ue_context_setup_timeout, "UE context setup timeout in milliseconds") - ->capture_default_str(); -} - -static void configure_cli11_cu_cp_args(CLI::App& app, cu_cp_appconfig& cu_cp_params) -{ - app.add_option( - "--max_nof_dus", cu_cp_params.max_nof_dus, "Maximum number of DU connections that the CU-CP may accept"); - - app.add_option( - "--max_nof_cu_ups", cu_cp_params.max_nof_cu_ups, "Maximum number of CU-UP connections that the CU-CP may accept"); - - app.add_option("--inactivity_timer", cu_cp_params.inactivity_timer, "UE/PDU Session/DRB inactivity timer in seconds") - ->capture_default_str() - ->check(CLI::Range(1, 7200)); - - app.add_option("--pdu_session_setup_timeout", - cu_cp_params.pdu_session_setup_timeout, - "Timeout for the setup of a PDU session after an InitialUEMessage was sent to the core, in " - "seconds. The timeout must be larger than T310. If the value is reached, the UE will be released") - ->capture_default_str(); - - CLI::App* mobility_subcmd = app.add_subcommand("mobility", "Mobility configuration"); - configure_cli11_mobility_args(*mobility_subcmd, cu_cp_params.mobility_config); - - CLI::App* rrc_subcmd = app.add_subcommand("rrc", "RRC specific configuration"); - configure_cli11_rrc_args(*rrc_subcmd, cu_cp_params.rrc_config); - - CLI::App* security_subcmd = app.add_subcommand("security", "Security configuration"); - configure_cli11_security_args(*security_subcmd, cu_cp_params.security_config); - - CLI::App* f1ap_subcmd = app.add_subcommand("f1ap", "F1AP configuration"); - configure_cli11_f1ap_args(*f1ap_subcmd, cu_cp_params.f1ap_config); -} - static void configure_cli11_cu_up_args(CLI::App& app, cu_up_appconfig& cu_up_params) { app.add_option("--gtpu_queue_size", cu_up_params.gtpu_queue_size, "GTP-U queue size, in PDUs")->capture_default_str(); @@ -657,6 +416,12 @@ static void configure_cli11_pdsch_args(CLI::App& app, pdsch_appconfig& pdsch_par app.add_option("--max_rb_size", pdsch_params.max_rb_size, "Maximum RB size for UE PDSCH resource allocation") ->capture_default_str() ->check(CLI::Range(1U, (unsigned)MAX_NOF_PRBS)); + app.add_option("--start_rb", pdsch_params.start_rb, "Start RB for resource allocation of UE PDSCHs") + ->capture_default_str() + ->check(CLI::Range(0U, (unsigned)MAX_NOF_PRBS)); + app.add_option("--end_rb", pdsch_params.end_rb, "End RB for resource allocation of UE PDSCHs") + ->capture_default_str() + ->check(CLI::Range(0U, (unsigned)MAX_NOF_PRBS)); app.add_option("--max_pdschs_per_slot", pdsch_params.max_pdschs_per_slot, "Maximum number of PDSCH grants per slot, including SIB, RAR, Paging and UE data grants.") @@ -848,6 +613,18 @@ static void configure_cli11_pusch_args(CLI::App& app, pusch_appconfig& pusch_par app.add_option("--dmrs_additional_position", pusch_params.dmrs_add_pos, "PUSCH DMRS additional position") ->capture_default_str() ->check(CLI::Range(0, 3)); + app.add_option("--min_rb_size", pusch_params.min_rb_size, "Minimum RB size for UE PUSCH resource allocation") + ->capture_default_str() + ->check(CLI::Range(1U, (unsigned)MAX_NOF_PRBS)); + app.add_option("--max_rb_size", pusch_params.max_rb_size, "Maximum RB size for UE PUSCH resource allocation") + ->capture_default_str() + ->check(CLI::Range(1U, (unsigned)MAX_NOF_PRBS)); + app.add_option("--start_rb", pusch_params.start_rb, "Start RB for resource allocation of UE PUSCHs") + ->capture_default_str() + ->check(CLI::Range(0U, (unsigned)MAX_NOF_PRBS)); + app.add_option("--end_rb", pusch_params.end_rb, "End RB for resource allocation of UE PUSCHs") + ->capture_default_str() + ->check(CLI::Range(0U, (unsigned)MAX_NOF_PRBS)); } static void configure_cli11_pucch_args(CLI::App& app, pucch_appconfig& pucch_params) @@ -1537,7 +1314,6 @@ static void configure_cli11_rlc_am_args(CLI::App& app, rlc_am_appconfig& rlc_am_ ->capture_default_str(); rlc_rx_am_subcmd->add_option("--max_sn_per_status", rlc_am_params.rx.max_sn_per_status, "RLC AM RX status SN limit") ->capture_default_str(); - ; } static void configure_cli11_rlc_args(CLI::App& app, rlc_appconfig& rlc_params) @@ -1634,29 +1410,13 @@ static void configure_cli11_qos_args(CLI::App& app, qos_appconfig& qos_params) configure_cli11_pdcp_args(*pdcp_subcmd, qos_params.pdcp); CLI::App* mac_subcmd = app.add_subcommand("mac", "MAC parameters"); configure_cli11_mac_args(*mac_subcmd, qos_params.mac); - auto verify_callback = [&]() { - CLI::App* rlc = app.get_subcommand("rlc"); - CLI::App* f1u_du = app.get_subcommand("f1u_du"); - CLI::App* f1u_cu_up = app.get_subcommand("f1u_cu_up"); - CLI::App* pdcp = app.get_subcommand("pdcp"); - CLI::App* mac = app.get_subcommand("mac"); - if (rlc->count_all() == 0) { - report_error("Error parsing QoS config for 5QI {}. RLC configuration not present.\n", qos_params.five_qi); - } - if (f1u_du->count_all() == 0) { - report_error("Error parsing QoS config for 5QI {}. F1-U DU configuration not present.\n", qos_params.five_qi); - } - if (f1u_cu_up->count_all() == 0) { - report_error("Error parsing QoS config for 5QI {}. F1-U CU_UP configuration not present.\n", qos_params.five_qi); - } - if (pdcp->count_all() == 0) { - report_error("Error parsing QoS config for 5QI {}. PDCP configuration not present.\n", qos_params.five_qi); - } - if (mac->count_all() == 0) { - report_error("Error parsing QoS config for 5QI {}. MAC configuration not present.\n", qos_params.five_qi); - } - }; - app.callback(verify_callback); + + // Mark the application that these subcommands need to be present. + app.needs(rlc_subcmd); + app.needs(pdcp_subcmd); + app.needs(f1u_du_subcmd); + app.needs(f1u_cu_up_subcmd); + app.needs(mac_subcmd); } static void configure_cli11_test_ue_mode_args(CLI::App& app, test_mode_ue_appconfig& test_params) @@ -2467,11 +2227,11 @@ static void configure_cli11_fapi_args(CLI::App& app, fapi_appconfig& config) void srsran::configure_cli11_with_gnb_appconfig_schema(CLI::App& app, gnb_parsed_appconfig& gnb_parsed_cfg) { gnb_appconfig& gnb_cfg = gnb_parsed_cfg.config; - app.add_option("--gnb_id", gnb_cfg.gnb_id.id, "gNodeB identifier")->capture_default_str(); - app.add_option("--gnb_id_bit_length", gnb_cfg.gnb_id.bit_length, "gNodeB identifier length in bits") + add_option(app, "--gnb_id", gnb_cfg.gnb_id.id, "gNodeB identifier")->capture_default_str(); + add_option(app, "--gnb_id_bit_length", gnb_cfg.gnb_id.bit_length, "gNodeB identifier length in bits") ->capture_default_str() ->check(CLI::Range(22, 32)); - app.add_option("--ran_node_name", gnb_cfg.ran_node_name, "RAN node name")->capture_default_str(); + add_option(app, "--ran_node_name", gnb_cfg.ran_node_name, "RAN node name")->capture_default_str(); // Logging section. CLI::App* log_subcmd = app.add_subcommand("log", "Logging configuration")->configurable(); @@ -2493,10 +2253,6 @@ void srsran::configure_cli11_with_gnb_appconfig_schema(CLI::App& app, gnb_parsed CLI::App* e2_subcmd = app.add_subcommand("e2", "E2 parameters")->configurable(); configure_cli11_e2_args(*e2_subcmd, gnb_cfg.e2_cfg); - // CU-CP section - CLI::App* cu_cp_subcmd = app.add_subcommand("cu_cp", "CU-CP parameters")->configurable(); - configure_cli11_cu_cp_args(*cu_cp_subcmd, gnb_cfg.cu_cp_cfg); - // CU-UP section. CLI::App* cu_up_subcmd = app.add_subcommand("cu_up", "CU-CP parameters")->configurable(); configure_cli11_cu_up_args(*cu_up_subcmd, gnb_cfg.cu_up_cfg); @@ -2577,7 +2333,7 @@ void srsran::configure_cli11_with_gnb_appconfig_schema(CLI::App& app, gnb_parsed // Format every QoS setting. for (unsigned i = 0, e = values.size(); i != e; ++i) { - CLI::App subapp("QoS parameters"); + CLI::App subapp("QoS parameters", "QoS config, item #" + std::to_string(i)); subapp.config_formatter(create_yaml_config_parser()); subapp.allow_config_extras(CLI::config_extras_mode::error); configure_cli11_qos_args(subapp, gnb_cfg.qos_cfg[i]); @@ -2585,8 +2341,8 @@ void srsran::configure_cli11_with_gnb_appconfig_schema(CLI::App& app, gnb_parsed subapp.parse_from_stream(ss); } }; - app.add_option_function>( - "--qos", qos_lambda, "Configures RLC and PDCP radio bearers on a per 5QI basis."); + + add_option_cell(app, "--qos", qos_lambda, "Configures RLC and PDCP radio bearers on a per 5QI basis."); // SRB section. auto srb_lambda = [&gnb_cfg](const std::vector& values) { @@ -2604,23 +2360,6 @@ void srsran::configure_cli11_with_gnb_appconfig_schema(CLI::App& app, gnb_parsed }; app.add_option_function>("--srbs", srb_lambda, "Configures signaling radio bearers."); - // Slicing section. - auto slicing_lambda = [&gnb_cfg](const std::vector& values) { - // Prepare the radio bearers - gnb_cfg.slice_cfg.resize(values.size()); - - // Format every QoS setting. - for (unsigned i = 0, e = values.size(); i != e; ++i) { - CLI::App subapp("Slicing parameters"); - subapp.config_formatter(create_yaml_config_parser()); - subapp.allow_config_extras(CLI::config_extras_mode::error); - configure_cli11_slicing_args(subapp, gnb_cfg.slice_cfg[i]); - std::istringstream ss(values[i]); - subapp.parse_from_stream(ss); - } - }; - app.add_option_function>("--slicing", slicing_lambda, "Network slicing configuration"); - // Expert PHY section. CLI::App* expert_phy_subcmd = app.add_subcommand("expert_phy", "Expert physical layer configuration")->configurable(); configure_cli11_expert_phy_args(*expert_phy_subcmd, gnb_cfg.expert_phy_cfg); diff --git a/apps/gnb/gnb_appconfig_translators.cpp b/apps/gnb/gnb_appconfig_translators.cpp index 710974669b..e061be03e5 100644 --- a/apps/gnb/gnb_appconfig_translators.cpp +++ b/apps/gnb/gnb_appconfig_translators.cpp @@ -21,6 +21,7 @@ */ #include "gnb_appconfig_translators.h" +#include "apps/units/cu_cp/cu_cp_unit_config.h" #include "gnb_appconfig.h" #include "srsran/cu_cp/cu_cp_configuration_helpers.h" #include "srsran/cu_up/cu_up_configuration_helpers.h" @@ -47,6 +48,155 @@ using namespace std::chrono_literals; /// Static configuration that the gnb supports. static constexpr cyclic_prefix cp = cyclic_prefix::NORMAL; +static std::map generate_cu_cp_qos_config(const cu_cp_unit_config& config) +{ + std::map out_cfg = {}; + if (config.qos_cfg.empty()) { + out_cfg = config_helpers::make_default_cu_cp_qos_config_list(); + return out_cfg; + } + + for (const auto& qos : config.qos_cfg) { + if (out_cfg.find(qos.five_qi) != out_cfg.end()) { + report_error("Duplicate 5QI configuration: {}\n", qos.five_qi); + } + // Convert PDCP config + pdcp_config& out_pdcp = out_cfg[qos.five_qi].pdcp; + + // RB type + out_pdcp.rb_type = pdcp_rb_type::drb; + + // RLC mode + rlc_mode mode = {}; + if (!from_string(mode, qos.rlc.mode)) { + report_error("Invalid RLC mode: {}, mode={}\n", qos.five_qi, qos.rlc.mode); + } + if (mode == rlc_mode::um_bidir || mode == rlc_mode::um_unidir_ul || mode == rlc_mode::um_unidir_dl) { + out_pdcp.rlc_mode = pdcp_rlc_mode::um; + } else if (mode == rlc_mode::am) { + out_pdcp.rlc_mode = pdcp_rlc_mode::am; + } else { + report_error("Invalid RLC mode: {}, mode={}\n", qos.five_qi, qos.rlc.mode); + } + + // Integrity Protection required + out_pdcp.integrity_protection_required = qos.pdcp.integrity_protection_required; + + // Ciphering required + out_pdcp.ciphering_required = true; + + // > Tx + // >> SN size + if (!pdcp_sn_size_from_uint(out_pdcp.tx.sn_size, qos.pdcp.tx.sn_field_length)) { + report_error("Invalid PDCP TX SN: {}, SN={}\n", qos.five_qi, qos.pdcp.tx.sn_field_length); + } + + // >> discard timer + out_pdcp.tx.discard_timer = pdcp_discard_timer{}; + if (!pdcp_discard_timer_from_int(out_pdcp.tx.discard_timer.value(), qos.pdcp.tx.discard_timer)) { + report_error("Invalid PDCP discard timer. 5QI {} discard_timer {}\n", qos.five_qi, qos.pdcp.tx.discard_timer); + } + + // >> status report required + out_pdcp.tx.status_report_required = qos.pdcp.tx.status_report_required; + + // > Rx + // >> SN size + if (!pdcp_sn_size_from_uint(out_pdcp.rx.sn_size, qos.pdcp.rx.sn_field_length)) { + report_error("Invalid PDCP RX SN: {}, SN={}\n", qos.five_qi, qos.pdcp.rx.sn_field_length); + } + + // >> out of order delivery + out_pdcp.rx.out_of_order_delivery = qos.pdcp.rx.out_of_order_delivery; + + // >> t-Reordering + if (!pdcp_t_reordering_from_int(out_pdcp.rx.t_reordering, qos.pdcp.rx.t_reordering)) { + report_error("Invalid PDCP t-Reordering. {} t-Reordering {}\n", qos.five_qi, qos.pdcp.rx.t_reordering); + } + } + return out_cfg; +} + +static security::preferred_integrity_algorithms +generate_preferred_integrity_algorithms_list(const cu_cp_unit_config& config) +{ + // String splitter helper + auto split = [](const std::string& s, char delim) -> std::vector { + std::vector result; + std::stringstream ss(s); + for (std::string item; getline(ss, item, delim);) { + result.push_back(item); + } + return result; + }; + + // > Remove spaces, convert to lower case and split on comma + std::string nia_preference_list = config.security_config.nia_preference_list; + nia_preference_list.erase(std::remove_if(nia_preference_list.begin(), nia_preference_list.end(), ::isspace), + nia_preference_list.end()); + std::transform(nia_preference_list.begin(), + nia_preference_list.end(), + nia_preference_list.begin(), + [](unsigned char c) { return std::tolower(c); }); + std::vector nea_v = split(nia_preference_list, ','); + + security::preferred_integrity_algorithms algo_list = {}; + int idx = 0; + for (const std::string& nea : nea_v) { + if (nea == "nia0") { + algo_list[idx] = security::integrity_algorithm::nia0; + } else if (nea == "nia1") { + algo_list[idx] = security::integrity_algorithm::nia1; + } else if (nea == "nia2") { + algo_list[idx] = security::integrity_algorithm::nia2; + } else if (nea == "nia3") { + algo_list[idx] = security::integrity_algorithm::nia3; + } + idx++; + } + return algo_list; +} + +static security::preferred_ciphering_algorithms +generate_preferred_ciphering_algorithms_list(const cu_cp_unit_config& config) +{ + // String splitter helper + auto split = [](const std::string& s, char delim) -> std::vector { + std::vector result; + std::stringstream ss(s); + for (std::string item; getline(ss, item, delim);) { + result.push_back(item); + } + return result; + }; + + // > Remove spaces, convert to lower case and split on comma + std::string nea_preference_list = config.security_config.nea_preference_list; + nea_preference_list.erase(std::remove_if(nea_preference_list.begin(), nea_preference_list.end(), ::isspace), + nea_preference_list.end()); + std::transform(nea_preference_list.begin(), + nea_preference_list.end(), + nea_preference_list.begin(), + [](unsigned char c) { return std::tolower(c); }); + std::vector nea_v = split(nea_preference_list, ','); + + security::preferred_ciphering_algorithms algo_list = {}; + int idx = 0; + for (const std::string& nea : nea_v) { + if (nea == "nea0") { + algo_list[idx] = security::ciphering_algorithm::nea0; + } else if (nea == "nea1") { + algo_list[idx] = security::ciphering_algorithm::nea1; + } else if (nea == "nea2") { + algo_list[idx] = security::ciphering_algorithm::nea2; + } else if (nea == "nea3") { + algo_list[idx] = security::ciphering_algorithm::nea3; + } + idx++; + } + return algo_list; +} + srs_cu_cp::rrc_ssb_mtc srsran::generate_rrc_ssb_mtc(unsigned period, unsigned offset, unsigned duration) { srs_cu_cp::rrc_ssb_mtc ssb_mtc; @@ -91,52 +241,53 @@ srsran::sctp_network_gateway_config srsran::generate_ngap_nw_config(const gnb_ap return out_cfg; } -srs_cu_cp::cu_cp_configuration srsran::generate_cu_cp_config(const gnb_appconfig& config) +srs_cu_cp::cu_cp_configuration srsran::generate_cu_cp_config(const gnb_appconfig& config, + const cu_cp_unit_config& cu_cfg) { + srs_cu_cp::cu_cp_configuration out_cfg = config_helpers::make_default_cu_cp_config(); + out_cfg.max_nof_dus = cu_cfg.max_nof_dus; + out_cfg.max_nof_cu_ups = cu_cfg.max_nof_cu_ups; + + out_cfg.ngap_config.gnb_id = cu_cfg.gnb_id; + out_cfg.ngap_config.ran_node_name = cu_cfg.ran_node_name; + out_cfg.ngap_config.slice_configurations = cu_cfg.slice_cfg; + + // :TODO: What happens when there are multiple cells? const base_cell_appconfig& cell = config.cells_cfg.front().cell; + out_cfg.ngap_config.plmn = cell.plmn; + out_cfg.ngap_config.tac = cell.tac; - srs_cu_cp::cu_cp_configuration out_cfg = config_helpers::make_default_cu_cp_config(); - out_cfg.max_nof_dus = config.cu_cp_cfg.max_nof_dus; - out_cfg.max_nof_cu_ups = config.cu_cp_cfg.max_nof_cu_ups; - - out_cfg.ngap_config.gnb_id = config.gnb_id; - out_cfg.ngap_config.ran_node_name = config.ran_node_name; - out_cfg.ngap_config.plmn = cell.plmn; - out_cfg.ngap_config.tac = cell.tac; - out_cfg.ngap_config.slice_configurations = config.slice_cfg; - - out_cfg.rrc_config.gnb_id = config.gnb_id; - out_cfg.rrc_config.force_reestablishment_fallback = config.cu_cp_cfg.rrc_config.force_reestablishment_fallback; - out_cfg.rrc_config.rrc_procedure_timeout_ms = config.cu_cp_cfg.rrc_config.rrc_procedure_timeout_ms; - out_cfg.rrc_config.int_algo_pref_list = generate_preferred_integrity_algorithms_list(config); - out_cfg.rrc_config.enc_algo_pref_list = generate_preferred_ciphering_algorithms_list(config); - out_cfg.rrc_config.drb_config = generate_cu_cp_qos_config(config); + out_cfg.rrc_config.gnb_id = cu_cfg.gnb_id; + out_cfg.rrc_config.force_reestablishment_fallback = cu_cfg.rrc_config.force_reestablishment_fallback; + out_cfg.rrc_config.rrc_procedure_timeout_ms = cu_cfg.rrc_config.rrc_procedure_timeout_ms; + out_cfg.rrc_config.int_algo_pref_list = generate_preferred_integrity_algorithms_list(cu_cfg); + out_cfg.rrc_config.enc_algo_pref_list = generate_preferred_ciphering_algorithms_list(cu_cfg); + out_cfg.rrc_config.drb_config = generate_cu_cp_qos_config(cu_cfg); if (!from_string(out_cfg.default_security_indication.integrity_protection_ind, - config.cu_cp_cfg.security_config.integrity_protection)) { - report_error("Invalid value for integrity_protection={}\n", config.cu_cp_cfg.security_config.integrity_protection); + cu_cfg.security_config.integrity_protection)) { + report_error("Invalid value for integrity_protection={}\n", cu_cfg.security_config.integrity_protection); } if (!from_string(out_cfg.default_security_indication.confidentiality_protection_ind, - config.cu_cp_cfg.security_config.confidentiality_protection)) { + cu_cfg.security_config.confidentiality_protection)) { report_error("Invalid value for confidentiality_protection={}\n", - config.cu_cp_cfg.security_config.confidentiality_protection); + cu_cfg.security_config.confidentiality_protection); } - out_cfg.ue_config.inactivity_timer = std::chrono::seconds{config.cu_cp_cfg.inactivity_timer}; - out_cfg.ue_config.max_nof_supported_ues = config.cu_cp_cfg.max_nof_dus * srsran::srs_cu_cp::MAX_NOF_UES_PER_DU; - out_cfg.ngap_config.pdu_session_setup_timeout = std::chrono::seconds{config.cu_cp_cfg.pdu_session_setup_timeout}; - out_cfg.statistics_report_period = std::chrono::seconds{config.metrics_cfg.cu_cp_statistics_report_period}; + out_cfg.ue_config.inactivity_timer = std::chrono::seconds{cu_cfg.inactivity_timer}; + out_cfg.ue_config.max_nof_supported_ues = cu_cfg.max_nof_dus * srsran::srs_cu_cp::MAX_NOF_UES_PER_DU; + out_cfg.ngap_config.pdu_session_setup_timeout = std::chrono::seconds{cu_cfg.pdu_session_setup_timeout}; + out_cfg.statistics_report_period = std::chrono::seconds{cu_cfg.metrics.cu_cp_statistics_report_period}; out_cfg.mobility_config.mobility_manager_config.trigger_handover_from_measurements = - config.cu_cp_cfg.mobility_config.trigger_handover_from_measurements; + cu_cfg.mobility_config.trigger_handover_from_measurements; // F1AP-CU config. - out_cfg.f1ap_config.ue_context_setup_timeout = - std::chrono::milliseconds{config.cu_cp_cfg.f1ap_config.ue_context_setup_timeout}; - out_cfg.f1ap_config.json_log_enabled = config.log_cfg.f1ap_json_enabled; + out_cfg.f1ap_config.ue_context_setup_timeout = std::chrono::milliseconds{cu_cfg.f1ap_config.ue_context_setup_timeout}; + out_cfg.f1ap_config.json_log_enabled = cu_cfg.loggers.f1ap_json_enabled; // Convert appconfig's cell list into cell manager type. - for (const auto& app_cfg_item : config.cu_cp_cfg.mobility_config.cells) { + for (const auto& app_cfg_item : cu_cfg.mobility_config.cells) { srs_cu_cp::cell_meas_config meas_cfg_item; meas_cfg_item.serving_cell_cfg.nci = app_cfg_item.nr_cell_id; if (app_cfg_item.periodic_report_cfg_id.has_value()) { @@ -177,7 +328,7 @@ srs_cu_cp::cu_cp_configuration srsran::generate_cu_cp_config(const gnb_appconfig } // Convert report config. - for (const auto& report_cfg_item : config.cu_cp_cfg.mobility_config.report_configs) { + for (const auto& report_cfg_item : cu_cfg.mobility_config.report_configs) { srs_cu_cp::rrc_report_cfg_nr report_cfg; if (report_cfg_item.report_type == "periodical") { @@ -278,6 +429,7 @@ srs_cu_up::cu_up_configuration srsran::generate_cu_up_config(const gnb_appconfig } else { out_cfg.net_cfg.n3_bind_addr = config.amf_cfg.n3_bind_addr; } + out_cfg.net_cfg.n3_ext_addr = config.amf_cfg.n3_ext_addr; out_cfg.net_cfg.n3_bind_interface = config.amf_cfg.n3_bind_interface; out_cfg.net_cfg.n3_rx_max_mmsg = config.amf_cfg.udp_rx_max_msgs; out_cfg.net_cfg.f1u_bind_addr = config.amf_cfg.bind_addr; // FIXME: check if this can be removed for co-located case @@ -880,155 +1032,6 @@ std::vector srsran::generate_du_cell_config(const gnb_appconfig& return out_cfg; } -srsran::security::preferred_integrity_algorithms -srsran::generate_preferred_integrity_algorithms_list(const gnb_appconfig& config) -{ - // String splitter helper - auto split = [](const std::string& s, char delim) -> std::vector { - std::vector result; - std::stringstream ss(s); - for (std::string item; getline(ss, item, delim);) { - result.push_back(item); - } - return result; - }; - - // > Remove spaces, convert to lower case and split on comma - std::string nia_preference_list = config.cu_cp_cfg.security_config.nia_preference_list; - nia_preference_list.erase(std::remove_if(nia_preference_list.begin(), nia_preference_list.end(), ::isspace), - nia_preference_list.end()); - std::transform(nia_preference_list.begin(), - nia_preference_list.end(), - nia_preference_list.begin(), - [](unsigned char c) { return std::tolower(c); }); - std::vector nea_v = split(nia_preference_list, ','); - - security::preferred_integrity_algorithms algo_list = {}; - int idx = 0; - for (const std::string& nea : nea_v) { - if (nea == "nia0") { - algo_list[idx] = security::integrity_algorithm::nia0; - } else if (nea == "nia1") { - algo_list[idx] = security::integrity_algorithm::nia1; - } else if (nea == "nia2") { - algo_list[idx] = security::integrity_algorithm::nia2; - } else if (nea == "nia3") { - algo_list[idx] = security::integrity_algorithm::nia3; - } - idx++; - } - return algo_list; -} - -srsran::security::preferred_ciphering_algorithms -srsran::generate_preferred_ciphering_algorithms_list(const gnb_appconfig& config) -{ - // String splitter helper - auto split = [](const std::string& s, char delim) -> std::vector { - std::vector result; - std::stringstream ss(s); - for (std::string item; getline(ss, item, delim);) { - result.push_back(item); - } - return result; - }; - - // > Remove spaces, convert to lower case and split on comma - std::string nea_preference_list = config.cu_cp_cfg.security_config.nea_preference_list; - nea_preference_list.erase(std::remove_if(nea_preference_list.begin(), nea_preference_list.end(), ::isspace), - nea_preference_list.end()); - std::transform(nea_preference_list.begin(), - nea_preference_list.end(), - nea_preference_list.begin(), - [](unsigned char c) { return std::tolower(c); }); - std::vector nea_v = split(nea_preference_list, ','); - - security::preferred_ciphering_algorithms algo_list = {}; - int idx = 0; - for (const std::string& nea : nea_v) { - if (nea == "nea0") { - algo_list[idx] = security::ciphering_algorithm::nea0; - } else if (nea == "nea1") { - algo_list[idx] = security::ciphering_algorithm::nea1; - } else if (nea == "nea2") { - algo_list[idx] = security::ciphering_algorithm::nea2; - } else if (nea == "nea3") { - algo_list[idx] = security::ciphering_algorithm::nea3; - } - idx++; - } - return algo_list; -} - -std::map srsran::generate_cu_cp_qos_config(const gnb_appconfig& config) -{ - std::map out_cfg = {}; - if (config.qos_cfg.empty()) { - out_cfg = config_helpers::make_default_cu_cp_qos_config_list(); - return out_cfg; - } - - for (const qos_appconfig& qos : config.qos_cfg) { - if (out_cfg.find(qos.five_qi) != out_cfg.end()) { - report_error("Duplicate 5QI configuration: {}\n", qos.five_qi); - } - // Convert PDCP config - pdcp_config& out_pdcp = out_cfg[qos.five_qi].pdcp; - - // RB type - out_pdcp.rb_type = pdcp_rb_type::drb; - - // RLC mode - rlc_mode mode = {}; - if (!from_string(mode, qos.rlc.mode)) { - report_error("Invalid RLC mode: {}, mode={}\n", qos.five_qi, qos.rlc.mode); - } - if (mode == rlc_mode::um_bidir || mode == rlc_mode::um_unidir_ul || mode == rlc_mode::um_unidir_dl) { - out_pdcp.rlc_mode = pdcp_rlc_mode::um; - } else if (mode == rlc_mode::am) { - out_pdcp.rlc_mode = pdcp_rlc_mode::am; - } else { - report_error("Invalid RLC mode: {}, mode={}\n", qos.five_qi, qos.rlc.mode); - } - - // Integrity Protection required - out_pdcp.integrity_protection_required = qos.pdcp.integrity_protection_required; - - // Ciphering required - out_pdcp.ciphering_required = true; - - // > Tx - // >> SN size - if (!pdcp_sn_size_from_uint(out_pdcp.tx.sn_size, qos.pdcp.tx.sn_field_length)) { - report_error("Invalid PDCP TX SN: {}, SN={}\n", qos.five_qi, qos.pdcp.tx.sn_field_length); - } - - // >> discard timer - out_pdcp.tx.discard_timer = pdcp_discard_timer{}; - if (!pdcp_discard_timer_from_int(out_pdcp.tx.discard_timer.value(), qos.pdcp.tx.discard_timer)) { - report_error("Invalid PDCP discard timer. 5QI {} discard_timer {}\n", qos.five_qi, qos.pdcp.tx.discard_timer); - } - - // >> status report required - out_pdcp.tx.status_report_required = qos.pdcp.tx.status_report_required; - - // > Rx - // >> SN size - if (!pdcp_sn_size_from_uint(out_pdcp.rx.sn_size, qos.pdcp.rx.sn_field_length)) { - report_error("Invalid PDCP RX SN: {}, SN={}\n", qos.five_qi, qos.pdcp.rx.sn_field_length); - } - - // >> out of order delivery - out_pdcp.rx.out_of_order_delivery = qos.pdcp.rx.out_of_order_delivery; - - // >> t-Reordering - if (!pdcp_t_reordering_from_int(out_pdcp.rx.t_reordering, qos.pdcp.rx.t_reordering)) { - report_error("Invalid PDCP t-Reordering. {} t-Reordering {}\n", qos.five_qi, qos.pdcp.rx.t_reordering); - } - } - return out_cfg; -} - std::map srsran::generate_cu_up_qos_config(const gnb_appconfig& config) { std::map out_cfg = {}; @@ -1765,6 +1768,7 @@ scheduler_expert_config srsran::generate_scheduler_expert_config(const gnb_appco // UE parameters. const pdsch_appconfig& pdsch = cell.pdsch_cfg; + const pusch_appconfig& pusch = cell.pusch_cfg; out_cfg.ue.dl_mcs = {pdsch.min_ue_mcs, pdsch.max_ue_mcs}; out_cfg.ue.pdsch_rv_sequence.assign(pdsch.rv_sequence.begin(), pdsch.rv_sequence.end()); out_cfg.ue.dl_harq_la_cqi_drop_threshold = pdsch.harq_la_cqi_drop_threshold; @@ -1773,10 +1777,10 @@ scheduler_expert_config srsran::generate_scheduler_expert_config(const gnb_appco out_cfg.ue.max_pdschs_per_slot = pdsch.max_pdschs_per_slot; out_cfg.ue.max_pdcch_alloc_attempts_per_slot = pdsch.max_pdcch_alloc_attempts_per_slot; out_cfg.ue.pdsch_nof_rbs = {pdsch.min_rb_size, pdsch.max_rb_size}; + out_cfg.ue.pusch_nof_rbs = {pusch.min_rb_size, pusch.max_rb_size}; out_cfg.ue.olla_dl_target_bler = pdsch.olla_target_bler; out_cfg.ue.olla_cqi_inc = pdsch.olla_cqi_inc; out_cfg.ue.olla_max_cqi_offset = pdsch.olla_max_cqi_offset; - const pusch_appconfig& pusch = cell.pusch_cfg; if (config.ntn_cfg.has_value()) { out_cfg.ue.auto_ack_harq = true; } @@ -1787,6 +1791,8 @@ scheduler_expert_config srsran::generate_scheduler_expert_config(const gnb_appco out_cfg.ue.olla_ul_target_bler = pusch.olla_target_bler; out_cfg.ue.olla_ul_snr_inc = pusch.olla_snr_inc; out_cfg.ue.olla_max_ul_snr_offset = pusch.olla_max_snr_offset; + out_cfg.ue.pdsch_crb_limits = {pdsch.start_rb, pdsch.end_rb}; + out_cfg.ue.pusch_crb_limits = {pusch.start_rb, pusch.end_rb}; // PUCCH and scheduler expert parameters. out_cfg.ue.max_ul_grants_per_slot = cell.ul_common_cfg.max_ul_grants_per_slot; diff --git a/apps/gnb/gnb_appconfig_translators.h b/apps/gnb/gnb_appconfig_translators.h index 2d13eaf5c7..cf37c30fe1 100644 --- a/apps/gnb/gnb_appconfig_translators.h +++ b/apps/gnb/gnb_appconfig_translators.h @@ -37,6 +37,7 @@ namespace srsran { +struct cu_cp_unit_config; struct gnb_appconfig; struct rlc_am_appconfig; struct mac_lc_appconfig; @@ -54,7 +55,7 @@ subcarrier_spacing generate_subcarrier_spacing(unsigned sc_spacing); srsran::sctp_network_gateway_config generate_ngap_nw_config(const gnb_appconfig& config); /// Converts and returns the given gnb application configuration to a CU-CP configuration. -srs_cu_cp::cu_cp_configuration generate_cu_cp_config(const gnb_appconfig& config); +srs_cu_cp::cu_cp_configuration generate_cu_cp_config(const gnb_appconfig& config, const cu_cp_unit_config& cu_cfg); /// Converts and returns the given gnb application configuration to a CU-UP configuration. srs_cu_up::cu_up_configuration generate_cu_up_config(const gnb_appconfig& config); @@ -62,22 +63,9 @@ srs_cu_up::cu_up_configuration generate_cu_up_config(const gnb_appconfig& config /// Converts and returns the given gnb application configuration to a DU cell configuration. std::vector generate_du_cell_config(const gnb_appconfig& config); -/// Converts and returns the given gnb application QoS configuration to a CU-CP configuration. -std::map generate_cu_cp_qos_config(const gnb_appconfig& config); - /// Converts and returns the given gnb application QoS configuration to a CU-UP configuration. std::map generate_cu_up_qos_config(const gnb_appconfig& config); -/// Converts and returns the given gnb application integrity protection algorithm preferences configuration to a -/// CU-CP configuration. -srsran::security::preferred_integrity_algorithms -generate_preferred_integrity_algorithms_list(const gnb_appconfig& config); - -/// Converts and returns the given gnb application ciphering protection algorithm preferences configuration to a CU-CP -/// configuration. -srsran::security::preferred_ciphering_algorithms -generate_preferred_ciphering_algorithms_list(const gnb_appconfig& config); - /// Converts and returns the given gnb RLC AM configuration to a RLC configuration. srsran::rlc_am_config generate_rlc_am_config(const rlc_am_appconfig& in_cfg); diff --git a/apps/gnb/gnb_appconfig_validators.cpp b/apps/gnb/gnb_appconfig_validators.cpp index 17cc03c58a..a72e9a894f 100644 --- a/apps/gnb/gnb_appconfig_validators.cpp +++ b/apps/gnb/gnb_appconfig_validators.cpp @@ -244,7 +244,7 @@ static bool validate_rv_sequence(span rv_sequence) } /// Validates the given PDSCH cell application configuration. Returns true on success, otherwise false. -static bool validate_pdsch_cell_app_config(const pdsch_appconfig& config) +static bool validate_pdsch_cell_app_config(const pdsch_appconfig& config, unsigned cell_bw_crbs) { if (config.min_ue_mcs > config.max_ue_mcs) { fmt::print("Invalid UE MCS range (i.e., [{}, {}]). The min UE MCS must be less than or equal to the max UE MCS.\n", @@ -264,11 +264,23 @@ static bool validate_pdsch_cell_app_config(const pdsch_appconfig& config) return false; } + if (config.end_rb <= config.start_rb) { + fmt::print("Invalid RB allocation range [{}, {}) for UE PDSCHs. The start_rb must be less or equal to the end_rb", + config.start_rb, + config.end_rb); + return false; + } + + if (config.start_rb >= cell_bw_crbs) { + fmt::print("Invalid start RB {} for UE PDSCHs. The start_rb must be less than the cell BW", config.start_rb); + return false; + } + return true; } /// Validates the given PUSCH cell application configuration. Returns true on success, otherwise false. -static bool validate_pusch_cell_app_config(const pusch_appconfig& config) +static bool validate_pusch_cell_app_config(const pusch_appconfig& config, unsigned cell_bw_crbs) { if (config.min_ue_mcs > config.max_ue_mcs) { fmt::print("Invalid UE MCS range (i.e., [{}, {}]). The min UE MCS must be less than or equal to the max UE MCS.\n", @@ -281,6 +293,25 @@ static bool validate_pusch_cell_app_config(const pusch_appconfig& config) return false; } + if (config.max_rb_size < config.min_rb_size) { + fmt::print("Invalid UE PUSCH RB range [{}, {}). The min_rb_size must be less or equal to the max_rb_size", + config.min_rb_size, + config.max_rb_size); + return false; + } + + if (config.end_rb <= config.start_rb) { + fmt::print("Invalid RB allocation range [{}, {}) for UE PUSCHs. The start_rb must be less or equal to the end_rb", + config.start_rb, + config.end_rb); + return false; + } + + if (config.start_rb >= cell_bw_crbs) { + fmt::print("Invalid start RB {} for UE PUSCHs. The start_rb must be less than the cell BW", config.start_rb); + return false; + } + return true; } @@ -583,7 +614,12 @@ static bool validate_base_cell_appconfig(const base_cell_appconfig& config) return false; } - if (!validate_pdsch_cell_app_config(config.pdsch_cfg)) { + const nr_band band = + config.band.has_value() ? config.band.value() : band_helper::get_band_from_dl_arfcn(config.dl_arfcn); + const unsigned nof_crbs = + band_helper::get_n_rbs_from_bw(config.channel_bw_mhz, config.common_scs, band_helper::get_freq_range(band)); + + if (!validate_pdsch_cell_app_config(config.pdsch_cfg, nof_crbs)) { return false; } @@ -595,11 +631,10 @@ static bool validate_base_cell_appconfig(const base_cell_appconfig& config) return false; } - if (!validate_pusch_cell_app_config(config.pusch_cfg)) { + if (!validate_pusch_cell_app_config(config.pusch_cfg, nof_crbs)) { return false; } - nr_band band = config.band.has_value() ? config.band.value() : band_helper::get_band_from_dl_arfcn(config.dl_arfcn); if (!validate_prach_cell_app_config(config.prach_cfg, band, config.nof_antennas_ul)) { return false; } @@ -639,82 +674,29 @@ static bool validate_cells_appconfig(span config) } } - return true; -} - -/// Validates the given AMF configuration. Returns true on success, otherwise false. -static bool validate_amf_appconfig(const amf_appconfig& config) -{ - // only check for non-empty AMF address and default port - if (config.ip_addr.empty() or config.port != 38412) { - return false; - } - return true; -} - -static bool validate_mobility_appconfig(const gnb_id_t gnb_id, const mobility_appconfig& config) -{ - // check that report config ids are unique - std::map report_cfg_ids_to_report_type; - for (const auto& report_cfg : config.report_configs) { - if (report_cfg_ids_to_report_type.find(report_cfg.report_cfg_id) != report_cfg_ids_to_report_type.end()) { - fmt::print("Report config ids must be unique\n"); - return false; - } - report_cfg_ids_to_report_type.emplace(report_cfg.report_cfg_id, report_cfg.report_type); - } - - // check cu_cp_cell_config - std::set ncis; - for (const auto& cell : config.cells) { - if (ncis.emplace(cell.nr_cell_id).second == false) { - fmt::print("Cells must be unique ({:#x} already present)\n"); - return false; - } - - if (cell.ssb_period.has_value() && cell.ssb_offset.has_value() && - cell.ssb_offset.value() >= cell.ssb_period.value()) { - fmt::print("ssb_offset must be smaller than ssb_period\n"); - return false; - } - - // check that for the serving cell only periodic reports are configured - if (cell.periodic_report_cfg_id.has_value()) { - if (report_cfg_ids_to_report_type.at(cell.periodic_report_cfg_id.value()) != "periodical") { - fmt::print("For the serving cell only periodic reports are allowed\n"); - return false; - } - } - - // Check if cell is an external managed cell - if (config_helpers::get_gnb_id(cell.nr_cell_id, gnb_id.bit_length) != gnb_id) { - if (!cell.gnb_id_bit_length.has_value() || !cell.pci.has_value() || !cell.band.has_value() || - !cell.ssb_arfcn.has_value() || !cell.ssb_scs.has_value() || !cell.ssb_period.has_value() || - !cell.ssb_offset.has_value() || !cell.ssb_duration.has_value()) { - fmt::print( - "For external cells, the gnb_id_bit_length, pci, band, ssb_arfcn, ssb_scs, ssb_period, ssb_offset and " - "ssb_duration must be configured in the mobility config\n"); - return false; - } - } else { - if (cell.pci.has_value() || cell.band.has_value() || cell.ssb_arfcn.has_value() || cell.ssb_scs.has_value() || - cell.ssb_period.has_value() || cell.ssb_offset.has_value() || cell.ssb_duration.has_value()) { - fmt::print("For cells managed by the CU-CP the gnb_id_bit_length, pci, band, ssb_argcn, ssb_scs, ssb_period, " - "ssb_offset and " - "ssb_duration must not be configured in the mobility config\n"); - return false; - } - } - } - - // verify that each configured neighbor cell is present - for (const auto& cell : config.cells) { - for (const auto& ncell : cell.ncells) { - if (ncis.find(ncell.nr_cell_id) == ncis.end()) { - fmt::print("Neighbor cell config for nci={:#x} incomplete. No valid configuration for cell nci={:#x} found.\n", - cell.nr_cell_id, - ncell.nr_cell_id); - return false; + // Checks parameter collisions between cells that can lead to problems. + for (const auto* it = config.begin(); it != config.end(); ++it) { + for (const auto* it2 = it + 1; it2 != config.end(); ++it2) { + const auto& cell1 = *it; + const auto& cell2 = *it2; + + // Check if both cells are on the same frequency. + if (cell1.cell.dl_arfcn == cell2.cell.dl_arfcn) { + // Two cells on the same frequency should not have the same physical cell identifier. + if (cell1.cell.pci == cell2.cell.pci) { + fmt::print("Warning: two cells with the same DL ARFCN (i.e., {}) have the same PCI (i.e., {}).", + cell1.cell.dl_arfcn, + cell1.cell.pci); + } + + // Two cells on the same frequency should not share the same PRACH root sequence index. + if ((cell1.cell.prach_cfg.prach_frequency_start == cell2.cell.prach_cfg.prach_frequency_start) && + (cell1.cell.prach_cfg.prach_root_sequence_index == cell2.cell.prach_cfg.prach_root_sequence_index)) { + fmt::print("Warning: two cells with the same DL ARFCN (i.e., {}) have the same PRACH root sequence index " + "(i.e., {}).", + cell1.cell.dl_arfcn, + cell1.cell.prach_cfg.prach_root_sequence_index); + } } } } @@ -722,22 +704,13 @@ static bool validate_mobility_appconfig(const gnb_id_t gnb_id, const mobility_ap return true; } -/// Validates the given CU-CP configuration. Returns true on success, otherwise false. -static bool validate_cu_cp_appconfig(const gnb_id_t gnb_id, const cu_cp_appconfig& config, const sib_appconfig& sib_cfg) +/// Validates the given AMF configuration. Returns true on success, otherwise false. +static bool validate_amf_appconfig(const amf_appconfig& config) { - // check if the pdu_session_setup_timout is larger than T310 - if (config.pdu_session_setup_timeout * 1000 < sib_cfg.ue_timers_and_constants.t310) { - fmt::print("pdu_session_setup_timeout ({}ms) must be larger than T310 ({}ms)\n", - config.pdu_session_setup_timeout * 1000, - sib_cfg.ue_timers_and_constants.t310); - return false; - } - - // validate mobility config - if (!validate_mobility_appconfig(gnb_id, config.mobility_config)) { + // only check for non-empty AMF address and default port + if (config.ip_addr.empty() or config.port != 38412) { return false; } - return true; } @@ -976,66 +949,6 @@ static bool validate_srb_appconfig(const std::map& conf return true; } -/// Validates the given security configuration. Returns true on success, otherwise false. -static bool validate_security_appconfig(const security_appconfig& config) -{ - // String splitter helper - auto split = [](const std::string& s, char delim) -> std::vector { - std::vector result; - std::stringstream ss(s); - std::string item; - - while (getline(ss, item, delim)) { - result.push_back(item); - } - - return result; - }; - - // > Remove spaces, convert to lower case and split on comma - std::string nea_preference_list = config.nea_preference_list; - nea_preference_list.erase(std::remove_if(nea_preference_list.begin(), nea_preference_list.end(), ::isspace), - nea_preference_list.end()); - std::transform(nea_preference_list.begin(), - nea_preference_list.end(), - nea_preference_list.begin(), - [](unsigned char c) { return std::tolower(c); }); - std::vector nea_v = split(nea_preference_list, ','); - - // > Check valid ciphering algos - for (const std::string& algo : nea_v) { - if (algo != "nea0" and algo != "nea1" and algo != "nea2" and algo != "nea3") { - fmt::print("Invalid ciphering algorithm. Valid values are \"nea0\", \"nia1\", \"nia2\" and \"nia3\". algo={}\n", - algo); - return false; - } - } - - // > Remove spaces, convert to lower case and split on comma - std::string nia_preference_list = config.nia_preference_list; - nia_preference_list.erase(std::remove_if(nia_preference_list.begin(), nia_preference_list.end(), ::isspace), - nia_preference_list.end()); - std::transform(nia_preference_list.begin(), - nia_preference_list.end(), - nia_preference_list.begin(), - [](unsigned char c) { return std::tolower(c); }); - std::vector nia_v = split(nia_preference_list, ','); - - // > Check valid integrity algos - for (const std::string& algo : nia_v) { - if (algo == "nia0") { - fmt::print("NIA0 cannot be selected in the algorithm preferences.\n"); - return false; - } - if (algo != "nia1" and algo != "nia2" and algo != "nia3") { - fmt::print("Invalid integrity algorithm. Valid values are \"nia1\", \"nia2\" and \"nia3\". algo={}\n", algo); - return false; - } - } - - return true; -} - /// Validates the given logging configuration. Returns true on success, otherwise false. static bool validate_log_appconfig(const log_appconfig& config) { @@ -1056,10 +969,6 @@ static bool validate_log_appconfig(const log_appconfig& config) return false; } - if (srslog::str_to_basic_level(config.cu_level) == srslog::basic_levels::none) { - return false; - } - if (srslog::str_to_basic_level(config.phy_level) == srslog::basic_levels::none) { return false; } @@ -1076,10 +985,6 @@ static bool validate_log_appconfig(const log_appconfig& config) return false; } - if (srslog::str_to_basic_level(config.rrc_level) == srslog::basic_levels::none) { - return false; - } - if (srslog::str_to_basic_level(config.radio_level) == srslog::basic_levels::none) { return false; } @@ -1368,10 +1273,6 @@ bool srsran::validate_appconfig(const gnb_appconfig& config) return false; } - if (!validate_cu_cp_appconfig(config.gnb_id, config.cu_cp_cfg, config.cells_cfg.front().cell.sib_cfg)) { - return false; - } - if (!validate_cells_appconfig(config.cells_cfg)) { return false; } @@ -1396,10 +1297,6 @@ bool srsran::validate_appconfig(const gnb_appconfig& config) return false; } - if (!validate_security_appconfig(config.cu_cp_cfg.security_config)) { - return false; - } - if (!validate_expert_phy_appconfig(config.expert_phy_cfg)) { return false; } diff --git a/apps/modules/CMakeLists.txt b/apps/units/CMakeLists.txt similarity index 97% rename from apps/modules/CMakeLists.txt rename to apps/units/CMakeLists.txt index 0471ddf91d..b96fec117f 100644 --- a/apps/modules/CMakeLists.txt +++ b/apps/units/CMakeLists.txt @@ -18,4 +18,5 @@ # and at http://www.gnu.org/licenses/. # +add_subdirectory(cu_cp) add_subdirectory(flexible_du) diff --git a/apps/units/application_unit.h b/apps/units/application_unit.h new file mode 100644 index 0000000000..bba308af5c --- /dev/null +++ b/apps/units/application_unit.h @@ -0,0 +1,51 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +namespace CLI { +class App; +} // namespace CLI + +namespace srsran { + +/// \brief Application unit interface. +/// +/// An application unit object is the most basic building block used by an application. Possible implementations of this +/// object can be a CU or DU. The application must register the unit capabilities in the given services, so it can use +/// them. An application is composed of one or more units. +class application_unit +{ +public: + virtual ~application_unit() = default; + + /// Registers the parsing configuration properties that will be used by this application unit. + virtual void on_parsing_configuration_registration(CLI::App& app) = 0; + + /// Validates the configuration of this application unit. Returns true on success, otherwise false. + virtual bool on_configuration_validation() const = 0; + + /// Registers the loggers of this application unit. + virtual void on_loggers_registration() = 0; +}; + +} // namespace srsran diff --git a/apps/units/cu_cp/CMakeLists.txt b/apps/units/cu_cp/CMakeLists.txt new file mode 100644 index 0000000000..204871c4bc --- /dev/null +++ b/apps/units/cu_cp/CMakeLists.txt @@ -0,0 +1,27 @@ +# +# Copyright 2021-2024 Software Radio Systems Limited +# +# This file is part of srsRAN +# +# srsRAN is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# srsRAN is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# A copy of the GNU Affero General Public License can be found in +# the LICENSE file in the top-level directory of this distribution +# and at http://www.gnu.org/licenses/. +# + +set(SOURCES + cu_cp_application_unit_impl.cpp + cu_cp_unit_config_validator.cpp + cu_cp_unit_config_cli11_schema.cpp) + +add_library(srsran_cu_cp_app_unit STATIC ${SOURCES}) +target_include_directories(srsran_cu_cp_app_unit PRIVATE ${CMAKE_SOURCE_DIR}) diff --git a/apps/units/cu_cp/cu_cp_application_unit_impl.cpp b/apps/units/cu_cp/cu_cp_application_unit_impl.cpp new file mode 100644 index 0000000000..59f7721561 --- /dev/null +++ b/apps/units/cu_cp/cu_cp_application_unit_impl.cpp @@ -0,0 +1,43 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "cu_cp_application_unit_impl.h" +#include "cu_cp_logger_registrator.h" +#include "cu_cp_unit_config_cli11_schema.h" +#include "cu_cp_unit_config_validator.h" + +using namespace srsran; + +void cu_cp_application_unit_impl::on_parsing_configuration_registration(CLI::App& app) +{ + configure_cli11_with_cu_cp_unit_config_schema(app, unit_cfg); +} + +bool cu_cp_application_unit_impl::on_configuration_validation() const +{ + return validate_cu_cp_unit_config(unit_cfg); +} + +void cu_cp_application_unit_impl::on_loggers_registration() +{ + register_loggers(unit_cfg.loggers); +} diff --git a/apps/units/cu_cp/cu_cp_application_unit_impl.h b/apps/units/cu_cp/cu_cp_application_unit_impl.h new file mode 100644 index 0000000000..61c29ab11f --- /dev/null +++ b/apps/units/cu_cp/cu_cp_application_unit_impl.h @@ -0,0 +1,47 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "../application_unit.h" +#include "cu_cp_unit_config.h" + +namespace srsran { + +/// CU-CP application unit implementation. +class cu_cp_application_unit_impl : public application_unit +{ +public: + // See interface for documentation. + void on_parsing_configuration_registration(CLI::App& app) override; + + // See interface for documentation. + bool on_configuration_validation() const override; + + // See interface for documentation. + void on_loggers_registration() override; + +private: + cu_cp_unit_config unit_cfg; +}; + +} // namespace srsran diff --git a/apps/units/cu_cp/cu_cp_logger_registrator.h b/apps/units/cu_cp/cu_cp_logger_registrator.h new file mode 100644 index 0000000000..69ea418e2f --- /dev/null +++ b/apps/units/cu_cp/cu_cp_logger_registrator.h @@ -0,0 +1,60 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "cu_cp_unit_logger_config.h" +#include "srsran/srslog/srslog.h" + +namespace srsran { + +/// Registers the CU-CP loggers in the logger service. +inline void register_loggers(const cu_cp_unit_logger_config& log_cfg) +{ + for (const auto& id : {"CU-CP", "CU-UEMNG", "CU-CP-E1"}) { + auto& cu_cp_logger = srslog::fetch_basic_logger(id, false); + cu_cp_logger.set_level(srslog::str_to_basic_level(log_cfg.cu_level)); + cu_cp_logger.set_hex_dump_max_size(log_cfg.hex_max_size); + } + + auto& pdcp_logger = srslog::fetch_basic_logger("PDCP", false); + pdcp_logger.set_level(srslog::str_to_basic_level(log_cfg.pdcp_level)); + pdcp_logger.set_hex_dump_max_size(log_cfg.hex_max_size); + + auto& rrc_logger = srslog::fetch_basic_logger("RRC", false); + rrc_logger.set_level(srslog::str_to_basic_level(log_cfg.rrc_level)); + rrc_logger.set_hex_dump_max_size(log_cfg.hex_max_size); + + auto& cu_f1ap_logger = srslog::fetch_basic_logger("CU-CP-F1", false); + cu_f1ap_logger.set_level(srslog::str_to_basic_level(log_cfg.f1ap_level)); + cu_f1ap_logger.set_hex_dump_max_size(log_cfg.hex_max_size); + + auto& ngap_logger = srslog::fetch_basic_logger("NGAP", false); + ngap_logger.set_level(srslog::str_to_basic_level(log_cfg.ngap_level)); + ngap_logger.set_hex_dump_max_size(log_cfg.hex_max_size); + + auto& sec_logger = srslog::fetch_basic_logger("SEC", false); + sec_logger.set_level(srslog::str_to_basic_level(log_cfg.sec_level)); + sec_logger.set_hex_dump_max_size(log_cfg.hex_max_size); +} + +} // namespace srsran diff --git a/apps/units/cu_cp/cu_cp_unit_config.h b/apps/units/cu_cp/cu_cp_unit_config.h new file mode 100644 index 0000000000..42de20f642 --- /dev/null +++ b/apps/units/cu_cp/cu_cp_unit_config.h @@ -0,0 +1,253 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "cu_cp_unit_logger_config.h" +#include "srsran/adt/optional.h" +#include "srsran/ran/five_qi.h" +#include "srsran/ran/gnb_id.h" +#include "srsran/ran/nr_band.h" +#include "srsran/ran/pci.h" +#include "srsran/ran/s_nssai.h" +#include + +namespace srsran { + +/// Report configuration, for now only supporting the A3 event. +struct cu_cp_report_unit_config { + unsigned report_cfg_id; + std::string report_type; + optional report_interval_ms; + std::string a3_report_type; + /// [-30..30] Note the actual value is field value * 0.5 dB. E.g. putting a value of -6 here results in -3dB offset. + optional a3_offset_db; + optional a3_hysteresis_db; + optional a3_time_to_trigger_ms; +}; + +struct cu_cp_neighbor_cell_unit_config_item { + /// Cell id. + uint64_t nr_cell_id; + /// Report config ids. + std::vector report_cfg_ids; +}; + +/// Each item describes the relationship between one cell to all other cells. +struct cu_cp_cell_unit_config_item { + /// Cell id. + uint64_t nr_cell_id; + optional periodic_report_cfg_id; + + // These parameters must only be set for external cells + /// gNodeB identifier bit length. + optional gnb_id_bit_length; + /// PCI. + optional pci; + /// NR band. + optional band; + /// SSB ARFCN. + optional ssb_arfcn; + /// SSB subcarrier spacing. + optional ssb_scs; + /// SSB period. + optional ssb_period; + /// SSB offset. + optional ssb_offset; + /// SSB duration. + optional ssb_duration; + /// Vector of cells that are a neighbor of this cell. + std::vector ncells; + // TODO: Add optional SSB parameters. +}; + +/// All mobility related configuration parameters. +struct mobility_unit_config { + /// List of all cells known to the CU-CP. + std::vector cells; + /// Report config. + std::vector report_configs; + /// Whether to start HO if neighbor cell measurements arrive. + bool trigger_handover_from_measurements = false; +}; + +/// RRC specific configuration parameters. +struct rrc_cu_cp_unit_config { + bool force_reestablishment_fallback = false; + /// Timeout for RRC procedures (2 * default SRB maxRetxThreshold * t-PollRetransmit = 2 * 8 * 45ms = 720ms, see + /// TS 38.331 Sec 9.2.1). + unsigned rrc_procedure_timeout_ms = 720; +}; + +/// Security configuration parameters. +struct security_unit_config { + std::string integrity_protection = "not_needed"; + std::string confidentiality_protection = "required"; + std::string nea_preference_list = "nea0,nea2,nea1,nea3"; + std::string nia_preference_list = "nia2,nia1,nia3"; +}; + +/// F1AP-CU configuration parameters. +struct f1ap_cu_unit_config { + /// Timeout for the UE context setup procedure in milliseconds. + unsigned ue_context_setup_timeout = 1000; +}; + +/// RLC UM TX configuration +struct rlc_tx_um_cu_cp_unit_config { + /// Number of bits used for sequence number. + uint16_t sn_field_length; + /// RLC SDU queue size. + uint32_t queue_size; +}; + +/// RLC UM RX configuration +struct rlc_rx_um_cu_cp_unit_config { + /// Number of bits used for sequence number. + uint16_t sn_field_length; + /// Timer used by rx to detect PDU loss (ms). + int32_t t_reassembly; +}; + +/// RLC UM configuration +struct rlc_um_cu_cp_unit_config { + rlc_tx_um_cu_cp_unit_config tx; + rlc_rx_um_cu_cp_unit_config rx; +}; + +/// RLC UM TX configuration +struct rlc_tx_am_cu_cp_unit_config { + /// Number of bits used for sequence number. + uint16_t sn_field_length; + /// Poll retx timeout (ms). + int32_t t_poll_retx; + /// Max retx threshold. + uint32_t max_retx_thresh; + /// Insert poll bit after this many PDUs. + int32_t poll_pdu; + /// Insert poll bit after this much data (bytes). + int32_t poll_byte; + /// Custom parameter to limit the maximum window size for memory reasons. 0 means no limit. + uint32_t max_window = 0; + /// RLC SDU queue size. + uint32_t queue_size = 4096; +}; + +/// RLC UM RX configuration +struct rlc_rx_am_cu_cp_unit_config { + /// Number of bits used for sequence number. + uint16_t sn_field_length; + /// Timer used by rx to detect PDU loss (ms). + int32_t t_reassembly; + /// Timer used by rx to prohibit tx of status PDU (ms). + int32_t t_status_prohibit; + + /// Implementation-specific parameters that are not specified by 3GPP + + /// Maximum number of visited SNs in the RX window when building a status report. 0 means no limit. + uint32_t max_sn_per_status = 0; +}; + +/// RLC AM configuration +struct rlc_am_cu_cp_unit_config { + rlc_tx_am_cu_cp_unit_config tx; + rlc_rx_am_cu_cp_unit_config rx; +}; + +/// RLC configuration +struct rlc_cu_cp_unit_config { + std::string mode = "am"; + rlc_um_cu_cp_unit_config um; + rlc_am_cu_cp_unit_config am; +}; + +struct pdcp_rx_cu_cp_unit_config { + /// Number of bits used for sequence number. + uint16_t sn_field_length; + /// Timer used to detect PDUs losses (ms). + int32_t t_reordering; + /// Whether out-of-order delivery to upper layers is enabled. + bool out_of_order_delivery; +}; + +struct pdcp_tx_cu_cp_unit_config { + /// Number of bits used for sequence number. + uint16_t sn_field_length; + /// Timer used to notify lower layers to discard PDUs (ms). + int32_t discard_timer; + /// Whether PDCP status report is required. + bool status_report_required; +}; + +struct pdcp_cu_cp_unit_config { + /// Whether DRB integrity is required. + bool integrity_protection_required; + pdcp_tx_cu_cp_unit_config tx; + pdcp_rx_cu_cp_unit_config rx; +}; + +/// QoS configuration. +struct qos_cu_cp_unit_config { + five_qi_t five_qi = uint_to_five_qi(9); + rlc_cu_cp_unit_config rlc; + pdcp_cu_cp_unit_config pdcp; +}; + +/// Metrics configuration. +struct metrics_cu_cp_unit_config { + /// Statistics report period in seconds + unsigned cu_cp_statistics_report_period = 1; +}; + +/// CU-CP application unit configuration. +struct cu_cp_unit_config { + /// Loggers configuration. + cu_cp_unit_logger_config loggers; + /// Node name. + std::string ran_node_name = "cu_cp_01"; + /// gNB identifier. + gnb_id_t gnb_id = {411, 22}; + /// Maximum number of DUs. + uint16_t max_nof_dus = 6; + /// Maximum number of CU-UPs. + uint16_t max_nof_cu_ups = 6; + /// Inactivity timer in seconds. + int inactivity_timer = 120; + /// PDU session setup timeout in seconds (must be larger than T310). + unsigned pdu_session_setup_timeout = 3; + /// Metrics configuration. + metrics_cu_cp_unit_config metrics; + /// Mobility configuration. + mobility_unit_config mobility_config; + /// RRC configuration. + rrc_cu_cp_unit_config rrc_config; + /// Security configuration. + security_unit_config security_config; + /// F1-AP configuration. + f1ap_cu_unit_config f1ap_config; + /// QoS configuration. + std::vector qos_cfg; + /// Network slice configuration. + std::vector slice_cfg = {s_nssai_t{1}}; +}; + +} // namespace srsran diff --git a/apps/units/cu_cp/cu_cp_unit_config_cli11_schema.cpp b/apps/units/cu_cp/cu_cp_unit_config_cli11_schema.cpp new file mode 100644 index 0000000000..26d054ad6e --- /dev/null +++ b/apps/units/cu_cp/cu_cp_unit_config_cli11_schema.cpp @@ -0,0 +1,437 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "cu_cp_unit_config_cli11_schema.h" +#include "cu_cp_unit_config.h" +#include "srsran/support/cli11_utils.h" +#include "srsran/support/config_parsers.h" + +using namespace srsran; + +static void configure_cli11_report_args(CLI::App& app, cu_cp_report_unit_config& report_params) +{ + add_option(app, "--report_cfg_id", report_params.report_cfg_id, "Report configuration id to be configured") + ->check(CLI::Range(1, 64)); + add_option(app, "--report_type", report_params.report_type, "Type of the report configuration") + ->check(CLI::IsMember({"periodical", "event_triggered"})); + add_option(app, "--report_interval_ms", report_params.report_interval_ms, "Report interval in ms") + ->check( + CLI::IsMember({120, 240, 480, 640, 1024, 2048, 5120, 10240, 20480, 40960, 60000, 360000, 720000, 1800000})); + add_option(app, "--a3_report_type", report_params.a3_report_type, "A3 report type") + ->check(CLI::IsMember({"rsrp", "rsrq", "sinr"})); + add_option(app, + "--a3_offset_db", + report_params.a3_offset_db, + "A3 offset in dB used for measurement report trigger. Note the actual value is field value * 0.5 dB") + ->check(CLI::Range(-30, 30)); + add_option(app, + "--a3_hysteresis_db", + report_params.a3_hysteresis_db, + "A3 hysteresis in dB used for measurement report trigger. Note the actual value is field value * 0.5 dB") + ->check(CLI::Range(0, 30)); + add_option(app, + "--a3_time_to_trigger_ms", + report_params.a3_time_to_trigger_ms, + "Time in ms during which A3 condition must be met before measurement report trigger") + ->check(CLI::IsMember({0, 40, 64, 80, 100, 128, 160, 256, 320, 480, 512, 640, 1024, 1280, 2560, 5120})); +} + +static void configure_cli11_ncell_args(CLI::App& app, cu_cp_neighbor_cell_unit_config_item& config) +{ + add_option(app, "--nr_cell_id", config.nr_cell_id, "Neighbor cell id"); + add_option( + app, "--report_configs", config.report_cfg_ids, "Report configurations to configure for this neighbor cell"); +} + +static void configure_cli11_cells_args(CLI::App& app, cu_cp_cell_unit_config_item& config) +{ + add_option(app, "--nr_cell_id", config.nr_cell_id, "Cell id to be configured"); + add_option(app, + "--periodic_report_cfg_id", + config.periodic_report_cfg_id, + "Periodical report configuration for the serving cell") + ->check(CLI::Range(1, 64)); + + add_auto_enum_option(app, "--band", config.band, "NR frequency band"); + + add_option(app, "--gnb_id_bit_length", config.gnb_id_bit_length, "gNodeB identifier bit length") + ->check(CLI::Range(22, 32)); + add_option(app, "--pci", config.pci, "Physical Cell Id")->check(CLI::Range(0, 1007)); + add_option(app, "--ssb_arfcn", config.ssb_arfcn, "SSB ARFCN"); + add_option(app, "--ssb_scs", config.ssb_scs, "SSB subcarrier spacing")->check(CLI::IsMember({15, 30, 60, 120, 240})); + add_option(app, "--ssb_period", config.ssb_period, "SSB period in ms") + ->check(CLI::IsMember({5, 10, 20, 40, 80, 160})); + add_option(app, "--ssb_offset", config.ssb_offset, "SSB offset"); + add_option(app, "--ssb_duration", config.ssb_duration, "SSB duration")->check(CLI::IsMember({1, 2, 3, 4, 5})); + + // report configuration parameters. + app.add_option_function>( + "--ncells", + [&config](const std::vector& values) { + config.ncells.resize(values.size()); + + for (unsigned i = 0, e = values.size(); i != e; ++i) { + CLI::App subapp("CU-CP neighbor cell list"); + subapp.config_formatter(create_yaml_config_parser()); + subapp.allow_config_extras(CLI::config_extras_mode::error); + configure_cli11_ncell_args(subapp, config.ncells[i]); + std::istringstream ss(values[i]); + subapp.parse_from_stream(ss); + } + }, + "Sets the list of neighbor cells known to the CU-CP"); +} + +static void configure_cli11_mobility_args(CLI::App& app, mobility_unit_config& config) +{ + add_option(app, + "--trigger_handover_from_measurements", + config.trigger_handover_from_measurements, + "Whether to start HO if neighbor cells become stronger") + ->capture_default_str(); + + // Cell map parameters. + app.add_option_function>( + "--cells", + [&config](const std::vector& values) { + config.cells.resize(values.size()); + + for (unsigned i = 0, e = values.size(); i != e; ++i) { + CLI::App subapp("CU-CP cell list"); + subapp.config_formatter(create_yaml_config_parser()); + subapp.allow_config_extras(CLI::config_extras_mode::error); + configure_cli11_cells_args(subapp, config.cells[i]); + std::istringstream ss(values[i]); + subapp.parse_from_stream(ss); + } + }, + "Sets the list of cells known to the CU-CP"); + + // report configuration parameters. + app.add_option_function>( + "--report_configs", + [&config](const std::vector& values) { + config.report_configs.resize(values.size()); + + for (unsigned i = 0, e = values.size(); i != e; ++i) { + CLI::App subapp("CU-CP measurement report config list"); + subapp.config_formatter(create_yaml_config_parser()); + subapp.allow_config_extras(CLI::config_extras_mode::error); + configure_cli11_report_args(subapp, config.report_configs[i]); + std::istringstream ss(values[i]); + subapp.parse_from_stream(ss); + } + }, + "Sets report configurations"); +} + +static void configure_cli11_rrc_args(CLI::App& app, rrc_cu_cp_unit_config& config) +{ + add_option(app, + "--force_reestablishment_fallback", + config.force_reestablishment_fallback, + "Force RRC re-establishment fallback to RRC setup") + ->capture_default_str(); + + add_option( + app, + "--rrc_procedure_timeout_ms", + config.rrc_procedure_timeout_ms, + "Timeout in ms used for RRC message exchange with UE. It needs to suit the expected communication delay and " + "account for potential retransmissions UE processing delays, SR delays, etc.") + ->capture_default_str(); +} + +static void configure_cli11_security_args(CLI::App& app, security_unit_config& config) +{ + auto sec_check = [](const std::string& value) -> std::string { + if (value == "required" || value == "preferred" || value == "not_needed") { + return {}; + } + return "Security indication value not supported. Accepted values [required,preferred,not_needed]"; + }; + + add_option(app, "--integrity", config.integrity_protection, "Default integrity protection indication for DRBs") + ->capture_default_str() + ->check(sec_check); + + add_option(app, + "--confidentiality", + config.confidentiality_protection, + "Default confidentiality protection indication for DRBs") + ->capture_default_str() + ->check(sec_check); + + add_option(app, + "--nea_pref_list", + config.nea_preference_list, + "Ordered preference list for the selection of encryption algorithm (NEA) (default: NEA0, NEA2, NEA1)"); + + add_option(app, + "--nia_pref_list", + config.nia_preference_list, + "Ordered preference list for the selection of encryption algorithm (NIA) (default: NIA2, NIA1)") + ->capture_default_str(); +} + +static void configure_cli11_f1ap_args(CLI::App& app, f1ap_cu_unit_config& f1ap_params) +{ + add_option(app, + "--ue_context_setup_timeout", + f1ap_params.ue_context_setup_timeout, + "UE context setup timeout in milliseconds") + ->capture_default_str(); +} + +static void configure_cli11_cu_cp_args(CLI::App& app, cu_cp_unit_config& cu_cp_params) +{ + add_option( + app, "--max_nof_dus", cu_cp_params.max_nof_dus, "Maximum number of DU connections that the CU-CP may accept"); + + add_option(app, + "--max_nof_cu_ups", + cu_cp_params.max_nof_cu_ups, + "Maximum number of CU-UP connections that the CU-CP may accept"); + + add_option(app, "--inactivity_timer", cu_cp_params.inactivity_timer, "UE/PDU Session/DRB inactivity timer in seconds") + ->capture_default_str() + ->check(CLI::Range(1, 7200)); + + add_option(app, + "--pdu_session_setup_timeout", + cu_cp_params.pdu_session_setup_timeout, + "Timeout for the setup of a PDU session after an InitialUeMessage was sent to the core, in " + "seconds. The timeout must be larger than T310. If the value is reached, the UE will be released") + ->capture_default_str(); + + CLI::App* mobility_subcmd = app.add_subcommand("mobility", "Mobility configuration"); + configure_cli11_mobility_args(*mobility_subcmd, cu_cp_params.mobility_config); + + CLI::App* rrc_subcmd = app.add_subcommand("rrc", "RRC specific configuration"); + configure_cli11_rrc_args(*rrc_subcmd, cu_cp_params.rrc_config); + + CLI::App* security_subcmd = app.add_subcommand("security", "Security configuration"); + configure_cli11_security_args(*security_subcmd, cu_cp_params.security_config); + + CLI::App* f1ap_subcmd = app.add_subcommand("f1ap", "F1AP configuration"); + configure_cli11_f1ap_args(*f1ap_subcmd, cu_cp_params.f1ap_config); +} + +static void configure_cli11_log_args(CLI::App& app, cu_cp_unit_logger_config& log_params) +{ + auto level_check = [](const std::string& value) -> std::string { + if (value == "info" || value == "debug" || value == "warning" || value == "error") { + return {}; + } + return "Log level value not supported. Accepted values [info,debug,warning,error]"; + }; + + add_option(app, "--pdcp_level", log_params.pdcp_level, "PDCP log level")->capture_default_str()->check(level_check); + add_option(app, "--rrc_level", log_params.rrc_level, "RRC log level")->capture_default_str()->check(level_check); + add_option(app, "--ngap_level", log_params.ngap_level, "NGAP log level")->capture_default_str()->check(level_check); + add_option(app, "--f1ap_level", log_params.f1ap_level, "F1AP log level")->capture_default_str()->check(level_check); + add_option(app, "--cu_level", log_params.cu_level, "Log level for the CU")->capture_default_str()->check(level_check); + add_option(app, "--sec_level", log_params.sec_level, "Security functions log level") + ->capture_default_str() + ->check(level_check); + + add_option( + app, "--hex_max_size", log_params.hex_max_size, "Maximum number of bytes to print in hex (zero for no hex dumps)") + ->capture_default_str() + ->check(CLI::Range(0, 1024)); + + add_option(app, "--f1ap_json_enabled", log_params.f1ap_json_enabled, "Enable JSON logging of F1AP PDUs") + ->always_capture_default(); +} + +static void configure_cli11_rlc_um_args(CLI::App& app, rlc_um_cu_cp_unit_config& rlc_um_params) +{ + CLI::App* rlc_tx_um_subcmd = app.add_subcommand("tx", "UM TX parameters"); + rlc_tx_um_subcmd->add_option("--sn", rlc_um_params.tx.sn_field_length, "RLC UM TX SN")->capture_default_str(); + rlc_tx_um_subcmd->add_option("--queue-size", rlc_um_params.tx.queue_size, "RLC UM TX SDU queue size") + ->capture_default_str(); + CLI::App* rlc_rx_um_subcmd = app.add_subcommand("rx", "UM TX parameters"); + rlc_rx_um_subcmd->add_option("--sn", rlc_um_params.rx.sn_field_length, "RLC UM RX SN")->capture_default_str(); + rlc_rx_um_subcmd->add_option("--t-reassembly", rlc_um_params.rx.t_reassembly, "RLC UM t-Reassembly") + ->capture_default_str(); +} + +static void configure_cli11_rlc_am_args(CLI::App& app, rlc_am_cu_cp_unit_config& rlc_am_params) +{ + CLI::App* tx_subcmd = app.add_subcommand("tx", "AM TX parameters"); + add_option(*tx_subcmd, "--sn", rlc_am_params.tx.sn_field_length, "RLC AM TX SN size")->capture_default_str(); + add_option(*tx_subcmd, "--t-poll-retransmit", rlc_am_params.tx.t_poll_retx, "RLC AM TX t-PollRetransmit (ms)") + ->capture_default_str(); + add_option(*tx_subcmd, "--max-retx-threshold", rlc_am_params.tx.max_retx_thresh, "RLC AM max retx threshold") + ->capture_default_str(); + add_option(*tx_subcmd, "--poll-pdu", rlc_am_params.tx.poll_pdu, "RLC AM TX PollPdu")->capture_default_str(); + add_option(*tx_subcmd, "--poll-byte", rlc_am_params.tx.poll_byte, "RLC AM TX PollByte")->capture_default_str(); + add_option(*tx_subcmd, + "--max_window", + rlc_am_params.tx.max_window, + "Non-standard parameter that limits the tx window size. Can be used for limiting memory usage with " + "large windows. 0 means no limits other than the SN size (i.e. 2^[sn_size-1])."); + add_option(*tx_subcmd, "--queue-size", rlc_am_params.tx.queue_size, "RLC AM TX SDU queue size") + ->capture_default_str(); + + CLI::App* rx_subcmd = app.add_subcommand("rx", "AM RX parameters"); + add_option(*rx_subcmd, "--sn", rlc_am_params.rx.sn_field_length, "RLC AM RX SN")->capture_default_str(); + add_option(*rx_subcmd, "--t-reassembly", rlc_am_params.rx.t_reassembly, "RLC AM RX t-Reassembly") + ->capture_default_str(); + add_option(*rx_subcmd, "--t-status-prohibit", rlc_am_params.rx.t_status_prohibit, "RLC AM RX t-StatusProhibit") + ->capture_default_str(); + add_option(*rx_subcmd, "--max_sn_per_status", rlc_am_params.rx.max_sn_per_status, "RLC AM RX status SN limit") + ->capture_default_str(); +} + +static void configure_cli11_rlc_args(CLI::App& app, rlc_cu_cp_unit_config& rlc_params) +{ + add_option(app, "--mode", rlc_params.mode, "RLC mode")->capture_default_str(); + + // UM section. + CLI::App* rlc_um_subcmd = app.add_subcommand("um-bidir", "UM parameters"); + configure_cli11_rlc_um_args(*rlc_um_subcmd, rlc_params.um); + + // AM section. + CLI::App* rlc_am_subcmd = app.add_subcommand("am", "AM parameters"); + configure_cli11_rlc_am_args(*rlc_am_subcmd, rlc_params.am); +} + +static void configure_cli11_pdcp_tx_args(CLI::App& app, pdcp_tx_cu_cp_unit_config& pdcp_tx_params) +{ + add_option(app, "--sn", pdcp_tx_params.sn_field_length, "PDCP TX SN size")->capture_default_str(); + add_option(app, "--discard_timer", pdcp_tx_params.discard_timer, "PDCP TX discard timer (ms)")->capture_default_str(); + add_option(app, "--status_report_required", pdcp_tx_params.status_report_required, "PDCP TX status report required") + ->capture_default_str(); +} + +static void configure_cli11_pdcp_rx_args(CLI::App& app, pdcp_rx_cu_cp_unit_config& pdcp_rx_params) +{ + add_option(app, "--sn", pdcp_rx_params.sn_field_length, "PDCP RX SN size")->capture_default_str(); + add_option(app, "--t_reordering", pdcp_rx_params.t_reordering, "PDCP RX t-Reordering (ms)")->capture_default_str(); + add_option( + app, "--out_of_order_delivery", pdcp_rx_params.out_of_order_delivery, "PDCP RX enable out-of-order delivery") + ->capture_default_str(); +} + +static void configure_cli11_pdcp_args(CLI::App& app, pdcp_cu_cp_unit_config& pdcp_params) +{ + add_option(app, "integrity_required", pdcp_params.integrity_protection_required, "DRB Integrity required") + ->capture_default_str(); + + // Transmission section. + CLI::App* pdcp_tx_subcmd = app.add_subcommand("tx", "PDCP TX parameters"); + configure_cli11_pdcp_tx_args(*pdcp_tx_subcmd, pdcp_params.tx); + + /// Reception section. + CLI::App* pdcp_rx_subcmd = app.add_subcommand("rx", "PDCP RX parameters"); + configure_cli11_pdcp_rx_args(*pdcp_rx_subcmd, pdcp_params.rx); +} + +static void configure_cli11_qos_args(CLI::App& app, qos_cu_cp_unit_config& qos_params) +{ + add_option(app, "--five_qi", qos_params.five_qi, "5QI")->capture_default_str()->check(CLI::Range(0, 255)); + + // RLC section. + CLI::App* rlc_subcmd = app.add_subcommand("rlc", "RLC parameters"); + configure_cli11_rlc_args(*rlc_subcmd, qos_params.rlc); + + // PDCP section. + CLI::App* pdcp_subcmd = app.add_subcommand("pdcp", "PDCP parameters"); + configure_cli11_pdcp_args(*pdcp_subcmd, qos_params.pdcp); + + // Mark the application that these subcommands need to be present. + app.needs(rlc_subcmd); + app.needs(pdcp_subcmd); +} + +static void configure_cli11_metrics_args(CLI::App& app, metrics_cu_cp_unit_config& metrics_params) +{ + add_option(app, + "--cu_cp_statistics_report_period", + metrics_params.cu_cp_statistics_report_period, + "CU-CP statistics report period in seconds. Set this value to 0 to disable this feature") + ->capture_default_str(); +} + +static void configure_cli11_slicing_args(CLI::App& app, s_nssai_t& slice_params) +{ + add_option(app, "--sst", slice_params.sst, "Slice Service Type")->capture_default_str()->check(CLI::Range(0, 255)); + add_option(app, "--sd", slice_params.sd, "Service Differentiator") + ->capture_default_str() + ->check(CLI::Range(0, 0xffffff)); +} + +void srsran::configure_cli11_with_cu_cp_unit_config_schema(CLI::App& app, cu_cp_unit_config& parsed_cfg) +{ + add_option(app, "--gnb_id", parsed_cfg.gnb_id.id, "gNodeB identifier")->capture_default_str(); + add_option(app, "--gnb_id_bit_length", parsed_cfg.gnb_id.bit_length, "gNodeB identifier length in bits") + ->capture_default_str() + ->check(CLI::Range(22, 32)); + add_option(app, "--ran_node_name", parsed_cfg.ran_node_name, "RAN node name")->capture_default_str(); + + // CU-CP section + CLI::App* cu_cp_subcmd = add_subcommand(app, "cu_cp", "CU-CP parameters")->configurable(); + configure_cli11_cu_cp_args(*cu_cp_subcmd, parsed_cfg); + + // Loggers section. + CLI::App* log_subcmd = add_subcommand(app, "log", "Logging configuration")->configurable(); + configure_cli11_log_args(*log_subcmd, parsed_cfg.loggers); + + // Metrics section. + CLI::App* metrics_subcmd = add_subcommand(app, "metrics", "Metrics configuration")->configurable(); + configure_cli11_metrics_args(*metrics_subcmd, parsed_cfg.metrics); + + // QoS section. + auto qos_lambda = [&parsed_cfg](const std::vector& values) { + // Prepare the radio bearers + parsed_cfg.qos_cfg.resize(values.size()); + + // Format every QoS setting. + for (unsigned i = 0, e = values.size(); i != e; ++i) { + CLI::App subapp("QoS parameters", "QoS config, item #" + std::to_string(i)); + subapp.config_formatter(create_yaml_config_parser()); + subapp.allow_config_extras(CLI::config_extras_mode::capture); + configure_cli11_qos_args(subapp, parsed_cfg.qos_cfg[i]); + std::istringstream ss(values[i]); + subapp.parse_from_stream(ss); + } + }; + add_option_cell(app, "--qos", qos_lambda, "Configures RLC and PDCP radio bearers on a per 5QI basis."); + + // Slicing section. + auto slicing_lambda = [&parsed_cfg](const std::vector& values) { + // Prepare the radio bearers + parsed_cfg.slice_cfg.resize(values.size()); + + // Format every QoS setting. + for (unsigned i = 0, e = values.size(); i != e; ++i) { + CLI::App subapp("Slicing parameters", "Slicing config, item #" + std::to_string(i)); + subapp.config_formatter(create_yaml_config_parser()); + subapp.allow_config_extras(CLI::config_extras_mode::capture); + configure_cli11_slicing_args(subapp, parsed_cfg.slice_cfg[i]); + std::istringstream ss(values[i]); + subapp.parse_from_stream(ss); + } + }; + add_option_cell(app, "--slicing", slicing_lambda, "Network slicing configuration"); +} diff --git a/apps/units/cu_cp/cu_cp_unit_config_cli11_schema.h b/apps/units/cu_cp/cu_cp_unit_config_cli11_schema.h new file mode 100644 index 0000000000..ae552f2532 --- /dev/null +++ b/apps/units/cu_cp/cu_cp_unit_config_cli11_schema.h @@ -0,0 +1,34 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "CLI/CLI11.hpp" + +namespace srsran { + +struct cu_cp_unit_config; + +/// Configures the given CLI11 application with the CU-CP application unit configuration schema. +void configure_cli11_with_cu_cp_unit_config_schema(CLI::App& app, cu_cp_unit_config& parsed_cfg); + +} // namespace srsran diff --git a/apps/units/cu_cp/cu_cp_unit_config_validator.cpp b/apps/units/cu_cp/cu_cp_unit_config_validator.cpp new file mode 100644 index 0000000000..66f4f0fc8a --- /dev/null +++ b/apps/units/cu_cp/cu_cp_unit_config_validator.cpp @@ -0,0 +1,395 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "cu_cp_unit_config_validator.h" +#include "srsran/adt/span.h" +#include "srsran/pdcp/pdcp_t_reordering.h" +#include "srsran/ran/nr_cgi.h" +#include "srsran/ran/nr_cgi_helpers.h" +#include "srsran/rlc/rlc_config.h" +#include +#include +#include + +using namespace srsran; + +static bool validate_mobility_appconfig(const gnb_id_t gnb_id, const mobility_unit_config& config) +{ + // check that report config ids are unique + std::map report_cfg_ids_to_report_type; + for (const auto& report_cfg : config.report_configs) { + if (report_cfg_ids_to_report_type.find(report_cfg.report_cfg_id) != report_cfg_ids_to_report_type.end()) { + fmt::print("Report config ids must be unique\n"); + return false; + } + report_cfg_ids_to_report_type.emplace(report_cfg.report_cfg_id, report_cfg.report_type); + } + + // check cu_cp_cell_config + for (const auto& cell : config.cells) { + std::set ncis; + if (ncis.emplace(cell.nr_cell_id).second == false) { + fmt::print("Cells must be unique ({:#x} already present)\n"); + return false; + } + + if (cell.ssb_period.has_value() && cell.ssb_offset.has_value() && + cell.ssb_offset.value() >= cell.ssb_period.value()) { + fmt::print("ssb_offset must be smaller than ssb_period\n"); + return false; + } + + // check that for the serving cell only periodic reports are configured + if (cell.periodic_report_cfg_id.has_value()) { + if (report_cfg_ids_to_report_type.at(cell.periodic_report_cfg_id.value()) != "periodical") { + fmt::print("For the serving cell only periodic reports are allowed\n"); + return false; + } + } + + // Check if cell is an external managed cell + if (config_helpers::get_gnb_id(cell.nr_cell_id, gnb_id.bit_length) != gnb_id) { + if (!cell.gnb_id_bit_length.has_value() || !cell.pci.has_value() || !cell.band.has_value() || + !cell.ssb_arfcn.has_value() || !cell.ssb_scs.has_value() || !cell.ssb_period.has_value() || + !cell.ssb_offset.has_value() || !cell.ssb_duration.has_value()) { + fmt::print( + "For external cells, the gnb_id_bit_length, pci, band, ssb_argcn, ssb_scs, ssb_period, ssb_offset and " + "ssb_duration must be configured in the mobility config\n"); + return false; + } + } else { + if (cell.pci.has_value() || cell.band.has_value() || cell.ssb_arfcn.has_value() || cell.ssb_scs.has_value() || + cell.ssb_period.has_value() || cell.ssb_offset.has_value() || cell.ssb_duration.has_value()) { + fmt::print("For cells managed by the CU-CP the gnb_id_bit_length, pci, band, ssb_argcn, ssb_scs, ssb_period, " + "ssb_offset and " + "ssb_duration must not be configured in the mobility config\n"); + return false; + } + } + } + + return true; +} + +/// Validates the given security configuration. Returns true on success, otherwise false. +static bool validate_security_appconfig(const security_unit_config& config) +{ + // String splitter helper + auto split = [](const std::string& s, char delim) -> std::vector { + std::vector result; + std::stringstream ss(s); + std::string item; + + while (getline(ss, item, delim)) { + result.push_back(item); + } + + return result; + }; + + // > Remove spaces, convert to lower case and split on comma + std::string nea_preference_list = config.nea_preference_list; + nea_preference_list.erase(std::remove_if(nea_preference_list.begin(), nea_preference_list.end(), ::isspace), + nea_preference_list.end()); + std::transform(nea_preference_list.begin(), + nea_preference_list.end(), + nea_preference_list.begin(), + [](unsigned char c) { return std::tolower(c); }); + std::vector nea_v = split(nea_preference_list, ','); + + // > Check valid ciphering algos + for (const std::string& algo : nea_v) { + if (algo != "nea0" and algo != "nea1" and algo != "nea2" and algo != "nea3") { + fmt::print("Invalid ciphering algorithm. Valid values are \"nea0\", \"nia1\", \"nia2\" and \"nia3\". algo={}\n", + algo); + return false; + } + } + + // > Remove spaces, convert to lower case and split on comma + std::string nia_preference_list = config.nia_preference_list; + nia_preference_list.erase(std::remove_if(nia_preference_list.begin(), nia_preference_list.end(), ::isspace), + nia_preference_list.end()); + std::transform(nia_preference_list.begin(), + nia_preference_list.end(), + nia_preference_list.begin(), + [](unsigned char c) { return std::tolower(c); }); + std::vector nia_v = split(nia_preference_list, ','); + + // > Check valid integrity algos + for (const std::string& algo : nia_v) { + if (algo == "nia0") { + fmt::print("NIA0 cannot be selected in the algorithm preferences.\n"); + return false; + } + if (algo != "nia1" and algo != "nia2" and algo != "nia3") { + fmt::print("Invalid integrity algorithm. Valid values are \"nia1\", \"nia2\" and \"nia3\". algo={}\n", algo); + return false; + } + } + + return true; +} + +/// Validates the given PDCP configuration. Returns true on success, otherwise false. +static bool validate_pdcp_appconfig(five_qi_t five_qi, const pdcp_cu_cp_unit_config& config) +{ + if (config.integrity_protection_required) { + fmt::print("PDCP DRB integrity protection is not supported yet. {}\n", five_qi); + return false; + } + + // Check TX. + if (config.tx.sn_field_length != 12 && config.tx.sn_field_length != 18) { + fmt::print("PDCP TX SN length is neither 12 or 18 bits. {} SN={}\n", five_qi, config.tx.sn_field_length); + return false; + } + if (config.tx.status_report_required) { + fmt::print("PDCP TX status report required not supported yet. {}\n", five_qi); + return false; + } + + // Check RX. + if (config.rx.sn_field_length != 12 && config.rx.sn_field_length != 18) { + fmt::print("PDCP RX SN length is neither 12 or 18 bits. {} SN={}\n", five_qi, config.rx.sn_field_length); + return false; + } + + pdcp_t_reordering t_reordering = {}; + if (!pdcp_t_reordering_from_int(t_reordering, config.rx.t_reordering)) { + fmt::print("PDCP RX t-Reordering is not a valid value. {}, t-Reordering={}\n", five_qi, config.rx.t_reordering); + fmt::print("Valid values: " + "\"infinity, ms0, ms1, ms2, ms4, ms5, ms8, ms10, ms15, ms20, ms30, ms40,ms50, ms60, ms80, " + "ms100, ms120, ms140, ms160, ms180, ms200, ms220,ms240, ms260, ms280, ms300, ms500, ms750, ms1000, " + "ms1250, ms1500, ms1750, ms2000, ms2250, ms2500, ms2750\"\n"); + return false; + } + if (t_reordering == pdcp_t_reordering::infinity) { + fmt::print("PDCP t-Reordering=infinity on DRBs is not advised. It can cause data stalls. {}\n", five_qi); + } + + if (config.rx.out_of_order_delivery) { + fmt::print("PDCP RX out-of-order delivery is not supported. {}\n", five_qi); + return false; + } + return true; +} + +static bool validate_rlc_um_appconfig(five_qi_t five_qi, const rlc_um_cu_cp_unit_config& config) +{ + // Validate TX. + + rlc_um_sn_size tmp_sn_size; + if (!from_number(tmp_sn_size, config.tx.sn_field_length)) { + fmt::print("RLC UM TX SN length is neither 6 or 12 bits. {} sn_size={}\n", five_qi, config.tx.sn_field_length); + return false; + } + + if (config.tx.queue_size == 0) { + fmt::print("RLC TX queue size cannot be 0. {}\n", five_qi); + return false; + } + + // Validate RX. + + if (!from_number(tmp_sn_size, config.rx.sn_field_length)) { + fmt::print("RLC TX queue size cannot be 0. {}\n", five_qi); + return false; + } + + rlc_t_reassembly tmp_t_reassembly; + if (!rlc_t_reassembly_from_int(tmp_t_reassembly, config.rx.t_reassembly)) { + fmt::print("RLC UM RX t-Reassembly is invalid. {} t_reassembly={}\n", five_qi, config.rx.t_reassembly); + fmt::print("Valid values are:" + " ms40, ms45, ms50, ms55, ms60, ms65, ms70," + " ms75, ms80, ms85, ms90, ms95, ms100, ms110," + " ms120, ms130, ms140, ms150, ms160, ms170," + " ms180, ms190, ms200\n"); + return false; + } + return true; +} + +template +static bool validate_rlc_am_appconfig(id_type id, const rlc_am_cu_cp_unit_config& config) +{ + // Validate TX. + + rlc_am_sn_size tmp_sn_size; + if (!from_number(tmp_sn_size, config.tx.sn_field_length)) { + fmt::print("RLC AM TX SN length is neither 12 or 18 bits. {} sn_size={}\n", id, config.tx.sn_field_length); + return false; + } + + rlc_t_poll_retransmit tmp_t_poll_retransmit; + if (!rlc_t_poll_retransmit_from_int(tmp_t_poll_retransmit, config.tx.t_poll_retx)) { + fmt::print("Invalid RLC AM TX t-PollRetransmission. {} t_poll_retx={}\n", id, config.tx.t_poll_retx); + fmt::print(" Valid values are: ms5, ms10, ms15, ms20, ms25, ms30, ms35," + " ms40, ms45, ms50, ms55, ms60, ms65, ms70, ms75, ms80, ms85," + " ms90, ms95, ms100, ms105, ms110, ms115, ms120, ms125, ms130," + " ms135, ms140, ms145, ms150, ms155, ms160, ms165, ms170, ms175," + " ms180, ms185, ms190, ms195, ms200, ms205, ms210, ms215, ms220," + " ms225, ms230, ms235, ms240, ms245, ms250, ms300, ms350, ms400," + " ms450, ms500, ms800, ms1000, ms2000, ms4000\n"); + return false; + } + + rlc_max_retx_threshold tmp_max_retx_threshold; + if (!rlc_max_retx_threshold_from_int(tmp_max_retx_threshold, config.tx.max_retx_thresh)) { + fmt::print("Invalid RLC AM TX max retx threshold. {} max_retx_threshold={}\n", id, config.tx.max_retx_thresh); + fmt::print(" Valid values are: t1, t2, t3, t4, t6, t8, t16, t32\n"); + return false; + } + + rlc_poll_pdu tmp_poll_pdu; + if (!rlc_poll_pdu_from_int(tmp_poll_pdu, config.tx.poll_pdu)) { + fmt::print("Invalid RLC AM TX PollPDU. {} poll_pdu={}\n", id, config.tx.poll_pdu); + fmt::print(" Valid values are:" + "p4, p8, p16, p32, p64, p128, p256, p512, p1024, p2048," + " p4096, p6144, p8192, p12288, p16384,p20480," + " p24576, p28672, p32768, p40960, p49152, p57344, p65536\n"); + return false; + } + + rlc_poll_kilo_bytes tmp_poll_bytes; + if (!rlc_poll_kilo_bytes_from_int(tmp_poll_bytes, config.tx.poll_byte)) { + fmt::print("Invalid RLC AM TX PollBytes. {} poll_bytes={}\n", id, config.tx.poll_byte); + fmt::print(" Valid values are (in KBytes):" + " kB1, kB2, kB5, kB8, kB10, kB15, kB25, kB50, kB75," + " kB100, kB125, kB250, kB375, kB500, kB750, kB1000," + " kB1250, kB1500, kB2000, kB3000, kB4000, kB4500," + " kB5000, kB5500, kB6000, kB6500, kB7000, kB7500," + " mB8, mB9, mB10, mB11, mB12, mB13, mB14, mB15," + " mB16, mB17, mB18, mB20, mB25, mB30, mB40, infinity\n"); + return false; + } + + if (config.tx.queue_size == 0) { + fmt::print("RLC AM TX queue size cannot be 0. {}\n", id); + return false; + } + + // Validate RX. + + if (!from_number(tmp_sn_size, config.rx.sn_field_length)) { + fmt::print("RLC AM RX SN length is neither 12 or 18 bits. {} sn_size={}\n", id, config.rx.sn_field_length); + return false; + } + + rlc_t_reassembly tmp_t_reassembly; + if (!rlc_t_reassembly_from_int(tmp_t_reassembly, config.rx.t_reassembly)) { + fmt::print("RLC AM RX t-Reassembly is invalid. {} t_reassembly={}\n", id, config.rx.t_reassembly); + fmt::print("Valid values are:" + " ms40, ms45, ms50, ms55, ms60, ms65, ms70," + " ms75, ms80, ms85, ms90, ms95, ms100, ms110," + " ms120, ms130, ms140, ms150, ms160, ms170," + " ms180, ms190, ms200\n"); + return false; + } + + rlc_t_status_prohibit tmp_t_status_prohibit; + if (!rlc_t_status_prohibit_from_int(tmp_t_status_prohibit, config.rx.t_status_prohibit)) { + fmt::print("RLC AM RX t-statusProhibit is invalid. {} t_status_prohibit={}\n", id, config.rx.t_status_prohibit); + fmt::print("Valid values are:" + "ms0, ms5, ms10, ms15, ms20, ms25, ms30, ms35," + "ms40, ms45, ms50, ms55, ms60, ms65, ms70," + "ms75, ms80, ms85, ms90, ms95, ms100, ms105," + "ms110, ms115, ms120, ms125, ms130, ms135," + "ms140, ms145, ms150, ms155, ms160, ms165," + "ms170, ms175, ms180, ms185, ms190, ms195," + "ms200, ms205, ms210, ms215, ms220, ms225," + "ms230, ms235, ms240, ms245, ms250, ms300," + "ms350, ms400, ms450, ms500, ms800, ms1000," + "ms1200, ms1600, ms2000, ms2400\n"); + return false; + } + + if (config.rx.max_sn_per_status >= window_size(config.rx.sn_field_length)) { + fmt::print("RLC AM RX max_sn_per_status={} exceeds window_size={}. sn_size={}\n", + config.rx.max_sn_per_status, + window_size(config.rx.sn_field_length), + config.rx.sn_field_length); + return false; + } + + return true; +} + +static bool validate_rlc_appconfig(five_qi_t five_qi, const rlc_cu_cp_unit_config& config) +{ + // Check mode. + if (config.mode != "am" && config.mode != "um-bidir") { + fmt::print("RLC mode is neither \"am\" or \"um-bidir\". {} mode={}\n", five_qi, config.mode); + return false; + } + + // Check AM. + if (config.mode == "am" && !validate_rlc_am_appconfig(five_qi, config.am)) { + fmt::print("RLC AM config is invalid. {}\n", five_qi); + return false; + } + + // Check UM. + if (config.mode == "um-bidir" && !validate_rlc_um_appconfig(five_qi, config.um)) { + fmt::print("RLC UM config is invalid. {}\n", five_qi); + return false; + } + return true; +} + +/// Validates the given QoS configuration. Returns true on success, otherwise false. +static bool validate_qos_appconfig(span config) +{ + for (const auto& qos : config) { + if (!validate_pdcp_appconfig(qos.five_qi, qos.pdcp)) { + return false; + } + if (!validate_rlc_appconfig(qos.five_qi, qos.rlc)) { + return false; + } + } + return true; +} + +/// Validates the given CU-CP configuration. Returns true on success, otherwise false. +static bool validate_cu_cp_appconfig(const gnb_id_t gnb_id, const cu_cp_unit_config& config) +{ + // validate mobility config + if (!validate_mobility_appconfig(gnb_id, config.mobility_config)) { + return false; + } + + if (!validate_security_appconfig(config.security_config)) { + return false; + } + + if (!validate_qos_appconfig(config.qos_cfg)) { + return false; + } + + return true; +} + +bool srsran::validate_cu_cp_unit_config(const cu_cp_unit_config& config) +{ + return validate_cu_cp_appconfig(config.gnb_id, config); +} diff --git a/apps/units/cu_cp/cu_cp_unit_config_validator.h b/apps/units/cu_cp/cu_cp_unit_config_validator.h new file mode 100644 index 0000000000..d4d5494d10 --- /dev/null +++ b/apps/units/cu_cp/cu_cp_unit_config_validator.h @@ -0,0 +1,32 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "cu_cp_unit_config.h" + +namespace srsran { + +/// Validates the given CU-CP unit configuration. Returns true on success, false otherwise. +bool validate_cu_cp_unit_config(const cu_cp_unit_config& config); + +} // namespace srsran diff --git a/apps/units/cu_cp/cu_cp_unit_logger_config.h b/apps/units/cu_cp/cu_cp_unit_logger_config.h new file mode 100644 index 0000000000..5115cf4d98 --- /dev/null +++ b/apps/units/cu_cp/cu_cp_unit_logger_config.h @@ -0,0 +1,43 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include + +namespace srsran { + +/// Configuration of logging functionalities. +struct cu_cp_unit_logger_config { + std::string cu_level = "warning"; + std::string f1ap_level = "warning"; + std::string pdcp_level = "warning"; + std::string rrc_level = "warning"; + std::string ngap_level = "warning"; + std::string sec_level = "warning"; + /// Maximum number of bytes to write when dumping hex arrays. + int hex_max_size = 0; + /// Enable JSON generation for the F1AP Tx and Rx PDUs. + bool f1ap_json_enabled = false; +}; + +} // namespace srsran diff --git a/apps/modules/cu_cp/logger_registrator.h b/apps/units/cu_cp/logger_registrator.h similarity index 100% rename from apps/modules/cu_cp/logger_registrator.h rename to apps/units/cu_cp/logger_registrator.h diff --git a/apps/modules/cu_cp/pcap_factory.h b/apps/units/cu_cp/pcap_factory.h similarity index 100% rename from apps/modules/cu_cp/pcap_factory.h rename to apps/units/cu_cp/pcap_factory.h diff --git a/apps/modules/cu_up/logger_registrator.h b/apps/units/cu_up/logger_registrator.h similarity index 100% rename from apps/modules/cu_up/logger_registrator.h rename to apps/units/cu_up/logger_registrator.h diff --git a/apps/modules/cu_up/pcap_factory.h b/apps/units/cu_up/pcap_factory.h similarity index 100% rename from apps/modules/cu_up/pcap_factory.h rename to apps/units/cu_up/pcap_factory.h diff --git a/apps/modules/flexible_du/CMakeLists.txt b/apps/units/flexible_du/CMakeLists.txt similarity index 100% rename from apps/modules/flexible_du/CMakeLists.txt rename to apps/units/flexible_du/CMakeLists.txt diff --git a/apps/modules/flexible_du/du_high/logger_registrator.h b/apps/units/flexible_du/du_high/logger_registrator.h similarity index 100% rename from apps/modules/flexible_du/du_high/logger_registrator.h rename to apps/units/flexible_du/du_high/logger_registrator.h diff --git a/apps/modules/flexible_du/du_high/pcap_factory.h b/apps/units/flexible_du/du_high/pcap_factory.h similarity index 100% rename from apps/modules/flexible_du/du_high/pcap_factory.h rename to apps/units/flexible_du/du_high/pcap_factory.h diff --git a/apps/modules/flexible_du/du_low/logger_registrator.h b/apps/units/flexible_du/du_low/logger_registrator.h similarity index 100% rename from apps/modules/flexible_du/du_low/logger_registrator.h rename to apps/units/flexible_du/du_low/logger_registrator.h diff --git a/apps/modules/flexible_du/fapi/logger_registrator.h b/apps/units/flexible_du/fapi/logger_registrator.h similarity index 100% rename from apps/modules/flexible_du/fapi/logger_registrator.h rename to apps/units/flexible_du/fapi/logger_registrator.h diff --git a/apps/modules/flexible_du/split_6/logger_registrator.h b/apps/units/flexible_du/split_6/logger_registrator.h similarity index 100% rename from apps/modules/flexible_du/split_6/logger_registrator.h rename to apps/units/flexible_du/split_6/logger_registrator.h diff --git a/apps/modules/flexible_du/split_7_2/logger_registrator.h b/apps/units/flexible_du/split_7_2/logger_registrator.h similarity index 100% rename from apps/modules/flexible_du/split_7_2/logger_registrator.h rename to apps/units/flexible_du/split_7_2/logger_registrator.h diff --git a/apps/modules/flexible_du/split_8/logger_registrator.h b/apps/units/flexible_du/split_8/logger_registrator.h similarity index 100% rename from apps/modules/flexible_du/split_8/logger_registrator.h rename to apps/units/flexible_du/split_8/logger_registrator.h diff --git a/apps/modules/flexible_du/split_ru_dynamic/CMakeLists.txt b/apps/units/flexible_du/split_ru_dynamic/CMakeLists.txt similarity index 100% rename from apps/modules/flexible_du/split_ru_dynamic/CMakeLists.txt rename to apps/units/flexible_du/split_ru_dynamic/CMakeLists.txt diff --git a/apps/modules/flexible_du/split_ru_dynamic/dynamic_du_factory.cpp b/apps/units/flexible_du/split_ru_dynamic/dynamic_du_factory.cpp similarity index 100% rename from apps/modules/flexible_du/split_ru_dynamic/dynamic_du_factory.cpp rename to apps/units/flexible_du/split_ru_dynamic/dynamic_du_factory.cpp diff --git a/apps/modules/flexible_du/split_ru_dynamic/dynamic_du_factory.h b/apps/units/flexible_du/split_ru_dynamic/dynamic_du_factory.h similarity index 100% rename from apps/modules/flexible_du/split_ru_dynamic/dynamic_du_factory.h rename to apps/units/flexible_du/split_ru_dynamic/dynamic_du_factory.h diff --git a/apps/modules/flexible_du/split_ru_dynamic/logger_registrator.h b/apps/units/flexible_du/split_ru_dynamic/logger_registrator.h similarity index 100% rename from apps/modules/flexible_du/split_ru_dynamic/logger_registrator.h rename to apps/units/flexible_du/split_ru_dynamic/logger_registrator.h diff --git a/include/srsran/adt/byte_buffer.h b/include/srsran/adt/byte_buffer.h index 3c2d2be56f..30f033237a 100644 --- a/include/srsran/adt/byte_buffer.h +++ b/include/srsran/adt/byte_buffer.h @@ -231,7 +231,9 @@ class byte_buffer /// Move constructor. byte_buffer(byte_buffer&& other) noexcept = default; + /// Creates a byte_buffer that in case it fails to allocate from the default pool, it resorts to malloc as fallback. byte_buffer(fallback_allocation_tag tag, span other = {}) noexcept; + byte_buffer(fallback_allocation_tag tag, const std::initializer_list& other) noexcept; byte_buffer(fallback_allocation_tag tag, const byte_buffer& other) noexcept; /// Copy assignment operator is disabled. Use std::move, .copy() or .deep_copy() instead. @@ -241,20 +243,8 @@ class byte_buffer byte_buffer& operator=(byte_buffer&& other) noexcept = default; /// Performs a deep copy (byte by bytes) of this byte_buffer. - expected deep_copy() const - { - if (ctrl_blk_ptr == nullptr) { - return byte_buffer{}; - } - - byte_buffer buf; - for (node_t* seg = ctrl_blk_ptr->segments.head; seg != nullptr; seg = seg->next) { - if (not buf.append(span{seg->data(), seg->length()})) { - return default_error_t{}; - } - } - return buf; - } + expected deep_copy() const; + expected deep_copy(fallback_allocation_tag tag) const; /// Performs a shallow copy. Head segment reference counter is incremented. byte_buffer copy() const { return byte_buffer{*this}; } @@ -266,7 +256,12 @@ class byte_buffer static_assert(std::is_same, uint8_t>::value or std::is_same, const uint8_t>::value, "Iterator value type is not uint8_t"); - // TODO: use segment-wise copy if it is a span. + using iter_category = typename std::iterator_traits::iterator_category; + + if (std::is_same::value) { + return append(span(&*begin, &*end)); + } + // TODO: use segment-wise copy if it is a byte buffer-like type. for (auto it = begin; it != end; ++it) { if (not append(*it)) { return false; @@ -279,10 +274,7 @@ class byte_buffer SRSRAN_NODISCARD bool append(span bytes); /// Appends an initializer list of bytes. - SRSRAN_NODISCARD bool append(const std::initializer_list& bytes) - { - return append(span(bytes.begin(), bytes.size())); - } + SRSRAN_NODISCARD bool append(const std::initializer_list& bytes); /// Appends bytes from another byte_buffer. This function may allocate new segments. SRSRAN_NODISCARD bool append(const byte_buffer& other); @@ -304,17 +296,7 @@ class byte_buffer } /// Appends a view of bytes into current byte buffer. - SRSRAN_NODISCARD bool append(const byte_buffer_view& view) - { - // Append segment by segment. - auto view_segs = view.segments(); - for (span seg : view_segs) { - if (not append(seg)) { - return false; - } - } - return true; - } + SRSRAN_NODISCARD bool append(const byte_buffer_view& view); /// Appends an owning view of bytes into current byte buffer. SRSRAN_NODISCARD bool append(const byte_buffer_slice& view); @@ -424,22 +406,7 @@ class byte_buffer /// \brief Removes last segment of the byte_buffer. /// Note: This operation is O(N), as it requires recomputing the tail. - void pop_last_segment() - { - node_t* tail = ctrl_blk_ptr->segments.tail; - if (tail == nullptr) { - return; - } - - // Decrement bytes stored in the tail. - ctrl_blk_ptr->pkt_len -= tail->length(); - - // Remove tail from linked list. - ctrl_blk_ptr->segments.pop_back(); - - // Deallocate tail segment. - ctrl_blk_ptr->destroy_node(tail); - } + void pop_last_segment(); static void warn_alloc_failure(); @@ -652,16 +619,7 @@ class byte_buffer_writer SRSRAN_NODISCARD bool append(uint8_t byte) { return buffer->append(byte); } /// Appends the specified amount of zeros. - SRSRAN_NODISCARD bool append_zeros(size_t nof_zeros) - { - // TODO: optimize. - for (size_t i = 0; i != nof_zeros; ++i) { - if (not buffer->append(0)) { - return false; - } - } - return true; - } + SRSRAN_NODISCARD bool append_zeros(size_t nof_zeros); /// Checks last appended byte. uint8_t& back() { return buffer->back(); } @@ -676,22 +634,8 @@ class byte_buffer_writer byte_buffer* buffer; }; -/// Converts a hex string (e.g. 01FA02) to a byte buffer. -inline byte_buffer make_byte_buffer(const std::string& hex_str) -{ - srsran_assert(hex_str.size() % 2 == 0, "The number of hex digits must be even"); - - byte_buffer ret; - for (size_t i = 0, e = hex_str.size(); i != e; i += 2) { - uint8_t val; - std::sscanf(hex_str.data() + i, "%02hhX", &val); - if (not ret.append(val)) { - ret.clear(); - break; - } - } - return ret; -} +/// Converts a string of hexadecimal digits (e.g. "01FA02") to a byte buffer. +byte_buffer make_byte_buffer(const std::string& hex_str); /// Performs a segment-wise copy of the byte_buffer into a span object. /// The length is limited by the length of src and dst, whichever is smaller. @@ -728,27 +672,7 @@ inline size_t copy_segments(const ByteBufferType& src, span dst) /// \param src Source byte_buffer. /// \param tmp_mem Temporary memory for a possible copy. Must be at least as large as \p src. /// \return A contiguous view of the byte_buffer -inline span to_span(const byte_buffer& src, span tmp_mem) -{ - // Empty buffer. - if (src.empty()) { - return {}; - } - - // Is contiguous: shortcut without copy. - if (src.is_contiguous()) { - return *src.segments().begin(); - } - - // Non-contiguous: copy required. - srsran_assert(src.length() <= tmp_mem.size(), - "Insufficient temporary memory to fit the byte_buffer. buffer_size={}, tmp_size={}", - src.length(), - tmp_mem.size()); - span result = {tmp_mem.data(), src.length()}; - copy_segments(src, result); - return result; -} +span to_span(const byte_buffer& src, span tmp_mem); } // namespace srsran diff --git a/include/srsran/cu_up/cu_up_configuration.h b/include/srsran/cu_up/cu_up_configuration.h index a628f0b404..906162fdc9 100644 --- a/include/srsran/cu_up/cu_up_configuration.h +++ b/include/srsran/cu_up/cu_up_configuration.h @@ -47,6 +47,10 @@ struct network_interface_config { /// Local IP address to bind for connection from UPF to receive downlink user-plane traffic (N3 interface). std::string n3_bind_addr = "127.0.1.1"; + /// External IP address that is advertised to receive GTP-U packets from UPF via N3 interface. + /// It defaults to \c n3_bind_addr but may differ in case the CU-UP is behind a NAT. + std::string n3_ext_addr = "auto"; + /// Interface name to bind the N3. `auto` does not force a specific interface and uses a normal `bind()`. std::string n3_bind_interface = "auto"; diff --git a/include/srsran/mac/mac_ue_configurator.h b/include/srsran/mac/mac_ue_configurator.h index 19f05bf37a..02386c97d3 100644 --- a/include/srsran/mac/mac_ue_configurator.h +++ b/include/srsran/mac/mac_ue_configurator.h @@ -76,7 +76,9 @@ struct mac_ue_create_request { std::vector bearers; mac_cell_group_config mac_cell_group_cfg; physical_cell_group_config phy_cell_group_cfg; - const byte_buffer* ul_ccch_msg = nullptr; + bool initial_fallback = true; + const byte_buffer* ul_ccch_msg = nullptr; + // Scheduler-only params. sched_ue_config_request sched_cfg; }; diff --git a/include/srsran/phy/support/time_alignment_estimator/time_alignment_estimator.h b/include/srsran/phy/support/time_alignment_estimator/time_alignment_estimator.h index f28d252fae..c4207eb9ae 100644 --- a/include/srsran/phy/support/time_alignment_estimator/time_alignment_estimator.h +++ b/include/srsran/phy/support/time_alignment_estimator/time_alignment_estimator.h @@ -47,11 +47,14 @@ class time_alignment_estimator /// \param[in] symbols Complex frequency domain symbols. /// \param[in] mask Distribution of the complex symbols within an OFDM symbol. /// \param[in] scs Subcarrier spacing. + /// \param[in] max_ta Maximum absolute time alignment measurement if it is not zero. /// \return The measured time alignment. /// \remark An assertion is triggered if the number of symbols is not equal to the number of active elements in the /// mask. - virtual time_alignment_measurement - estimate(span symbols, bounded_bitset mask, subcarrier_spacing scs) = 0; + virtual time_alignment_measurement estimate(span symbols, + bounded_bitset mask, + subcarrier_spacing scs, + double max_ta = 0.0) = 0; }; } // namespace srsran diff --git a/include/srsran/phy/upper/channel_processors/channel_processor_formatters.h b/include/srsran/phy/upper/channel_processors/channel_processor_formatters.h index f6f4c2ccee..37d056fe58 100644 --- a/include/srsran/phy/upper/channel_processors/channel_processor_formatters.h +++ b/include/srsran/phy/upper/channel_processors/channel_processor_formatters.h @@ -216,7 +216,7 @@ struct formatter { auto format(const srsran::prach_detector::configuration& config, FormatContext& ctx) -> decltype(std::declval().out()) { - helper.format_if_verbose(ctx, "rsi={}", config.root_sequence_index); + helper.format_always(ctx, "rsi={}", config.root_sequence_index); helper.format_if_verbose(ctx, "preambles=[{}, {})", config.start_preamble_index, diff --git a/include/srsran/phy/upper/channel_processors/pucch_detector.h b/include/srsran/phy/upper/channel_processors/pucch_detector.h index cb04f731d9..dca749850e 100644 --- a/include/srsran/phy/upper/channel_processors/pucch_detector.h +++ b/include/srsran/phy/upper/channel_processors/pucch_detector.h @@ -65,8 +65,8 @@ class pucch_detector unsigned nof_symbols; /// Group hopping scheme. pucch_group_hopping group_hopping; - /// Antenna port the PUCCH is received at. - unsigned port; + /// Port indexes used for the PUCCH reception. + static_vector ports; /// Amplitude scaling factor. float beta_pucch; /// \brief Time-domain orthogonal cover code index {0, ..., 6}. diff --git a/include/srsran/phy/upper/signal_processors/srs/formatters.h b/include/srsran/phy/upper/signal_processors/srs/formatters.h index c8c85e5b8c..cfafbd16e7 100644 --- a/include/srsran/phy/upper/signal_processors/srs/formatters.h +++ b/include/srsran/phy/upper/signal_processors/srs/formatters.h @@ -77,7 +77,7 @@ struct formatter { auto format(const srsran::srs_estimator_result& config, FormatContext& ctx) -> decltype(std::declval().out()) { - helper.format_always(ctx, "t_align={}", config.time_align.to_seconds()); + helper.format_always(ctx, "t_align={:.1}us", config.time_alignment.time_alignment * 1e6); helper.format_always(ctx, "noise_var={}", config.noise_variance); helper.format_if_verbose(ctx, "H={}", config.channel_matrix); diff --git a/include/srsran/phy/upper/signal_processors/srs/srs_estimator_factory.h b/include/srsran/phy/upper/signal_processors/srs/srs_estimator_factory.h index e54823c056..3a04cae48f 100644 --- a/include/srsran/phy/upper/signal_processors/srs/srs_estimator_factory.h +++ b/include/srsran/phy/upper/signal_processors/srs/srs_estimator_factory.h @@ -22,6 +22,7 @@ #pragma once +#include "srsran/phy/support/time_alignment_estimator/time_alignment_estimator_factories.h" #include "srsran/phy/upper/sequence_generators/sequence_generator_factories.h" #include "srsran/phy/upper/signal_processors/srs/srs_estimator.h" #include "srsran/phy/upper/signal_processors/srs/srs_estimator_configuration_validator.h" @@ -48,6 +49,7 @@ class srs_estimator_factory /// Create a generic SRS propagation channel estimator factory. std::shared_ptr -create_srs_estimator_generic_factory(std::shared_ptr sequence_generator_factory); +create_srs_estimator_generic_factory(std::shared_ptr sequence_generator_factory, + std::shared_ptr ta_estimator_factory); } // namespace srsran diff --git a/include/srsran/phy/upper/signal_processors/srs/srs_estimator_result.h b/include/srsran/phy/upper/signal_processors/srs/srs_estimator_result.h index fe5aec745b..2270a37106 100644 --- a/include/srsran/phy/upper/signal_processors/srs/srs_estimator_result.h +++ b/include/srsran/phy/upper/signal_processors/srs/srs_estimator_result.h @@ -22,6 +22,7 @@ #pragma once +#include "srsran/phy/support/time_alignment_estimator/time_alignment_measurement.h" #include "srsran/ran/phy_time_unit.h" #include "srsran/ran/srs/srs_channel_matrix.h" @@ -34,7 +35,7 @@ struct srs_estimator_result { /// Wideband measured noise variance. float noise_variance; /// Measured time alignment. - phy_time_unit time_align; + time_alignment_measurement time_alignment; }; } // namespace srsran diff --git a/include/srsran/ran/srs/srs_channel_matrix.h b/include/srsran/ran/srs/srs_channel_matrix.h index abefe0e51d..9f905a6a38 100644 --- a/include/srsran/ran/srs/srs_channel_matrix.h +++ b/include/srsran/ran/srs/srs_channel_matrix.h @@ -30,8 +30,10 @@ #include "srsran/srsvec/sc_prod.h" #include "srsran/support/srsran_assert.h" #include +#include #include #include +#include namespace srsran { @@ -50,7 +52,7 @@ class srs_channel_matrix /// \brief Constructs a channel matrix with the desired number of transmit and receive ports. /// \param[in] nof_rx_ports Number of receive ports. - /// \param[in] nof_tx_ports Number of transmit ports. + /// \param[in] nof_tx_ports Number of transmit ports. /// \remark An assertion is triggered if the number of receive ports exceeds \ref srs_constants::max_nof_rx_ports. /// \remark An assertion is triggered if the number of transmit ports exceeds \ref srs_constants::max_nof_tx_ports. srs_channel_matrix(unsigned nof_rx_ports, unsigned nof_tx_ports) : data({nof_rx_ports, nof_tx_ports}) @@ -71,7 +73,7 @@ class srs_channel_matrix /// /// \param[in] coefficients Channel coefficient list, arranged by i) receive port and ii) transmit port. /// \param[in] nof_rx_ports Number of receive ports. - /// \param[in] nof_tx_ports Number of transmit ports. + /// \param[in] nof_tx_ports Number of transmit ports. /// \remark An assertion is triggered if the number of receive ports exceeds \ref srs_constants::max_nof_rx_ports. /// \remark An assertion is triggered if the number of transmit ports exceeds \ref srs_constants::max_nof_tx_ports. srs_channel_matrix(const std::initializer_list& coefficients, unsigned nof_rx_ports, unsigned nof_tx_ports) : @@ -86,7 +88,7 @@ class srs_channel_matrix /// /// \param[in] coefficients Channel coefficient list, arranged by i) receive port and ii) transmit port. /// \param[in] nof_rx_ports Number of receive ports. - /// \param[in] nof_tx_ports Number of transmit ports. + /// \param[in] nof_tx_ports Number of transmit ports. /// \remark An assertion is triggered if the number of receive ports exceeds \ref srs_constants::max_nof_rx_ports. /// \remark An assertion is triggered if the number of transmit ports exceeds \ref srs_constants::max_nof_tx_ports. srs_channel_matrix(span coefficients, unsigned nof_rx_ports, unsigned nof_tx_ports) : @@ -109,15 +111,14 @@ class srs_channel_matrix srs_constants::max_nof_tx_ports); // Copy the weights into the tensor. - srsvec::copy(data.get_view(dims::all)>({}), coefficients); + srsvec::copy(data.get_data(), coefficients); } /// Copy constructor. srs_channel_matrix(const srs_channel_matrix& other) : data({other.get_nof_rx_ports(), other.get_nof_tx_ports()}) { // Copy the weights into the tensor. - srsvec::copy(data.get_view(dims::all)>({}), - other.data.get_view(dims::all)>({})); + srsvec::copy(data.get_data(), other.data.get_data()); } /// \brief Overload assignment operator. @@ -131,15 +132,18 @@ class srs_channel_matrix // Resize the tensor. resize(other.get_nof_rx_ports(), other.get_nof_tx_ports()); // Copy the weights into the tensor. - srsvec::copy(data.get_view(dims::all)>({}), - other.data.get_view(dims::all)>({})); + srsvec::copy(data.get_data(), other.data.get_data()); return *this; } /// \brief Near equal comparison method. + /// + /// Checks whether two matrices are similar, that is whether their Frobenius distance does not exceed the given + /// tolerance, relative to the norm of the first matrix. + /// /// \param[in] other Channel matrix to compare against. - /// \param[in] tolerance Maximum absolute error tolerated for considering two propagation channel coefficients equal. - /// \return \c true if the absolute error between both channel matrices is lower than \c tolerance, \c false + /// \param[in] tolerance Maximum relative error. + /// \return \c true if the relative distance between the two channel matrices is lower than \c tolerance, \c false /// otherwise. bool is_near(const srs_channel_matrix& other, float tolerance) const { @@ -153,16 +157,15 @@ class srs_channel_matrix return false; } - for (unsigned i_rx_port = 0; i_rx_port != nof_rx_ports; ++i_rx_port) { - for (unsigned i_tx_port = 0; i_tx_port != nof_tx_ports; ++i_tx_port) { - float error = std::abs(get_coefficient(i_rx_port, i_tx_port) - other.get_coefficient(i_rx_port, i_tx_port)); - if (error > tolerance) { - return false; - } - } - } + // Normalize both matrices. + srs_channel_matrix left = this->normalize(); + srs_channel_matrix right = other.normalize(); - return true; + // Calculate the difference of both matrices. + srs_channel_matrix diff = left - right; + + // Finally, calculate the distance between the matrices. + return (diff.frobenius_norm() / left.frobenius_norm()) < tolerance; } /// Gets the current number of receive ports. @@ -207,13 +210,76 @@ class srs_channel_matrix data[{i_rx_port, i_tx_port}] = coefficient; } - /// Scales all the coefficients by a scaling factor. + /// Scales all the coefficients by a real scaling factor. srs_channel_matrix& operator*=(float scale) { srsvec::sc_prod(data.get_data(), scale, data.get_data()); return *this; } + /// Scales all the coefficients by a complex scaling factor. + srs_channel_matrix& operator*=(cf_t scale) + { + srsvec::sc_prod(data.get_data(), scale, data.get_data()); + return *this; + } + + /// Calculates the Frobenius norm of the channel matrix. + float frobenius_norm() const + { + span raw_data = data.get_data(); + + float square_sum = + std::accumulate(raw_data.begin(), raw_data.end(), 0.0F, [](float sum, cf_t in) { return sum + std::norm(in); }); + + return std::sqrt(square_sum); + } + + /// \brief Subtracts two channel matrices. + /// \param[in] other The matrix to subtract. + /// \return The difference matrix. + /// \remark An assertion is triggered if the dimensions are not equal. + srs_channel_matrix operator-(const srs_channel_matrix& other) const + { + srsran_assert(get_nof_tx_ports() == other.get_nof_tx_ports(), "The number of transmit ports is not equal."); + srsran_assert(get_nof_rx_ports() == other.get_nof_rx_ports(), "The number of receive ports is not equal."); + + unsigned nof_rx_ports = get_nof_rx_ports(); + unsigned nof_tx_ports = get_nof_tx_ports(); + + srs_channel_matrix result(nof_rx_ports, nof_tx_ports); + for (unsigned i_rx_port = 0; i_rx_port != nof_rx_ports; ++i_rx_port) { + for (unsigned i_tx_port = 0; i_tx_port != nof_tx_ports; ++i_tx_port) { + result.set_coefficient( + get_coefficient(i_rx_port, i_tx_port) - other.get_coefficient(i_rx_port, i_tx_port), i_rx_port, i_tx_port); + } + } + + return result; + } + + /// \brief Normalizes the channel matrix. + /// + /// Applies a scaling factor to all the channel matrix coefficients. The scaling magnitude is equal to the square + /// root of the number of coefficients divided by the Frobenius norm. The scaling argument is the opposite of the + /// first coefficient argument. + /// + /// \return A normalized channel matrix with purely-real first coefficient and Frobenius norm equal to the square root + /// of the number of elements. + srs_channel_matrix normalize() const + { + unsigned nof_rx_ports = get_nof_rx_ports(); + unsigned nof_tx_ports = get_nof_tx_ports(); + + float argument = -std::arg(get_coefficient(0, 0)); + float amplitude = std::sqrt(nof_rx_ports * nof_tx_ports) / frobenius_norm(); + cf_t scaling = std::polar(amplitude, argument); + + srs_channel_matrix result = *this; + result *= scaling; + return result; + } + private: /// \brief Resizes the number of coefficients to a desired number of receive and transmit ports. /// \param[in] nof_rx_ports Number of receive ports. diff --git a/include/srsran/ran/srs/srs_channel_matrix_formatters.h b/include/srsran/ran/srs/srs_channel_matrix_formatters.h index d2d7c040b7..7ca602341d 100644 --- a/include/srsran/ran/srs/srs_channel_matrix_formatters.h +++ b/include/srsran/ran/srs/srs_channel_matrix_formatters.h @@ -54,7 +54,7 @@ struct formatter { if (i_tx_port != 0) { format_to(ctx.out(), ","); } - format_to(ctx.out(), "{:+.2f}", matrix.get_coefficient(i_rx_port, i_tx_port)); + format_to(ctx.out(), "{:+.3f}", matrix.get_coefficient(i_rx_port, i_tx_port)); } } format_to(ctx.out(), "]"); diff --git a/include/srsran/ran/srs/srs_information.h b/include/srsran/ran/srs/srs_information.h index 1481d0fd49..de5d843c94 100644 --- a/include/srsran/ran/srs/srs_information.h +++ b/include/srsran/ran/srs/srs_information.h @@ -52,12 +52,13 @@ struct srs_information { }; /// \brief Get Sounding Reference Signal information. +/// +/// Simplified SRS information. It does not implement sequence, group or frequency hopping. +/// /// \param resource SRS resource configuration. /// \param i_antenna_port Transmit antenna 0-based port index. -/// \param i_symbol OFDM symbol index within the slot. /// \return The SRS information. /// \remark An assertion is triggered if one or more resource parameters are invalid or not supported. -srs_information -get_srs_information(const srs_resource_configuration& resource, unsigned i_antenna_port, unsigned i_symbol); +srs_information get_srs_information(const srs_resource_configuration& resource, unsigned i_antenna_port); } // namespace srsran diff --git a/include/srsran/rrc/rrc_ue.h b/include/srsran/rrc/rrc_ue.h index 078b0931f7..5b2af8b807 100644 --- a/include/srsran/rrc/rrc_ue.h +++ b/include/srsran/rrc/rrc_ue.h @@ -22,7 +22,6 @@ #pragma once -#include "rrc_cell_context.h" #include "rrc_types.h" #include "srsran/adt/byte_buffer.h" #include "srsran/adt/static_vector.h" @@ -159,11 +158,6 @@ class rrc_ue_du_processor_notifier public: virtual ~rrc_ue_du_processor_notifier() = default; - /// \brief Notify about a UE Context Release Command. - /// \param[in] cmd The UE Context Release Command. - virtual async_task - on_ue_context_release_command(const cu_cp_ue_context_release_command& cmd) = 0; - /// \brief Notify about a required reestablishment context modification. /// \param[in] ue_index The index of the UE that needs the context modification. virtual async_task on_rrc_reestablishment_context_modification_required(ue_index_t ue_index) = 0; @@ -214,8 +208,6 @@ class rrc_ue_control_notifier public: virtual ~rrc_ue_control_notifier() = default; - virtual async_task on_ue_context_release_request(const cu_cp_ue_context_release_request& msg) = 0; - /// \brief Notify about the reception of an inter CU handove related RRC Reconfiguration Complete. virtual void on_inter_cu_ho_rrc_recfg_complete_received(const ue_index_t ue_index, const nr_cell_global_id_t& cgi, @@ -331,17 +323,26 @@ class rrc_ue_context_update_notifier /// \param[in] old_c_rnti The old C-RNTI contained in the RRC Reestablishment Request. /// \param[in] ue_index The new UE index of the UE that sent the Reestablishment Request. /// \returns The RRC Reestablishment UE context for the old UE. - virtual rrc_ue_reestablishment_context_response - on_rrc_reestablishment_request(pci_t old_pci, rnti_t old_c_rnti, ue_index_t ue_index) = 0; + virtual rrc_ue_reestablishment_context_response on_rrc_reestablishment_request(pci_t old_pci, rnti_t old_c_rnti) = 0; + + /// \brief Notify the CU-CP to release the old UE after a reestablishment failure. + /// \param[in] request The release request. + virtual void on_rrc_reestablishment_failure(const cu_cp_ue_context_release_request& request) = 0; + + /// \brief Notify the CU-CP to remove the old UE from the CU-CP after an successful reestablishment. + /// \param[in] old_ue_index The index of the old UE to remove. + virtual void on_rrc_reestablishment_complete(ue_index_t old_ue_index) = 0; /// \brief Notify the CU-CP to transfer and remove ue contexts. - /// \param[in] ue_index The new UE index of the UE that sent the Reestablishment Request. /// \param[in] old_ue_index The old UE index of the UE that sent the Reestablishment Request. - virtual async_task on_ue_transfer_required(ue_index_t ue_index, ue_index_t old_ue_index) = 0; + virtual async_task on_ue_transfer_required(ue_index_t old_ue_index) = 0; + + /// \brief Notify the CU-CP to remove a UE from the CU-CP. + virtual async_task on_ue_removal_required() = 0; - /// \brief Notify the CU-CP to completly remove a UE from the CU-CP. - /// \param[in] ue_index The index of the UE to remove. - virtual async_task on_ue_removal_required(ue_index_t ue_index) = 0; + /// \brief Notify the CU-CP to release a UE. + /// \param[in] request The release request. + virtual async_task on_ue_release_required(const cu_cp_ue_context_release_request& request) = 0; }; /// Interface to notify about measurements @@ -351,15 +352,13 @@ class rrc_ue_measurement_notifier virtual ~rrc_ue_measurement_notifier() = default; /// \brief Retrieve the measurement config (for any UE) connected to the given serving cell. - /// \param[in] ue_index The index of the UE to retrieve the measurement config for. /// \param[in] nci The cell id of the serving cell to update. /// \param[in] current_meas_config The current meas config of the UE (if applicable). - virtual optional on_measurement_config_request(ue_index_t ue_index, - nr_cell_id_t nci, + virtual optional on_measurement_config_request(nr_cell_id_t nci, optional current_meas_config = {}) = 0; /// \brief Submit measurement report for given UE to cell manager. - virtual void on_measurement_report(const ue_index_t ue_index, const rrc_meas_results& meas_results) = 0; + virtual void on_measurement_report(const rrc_meas_results& meas_results) = 0; }; class rrc_ue_context_handler diff --git a/include/srsran/scheduler/config/scheduler_expert_config.h b/include/srsran/scheduler/config/scheduler_expert_config.h index 01295e8cc5..136749564f 100644 --- a/include/srsran/scheduler/config/scheduler_expert_config.h +++ b/include/srsran/scheduler/config/scheduler_expert_config.h @@ -57,6 +57,8 @@ struct scheduler_ue_expert_config { bool enable_csi_rs_pdsch_multiplexing; /// Set boundaries, in number of RBs, for UE PDSCH grants. interval pdsch_nof_rbs{1, MAX_NOF_PRBS}; + /// Set boundaries, in number of RBs, for UE PUSCH grants. + interval pusch_nof_rbs{1, MAX_NOF_PRBS}; /// Measurements periodicity in nof. slots over which the new Timing Advance Command is computed. unsigned ta_measurement_slot_period{80}; /// Timing Advance Command (T_A) offset threshold above which Timing Advance Command is triggered. Possible valid @@ -97,8 +99,12 @@ struct scheduler_ue_expert_config { uint8_t dl_harq_la_cqi_drop_threshold{2}; /// Threshold for drop in nof. layers of the first HARQ transmission above which HARQ retransmission is cancelled. uint8_t dl_harq_la_ri_drop_threshold{1}; - // Automatic HARQ acknowledgement (used for NTN cases with no HARQ feedback) + /// Automatic HARQ acknowledgement (used for NTN cases with no HARQ feedback) bool auto_ack_harq{false}; + /// Boundaries in RB interval for resource allocation of UE PDSCHs. + crb_interval pdsch_crb_limits{0, MAX_NOF_PRBS}; + /// Boundaries in RB interval for resource allocation of UE PUSCHs. + crb_interval pusch_crb_limits{0, MAX_NOF_PRBS}; }; /// \brief System Information scheduling statically configurable expert parameters. diff --git a/include/srsran/scheduler/scheduler_configurator.h b/include/srsran/scheduler/scheduler_configurator.h index dcff799f19..6e5c6a009d 100644 --- a/include/srsran/scheduler/scheduler_configurator.h +++ b/include/srsran/scheduler/scheduler_configurator.h @@ -108,8 +108,12 @@ struct sched_cell_configuration_request_message { struct sched_ue_resource_alloc_config { /// Minimum and maximum PDSCH grant sizes for the given UE. prb_interval pdsch_grant_size_limits{0, MAX_NOF_PRBS}; + /// Boundaries within which PDSCH needs to be allocated. + crb_interval pdsch_crb_limits{0, MAX_NOF_PRBS}; /// Minimum and maximum PUSCH grant sizes for the given UE. prb_interval pusch_grant_size_limits{0, MAX_NOF_PRBS}; + /// Boundaries within which PUSCH needs to be allocated. + crb_interval pusch_crb_limits{0, MAX_NOF_PRBS}; /// Maximum PDSCH HARQ retransmissions. unsigned max_pdsch_harq_retxs = 4; /// Maximum PUSCH HARQ retransmissions. diff --git a/include/srsran/support/cli11_utils.h b/include/srsran/support/cli11_utils.h index 58d1edf284..a8706d4ef8 100644 --- a/include/srsran/support/cli11_utils.h +++ b/include/srsran/support/cli11_utils.h @@ -27,7 +27,135 @@ namespace srsran { -/// \brief Parse string into optional type. +using cli11_cell = std::vector; + +/// \brief Adds a subcommand to the given application using the given subcommand name and description. +/// +/// If the subcommand already exists in the application, returns a pointer to it. +/// +/// \param app Application where the subcommand will be added. +/// \param name Subcommand name. +/// \param desc Human readable description of the subcommand. +/// \return A pointer to the subcommand added to the application. +inline CLI::App* add_subcommand(CLI::App& app, const std::string& name, const std::string& desc) +{ + try { + if (CLI::App* subcommand = app.get_subcommand(name)) { + return subcommand; + } + } catch (CLI::OptionNotFound& e) { + } + + return app.add_subcommand(name, desc)->configurable(); +} + +/// \brief Adds an option to the given application. +/// +/// This function adds an option to the given application using the given parameters. If the option is already present +/// in the application, it is removed and a new option is added that will call the callback of the deleted callback +/// and the conversion of the result for the given parameter. By doing this, it allows to add multiple parameters for +/// one option, so one option will be present in the configuration but the result will be written in all the +/// parameters registered for that option. +/// +/// \param app Application where the option will be added. +/// \param option_name Option name. +/// \param param Parameter where the option value will be stored after parsing. +/// \param desc Human readable description of the option. +/// \return A pointer to the option added to the application. +template +CLI::Option* add_option(CLI::App& app, const std::string& option_name, T& param, const std::string& desc) +{ + auto* opt = app.get_option_no_throw(option_name); + if (!opt) { + return app.add_option(option_name, param, desc); + } + + // Option was found. Get the callback and create new option. + auto callbck = opt->get_callback(); + app.remove_option(opt); + + return app + .add_option( + option_name, + [¶m, callback = std::move(callbck)](const CLI::results_t& res) { + callback(res); + return CLI::detail::lexical_conversion(res, param); + }, + desc) + ->run_callback_for_default(); +} + +/// \brief Adds an option function to the given application. +/// +/// This function adds an option function to the given application using the given parameters. If the option is +/// already present in the application, it is removed and a new option is added that will contain the given function +/// and deleted callback as function. By doing this, it allows to add multiple parameters for one option, so one +/// option will be present in the configuration and the all the functions registered for that option will be called. +/// +/// \param app Application where the option will be added. +/// \param option_name Option name. +/// \param func Function to execute during parsing. +/// \param desc Human readable description of the option. +/// \return A pointer to the option added to the application. +template +CLI::Option* add_option_function(CLI::App& app, + const std::string& option_name, + const std::function& func, + const std::string& desc) +{ + auto* opt = app.get_option_no_throw(option_name); + if (!opt) { + return app.add_option_function(option_name, func, desc); + } + + // Option was found. Get the callback and create new option. + auto callbck = opt->get_callback(); + app.remove_option(opt); + + return app + .add_option_function( + option_name, + [func, callback = std::move(callbck)](const std::string& value) { + func(value); + callback({value}); + }, + desc) + ->run_callback_for_default(); +} + +/// \brief Adds an option of type cell to the given application. +/// +/// \param app Application where the option will be added. +/// \param option_name Option name. +/// \param func Function to execute during parsing. +/// \param desc Human readable description of the option. +/// \return A pointer to the option added to the application. +inline CLI::Option* add_option_cell(CLI::App& app, + const std::string& option_name, + const std::function& func, + const std::string& desc) +{ + auto* opt = app.get_option_no_throw(option_name); + if (!opt) { + return app.add_option_function>(option_name, func, desc); + } + + // Option was found. Get the callback and create new option. + auto callbck = opt->get_callback(); + app.remove_option(opt); + + return app + .add_option_function( + option_name, + [func, callback = std::move(callbck)](const cli11_cell& value) { + func(value); + callback(value); + }, + desc) + ->run_callback_for_default(); +} + +/// Parse string into optional type. template bool lexical_cast(const std::string& in, srsran::optional& output) { @@ -41,25 +169,26 @@ bool lexical_cast(const std::string& in, srsran::optional& output) return true; } -/// \brief Parsing an integer with additional option "auto" into an optional of an enum type. +/// Parsing an integer with additional option "auto" into an optional of an enum type. template void add_auto_enum_option(CLI::App& app, const std::string& option_name, optional& param, const std::string& desc) { - app.add_option_function( - option_name, - [¶m](const std::string& in) -> void { - if (in.empty() or in == "auto") { - return; - } - std::stringstream ss(in); - std::underlying_type_t val; - ss >> val; - param = (Param)val; - }, - desc) + add_option_function( + app, + option_name, + [¶m](const std::string& in) -> void { + if (in.empty() or in == "auto") { + return; + } + std::stringstream ss(in); + std::underlying_type_t val; + ss >> val; + param = (Param)val; + }, + desc) ->check([](const std::string& in_str) -> std::string { if (in_str == "auto" or in_str.empty()) { return ""; @@ -71,4 +200,4 @@ void add_auto_enum_option(CLI::App& app, ->default_str("auto"); } -} // namespace srsran \ No newline at end of file +} // namespace srsran diff --git a/lib/cu_cp/adapters/du_processor_adapters.h b/lib/cu_cp/adapters/du_processor_adapters.h index bc887da8e7..ac0ed032b5 100644 --- a/lib/cu_cp/adapters/du_processor_adapters.h +++ b/lib/cu_cp/adapters/du_processor_adapters.h @@ -108,6 +108,12 @@ class du_processor_cu_cp_adapter : public du_processor_cu_cp_notifier return ue_removal_handler->handle_ue_removal_request(ue_index); } + async_task on_ue_release_required(const cu_cp_ue_context_release_request& request) override + { + srsran_assert(ue_context_handler != nullptr, "UE context handler must not be nullptr"); + return ue_context_handler->handle_ue_context_release(request); + } + async_task on_ue_transfer_required(ue_index_t ue_index, ue_index_t old_ue_index) override { srsran_assert(ue_context_handler != nullptr, "UE context handler must not be nullptr"); diff --git a/lib/cu_cp/adapters/rrc_ue_adapters.h b/lib/cu_cp/adapters/rrc_ue_adapters.h index 7cbbb266cb..ac37aec98b 100644 --- a/lib/cu_cp/adapters/rrc_ue_adapters.h +++ b/lib/cu_cp/adapters/rrc_ue_adapters.h @@ -65,13 +65,6 @@ class rrc_ue_du_processor_adapter : public rrc_ue_du_processor_notifier du_processor_rrc_ue_handler = &du_processor_rrc_ue_; } - async_task - on_ue_context_release_command(const cu_cp_ue_context_release_command& cmd) override - { - srsran_assert(du_processor_rrc_ue_handler != nullptr, "DU processor handler must not be nullptr"); - return du_processor_rrc_ue_handler->handle_ue_context_release_command(cmd); - } - async_task on_rrc_reestablishment_context_modification_required(ue_index_t ue_index) override { srsran_assert(du_processor_rrc_ue_handler != nullptr, "DU Processor task handler must not be nullptr"); @@ -150,13 +143,6 @@ class rrc_ue_ngap_adapter : public rrc_ue_nas_notifier, public rrc_ue_control_no ngap_nas_msg_handler->handle_ul_nas_transport_message(msg); } - async_task on_ue_context_release_request(const cu_cp_ue_context_release_request& msg) override - { - srsran_assert(ngap_ctrl_msg_handler != nullptr, "NGAP handler must not be nullptr"); - - return ngap_ctrl_msg_handler->handle_ue_context_release_request(msg); - } - void on_inter_cu_ho_rrc_recfg_complete_received(const ue_index_t ue_index, const nr_cell_global_id_t& cgi, const unsigned tac) override @@ -175,6 +161,8 @@ class rrc_ue_ngap_adapter : public rrc_ue_nas_notifier, public rrc_ue_control_no class rrc_ue_cu_cp_adapter : public rrc_ue_context_update_notifier, public rrc_ue_measurement_notifier { public: + rrc_ue_cu_cp_adapter(ue_index_t ue_index_) : ue_index(ue_index_) {} + void connect_cu_cp(cu_cp_rrc_ue_interface& cu_cp_rrc_ue_, cu_cp_ue_removal_handler& ue_removal_handler_, cu_cp_controller& ctrl_, @@ -192,34 +180,50 @@ class rrc_ue_cu_cp_adapter : public rrc_ue_context_update_notifier, public rrc_u return controller->request_ue_setup(); } - rrc_ue_reestablishment_context_response - on_rrc_reestablishment_request(pci_t old_pci, rnti_t old_c_rnti, ue_index_t ue_index) override + rrc_ue_reestablishment_context_response on_rrc_reestablishment_request(pci_t old_pci, rnti_t old_c_rnti) override { srsran_assert(cu_cp_rrc_ue_handler != nullptr, "CU-CP handler must not be nullptr"); return cu_cp_rrc_ue_handler->handle_rrc_reestablishment_request(old_pci, old_c_rnti, ue_index); } - async_task on_ue_transfer_required(ue_index_t ue_index, ue_index_t old_ue_index) override + void on_rrc_reestablishment_failure(const cu_cp_ue_context_release_request& request) override + { + srsran_assert(cu_cp_rrc_ue_handler != nullptr, "CU-CP handler must not be nullptr"); + return cu_cp_rrc_ue_handler->handle_rrc_reestablishment_failure(request); + } + + void on_rrc_reestablishment_complete(ue_index_t old_ue_index) override + { + srsran_assert(cu_cp_rrc_ue_handler != nullptr, "CU-CP handler must not be nullptr"); + return cu_cp_rrc_ue_handler->handle_rrc_reestablishment_complete(old_ue_index); + } + + async_task on_ue_transfer_required(ue_index_t old_ue_index) override { srsran_assert(cu_cp_rrc_ue_handler != nullptr, "CU-CP handler must not be nullptr"); return cu_cp_rrc_ue_handler->handle_ue_context_transfer(ue_index, old_ue_index); } - async_task on_ue_removal_required(ue_index_t ue_index) override + async_task on_ue_removal_required() override { srsran_assert(ue_removal_handler != nullptr, "CU-CP UE removal handler must not be nullptr"); return ue_removal_handler->handle_ue_removal_request(ue_index); } - optional on_measurement_config_request(ue_index_t ue_index, - nr_cell_id_t nci, + async_task on_ue_release_required(const cu_cp_ue_context_release_request& request) override + { + srsran_assert(cu_cp_rrc_ue_handler != nullptr, "CU-CP handler must not be nullptr"); + return cu_cp_rrc_ue_handler->handle_ue_context_release(request); + } + + optional on_measurement_config_request(nr_cell_id_t nci, optional current_meas_config = {}) override { srsran_assert(meas_handler != nullptr, "Measurement handler must not be nullptr"); return meas_handler->handle_measurement_config_request(ue_index, nci, current_meas_config); } - void on_measurement_report(const ue_index_t ue_index, const rrc_meas_results& meas_results) override + void on_measurement_report(const rrc_meas_results& meas_results) override { srsran_assert(meas_handler != nullptr, "Measurement handler must not be nullptr"); meas_handler->handle_measurement_report(ue_index, meas_results); @@ -230,6 +234,7 @@ class rrc_ue_cu_cp_adapter : public rrc_ue_context_update_notifier, public rrc_u cu_cp_ue_removal_handler* ue_removal_handler = nullptr; cu_cp_controller* controller = nullptr; cu_cp_measurement_handler* meas_handler = nullptr; + ue_index_t ue_index; }; } // namespace srs_cu_cp diff --git a/lib/cu_cp/cu_cp_impl.cpp b/lib/cu_cp/cu_cp_impl.cpp index 9fce317add..d09a0e7267 100644 --- a/lib/cu_cp/cu_cp_impl.cpp +++ b/lib/cu_cp/cu_cp_impl.cpp @@ -21,6 +21,7 @@ */ #include "cu_cp_impl.h" +#include "du_processor/du_processor_repository.h" #include "metrics_handler/metrics_handler_impl.h" #include "routines/ue_removal_routine.h" #include "srsran/cu_cp/cu_cp_types.h" @@ -209,6 +210,22 @@ cu_cp_impl::handle_rrc_reestablishment_request(pci_t old_pci, rnti_t old_c_rnti, return reest_context; } +void cu_cp_impl::handle_rrc_reestablishment_failure(const cu_cp_ue_context_release_request& request) +{ + auto* ue = ue_mng.find_ue(request.ue_index); + if (ue != nullptr) { + ue->get_task_sched().schedule_async_task(handle_ue_context_release(request)); + }; +} + +void cu_cp_impl::handle_rrc_reestablishment_complete(ue_index_t old_ue_index) +{ + auto* ue = ue_mng.find_ue(old_ue_index); + if (ue != nullptr) { + ue->get_task_sched().schedule_async_task(handle_ue_removal_request(old_ue_index)); + }; +} + async_task cu_cp_impl::handle_ue_context_transfer(ue_index_t ue_index, ue_index_t old_ue_index) { // Task to run in old UE task scheduler. @@ -276,6 +293,24 @@ void cu_cp_impl::handle_handover_ue_context_push(ue_index_t source_ue_index, ue_ cu_up_db.get_cu_up(uint_to_cu_up_index(0)).update_ue_index(target_ue_index, source_ue_index); } +async_task cu_cp_impl::handle_ue_context_release(const cu_cp_ue_context_release_request& request) +{ + return launch_async([this, result = bool{false}, request](coro_context>& ctx) mutable { + CORO_BEGIN(ctx); + + // Notify NGAP to request a release from the AMF + CORO_AWAIT_VALUE(result, + ngap_entity->get_ngap_control_message_handler().handle_ue_context_release_request(request)); + if (!result) { + // If NGAP release request was not sent to AMF, release UE from DU processor, RRC and F1AP + CORO_AWAIT(du_db.get_du(get_du_index_from_ue_index(request.ue_index)) + .get_du_processor_ue_context_notifier() + .handle_ue_context_release_command({request.ue_index, request.cause})); + } + CORO_RETURN(); + }); +} + optional cu_cp_impl::handle_measurement_config_request(ue_index_t ue_index, nr_cell_id_t nci, optional current_meas_config) diff --git a/lib/cu_cp/cu_cp_impl.h b/lib/cu_cp/cu_cp_impl.h index a31513ffa0..29d3e7d2e9 100644 --- a/lib/cu_cp/cu_cp_impl.h +++ b/lib/cu_cp/cu_cp_impl.h @@ -26,7 +26,6 @@ #include "adapters/cu_cp_adapters.h" #include "adapters/du_processor_adapters.h" #include "adapters/e1ap_adapters.h" -#include "adapters/f1ap_adapters.h" #include "adapters/ngap_adapters.h" #include "adapters/rrc_du_adapters.h" #include "adapters/rrc_ue_adapters.h" @@ -36,7 +35,6 @@ #include "du_processor/du_processor_repository.h" #include "routine_managers/cu_cp_routine_manager.h" #include "ue_manager/ue_manager_impl.h" -#include "ue_manager/ue_task_scheduler.h" #include "srsran/cu_cp/cu_cp_configuration.h" #include "srsran/cu_cp/cu_cp_types.h" #include "srsran/f1ap/cu_cp/f1ap_cu.h" @@ -70,8 +68,11 @@ class cu_cp_impl final : public cu_cp, public cu_cp_impl_interface, public cu_cp // RRC UE handler rrc_ue_reestablishment_context_response handle_rrc_reestablishment_request(pci_t old_pci, rnti_t old_c_rnti, ue_index_t ue_index) override; + void handle_rrc_reestablishment_failure(const cu_cp_ue_context_release_request& request) override; + void handle_rrc_reestablishment_complete(ue_index_t old_ue_index) override; async_task handle_ue_context_transfer(ue_index_t ue_index, ue_index_t old_ue_index) override; void handle_handover_ue_context_push(ue_index_t source_ue_index, ue_index_t target_ue_index) override; + async_task handle_ue_context_release(const cu_cp_ue_context_release_request& request) override; // cu_cp_measurement_handler optional handle_measurement_config_request(ue_index_t ue_index, diff --git a/lib/cu_cp/cu_cp_impl_interface.h b/lib/cu_cp/cu_cp_impl_interface.h index 326d91e4f8..1a65dfbf74 100644 --- a/lib/cu_cp/cu_cp_impl_interface.h +++ b/lib/cu_cp/cu_cp_impl_interface.h @@ -198,10 +198,22 @@ class cu_cp_rrc_ue_interface virtual rrc_ue_reestablishment_context_response handle_rrc_reestablishment_request(pci_t old_pci, rnti_t old_c_rnti, ue_index_t ue_index) = 0; + /// \brief Handle reestablishment failure by releasing the old UE. + /// \param[in] request The release request. + virtual void handle_rrc_reestablishment_failure(const cu_cp_ue_context_release_request& request) = 0; + + /// \brief Handle an successful reestablishment by removing the old UE. + /// \param[in] ue_index The index of the old UE to remove. + virtual void handle_rrc_reestablishment_complete(ue_index_t old_ue_index) = 0; + /// \brief Transfer and remove UE contexts for an ongoing Reestablishment. /// \param[in] ue_index The new UE index of the UE that sent the Reestablishment Request. /// \param[in] old_ue_index The old UE index of the UE that sent the Reestablishment Request. virtual async_task handle_ue_context_transfer(ue_index_t ue_index, ue_index_t old_ue_index) = 0; + + /// \brief Handle a UE release request. + /// \param[in] request The release request. + virtual async_task handle_ue_context_release(const cu_cp_ue_context_release_request& request) = 0; }; /// Interface for entities (e.g. DU processor) that wish to manipulate the context of a UE. @@ -210,6 +222,10 @@ class cu_cp_ue_context_manipulation_handler public: virtual ~cu_cp_ue_context_manipulation_handler() = default; + /// \brief Handle a UE release request. + /// \param[in] request The release request. + virtual async_task handle_ue_context_release(const cu_cp_ue_context_release_request& request) = 0; + /// \brief Transfer and remove UE contexts for an ongoing Reestablishment/Handover. /// \param[in] ue_index The new UE index of the UE that sent the Reestablishment Request or is the target UE. /// \param[in] old_ue_index The old UE index of the UE that sent the Reestablishment Request or is the source UE. diff --git a/lib/cu_cp/du_processor/du_processor_impl.cpp b/lib/cu_cp/du_processor/du_processor_impl.cpp index 1ae2435535..e9e4c23d10 100644 --- a/lib/cu_cp/du_processor/du_processor_impl.cpp +++ b/lib/cu_cp/du_processor/du_processor_impl.cpp @@ -313,26 +313,13 @@ void du_processor_impl::handle_du_initiated_ue_context_release_request(const f1a logger.debug("ue={}: Handling DU initiated UE context release request", request.ue_index); - bool ngap_release_successful = false; - // Schedule on UE task scheduler task_sched.schedule_async_task( - request.ue_index, - launch_async([this, request, ue, ngap_release_successful](coro_context>& ctx) mutable { + request.ue_index, launch_async([this, request, ue](coro_context>& ctx) mutable { CORO_BEGIN(ctx); - // Notify NGAP to request a release from the AMF - CORO_AWAIT_VALUE(ngap_release_successful, - ngap_ctrl_notifier.on_ue_context_release_request( - cu_cp_ue_context_release_request{request.ue_index, - ue->get_up_resource_manager().get_pdu_sessions(), - f1ap_to_ngap_cause(request.cause)})); - if (!ngap_release_successful) { - // Release UE from DU, if it doesn't exist in the NGAP - logger.debug("ue={}: Releasing UE from DU. ReleaseRequest not sent to AMF", request.ue_index); - CORO_AWAIT(handle_ue_context_release_command( - cu_cp_ue_context_release_command{request.ue_index, ngap_cause_radio_network_t::user_inactivity})); - } + CORO_AWAIT(cu_cp_notifier.on_ue_release_required( + {request.ue_index, ue->get_up_resource_manager().get_pdu_sessions(), f1ap_to_ngap_cause(request.cause)})); CORO_RETURN(); })); } diff --git a/lib/cu_cp/du_processor/du_processor_impl.h b/lib/cu_cp/du_processor/du_processor_impl.h index 3fc2d7b827..e552480a08 100644 --- a/lib/cu_cp/du_processor/du_processor_impl.h +++ b/lib/cu_cp/du_processor/du_processor_impl.h @@ -124,7 +124,7 @@ class du_processor_impl : public du_processor_impl_interface, public du_setup_ha du_processor_ue_task_handler& get_du_processor_ue_task_handler() override { return *this; } du_processor_ue_context_notifier& get_du_processor_ue_context_notifier() override { - return get_du_processor_rrc_ue_interface(); + return get_du_processor_ngap_interface(); } du_processor_paging_handler& get_du_processor_paging_handler() override { return *this; } du_processor_inactivity_handler& get_du_processor_inactivity_handler() override { return *this; } diff --git a/lib/cu_cp/du_processor/du_processor_impl_interface.h b/lib/cu_cp/du_processor/du_processor_impl_interface.h index f0961007e3..0adeca6ae9 100644 --- a/lib/cu_cp/du_processor/du_processor_impl_interface.h +++ b/lib/cu_cp/du_processor/du_processor_impl_interface.h @@ -26,13 +26,9 @@ #include "srsran/adt/optional.h" #include "srsran/adt/static_vector.h" #include "srsran/e1ap/cu_cp/e1ap_cu_cp_bearer_context_update.h" -#include "srsran/f1ap/cu_cp/du_setup_notifier.h" #include "srsran/f1ap/cu_cp/f1ap_cu.h" #include "srsran/ngap/ngap_handover.h" -#include "srsran/pdcp/pdcp_entity.h" #include "srsran/ran/nr_cgi.h" -#include "srsran/ran/rnti.h" -#include "srsran/rrc/rrc.h" #include "srsran/rrc/rrc_du.h" #include @@ -167,7 +163,7 @@ class du_processor_rrc_du_ue_notifier }; /// Interface for an RRC UE entity to communicate with the DU processor. -class du_processor_rrc_ue_interface : public du_processor_ue_context_notifier +class du_processor_rrc_ue_interface { public: virtual ~du_processor_rrc_ue_interface() = default; @@ -389,6 +385,10 @@ class du_processor_cu_cp_notifier /// \param[in] source_ue_index The index of the UE that is the source of the handover. /// \param[in] target_ue_index The index of the UE that is the target of the handover. virtual void on_handover_ue_context_push(ue_index_t source_ue_index, ue_index_t target_ue_index) = 0; + + /// \brief Notify the CU-CP to release a UE. + /// \param[in] request The release request. + virtual async_task on_ue_release_required(const cu_cp_ue_context_release_request& request) = 0; }; /// DU processor Paging handler. diff --git a/lib/cu_cp/ue_manager/ue_manager_impl.h b/lib/cu_cp/ue_manager/ue_manager_impl.h index df08c26e8d..aaac86e2d0 100644 --- a/lib/cu_cp/ue_manager/ue_manager_impl.h +++ b/lib/cu_cp/ue_manager/ue_manager_impl.h @@ -53,12 +53,15 @@ struct ngap_ue_t { class cu_cp_ue final : public du_ue, public ngap_ue, public rrc_ue_task_scheduler { public: - cu_cp_ue(const ue_index_t ue_index_, - const up_resource_manager_cfg up_cfg, - ue_task_scheduler task_sched_, - const pci_t pci_ = INVALID_PCI, - const rnti_t c_rnti_ = rnti_t::INVALID_RNTI) : - ue_index(ue_index_), task_sched(std::move(task_sched_)), up_mng(create_up_resource_manager(up_cfg)) + cu_cp_ue(const ue_index_t ue_index_, + const up_resource_manager_cfg& up_cfg, + ue_task_scheduler task_sched_, + const pci_t pci_ = INVALID_PCI, + const rnti_t c_rnti_ = rnti_t::INVALID_RNTI) : + ue_index(ue_index_), + task_sched(std::move(task_sched_)), + up_mng(create_up_resource_manager(up_cfg)), + rrc_ue_cu_cp_ev_notifier(ue_index) { if (pci_ != INVALID_PCI) { pci = pci_; diff --git a/lib/cu_cp/ue_manager/ue_task_scheduler.cpp b/lib/cu_cp/ue_manager/ue_task_scheduler.cpp index 788d989958..43e8be0db6 100644 --- a/lib/cu_cp/ue_manager/ue_task_scheduler.cpp +++ b/lib/cu_cp/ue_manager/ue_task_scheduler.cpp @@ -56,7 +56,7 @@ void ue_task_scheduler::stop() ue_task_scheduler_manager::ue_task_scheduler_manager(timer_manager& timers_, task_executor& exec_, srslog::basic_logger& logger_) : - timers(timers_), exec(exec_), logger(logger_), ues_to_rem(16) + timers(timers_), exec(exec_), logger(logger_), ues_to_rem(1024) { } @@ -118,14 +118,16 @@ void ue_task_scheduler_manager::rem_ue_task_loop(ue_index_t ue_idx) // Given that it might be a UE coroutine that calls the removal of the UE object, we defer the destruction of the UE // task scheduler to a separate task loop. // eager_async_task ue_task_loop = it->second.request_stop(); - ues_to_rem.schedule(launch_async([ue_sched = std::move(it->second)](coro_context>& ctx) mutable { - CORO_BEGIN(ctx); + bool ret = + ues_to_rem.schedule(launch_async([ue_sched = std::move(it->second)](coro_context>& ctx) mutable { + CORO_BEGIN(ctx); - // Cancel pending UE tasks and stop the task loop. - CORO_AWAIT(ue_sched->request_stop()); + // Cancel pending UE tasks and stop the task loop. + CORO_AWAIT(ue_sched->request_stop()); - CORO_RETURN(); - })); + CORO_RETURN(); + })); + srsran_assert(ret, "Failed to schedule UE task loop removal"); // Remove UE and free its index. ue_ctrl_loop.erase(it); diff --git a/lib/cu_up/pdu_session_manager_impl.cpp b/lib/cu_up/pdu_session_manager_impl.cpp index 65aafe9196..835d74ef8f 100644 --- a/lib/cu_up/pdu_session_manager_impl.cpp +++ b/lib/cu_up/pdu_session_manager_impl.cpp @@ -272,8 +272,12 @@ pdu_session_setup_result pdu_session_manager_impl::setup_pdu_session(const e1ap_ // Allocate local TEID new_session->local_teid = allocate_local_teid(new_session->pdu_session_id); - pdu_session_result.gtp_tunnel = up_transport_layer_info( - transport_layer_address::create_from_string(net_config.n3_bind_addr), new_session->local_teid); + // Advertise either local or external IP address of N3 interface + const std::string& n3_addr = net_config.n3_ext_addr.empty() || net_config.n3_ext_addr == "auto" + ? net_config.n3_bind_addr + : net_config.n3_ext_addr; + pdu_session_result.gtp_tunnel = + up_transport_layer_info(transport_layer_address::create_from_string(n3_addr), new_session->local_teid); // Create SDAP entity sdap_entity_creation_message sdap_msg = {ue_index, session.pdu_session_id, &new_session->sdap_to_gtpu_adapter}; diff --git a/lib/du_high/adapters/mac_test_mode_adapter.cpp b/lib/du_high/adapters/mac_test_mode_adapter.cpp index 6ab9bb3c9c..55ffd2c7c6 100644 --- a/lib/du_high/adapters/mac_test_mode_adapter.cpp +++ b/lib/du_high/adapters/mac_test_mode_adapter.cpp @@ -615,6 +615,7 @@ async_task mac_test_mode_adapter::handle_ue_create_reque if (ue_info_mgr.is_test_ue(cfg.crnti)) { // It is the test UE. mac_ue_create_request cfg_copy = cfg; + cfg_copy.initial_fallback = false; // Save UE index and configuration of test mode UE. ue_info_mgr.add_ue(cfg.crnti, cfg_copy.ue_index, cfg_copy.sched_cfg); diff --git a/lib/f1ap/common/CMakeLists.txt b/lib/f1ap/common/CMakeLists.txt index 032f9be32a..7d8de603c4 100644 --- a/lib/f1ap/common/CMakeLists.txt +++ b/lib/f1ap/common/CMakeLists.txt @@ -18,5 +18,5 @@ # and at http://www.gnu.org/licenses/. # -add_library(srsran_f1ap_common asn1_helpers.cpp f1ap_asn1_packer.cpp) +add_library(srsran_f1ap_common asn1_helpers.cpp f1ap_asn1_packer.cpp log_helpers.cpp) target_link_libraries(srsran_f1ap_common f1ap_asn1) diff --git a/lib/f1ap/common/f1ap_asn1_utils.h b/lib/f1ap/common/f1ap_asn1_utils.h index 623f55011e..2534ad76c0 100644 --- a/lib/f1ap/common/f1ap_asn1_utils.h +++ b/lib/f1ap/common/f1ap_asn1_utils.h @@ -72,17 +72,34 @@ inline const char* get_message_type_str(const asn1::f1ap::f1ap_pdu_c& pdu) inline optional get_transaction_id(const asn1::f1ap::init_msg_s& out) { using namespace asn1::f1ap; + using init_types = f1ap_elem_procs_o::init_msg_c::types_opts; switch (out.value.type().value) { - case f1ap_elem_procs_o::init_msg_c::types_opts::f1_setup_request: + case init_types::reset: + return out.value.reset()->transaction_id; + case init_types::f1_setup_request: return out.value.f1_setup_request()->transaction_id; - case f1ap_elem_procs_o::init_msg_c::types_opts::gnb_cu_cfg_upd: - return out.value.gnb_cu_cfg_upd()->transaction_id; - case f1ap_elem_procs_o::init_msg_c::types_opts::gnb_du_cfg_upd: + case init_types::gnb_du_cfg_upd: return out.value.gnb_du_cfg_upd()->transaction_id; - case f1ap_elem_procs_o::init_msg_c::types_opts::f1_removal_request: - return (*out.value.f1_removal_request())[0]->transaction_id(); - case f1ap_elem_procs_o::init_msg_c::types_opts::init_ul_rrc_msg_transfer: - return (*out.value.init_ul_rrc_msg_transfer()).transaction_id; + case init_types::gnb_cu_cfg_upd: + return out.value.gnb_cu_cfg_upd()->transaction_id; + case init_types::write_replace_warning_request: + return out.value.write_replace_warning_request()->transaction_id; + case init_types::pws_cancel_request: + return out.value.pws_cancel_request()->transaction_id; + case init_types::gnb_du_res_coordination_request: + return out.value.gnb_du_res_coordination_request()->transaction_id; + case init_types::f1_removal_request: { + const auto& list = *out.value.f1_removal_request(); + if (list.size() == 0) { + return nullopt; + } + return list[0]->transaction_id(); + } + // TODO: Remaining cases. + case init_types::error_ind: + return out.value.error_ind()->transaction_id; + case init_types::init_ul_rrc_msg_transfer: + return out.value.init_ul_rrc_msg_transfer()->transaction_id; // TODO: Remaining cases. default: break; @@ -94,14 +111,23 @@ inline optional get_transaction_id(const asn1::f1ap::init_msg_s& out) inline optional get_transaction_id(const asn1::f1ap::successful_outcome_s& out) { using namespace asn1::f1ap; + using success_types = f1ap_elem_procs_o::successful_outcome_c::types_opts; switch (out.value.type().value) { - case f1ap_elem_procs_o::successful_outcome_c::types_opts::f1_setup_resp: + case success_types::reset_ack: + return out.value.reset_ack()->transaction_id; + case success_types::f1_setup_resp: return out.value.f1_setup_resp()->transaction_id; - case f1ap_elem_procs_o::successful_outcome_c::types_opts::gnb_cu_cfg_upd_ack: - return out.value.gnb_cu_cfg_upd_ack()->transaction_id; - case f1ap_elem_procs_o::successful_outcome_c::types_opts::gnb_du_cfg_upd_ack: + case success_types::gnb_du_cfg_upd_ack: return out.value.gnb_du_cfg_upd_ack()->transaction_id; - case f1ap_elem_procs_o::successful_outcome_c::types_opts::f1_removal_resp: + case success_types::gnb_cu_cfg_upd_ack: + return out.value.gnb_cu_cfg_upd_ack()->transaction_id; + case success_types::write_replace_warning_resp: + return out.value.write_replace_warning_resp()->transaction_id; + case success_types::pws_cancel_resp: + return out.value.pws_cancel_resp()->transaction_id; + case success_types::gnb_du_res_coordination_resp: + return out.value.gnb_du_res_coordination_resp()->transaction_id; + case success_types::f1_removal_resp: return out.value.f1_removal_resp()->transaction_id; // TODO: Remaining cases. default: diff --git a/lib/f1ap/common/log_helpers.cpp b/lib/f1ap/common/log_helpers.cpp new file mode 100644 index 0000000000..7f50a580a9 --- /dev/null +++ b/lib/f1ap/common/log_helpers.cpp @@ -0,0 +1,95 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "log_helpers.h" +#include "f1ap_asn1_utils.h" +#include "srsran/support/format_utils.h" + +using namespace srsran; + +namespace fmt { + +template <> +struct formatter : public basic_fmt_parser { + template + auto format(const asn1::f1ap::f1ap_pdu_c& p, FormatContext& ctx) + { + asn1::json_writer js; + p.to_json(js); + return fmt::format_to(ctx.out(), "{}", js.to_string()); + } +}; + +} // namespace fmt + +template +void srsran::log_f1ap_pdu(srslog::basic_logger& logger, + bool is_rx, + gnb_du_id_t du_id, + const optional& ue_id, + const f1ap_message& msg, + bool json_enabled) +{ + if (not logger.info.enabled()) { + return; + } + + // Determine if it is a UE-dedicated message or common message. + optional transaction_id = get_transaction_id(msg.pdu); + optional cu_ue_id = get_gnb_cu_ue_f1ap_id(msg.pdu); + optional du_ue_id = get_gnb_du_ue_f1ap_id(msg.pdu); + const char* msg_name = get_message_type_str(msg.pdu); + + // Create PDU formatter that runs in log backend. + // Note: msg_name is a string literal and therefore it is ok to pass by pointer. + auto pdu_description = + make_formattable([is_rx, du_id, cu_ue_id, du_ue_id, ue_id, transaction_id, msg_name = msg_name](auto& ctx) { + return fmt::format_to(ctx.out(), + "{} PDU du={}{}{}{}{}: {}", + is_rx ? "Rx" : "Tx", + du_id, + add_prefix_if_set(" tid=", transaction_id), + add_prefix_if_set(" ue=", ue_id), + add_prefix_if_set(" cu_ue=", cu_ue_id), + add_prefix_if_set(" du_ue=", du_ue_id), + msg_name); + }); + + if (json_enabled) { + logger.info("{}\n{}", pdu_description, msg.pdu); + } else { + logger.info("{}", pdu_description); + } +} + +template void srsran::log_f1ap_pdu(srslog::basic_logger& logger, + bool is_rx, + gnb_du_id_t du_id, + const optional& ue_id, + const f1ap_message& msg, + bool json_enabled); +template void srsran::log_f1ap_pdu(srslog::basic_logger& logger, + bool is_rx, + gnb_du_id_t du_id, + const optional& ue_id, + const f1ap_message& msg, + bool json_enabled); diff --git a/lib/f1ap/common/log_helpers.h b/lib/f1ap/common/log_helpers.h new file mode 100644 index 0000000000..76de013bb5 --- /dev/null +++ b/lib/f1ap/common/log_helpers.h @@ -0,0 +1,56 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/adt/optional.h" +#include "srsran/cu_cp/cu_cp_types.h" +#include "srsran/f1ap/common/f1ap_message.h" +#include "srsran/ran/du_types.h" +#include "srsran/ran/gnb_du_id.h" +#include "srsran/srslog/srslog.h" + +namespace srsran { + +/// \brief Helper for logging Rx/Tx F1AP PDUs for the CU-CP and DU. +template +void log_f1ap_pdu(srslog::basic_logger& logger, + bool is_rx, + gnb_du_id_t du_id, + const optional& ue_id, + const f1ap_message& msg, + bool json_enabled); + +extern template void log_f1ap_pdu(srslog::basic_logger& logger, + bool is_rx, + gnb_du_id_t du_id, + const optional& ue_id, + const f1ap_message& msg, + bool json_enabled); +extern template void log_f1ap_pdu(srslog::basic_logger& logger, + bool is_rx, + gnb_du_id_t du_id, + const optional& ue_id, + const f1ap_message& msg, + bool json_enabled); + +} // namespace srsran \ No newline at end of file diff --git a/lib/f1ap/common/proc_logger.h b/lib/f1ap/common/proc_logger.h index 533c91b59c..bec900c832 100644 --- a/lib/f1ap/common/proc_logger.h +++ b/lib/f1ap/common/proc_logger.h @@ -66,10 +66,10 @@ struct formatter { bool first_id = true; auto get_sep = [&first_id]() { return std::exchange(first_id, false) ? "" : " "; }; if (prefix.du_ue_id != srsran::gnb_du_ue_f1ap_id_t::invalid) { - format_to(ctx.out(), "{}du_ue_id={}", get_sep(), prefix.du_ue_id); + format_to(ctx.out(), "{}du_ue={}", get_sep(), prefix.du_ue_id); } if (prefix.cu_ue_id != srsran::gnb_cu_ue_f1ap_id_t::invalid) { - format_to(ctx.out(), "{}cu_ue_id={}", get_sep(), prefix.cu_ue_id); + format_to(ctx.out(), "{}cu_ue={}", get_sep(), prefix.cu_ue_id); } if (prefix.proc_name != nullptr) { format_to(ctx.out(), "{}proc=\"{}\"", get_sep(), prefix.proc_name); diff --git a/lib/f1ap/cu_cp/f1ap_cu_impl.cpp b/lib/f1ap/cu_cp/f1ap_cu_impl.cpp index 4dfc6a0296..c703435447 100644 --- a/lib/f1ap/cu_cp/f1ap_cu_impl.cpp +++ b/lib/f1ap/cu_cp/f1ap_cu_impl.cpp @@ -23,6 +23,7 @@ #include "f1ap_cu_impl.h" #include "../common/asn1_helpers.h" #include "../common/f1ap_asn1_utils.h" +#include "../common/log_helpers.h" #include "f1ap_asn1_helpers.h" #include "procedures/f1_setup_procedure.h" #include "procedures/ue_context_modification_procedure.h" @@ -158,7 +159,8 @@ void f1ap_cu_impl::handle_message(const f1ap_message& msg) { // Run F1AP protocols in Control executor. if (not ctrl_exec.execute([this, msg]() { - log_rx_pdu(msg); + // Log received message. + log_pdu(true, msg); switch (msg.pdu.type().value) { case asn1::f1ap::f1ap_pdu_c::types_opts::init_msg: @@ -225,31 +227,30 @@ void f1ap_cu_impl::handle_initial_ul_rrc_message(const init_ul_rrc_msg_transfer_ nr_cell_global_id_t cgi = cgi_from_asn1(msg->nr_cgi); if (not config_helpers::is_valid(cgi)) { - logger.warning("du_ue_f1ap_id={}: Dropping InitialULRRCMessageTransfer. Invalid CGI", du_ue_id); + logger.warning("du_ue={}: Dropping InitialULRRCMessageTransfer. Invalid CGI", du_ue_id); return; } rnti_t crnti = to_rnti(msg->c_rnti); if (crnti == rnti_t::INVALID_RNTI) { - logger.warning("du_ue_f1ap_id={}: Dropping InitialULRRCMessageTransfer. Cause: Invalid C-RNTI", du_ue_id); + logger.warning("du_ue={}: Dropping InitialULRRCMessageTransfer. Cause: Invalid C-RNTI", du_ue_id); return; } if (msg->sul_access_ind_present) { - logger.debug("du_ue_f1ap_id={}: Ignoring SUL access indicator", du_ue_id); + logger.debug("du_ue={}: Ignoring SUL access indicator", du_ue_id); } const gnb_cu_ue_f1ap_id_t cu_ue_f1ap_id = ue_ctxt_list.next_gnb_cu_ue_f1ap_id(); if (cu_ue_f1ap_id == gnb_cu_ue_f1ap_id_t::invalid) { - logger.warning("du_ue_f1ap_id={}: Dropping InitialULRRCMessageTransfer. Cause: Failed to allocate CU-UE-F1AP-ID", - du_ue_id); + logger.warning("du_ue={}: Dropping InitialULRRCMessageTransfer. Cause: Failed to allocate CU-UE-F1AP-ID", du_ue_id); return; } // Create CU-CP UE instance. const ue_index_t ue_index = du_processor_notifier.on_new_cu_cp_ue_required(); if (ue_index == ue_index_t::invalid) { - logger.warning("du_ue_f1ap_id={}: Dropping InitialULRRCMessageTransfer. Cause: CU-CP UE creation failed", + logger.warning("du_ue={}: Dropping InitialULRRCMessageTransfer. Cause: CU-CP UE creation failed", msg->gnb_du_ue_f1ap_id); return; } @@ -264,7 +265,7 @@ void f1ap_cu_impl::handle_initial_ul_rrc_message(const init_ul_rrc_msg_transfer_ } else { // Assume the DU can't serve the UE, so the CU-CP should reject the UE, see TS 38.473 section 8.4.1.2. // We will forward an empty container to the RRC UE, that will trigger an RRC Reject - logger.debug("du_ue_f1ap_id={}: Forwarding InitialULRRCMessageTransfer to RRC to reject the UE. Cause: Missing DU " + logger.debug("du_ue={}: Forwarding InitialULRRCMessageTransfer to RRC to reject the UE. Cause: Missing DU " "to CU container", du_ue_id); req.du_to_cu_rrc_container = byte_buffer{}; @@ -273,7 +274,7 @@ void f1ap_cu_impl::handle_initial_ul_rrc_message(const init_ul_rrc_msg_transfer_ // Remove the UE if the creation was not successful if (resp.f1ap_rrc_notifier == nullptr) { - logger.warning("du_ue_f1ap_id={}: Dropping InitialULRRCMessageTransfer. Cause: UE RRC context creation failed", + logger.warning("du_ue={}: Dropping InitialULRRCMessageTransfer. Cause: UE RRC context creation failed", msg->gnb_du_ue_f1ap_id); return; } @@ -301,7 +302,7 @@ void f1ap_cu_impl::handle_initial_ul_rrc_message(const init_ul_rrc_msg_transfer_ void f1ap_cu_impl::handle_ul_rrc_message(const ul_rrc_msg_transfer_s& msg) { if (!ue_ctxt_list.contains(int_to_gnb_cu_ue_f1ap_id(msg->gnb_cu_ue_f1ap_id))) { - logger.warning("cu_ue_f1ap_id={} du_ue_f1ap_id={}: Dropping ULRRCMessageTransfer. UE context does not exist", + logger.warning("cu_ue={} du_ue={}: Dropping ULRRCMessageTransfer. UE context does not exist", msg->gnb_cu_ue_f1ap_id, msg->gnb_du_ue_f1ap_id); return; @@ -315,8 +316,6 @@ void f1ap_cu_impl::handle_ul_rrc_message(const ul_rrc_msg_transfer_s& msg) void f1ap_cu_impl::handle_f1_removal_request(const asn1::f1ap::f1_removal_request_s& msg) { - logger.debug("Received F1 Removal Request"); - du_index_t du_index = du_processor_notifier.get_du_index(); du_management_notifier.on_du_remove_request_received(du_index); } @@ -324,7 +323,7 @@ void f1ap_cu_impl::handle_f1_removal_request(const asn1::f1ap::f1_removal_reques void f1ap_cu_impl::handle_ue_context_release_request(const asn1::f1ap::ue_context_release_request_s& msg) { if (!ue_ctxt_list.contains(int_to_gnb_cu_ue_f1ap_id(msg->gnb_cu_ue_f1ap_id))) { - logger.warning("cu_ue_f1ap_id={} du_ue_f1ap_id={}: Dropping UeContextReleaseRequest. UE context does not exist", + logger.warning("cu_ue={} du_ue={}: Dropping UeContextReleaseRequest. UE context does not exist", msg->gnb_cu_ue_f1ap_id, msg->gnb_du_ue_f1ap_id); return; @@ -396,63 +395,42 @@ void f1ap_cu_impl::handle_unsuccessful_outcome(const asn1::f1ap::unsuccessful_ou } } -static auto log_pdu_helper(srslog::basic_logger& logger, - bool json_log, - bool is_rx, - gnb_du_id_t du_id, - const f1ap_ue_context_list& ue_ctxt_list, - const asn1::f1ap::f1ap_pdu_c& pdu) +void f1ap_cu_impl::log_pdu(bool is_rx, const f1ap_message& msg) { + using namespace asn1::f1ap; + if (not logger.info.enabled()) { return; } - optional du_ue_id = get_gnb_du_ue_f1ap_id(pdu); - optional cu_ue_id = get_gnb_cu_ue_f1ap_id(pdu); - ue_index_t ue_idx = ue_index_t::invalid; - if (cu_ue_id.has_value()) { - auto* ue = ue_ctxt_list.find(cu_ue_id.value()); - if (ue != nullptr) { - ue_idx = ue->ue_ids.ue_index; + // In case of F1 Setup, the gNB-DU-Id might not be set yet. + gnb_du_id_t du_id = du_ctxt.du_id; + if (du_id == gnb_du_id_t::invalid) { + if (msg.pdu.type().value == f1ap_pdu_c::types_opts::init_msg and + msg.pdu.init_msg().value.type().value == f1ap_elem_procs_o::init_msg_c::types_opts::f1_setup_request) { + du_id = int_to_gnb_du_id(msg.pdu.init_msg().value.f1_setup_request()->gnb_du_id); } } - // Custom formattable object whose formatting function will run in the log backend. - auto rx_pdu_log_entry = - make_formattable([is_rx, du_id, du_ue_id, cu_ue_id, ue_idx, msg_name = get_message_type_str(pdu)](auto& ctx) { - fmt::format_to(ctx.out(), "{} PDU", is_rx ? "Rx" : "Tx"); - if (du_id != srsran::gnb_du_id_t::invalid) { - fmt::format_to(ctx.out(), " du_id={}", du_id); - } - if (ue_idx != ue_index_t::invalid) { - fmt::format_to(ctx.out(), " ue={}", ue_idx); - } - if (du_ue_id.has_value()) { - fmt::format_to(ctx.out(), " du_ue_id={}", du_ue_id.value()); - } - if (cu_ue_id.has_value()) { - fmt::format_to(ctx.out(), " cu_ue_id={}", cu_ue_id.value()); - } - return fmt::format_to(ctx.out(), ": {}", msg_name); - }); - - if (json_log) { - asn1::json_writer js; - pdu.to_json(js); - logger.info("{}. Content:\n{}", rx_pdu_log_entry, js.to_string()); - } else { - logger.info("{}", rx_pdu_log_entry); + // Fetch UE index. + auto cu_ue_id = get_gnb_cu_ue_f1ap_id(msg.pdu); + optional ue_idx; + if (cu_ue_id.has_value()) { + auto* ue_ptr = ue_ctxt_list.find(cu_ue_id.value()); + if (ue_ptr != nullptr and ue_ptr->ue_ids.ue_index != ue_index_t::invalid) { + ue_idx = ue_ptr->ue_ids.ue_index; + } } -} -void f1ap_cu_impl::log_rx_pdu(const f1ap_message& msg) -{ - log_pdu_helper(logger, cfg.json_log_enabled, true, du_ctxt.du_id, ue_ctxt_list, msg.pdu); + // Log PDU. + log_f1ap_pdu(logger, is_rx, du_id, ue_idx, msg, cfg.json_log_enabled); } void f1ap_cu_impl::tx_pdu_notifier_with_logging::on_new_message(const f1ap_message& msg) { - log_pdu_helper(parent.logger, parent.cfg.json_log_enabled, false, parent.du_ctxt.du_id, parent.ue_ctxt_list, msg.pdu); + // Log message. + parent.log_pdu(false, msg); + // Forward message to DU. decorated.on_new_message(msg); } diff --git a/lib/f1ap/cu_cp/f1ap_cu_impl.h b/lib/f1ap/cu_cp/f1ap_cu_impl.h index 0af03cd04a..0b8b4b1719 100644 --- a/lib/f1ap/cu_cp/f1ap_cu_impl.h +++ b/lib/f1ap/cu_cp/f1ap_cu_impl.h @@ -134,8 +134,8 @@ class f1ap_cu_impl final : public f1ap_cu /// \param[in] msg The UE Context Release Request message. void handle_ue_context_release_request(const asn1::f1ap::ue_context_release_request_s& msg); - /// \brief Log F1AP RX PDU. - void log_rx_pdu(const f1ap_message& pdu); + /// \brief Log F1AP PDU. + void log_pdu(bool is_rx, const f1ap_message& pdu); const f1ap_configuration cfg; srslog::basic_logger& logger; diff --git a/lib/f1ap/cu_cp/procedures/ue_context_release_procedure.cpp b/lib/f1ap/cu_cp/procedures/ue_context_release_procedure.cpp index b38ee19f79..6062c644f7 100644 --- a/lib/f1ap/cu_cp/procedures/ue_context_release_procedure.cpp +++ b/lib/f1ap/cu_cp/procedures/ue_context_release_procedure.cpp @@ -78,13 +78,6 @@ void ue_context_release_procedure::send_ue_context_release_command() f1ap_ue_ctxt_rel_msg.pdu.init_msg().load_info_obj(ASN1_F1AP_ID_UE_CONTEXT_RELEASE); f1ap_ue_ctxt_rel_msg.pdu.init_msg().value.ue_context_release_cmd() = command; - if (ue_ctxt.logger.get_basic_logger().debug.enabled()) { - asn1::json_writer js; - f1ap_ue_ctxt_rel_msg.pdu.to_json(js); - logger.debug( - "{}: Containerized UEContextReleaseCommand: {}", f1ap_ue_log_prefix{ue_ctxt.ue_ids, name()}, js.to_string()); - } - // send UE Context Release Command f1ap_notifier.on_new_message(f1ap_ue_ctxt_rel_msg); } diff --git a/lib/f1ap/cu_cp/ue_context/f1ap_cu_ue_context.h b/lib/f1ap/cu_cp/ue_context/f1ap_cu_ue_context.h index 0d81201cad..9c5fc283c9 100644 --- a/lib/f1ap/cu_cp/ue_context/f1ap_cu_ue_context.h +++ b/lib/f1ap/cu_cp/ue_context/f1ap_cu_ue_context.h @@ -69,7 +69,7 @@ class f1ap_ue_context_list f1ap_ue_context& operator[](gnb_cu_ue_f1ap_id_t cu_ue_id) { - srsran_assert(ues.find(cu_ue_id) != ues.end(), "cu_ue_f1ap_id={}: F1AP UE context not found", cu_ue_id); + srsran_assert(ues.find(cu_ue_id) != ues.end(), "cu_ue={}: F1AP UE context not found", cu_ue_id); return ues.at(cu_ue_id); } f1ap_ue_context& operator[](ue_index_t ue_index) @@ -78,7 +78,7 @@ class f1ap_ue_context_list "ue={} gNB-CU-UE-F1AP-ID not found", ue_index); srsran_assert(ues.find(ue_index_to_ue_f1ap_id.at(ue_index)) != ues.end(), - "cu_ue_f1ap_id={}: F1AP UE context not found", + "cu_ue={}: F1AP UE context not found", ue_index_to_ue_f1ap_id.at(ue_index)); return ues.at(ue_index_to_ue_f1ap_id.at(ue_index)); } @@ -112,9 +112,9 @@ class f1ap_ue_context_list f1ap_ue_context& add_ue(ue_index_t ue_index, gnb_cu_ue_f1ap_id_t cu_ue_id) { srsran_assert(ue_index != ue_index_t::invalid, "Invalid ue_index={}", ue_index); - srsran_assert(cu_ue_id != gnb_cu_ue_f1ap_id_t::invalid, "Invalid cu_ue_id={}", cu_ue_id); + srsran_assert(cu_ue_id != gnb_cu_ue_f1ap_id_t::invalid, "Invalid cu_ue={}", cu_ue_id); - logger.debug("ue={} cu_ue_f1ap_id={}: Adding F1AP UE context", ue_index, cu_ue_id); + logger.debug("ue={} cu_ue={}: Adding F1AP UE context", ue_index, cu_ue_id); ues.emplace( std::piecewise_construct, std::forward_as_tuple(cu_ue_id), std::forward_as_tuple(ue_index, cu_ue_id, timers)); ue_index_to_ue_f1ap_id.emplace(ue_index, cu_ue_id); @@ -123,12 +123,12 @@ class f1ap_ue_context_list void add_du_ue_f1ap_id(gnb_cu_ue_f1ap_id_t cu_ue_id, gnb_du_ue_f1ap_id_t du_ue_id) { - srsran_assert(cu_ue_id != gnb_cu_ue_f1ap_id_t::invalid, "Invalid cu_ue_id={}", cu_ue_id); - srsran_assert(du_ue_id != gnb_du_ue_f1ap_id_t::invalid, "Invalid du_ue_id={}", du_ue_id); - srsran_assert(ues.find(cu_ue_id) != ues.end(), "cu_ue_id={}: F1AP UE context not found", cu_ue_id); + srsran_assert(cu_ue_id != gnb_cu_ue_f1ap_id_t::invalid, "Invalid cu_ue={}", cu_ue_id); + srsran_assert(du_ue_id != gnb_du_ue_f1ap_id_t::invalid, "Invalid du_ue={}", du_ue_id); + srsran_assert(ues.find(cu_ue_id) != ues.end(), "cu_ue={}: F1AP UE context not found", cu_ue_id); auto& ue = ues.at(cu_ue_id); - ue.logger.log_debug("Adding du_ue_f1ap_id={}", du_ue_id); + ue.logger.log_debug("Adding du_ue={}", du_ue_id); ue.ue_ids.du_ue_f1ap_id = du_ue_id; ue.logger.set_prefix({ue.ue_ids.ue_index, ue.ue_ids.cu_ue_f1ap_id, ue.ue_ids.du_ue_f1ap_id}, ": "); @@ -148,11 +148,11 @@ class f1ap_ue_context_list ue_index_to_ue_f1ap_id.erase(ue_index); if (ues.find(cu_ue_id) == ues.end()) { - logger.warning("cu_ue_f1ap_id={}: F1AP UE context not found", cu_ue_id); + logger.warning("cu_ue={}: F1AP UE context not found", cu_ue_id); return; } - logger.debug("ue={} cu_ue_f1ap_id={}: Removing F1AP UE context", ue_index, cu_ue_id); + logger.debug("ue={} cu_ue={}: Removing F1AP UE context", ue_index, cu_ue_id); ues.erase(cu_ue_id); } @@ -163,7 +163,7 @@ class f1ap_ue_context_list "ue={}: gNB-CU-UE-F1AP-ID not found", ue_index); srsran_assert(ues.find(ue_index_to_ue_f1ap_id.at(ue_index)) != ues.end(), - "cu_ue_f1ap_id={}: F1AP UE context not found", + "cu_ue={}: F1AP UE context not found", ue_index_to_ue_f1ap_id.at(ue_index)); ues.at(ue_index_to_ue_f1ap_id.at(ue_index)).rrc_notifier = notifier; } diff --git a/lib/f1ap/du/f1ap_du_connection_handler.cpp b/lib/f1ap/du/f1ap_du_connection_handler.cpp index 191e1465a5..165f8ffacd 100644 --- a/lib/f1ap/du/f1ap_du_connection_handler.cpp +++ b/lib/f1ap/du/f1ap_du_connection_handler.cpp @@ -49,19 +49,14 @@ f1ap_du_connection_handler::f1ap_du_connection_handler(f1c_connection_client& f1 { } -SRSRAN_NODISCARD bool f1ap_du_connection_handler::connect_to_cu_cp() +SRSRAN_NODISCARD std::unique_ptr f1ap_du_connection_handler::connect_to_cu_cp() { - f1ap_notifier = + std::unique_ptr f1ap_notifier = f1c_client_handler.handle_du_connection_request(std::make_unique(f1ap_pdu_handler)); - return f1ap_notifier != nullptr; -} - -void f1ap_du_connection_handler::on_new_message(const f1ap_message& msg) -{ - if (is_connected()) { - f1ap_notifier->on_new_message(msg); - } else { - logger.error("Discarding F1AP DU message. Cause: Connection to CU-CP is dropped"); + if (f1ap_notifier != nullptr) { + connected = true; } + + return f1ap_notifier; } diff --git a/lib/f1ap/du/f1ap_du_connection_handler.h b/lib/f1ap/du/f1ap_du_connection_handler.h index 847a53f0ba..30185e80ef 100644 --- a/lib/f1ap/du/f1ap_du_connection_handler.h +++ b/lib/f1ap/du/f1ap_du_connection_handler.h @@ -28,22 +28,23 @@ namespace srsran { namespace srs_du { -class f1ap_du_connection_handler : public f1ap_message_notifier +/// \brief Handler of TNL connection between DU and CU-CP. +class f1ap_du_connection_handler { public: f1ap_du_connection_handler(f1c_connection_client& f1c_client_handler_, f1ap_message_handler& f1ap_pdu_handler_); - SRSRAN_NODISCARD bool connect_to_cu_cp(); - SRSRAN_NODISCARD bool is_connected() const { return f1ap_notifier != nullptr; } + SRSRAN_NODISCARD std::unique_ptr connect_to_cu_cp(); - void on_new_message(const srsran::f1ap_message& msg) override; + /// \brief Check if the connection is active. + SRSRAN_NODISCARD bool is_connected() const { return connected; } private: f1c_connection_client& f1c_client_handler; f1ap_message_handler& f1ap_pdu_handler; srslog::basic_logger& logger; - std::unique_ptr f1ap_notifier; + bool connected = false; }; } // namespace srs_du diff --git a/lib/f1ap/du/f1ap_du_context.h b/lib/f1ap/du/f1ap_du_context.h index 009085f576..262c96d507 100644 --- a/lib/f1ap/du/f1ap_du_context.h +++ b/lib/f1ap/du/f1ap_du_context.h @@ -25,6 +25,7 @@ #include "srsran/adt/slotted_array.h" #include "srsran/du_manager/du_manager.h" #include "srsran/f1ap/du/f1ap_du.h" +#include "srsran/ran/gnb_du_id.h" namespace srsran { namespace srs_du { @@ -35,7 +36,7 @@ struct f1ap_du_cell_context { /// DU Context stored in the F1AP-DU. It includes information about the DU serving cells. struct f1ap_du_context { - uint64_t gnb_du_id; + gnb_du_id_t du_id = gnb_du_id_t::invalid; std::string gnb_du_name; std::vector served_cells; }; diff --git a/lib/f1ap/du/f1ap_du_impl.cpp b/lib/f1ap/du/f1ap_du_impl.cpp index 939ac41a4a..faf51aaf72 100644 --- a/lib/f1ap/du/f1ap_du_impl.cpp +++ b/lib/f1ap/du/f1ap_du_impl.cpp @@ -21,7 +21,8 @@ */ #include "f1ap_du_impl.h" -#include "common/asn1_helpers.h" +#include "../common/asn1_helpers.h" +#include "../common/log_helpers.h" #include "f1ap_du_connection_handler.h" #include "procedures/f1ap_du_setup_procedure.h" #include "procedures/f1ap_du_ue_context_release_procedure.h" @@ -55,6 +56,28 @@ class f1ap_rx_pdu_adapter final : public f1ap_message_notifier } // namespace +class f1ap_du_impl::tx_pdu_notifier_with_logging final : public f1ap_message_notifier +{ +public: + tx_pdu_notifier_with_logging(f1ap_du_impl& parent_, std::unique_ptr decorated_) : + parent(parent_), decorated(std::move(decorated_)) + { + } + + void on_new_message(const f1ap_message& msg) override + { + // Log message. + parent.log_pdu(false, msg); + + // Forward message to DU. + decorated->on_new_message(msg); + } + +private: + f1ap_du_impl& parent; + std::unique_ptr decorated; +}; + f1ap_du_impl::f1ap_du_impl(f1c_connection_client& f1c_client_handler_, f1ap_du_configurator& du_mng_, task_executor& ctrl_exec_, @@ -62,11 +85,11 @@ f1ap_du_impl::f1ap_du_impl(f1c_connection_client& f1c_client_handler_, f1ap_du_paging_notifier& paging_notifier_) : logger(srslog::fetch_basic_logger("DU-F1")), ctrl_exec(ctrl_exec_), - connection_handler(f1c_client_handler_, *this), du_mng(du_mng_), - ues(du_mng_, connection_handler, ctrl_exec, ue_exec_mapper_), - events(std::make_unique(du_mng.get_timer_factory())), - paging_notifier(paging_notifier_) + paging_notifier(paging_notifier_), + connection_handler(f1c_client_handler_, *this), + ues(du_mng, ctrl_exec, ue_exec_mapper_), + events(std::make_unique(du_mng.get_timer_factory())) { } @@ -75,12 +98,27 @@ f1ap_du_impl::~f1ap_du_impl() {} bool f1ap_du_impl::connect_to_cu_cp() { - return connection_handler.connect_to_cu_cp(); + std::unique_ptr pdu_notifier = connection_handler.connect_to_cu_cp(); + if (pdu_notifier == nullptr) { + return false; + } + + if (logger.info.enabled()) { + // Decorate notifier with logging, if the logger is enabled. + tx_pdu_notifier.reset(new tx_pdu_notifier_with_logging(*this, std::move(pdu_notifier))); + } else { + tx_pdu_notifier = std::move(pdu_notifier); + } + + // Update other components Tx PDU notifier. + ues.update_tx_pdu_notifier(*tx_pdu_notifier); + + return true; } async_task f1ap_du_impl::handle_f1_setup_request(const f1_setup_request_message& request) { - return launch_async(request, connection_handler, *events, du_mng.get_timer_factory(), ctxt); + return launch_async(request, *tx_pdu_notifier, *events, du_mng.get_timer_factory(), ctxt); } f1ap_ue_creation_response f1ap_du_impl::handle_ue_creation_request(const f1ap_ue_creation_request& msg) @@ -109,7 +147,7 @@ void f1ap_du_impl::handle_ue_deletion_request(du_ue_index_t ue_index) void f1ap_du_impl::handle_gnb_cu_configuration_update(const asn1::f1ap::gnb_cu_cfg_upd_s& msg) { - du_mng.schedule_async_task(launch_async(msg, connection_handler)); + du_mng.schedule_async_task(launch_async(msg, *tx_pdu_notifier)); } void f1ap_du_impl::handle_ue_context_setup_request(const asn1::f1ap::ue_context_setup_request_s& msg) @@ -185,9 +223,8 @@ void f1ap_du_impl::handle_dl_rrc_message_transfer(const asn1::f1ap::dl_rrc_msg_t if (ue == nullptr) { // [TS38.473, 8.4.2.2.] If no UE-associated logical F1-connection exists, the UE-associated logical F1-connection // shall be established at reception of the DL RRC MESSAGE TRANSFER message. - logger.warning( - "du_ue_id={}: Discarding DLRRCMessageTransfer. Cause: No UE found with the provided gNB-DU-UE-F1AP-ID", - gnb_du_ue_f1ap_id); + logger.warning("du_ue={}: Discarding DLRRCMessageTransfer. Cause: No UE found with the provided gNB-DU-UE-F1AP-ID", + gnb_du_ue_f1ap_id); // TODO. return; } @@ -291,29 +328,11 @@ f1ap_du_impl::handle_ue_context_modification_required(const f1ap_ue_context_modi void f1ap_du_impl::handle_message(const f1ap_message& msg) { - // Log message. - optional gnb_du_ue_f1ap_id = srsran::get_gnb_du_ue_f1ap_id(msg.pdu); - optional transaction_id = get_transaction_id(msg.pdu); - if (transaction_id.has_value()) { - logger.debug("Rx PDU \"{}::{}\" transaction_id={}", - msg.pdu.type().to_string(), - get_message_type_str(msg.pdu), - transaction_id.value()); - } else if (gnb_du_ue_f1ap_id.has_value()) { - logger.debug("Rx PDU \"{}::{}\" du_ue_id={}", - msg.pdu.type().to_string(), - get_message_type_str(msg.pdu), - gnb_du_ue_f1ap_id.value()); - } - - if (logger.debug.enabled()) { - asn1::json_writer js; - msg.pdu.to_json(js); - logger.debug("Containerized PDU: {}", js.to_string()); - } - // Run F1AP protocols in Control executor. if (not ctrl_exec.execute([this, msg]() { + // Log message. + log_pdu(true, msg); + switch (msg.pdu.type().value) { case asn1::f1ap::f1ap_pdu_c::types_opts::init_msg: handle_initiating_message(msg.pdu.init_msg()); @@ -542,3 +561,25 @@ du_ue_index_t f1ap_du_impl::get_ue_index(const gnb_cu_ue_f1ap_id_t& gnb_cu_ue_f1 } return du_ue_index; } + +void f1ap_du_impl::log_pdu(bool is_rx, const f1ap_message& msg) +{ + using namespace asn1::f1ap; + + if (not logger.info.enabled()) { + return; + } + + // Fetch UE index. + auto cu_ue_id = srsran::get_gnb_du_ue_f1ap_id(msg.pdu); + optional ue_idx; + if (cu_ue_id.has_value()) { + auto* ue_ptr = ues.find(cu_ue_id.value()); + if (ue_ptr != nullptr and ue_ptr->context.ue_index != INVALID_DU_UE_INDEX) { + ue_idx = ue_ptr->context.ue_index; + } + } + + // Log PDU. + log_f1ap_pdu(logger, is_rx, ctxt.du_id, ue_idx, msg, logger.debug.enabled()); +} diff --git a/lib/f1ap/du/f1ap_du_impl.h b/lib/f1ap/du/f1ap_du_impl.h index b113c83913..5065179ecf 100644 --- a/lib/f1ap/du/f1ap_du_impl.h +++ b/lib/f1ap/du/f1ap_du_impl.h @@ -79,6 +79,8 @@ class f1ap_du_impl final : public f1ap_du du_ue_index_t get_ue_index(const gnb_cu_ue_f1ap_id_t& gnb_cu_ue_f1ap_id) override; private: + class tx_pdu_notifier_with_logging; + /// \brief Notify the DU about the reception of an initiating message. /// \param[in] msg The received initiating message. void handle_initiating_message(const asn1::f1ap::init_msg_s& msg); @@ -113,12 +115,15 @@ class f1ap_du_impl final : public f1ap_du /// \brief Handle Paging as per TS38.473, Section 8.7. void handle_paging_request(const asn1::f1ap::paging_s& msg); - srslog::basic_logger& logger; - task_executor& ctrl_exec; + /// \brief Log F1AP PDU. + void log_pdu(bool is_rx, const f1ap_message& pdu); - f1ap_du_connection_handler connection_handler; + srslog::basic_logger& logger; + task_executor& ctrl_exec; + f1ap_du_configurator& du_mng; + f1ap_du_paging_notifier& paging_notifier; - f1ap_du_configurator& du_mng; + f1ap_du_connection_handler connection_handler; f1ap_du_ue_manager ues; @@ -126,7 +131,7 @@ class f1ap_du_impl final : public f1ap_du std::unique_ptr events; - f1ap_du_paging_notifier& paging_notifier; + std::unique_ptr tx_pdu_notifier; }; } // namespace srs_du diff --git a/lib/f1ap/du/procedures/f1ap_du_setup_procedure.cpp b/lib/f1ap/du/procedures/f1ap_du_setup_procedure.cpp index 98f5e55a62..ccaf5ed381 100644 --- a/lib/f1ap/du/procedures/f1ap_du_setup_procedure.cpp +++ b/lib/f1ap/du/procedures/f1ap_du_setup_procedure.cpp @@ -81,6 +81,9 @@ void f1ap_du_setup_procedure::operator()(coro_contextcause)); - res.success = false; + res.success = false; + du_ctxt.du_id = gnb_du_id_t::invalid; } return res; } diff --git a/lib/f1ap/du/procedures/f1ap_du_ue_context_modification_procedure.cpp b/lib/f1ap/du/procedures/f1ap_du_ue_context_modification_procedure.cpp index 5e9089cad7..dbe5c70613 100644 --- a/lib/f1ap/du/procedures/f1ap_du_ue_context_modification_procedure.cpp +++ b/lib/f1ap/du/procedures/f1ap_du_ue_context_modification_procedure.cpp @@ -32,18 +32,29 @@ using namespace asn1::f1ap; f1ap_du_ue_context_modification_procedure::f1ap_du_ue_context_modification_procedure( const asn1::f1ap::ue_context_mod_request_s& msg, f1ap_du_ue& ue_) : - ue(ue_), logger(srslog::fetch_basic_logger("DU-F1")) + req(msg), ue(ue_), logger(srslog::fetch_basic_logger("DU-F1")) { - create_du_request(msg); } void f1ap_du_ue_context_modification_procedure::operator()(coro_context>& ctx) { CORO_BEGIN(ctx); - // Setup new UE configuration in DU. + // Modify UE configuration in DU. + create_du_request(req); CORO_AWAIT_VALUE(du_response, ue.du_handler.request_ue_context_update(du_request)); + // "If the UE CONTEXT MODIFICATION REQUEST message contains the RRC-Container IE, the gNB-DU shall send the + // corresponding RRC message to the UE." + if (du_response.result and req->rrc_container_present) { + auto* srb = ue.bearers.find_srb(srb_id_t::srb1); + if (srb != nullptr) { + srb->handle_pdu(req->rrc_container.copy()); + } else { + du_response.result = false; + } + } + if (du_response.result) { send_ue_context_modification_response(); } else { diff --git a/lib/f1ap/du/procedures/f1ap_du_ue_context_modification_procedure.h b/lib/f1ap/du/procedures/f1ap_du_ue_context_modification_procedure.h index dcc3a17081..9f168dc20c 100644 --- a/lib/f1ap/du/procedures/f1ap_du_ue_context_modification_procedure.h +++ b/lib/f1ap/du/procedures/f1ap_du_ue_context_modification_procedure.h @@ -40,8 +40,9 @@ class f1ap_du_ue_context_modification_procedure void send_ue_context_modification_response(); void send_ue_context_modification_failure(); - f1ap_du_ue& ue; - f1ap_ue_context_update_request du_request; + const asn1::f1ap::ue_context_mod_request_s req; + f1ap_du_ue& ue; + f1ap_ue_context_update_request du_request; f1ap_ue_context_update_response du_response; diff --git a/lib/f1ap/du/ue_context/f1ap_du_ue_manager.h b/lib/f1ap/du/ue_context/f1ap_du_ue_manager.h index a3e9c59f9a..a47d23ce8f 100644 --- a/lib/f1ap/du/ue_context/f1ap_du_ue_manager.h +++ b/lib/f1ap/du/ue_context/f1ap_du_ue_manager.h @@ -35,23 +35,23 @@ class f1ap_du_ue_manager { public: f1ap_du_ue_manager(f1ap_du_configurator& du_handler_, - f1ap_message_notifier& f1ap_msg_notifier_, task_executor& ctrl_exec_, du_high_ue_executor_mapper& ue_exec_mapper_) : - du_handler(du_handler_), - f1ap_msg_notifier(f1ap_msg_notifier_), - ctrl_exec(ctrl_exec_), - ue_exec_mapper(ue_exec_mapper_) + du_handler(du_handler_), ctrl_exec(ctrl_exec_), ue_exec_mapper(ue_exec_mapper_) { } + /// Called when a new connection is established to the CU-CP. + void update_tx_pdu_notifier(f1ap_message_notifier& f1ap_msg_notifier_) { f1ap_msg_notifier = &f1ap_msg_notifier_; } + f1ap_du_ue& add_ue(du_ue_index_t ue_index) { + srsran_sanity_check(f1ap_msg_notifier != nullptr, "Creating a UE before a connection to the CU-CP is established"); srsran_assert(not ues.contains(ue_index), "Duplicate ueId={} detected", ue_index); gnb_du_ue_f1ap_id_t f1ap_id = static_cast(next_gnb_f1ap_du_ue_id++); ues.emplace( - ue_index, ue_index, f1ap_id, du_handler, f1ap_msg_notifier, ctrl_exec, ue_exec_mapper.ctrl_executor(ue_index)); + ue_index, ue_index, f1ap_id, du_handler, *f1ap_msg_notifier, ctrl_exec, ue_exec_mapper.ctrl_executor(ue_index)); { std::lock_guard lock(map_mutex); @@ -113,7 +113,7 @@ class f1ap_du_ue_manager private: f1ap_du_configurator& du_handler; - f1ap_message_notifier& f1ap_msg_notifier; + f1ap_message_notifier* f1ap_msg_notifier = nullptr; task_executor& ctrl_exec; du_high_ue_executor_mapper& ue_exec_mapper; diff --git a/lib/f1ap/du/ue_context/f1ap_ue_context.h b/lib/f1ap/du/ue_context/f1ap_ue_context.h index bc4fbb45b8..1f22fb3489 100644 --- a/lib/f1ap/du/ue_context/f1ap_ue_context.h +++ b/lib/f1ap/du/ue_context/f1ap_ue_context.h @@ -61,10 +61,10 @@ struct formatter { auto format(const srsran::srs_du::f1ap_ue_context& ue, FormatContext& ctx) { if (ue.gnb_cu_ue_f1ap_id == srsran::gnb_cu_ue_f1ap_id_t::invalid) { - return format_to(ctx.out(), "ue={} c-rnti={} du_ue_id={}", ue.ue_index, ue.rnti, ue.gnb_du_ue_f1ap_id); + return format_to(ctx.out(), "ue={} c-rnti={} du_ue={}", ue.ue_index, ue.rnti, ue.gnb_du_ue_f1ap_id); } return format_to(ctx.out(), - "ue={} c-rnti={} du_ue_id={} cu_ue_id={}", + "ue={} c-rnti={} du_ue={} cu_ue={}", ue.ue_index, ue.rnti, ue.gnb_du_ue_f1ap_id, diff --git a/lib/f1ap/du/ue_context/f1c_du_bearer_impl.cpp b/lib/f1ap/du/ue_context/f1c_du_bearer_impl.cpp index f209c03267..6840fb9778 100644 --- a/lib/f1ap/du/ue_context/f1c_du_bearer_impl.cpp +++ b/lib/f1ap/du/ue_context/f1c_du_bearer_impl.cpp @@ -77,20 +77,16 @@ void f1c_srb0_du_bearer::handle_sdu(byte_buffer_chain sdu) // Notify upper layers of the initial UL RRC Message Transfer. f1ap_notifier.on_new_message(msg); - - logger.info("UL {} SRB0 Tx PDU: Initial UL RRC Message Transfer", ue_ctxt); })) { - logger.error("UL {} SRB0: Discarding Tx PDU. Cause: The task executor queue is full.", ue_ctxt); + logger.error("Tx PDU {}: Discarding SRB0 Tx PDU. Cause: The task executor queue is full.", ue_ctxt); } } void f1c_srb0_du_bearer::handle_pdu(byte_buffer pdu) { - logger.info("DL {} SRB0 Rx PDU: DL RRC Message Transfer", ue_ctxt); - // Change to UE execution context before forwarding the SDU to lower layers. if (not ue_exec.execute([this, sdu = std::move(pdu)]() mutable { sdu_notifier.on_new_sdu(std::move(sdu), {}); })) { - logger.error("DL {} SRB0 Rx PDU: Discarding Rx PDU. Cause: The task executor queue is full.", ue_ctxt); + logger.error("Rx {} PDU: Discarding SRB0 Rx PDU. Cause: The task executor queue is full.", ue_ctxt); } } @@ -116,12 +112,11 @@ void f1c_other_srb_du_bearer::handle_sdu(byte_buffer_chain sdu) if (not ctrl_exec.execute([this, sdu = std::move(sdu)]() { gnb_cu_ue_f1ap_id_t cu_ue_id = ue_ctxt.gnb_cu_ue_f1ap_id; if (cu_ue_id >= gnb_cu_ue_f1ap_id_t::max) { - logger.warning( - "ue={} rnti={} du_ue_id={} SRB={}: Discarding F1AP RX SDU. Cause: GNB-CU-UE-F1AP-ID is invalid.", - ue_ctxt.ue_index, - ue_ctxt.rnti, - ue_ctxt.gnb_du_ue_f1ap_id, - srb_id_to_uint(srb_id)); + logger.warning("ue={} rnti={} du_ue={} SRB={}: Discarding F1AP RX SDU. Cause: GNB-CU-UE-F1AP-ID is invalid.", + ue_ctxt.ue_index, + ue_ctxt.rnti, + ue_ctxt.gnb_du_ue_f1ap_id, + srb_id_to_uint(srb_id)); return; } @@ -134,7 +129,7 @@ void f1c_other_srb_du_bearer::handle_sdu(byte_buffer_chain sdu) ul_msg->gnb_cu_ue_f1ap_id = gnb_cu_ue_f1ap_id_to_uint(ue_ctxt.gnb_cu_ue_f1ap_id); ul_msg->srb_id = srb_id_to_uint(srb_id); if (not ul_msg->rrc_container.append(sdu.begin(), sdu.end())) { - logger.error("UL {} SRB{} Tx PDU: Discarding Tx PDU. Cause: Failed to append SDU to RRC container.", + logger.error("Tx PDU {}: Discarding SRB{} Tx PDU. Cause: Failed to append SDU to RRC container.", ue_ctxt, srb_id_to_uint(srb_id)); return; @@ -143,19 +138,16 @@ void f1c_other_srb_du_bearer::handle_sdu(byte_buffer_chain sdu) ul_msg->new_gnb_du_ue_f1ap_id_present = false; f1ap_notifier.on_new_message(msg); - - logger.info("UL {} SRB{} Tx PDU: UL RRC Message Transfer", ue_ctxt, srb_id_to_uint(srb_id)); })) { - logger.error("UL {} SRB{} Tx PDU: Discarding Tx PDU. Cause: The task executor queue is full.", - ue_ctxt, - srb_id_to_uint(srb_id)); + logger.error( + "Tx PDU {}: Discarding SRB{} Tx PDU. Cause: The task executor queue is full.", ue_ctxt, srb_id_to_uint(srb_id)); } } void f1c_other_srb_du_bearer::handle_pdu(srsran::byte_buffer pdu) { if (pdu.length() < 3) { - logger.warning("DL {} SRB{} Rx PDU: Invalid PDU length. Dropping PDU.", ue_ctxt, srb_id_to_uint(srb_id)); + logger.warning("DL {} Rx PDU: Dropping PDU. Cause: Invalid SRB{}.", ue_ctxt, srb_id_to_uint(srb_id)); return; } @@ -164,10 +156,9 @@ void f1c_other_srb_du_bearer::handle_pdu(srsran::byte_buffer pdu) // Change to UE execution context before forwarding the SDU to lower layers. if (not ue_exec.execute( [this, sdu = std::move(pdu), pdcp_sn]() mutable { sdu_notifier.on_new_sdu(std::move(sdu), pdcp_sn); })) { - logger.error( - "{} SRB{} Rx PDU: Discarding Rx PDU. Cause: The task executor queue is full.", ue_ctxt, srb_id_to_uint(srb_id)); - } else { - logger.info("DL {} SRB{} Rx PDU: DL RRC Message Transfer", ue_ctxt, srb_id_to_uint(srb_id)); + logger.error("Rx PDU {}: Discarding SRB{} Rx PDU. Cause: The task executor queue is full.", + ue_ctxt, + srb_id_to_uint(srb_id)); } } diff --git a/lib/fapi_adaptor/mac/messages/pdsch.cpp b/lib/fapi_adaptor/mac/messages/pdsch.cpp index 154a445ac3..109699536a 100644 --- a/lib/fapi_adaptor/mac/messages/pdsch.cpp +++ b/lib/fapi_adaptor/mac/messages/pdsch.cpp @@ -77,12 +77,13 @@ static void fill_codewords(fapi::dl_pdsch_pdu_builder& builder, span paylo { logger.debug("Received data of {} bytes", payload.size_bytes()); - auto payload_buffer = byte_buffer::create(payload.begin(), payload.end()); - if (payload_buffer.is_error()) { - logger.warning("Unable to allocate byte_buffer"); - return; - } - data_notifier.on_new_pdu(std::move(payload_buffer.value())); + // Note: For SCTP, we avoid byte buffer allocation failures by resorting to fallback allocation. + data_notifier.on_new_pdu(byte_buffer{byte_buffer::fallback_allocation_tag{}, payload}); } ///< Process outgoing PDU and send over SCTP socket to peer. diff --git a/lib/mac/mac_sched/srsran_scheduler_adapter.cpp b/lib/mac/mac_sched/srsran_scheduler_adapter.cpp index 1529a0ba92..8499d99ed0 100644 --- a/lib/mac/mac_sched/srsran_scheduler_adapter.cpp +++ b/lib/mac/mac_sched/srsran_scheduler_adapter.cpp @@ -32,7 +32,7 @@ static sched_ue_creation_request_message make_scheduler_ue_creation_request(cons sched_ue_creation_request_message ret{}; ret.ue_index = request.ue_index; ret.crnti = request.crnti; - ret.starts_in_fallback = true; + ret.starts_in_fallback = request.initial_fallback; ret.cfg = request.sched_cfg; ret.tag_config = request.mac_cell_group_cfg.tag_config; return ret; diff --git a/lib/ngap/CMakeLists.txt b/lib/ngap/CMakeLists.txt index 6166de9dfe..d1a00f6b3c 100644 --- a/lib/ngap/CMakeLists.txt +++ b/lib/ngap/CMakeLists.txt @@ -19,9 +19,11 @@ # set(SOURCES + ngap_asn1_packer.cpp + ngap_asn1_utils.cpp + log_helpers.cpp ngap_factory.cpp ngap_impl.cpp - ngap_asn1_packer.cpp ngap_validators/ngap_validators.cpp procedures/ng_setup_procedure.cpp procedures/ngap_initial_context_setup_procedure.cpp diff --git a/lib/ngap/log_helpers.cpp b/lib/ngap/log_helpers.cpp new file mode 100644 index 0000000000..3aa0071ac5 --- /dev/null +++ b/lib/ngap/log_helpers.cpp @@ -0,0 +1,74 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "log_helpers.h" +#include "ngap_asn1_utils.h" +#include "srsran/support/format_utils.h" + +using namespace srsran; + +namespace fmt { + +template <> +struct formatter : public basic_fmt_parser { + template + auto format(const asn1::ngap::ngap_pdu_c& p, FormatContext& ctx) + { + asn1::json_writer js; + p.to_json(js); + return fmt::format_to(ctx.out(), "{}", js.to_string()); + } +}; + +} // namespace fmt + +void srsran::srs_cu_cp::log_ngap_pdu(srslog::basic_logger& logger, + bool json_log, + bool is_rx, + const optional& ue_idx, + const asn1::ngap::ngap_pdu_c& pdu) +{ + if (not logger.info.enabled()) { + return; + } + + optional ran_ue_id = get_ran_ue_id(pdu); + optional amf_ue_id = get_amf_ue_id(pdu); + + // Custom formattable object whose formatting function will run in the log backend. + auto pdu_log_entry = + make_formattable([is_rx, ran_ue_id, amf_ue_id, ue_idx, msg_name = get_message_type_str(pdu)](auto& ctx) { + return fmt::format_to(ctx.out(), + "{} PDU{}{}{}: {}", + is_rx ? "Rx" : "Tx", + add_prefix_if_set(" ue=", ue_idx), + add_prefix_if_set(" ran_ue=", ran_ue_id), + add_prefix_if_set(" amf_ue=", amf_ue_id), + msg_name); + }); + + if (json_log) { + logger.info("{}\n{}", pdu_log_entry, pdu); + } else { + logger.info("{}", pdu_log_entry); + } +} diff --git a/lib/ngap/log_helpers.h b/lib/ngap/log_helpers.h new file mode 100644 index 0000000000..9e70ae91b4 --- /dev/null +++ b/lib/ngap/log_helpers.h @@ -0,0 +1,39 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/asn1/ngap/ngap.h" +#include "srsran/cu_cp/cu_cp_types.h" + +namespace srsran { +namespace srs_cu_cp { + +/// \brief Log Received/Transmitted NGAP PDU. +void log_ngap_pdu(srslog::basic_logger& logger, + bool json_log, + bool is_rx, + const optional& ue_idx, + const asn1::ngap::ngap_pdu_c& pdu); + +} // namespace srs_cu_cp +} // namespace srsran \ No newline at end of file diff --git a/lib/ngap/ngap_asn1_packer.cpp b/lib/ngap/ngap_asn1_packer.cpp index 9bebdce95a..467f32390d 100644 --- a/lib/ngap/ngap_asn1_packer.cpp +++ b/lib/ngap/ngap_asn1_packer.cpp @@ -56,14 +56,8 @@ void ngap_asn1_packer::handle_packed_pdu(const byte_buffer& bytes) // Receive populated ASN1 struct that needs to be packed and forwarded. void ngap_asn1_packer::handle_message(const srs_cu_cp::ngap_message& msg) { - if (logger.debug.enabled()) { - asn1::json_writer js; - msg.pdu.to_json(js); - logger.debug("Tx NGAP PDU: {}", js.to_string()); - } - // pack PDU into temporary buffer - byte_buffer tx_pdu; + byte_buffer tx_pdu{byte_buffer::fallback_allocation_tag{}}; asn1::bit_ref bref(tx_pdu); if (msg.pdu.pack(bref) != asn1::SRSASN_SUCCESS) { logger.error("Failed to pack PDU"); diff --git a/lib/ngap/ngap_asn1_utils.cpp b/lib/ngap/ngap_asn1_utils.cpp new file mode 100644 index 0000000000..3d66e850e2 --- /dev/null +++ b/lib/ngap/ngap_asn1_utils.cpp @@ -0,0 +1,322 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "ngap_asn1_utils.h" +#include "srsran/asn1/ngap/ngap.h" +#include "srsran/asn1/ngap/ngap_pdu_contents.h" +#include "srsran/ngap/ngap_types.h" + +using namespace srsran; +using namespace srsran::srs_cu_cp; +using namespace asn1::ngap; + +const char* srsran::srs_cu_cp::get_cause_str(const asn1::ngap::cause_c& cause) +{ + using namespace asn1::ngap; + switch (cause.type()) { + case cause_c::types_opts::radio_network: + return cause.radio_network().to_string(); + case cause_c::types_opts::transport: + return cause.transport().to_string(); + case cause_c::types_opts::nas: + return cause.nas().to_string(); + case cause_c::types_opts::protocol: + return cause.protocol().to_string(); + case cause_c::types_opts::misc: + return cause.misc().to_string(); + default: + break; + } + return "unknown"; +} + +const char* srsran::srs_cu_cp::get_message_type_str(const asn1::ngap::ngap_pdu_c& pdu) +{ + using namespace asn1::ngap; + switch (pdu.type().value) { + case ngap_pdu_c::types_opts::init_msg: + return pdu.init_msg().value.type().to_string(); + case ngap_pdu_c::types_opts::successful_outcome: + return pdu.successful_outcome().value.type().to_string(); + case ngap_pdu_c::types_opts::unsuccessful_outcome: + return pdu.unsuccessful_outcome().value.type().to_string(); + default: + break; + } + report_fatal_error("Invalid NGAP PDU type \"{}\"", pdu.type().to_string()); +} + +optional srsran::srs_cu_cp::get_ran_ue_id(const asn1::ngap::init_msg_s& init_msg) +{ + using namespace asn1::ngap; + using init_types = ngap_elem_procs_o::init_msg_c::types_opts; + + switch (init_msg.value.type()) { + case init_types::ho_cancel: + return uint_to_ran_ue_id(init_msg.value.ho_cancel()->ran_ue_ngap_id); + case init_types::ho_required: + return uint_to_ran_ue_id(init_msg.value.ho_required()->ran_ue_ngap_id); + case init_types::init_context_setup_request: + return uint_to_ran_ue_id(init_msg.value.init_context_setup_request()->ran_ue_ngap_id); + case init_types::pdu_session_res_modify_request: + return uint_to_ran_ue_id(init_msg.value.pdu_session_res_modify_request()->ran_ue_ngap_id); + case init_types::pdu_session_res_modify_ind: + return uint_to_ran_ue_id(init_msg.value.pdu_session_res_modify_ind()->ran_ue_ngap_id); + case init_types::pdu_session_res_release_cmd: + return uint_to_ran_ue_id(init_msg.value.pdu_session_res_release_cmd()->ran_ue_ngap_id); + case init_types::pdu_session_res_setup_request: + return uint_to_ran_ue_id(init_msg.value.pdu_session_res_setup_request()->ran_ue_ngap_id); + case init_types::ue_context_mod_request: + return uint_to_ran_ue_id(init_msg.value.ue_context_mod_request()->ran_ue_ngap_id); + case init_types::ue_context_release_cmd: + if (init_msg.value.ue_context_release_cmd()->ue_ngap_ids.type().value == + ue_ngap_ids_c::types_opts::ue_ngap_id_pair) { + return uint_to_ran_ue_id(init_msg.value.ue_context_release_cmd()->ue_ngap_ids.ue_ngap_id_pair().ran_ue_ngap_id); + } + return nullopt; + case init_types::ue_context_resume_request: + return uint_to_ran_ue_id(init_msg.value.ue_context_resume_request()->ran_ue_ngap_id); + case init_types::ue_context_suspend_request: + return uint_to_ran_ue_id(init_msg.value.ue_context_suspend_request()->ran_ue_ngap_id); + case init_types::dl_nas_transport: + return uint_to_ran_ue_id(init_msg.value.dl_nas_transport()->ran_ue_ngap_id); + case init_types::error_ind: + if (init_msg.value.error_ind()->ran_ue_ngap_id_present) { + return uint_to_ran_ue_id(init_msg.value.error_ind()->ran_ue_ngap_id); + } + break; + case init_types::init_ue_msg: + return uint_to_ran_ue_id(init_msg.value.init_ue_msg()->ran_ue_ngap_id); + case init_types::ul_nas_transport: + return uint_to_ran_ue_id(init_msg.value.ul_nas_transport()->ran_ue_ngap_id); + default: + break; + } + return nullopt; +} + +optional srsran::srs_cu_cp::get_ran_ue_id(const asn1::ngap::successful_outcome_s& success_outcome) +{ + using namespace asn1::ngap; + using success_types = ngap_elem_procs_o::successful_outcome_c::types_opts; + + switch (success_outcome.value.type().value) { + case success_types::ho_cancel_ack: + return uint_to_ran_ue_id(success_outcome.value.ho_cancel_ack()->ran_ue_ngap_id); + case success_types::ho_cmd: + return uint_to_ran_ue_id(success_outcome.value.ho_cmd()->ran_ue_ngap_id); + case success_types::ho_request_ack: + return uint_to_ran_ue_id(success_outcome.value.ho_request_ack()->ran_ue_ngap_id); + case success_types::init_context_setup_resp: + return uint_to_ran_ue_id(success_outcome.value.init_context_setup_resp()->ran_ue_ngap_id); + case success_types::pdu_session_res_modify_resp: + return uint_to_ran_ue_id(success_outcome.value.pdu_session_res_modify_resp()->ran_ue_ngap_id); + case success_types::pdu_session_res_modify_confirm: + return uint_to_ran_ue_id(success_outcome.value.pdu_session_res_modify_confirm()->ran_ue_ngap_id); + case success_types::pdu_session_res_release_resp: + return uint_to_ran_ue_id(success_outcome.value.pdu_session_res_release_resp()->ran_ue_ngap_id); + case success_types::pdu_session_res_setup_resp: + return uint_to_ran_ue_id(success_outcome.value.pdu_session_res_setup_resp()->ran_ue_ngap_id); + case success_types::ue_context_mod_resp: + return uint_to_ran_ue_id(success_outcome.value.ue_context_mod_resp()->ran_ue_ngap_id); + case success_types::ue_context_release_complete: + return uint_to_ran_ue_id(success_outcome.value.ue_context_release_complete()->ran_ue_ngap_id); + case success_types::ue_context_resume_resp: + return uint_to_ran_ue_id(success_outcome.value.ue_context_resume_resp()->ran_ue_ngap_id); + case success_types::ue_context_suspend_resp: + return uint_to_ran_ue_id(success_outcome.value.ue_context_suspend_resp()->ran_ue_ngap_id); + default: + break; + } + + return nullopt; +} + +optional srsran::srs_cu_cp::get_ran_ue_id(const asn1::ngap::unsuccessful_outcome_s& unsuccessful_outcome) +{ + using namespace asn1::ngap; + using unsuccess_types = ngap_elem_procs_o::unsuccessful_outcome_c::types_opts; + + switch (unsuccessful_outcome.value.type().value) { + case unsuccess_types::ho_prep_fail: + return uint_to_ran_ue_id(unsuccessful_outcome.value.ho_prep_fail()->ran_ue_ngap_id); + case unsuccess_types::ue_context_mod_fail: + return uint_to_ran_ue_id(unsuccessful_outcome.value.ue_context_mod_fail()->ran_ue_ngap_id); + case unsuccess_types::ue_context_resume_fail: + return uint_to_ran_ue_id(unsuccessful_outcome.value.ue_context_resume_fail()->ran_ue_ngap_id); + case unsuccess_types::ue_context_suspend_fail: + return uint_to_ran_ue_id(unsuccessful_outcome.value.ue_context_suspend_fail()->ran_ue_ngap_id); + default: + break; + } + + return nullopt; +} + +optional srsran::srs_cu_cp::get_ran_ue_id(const asn1::ngap::ngap_pdu_c& pdu) +{ + using namespace asn1::ngap; + switch (pdu.type().value) { + case ngap_pdu_c::types_opts::init_msg: + return get_ran_ue_id(pdu.init_msg()); + case ngap_pdu_c::types_opts::successful_outcome: + return get_ran_ue_id(pdu.successful_outcome()); + case ngap_pdu_c::types_opts::unsuccessful_outcome: + return get_ran_ue_id(pdu.unsuccessful_outcome()); + default: + break; + } + return nullopt; +} + +optional srsran::srs_cu_cp::get_amf_ue_id(const asn1::ngap::init_msg_s& init_msg) +{ + using namespace asn1::ngap; + using init_types = ngap_elem_procs_o::init_msg_c::types_opts; + + switch (init_msg.value.type()) { + case init_types::ho_cancel: + return uint_to_amf_ue_id(init_msg.value.ho_cancel()->amf_ue_ngap_id); + case init_types::ho_required: + return uint_to_amf_ue_id(init_msg.value.ho_required()->amf_ue_ngap_id); + case init_types::ho_request: + return uint_to_amf_ue_id(init_msg.value.ho_request()->amf_ue_ngap_id); + case init_types::init_context_setup_request: + return uint_to_amf_ue_id(init_msg.value.init_context_setup_request()->amf_ue_ngap_id); + case init_types::pdu_session_res_modify_request: + return uint_to_amf_ue_id(init_msg.value.pdu_session_res_modify_request()->amf_ue_ngap_id); + case init_types::pdu_session_res_modify_ind: + return uint_to_amf_ue_id(init_msg.value.pdu_session_res_modify_ind()->amf_ue_ngap_id); + case init_types::pdu_session_res_release_cmd: + return uint_to_amf_ue_id(init_msg.value.pdu_session_res_release_cmd()->amf_ue_ngap_id); + case init_types::pdu_session_res_setup_request: + return uint_to_amf_ue_id(init_msg.value.pdu_session_res_setup_request()->amf_ue_ngap_id); + case init_types::ue_context_mod_request: + return uint_to_amf_ue_id(init_msg.value.ue_context_mod_request()->amf_ue_ngap_id); + case init_types::ue_context_release_cmd: + switch (init_msg.value.ue_context_release_cmd()->ue_ngap_ids.type().value) { + case asn1::ngap::ue_ngap_ids_c::types_opts::ue_ngap_id_pair: + return uint_to_amf_ue_id( + init_msg.value.ue_context_release_cmd()->ue_ngap_ids.ue_ngap_id_pair().amf_ue_ngap_id); + case asn1::ngap::ue_ngap_ids_c::types_opts::amf_ue_ngap_id: + return uint_to_amf_ue_id(init_msg.value.ue_context_release_cmd()->ue_ngap_ids.amf_ue_ngap_id()); + default: + break; + } + return nullopt; + case init_types::dl_nas_transport: + return uint_to_amf_ue_id(init_msg.value.dl_nas_transport()->amf_ue_ngap_id); + case init_types::error_ind: + if (init_msg.value.error_ind()->amf_ue_ngap_id_present) { + return uint_to_amf_ue_id(init_msg.value.error_ind()->amf_ue_ngap_id); + } + break; + case init_types::init_ue_msg: + return nullopt; + case init_types::ul_nas_transport: + return uint_to_amf_ue_id(init_msg.value.ul_nas_transport()->amf_ue_ngap_id); + default: + break; + } + + return nullopt; +} + +optional srsran::srs_cu_cp::get_amf_ue_id(const asn1::ngap::successful_outcome_s& success_outcome) +{ + using namespace asn1::ngap; + using success_types = ngap_elem_procs_o::successful_outcome_c::types_opts; + + switch (success_outcome.value.type()) { + case success_types::ho_cancel_ack: + return uint_to_amf_ue_id(success_outcome.value.ho_cancel_ack()->amf_ue_ngap_id); + case success_types::ho_cmd: + return uint_to_amf_ue_id(success_outcome.value.ho_cmd()->amf_ue_ngap_id); + case success_types::ho_request_ack: + return uint_to_amf_ue_id(success_outcome.value.ho_request_ack()->amf_ue_ngap_id); + case success_types::init_context_setup_resp: + return uint_to_amf_ue_id(success_outcome.value.init_context_setup_resp()->amf_ue_ngap_id); + case success_types::pdu_session_res_modify_resp: + return uint_to_amf_ue_id(success_outcome.value.pdu_session_res_modify_resp()->amf_ue_ngap_id); + case success_types::pdu_session_res_modify_confirm: + return uint_to_amf_ue_id(success_outcome.value.pdu_session_res_modify_confirm()->amf_ue_ngap_id); + case success_types::pdu_session_res_release_resp: + return uint_to_amf_ue_id(success_outcome.value.pdu_session_res_release_resp()->amf_ue_ngap_id); + case success_types::pdu_session_res_setup_resp: + return uint_to_amf_ue_id(success_outcome.value.pdu_session_res_setup_resp()->amf_ue_ngap_id); + case success_types::ue_context_mod_resp: + return uint_to_amf_ue_id(success_outcome.value.ue_context_mod_resp()->amf_ue_ngap_id); + case success_types::ue_context_release_complete: + return uint_to_amf_ue_id(success_outcome.value.ue_context_release_complete()->amf_ue_ngap_id); + case success_types::ue_context_resume_resp: + return uint_to_amf_ue_id(success_outcome.value.ue_context_resume_resp()->amf_ue_ngap_id); + case success_types::ue_context_suspend_resp: + return uint_to_amf_ue_id(success_outcome.value.ue_context_suspend_resp()->amf_ue_ngap_id); + case success_types::ue_radio_cap_check_resp: + return uint_to_amf_ue_id(success_outcome.value.ue_radio_cap_check_resp()->amf_ue_ngap_id); + default: + break; + } + + return nullopt; +} + +optional srsran::srs_cu_cp::get_amf_ue_id(const asn1::ngap::unsuccessful_outcome_s& unsuccessful_outcome) +{ + using namespace asn1::ngap; + using unsuccess_types = ngap_elem_procs_o::unsuccessful_outcome_c::types_opts; + + switch (unsuccessful_outcome.value.type()) { + case unsuccess_types::ho_prep_fail: + return uint_to_amf_ue_id(unsuccessful_outcome.value.ho_prep_fail()->amf_ue_ngap_id); + case unsuccess_types::ho_fail: + return uint_to_amf_ue_id(unsuccessful_outcome.value.ho_fail()->amf_ue_ngap_id); + case unsuccess_types::init_context_setup_fail: + return uint_to_amf_ue_id(unsuccessful_outcome.value.init_context_setup_fail()->amf_ue_ngap_id); + case unsuccess_types::ue_context_mod_fail: + return uint_to_amf_ue_id(unsuccessful_outcome.value.ue_context_mod_fail()->amf_ue_ngap_id); + case unsuccess_types::ue_context_resume_fail: + return uint_to_amf_ue_id(unsuccessful_outcome.value.ue_context_resume_fail()->amf_ue_ngap_id); + case unsuccess_types::ue_context_suspend_fail: + return uint_to_amf_ue_id(unsuccessful_outcome.value.ue_context_suspend_fail()->amf_ue_ngap_id); + default: + break; + } + + return nullopt; +} + +optional srsran::srs_cu_cp::get_amf_ue_id(const asn1::ngap::ngap_pdu_c& pdu) +{ + using namespace asn1::ngap; + switch (pdu.type().value) { + case ngap_pdu_c::types_opts::init_msg: + return get_amf_ue_id(pdu.init_msg()); + case ngap_pdu_c::types_opts::successful_outcome: + return get_amf_ue_id(pdu.successful_outcome()); + case ngap_pdu_c::types_opts::unsuccessful_outcome: + return get_amf_ue_id(pdu.unsuccessful_outcome()); + default: + break; + } + return nullopt; +} diff --git a/lib/ngap/ngap_asn1_utils.h b/lib/ngap/ngap_asn1_utils.h index ed7aeccd38..938ea8fdfc 100644 --- a/lib/ngap/ngap_asn1_utils.h +++ b/lib/ngap/ngap_asn1_utils.h @@ -23,89 +23,43 @@ #pragma once #include "srsran/adt/expected.h" -#include "srsran/asn1/ngap/ngap.h" -#include "srsran/asn1/ngap/ngap_pdu_contents.h" +#include "srsran/asn1/asn1_utils.h" #include "srsran/cu_cp/cu_cp_types.h" +#include "srsran/ngap/ngap_types.h" #include "srsran/security/security.h" #include "srsran/support/error_handling.h" +namespace asn1 { +namespace ngap { + +struct cause_c; +struct init_msg_s; +struct successful_outcome_s; +struct unsuccessful_outcome_s; +struct ngap_pdu_c; + +} // namespace ngap +} // namespace asn1 + namespace srsran { namespace srs_cu_cp { /// Get string with NGAP error cause. -inline const char* get_cause_str(const asn1::ngap::cause_c& cause) -{ - using namespace asn1::ngap; - switch (cause.type()) { - case cause_c::types_opts::radio_network: - return cause.radio_network().to_string(); - case cause_c::types_opts::transport: - return cause.transport().to_string(); - case cause_c::types_opts::nas: - return cause.nas().to_string(); - case cause_c::types_opts::protocol: - return cause.protocol().to_string(); - case cause_c::types_opts::misc: - return cause.misc().to_string(); - default: - break; - } - return "unknown"; -} +const char* get_cause_str(const asn1::ngap::cause_c& cause); /// Extracts message type. -inline const char* get_message_type_str(const asn1::ngap::ngap_pdu_c& pdu) -{ - using namespace asn1::ngap; - switch (pdu.type().value) { - case ngap_pdu_c::types_opts::init_msg: - return pdu.init_msg().value.type().to_string(); - case ngap_pdu_c::types_opts::successful_outcome: - return pdu.successful_outcome().value.type().to_string(); - case ngap_pdu_c::types_opts::unsuccessful_outcome: - return pdu.unsuccessful_outcome().value.type().to_string(); - default: - break; - } - report_fatal_error("Invalid NGAP PDU type \"{}\"", pdu.type().to_string()); -} +const char* get_message_type_str(const asn1::ngap::ngap_pdu_c& pdu); -inline expected get_ran_ue_id(const asn1::ngap::init_msg_s& init_msg) -{ - switch (init_msg.value.type()) { - case asn1::ngap::ngap_elem_procs_o::init_msg_c::types_opts::init_ue_msg: - return uint_to_ran_ue_id(init_msg.value.init_ue_msg()->ran_ue_ngap_id); - default: - break; - } - return {default_error_t{}}; -} - -inline expected get_ran_ue_id(const asn1::ngap::successful_outcome_s& success_outcome) -{ - return {default_error_t{}}; -} - -inline expected get_ran_ue_id(const asn1::ngap::unsuccessful_outcome_s& unsuccessful_outcome) -{ - return {default_error_t{}}; -} +/// Extracts RAN-UE-NGAP-ID from NGAP PDU +optional get_ran_ue_id(const asn1::ngap::init_msg_s& init_msg); +optional get_ran_ue_id(const asn1::ngap::successful_outcome_s& success_outcome); +optional get_ran_ue_id(const asn1::ngap::unsuccessful_outcome_s& unsuccessful_outcome); +optional get_ran_ue_id(const asn1::ngap::ngap_pdu_c& pdu); -inline expected get_ran_ue_id(const asn1::ngap::ngap_pdu_c& pdu) -{ - using namespace asn1::ngap; - switch (pdu.type().value) { - case ngap_pdu_c::types_opts::init_msg: - return get_ran_ue_id(pdu.init_msg()); - case ngap_pdu_c::types_opts::successful_outcome: - return get_ran_ue_id(pdu.successful_outcome()); - case ngap_pdu_c::types_opts::unsuccessful_outcome: - return get_ran_ue_id(pdu.unsuccessful_outcome()); - default: - break; - } - return {default_error_t{}}; -} +optional get_amf_ue_id(const asn1::ngap::init_msg_s& init_msg); +optional get_amf_ue_id(const asn1::ngap::successful_outcome_s& success_outcome); +optional get_amf_ue_id(const asn1::ngap::unsuccessful_outcome_s& unsuccessful_outcome); +optional get_amf_ue_id(const asn1::ngap::ngap_pdu_c& pdu); inline void copy_asn1_key(security::sec_key& key_out, const asn1::fixed_bitstring<256, false, true>& key_in) { diff --git a/lib/ngap/ngap_error_indication_helper.h b/lib/ngap/ngap_error_indication_helper.h index 25c8eb825b..ce9f608007 100644 --- a/lib/ngap/ngap_error_indication_helper.h +++ b/lib/ngap/ngap_error_indication_helper.h @@ -24,6 +24,7 @@ #include "ngap_asn1_converters.h" #include "srsran/asn1/ngap/common.h" +#include "srsran/asn1/ngap/ngap_pdu_contents.h" #include "srsran/ngap/ngap.h" #include "srsran/ngap/ngap_message.h" #include "srsran/ngap/ngap_types.h" @@ -76,8 +77,8 @@ inline void send_error_indication(ngap_message_notifier& ngap_notifier, // Forward message to AMF logger.info("{}{}{}Sending ErrorIndication", - error_ind->ran_ue_ngap_id_present ? fmt::format(" ran_ue_id={}", error_ind->ran_ue_ngap_id) : "", - error_ind->amf_ue_ngap_id_present ? fmt::format(" amf_ue_id={}", error_ind->amf_ue_ngap_id) : "", + error_ind->ran_ue_ngap_id_present ? fmt::format(" ran_ue={}", error_ind->ran_ue_ngap_id) : "", + error_ind->amf_ue_ngap_id_present ? fmt::format(" amf_ue={}", error_ind->amf_ue_ngap_id) : "", error_ind->ran_ue_ngap_id_present || error_ind->amf_ue_ngap_id_present ? ": " : ""); ngap_notifier.on_new_message(ngap_msg); } diff --git a/lib/ngap/ngap_impl.cpp b/lib/ngap/ngap_impl.cpp index c34cf79ce0..a217b62e2e 100644 --- a/lib/ngap/ngap_impl.cpp +++ b/lib/ngap/ngap_impl.cpp @@ -21,6 +21,7 @@ */ #include "ngap_impl.h" +#include "log_helpers.h" #include "ngap_asn1_helpers.h" #include "ngap_asn1_utils.h" #include "ngap_error_indication_helper.h" @@ -36,7 +37,6 @@ #include "srsran/asn1/ngap/common.h" #include "srsran/ngap/ngap_types.h" #include "srsran/ran/cause/ngap_cause.h" -#include "srsran/support/srsran_assert.h" using namespace srsran; using namespace asn1::ngap; @@ -55,7 +55,7 @@ ngap_impl::ngap_impl(ngap_configuration& ngap_cfg_, cu_cp_du_repository_notifier(cu_cp_du_repository_notifier_), task_sched(task_sched_), ue_manager(ue_manager_), - ngap_notifier(ngap_notifier_), + tx_pdu_notifier(*this, ngap_notifier_), ctrl_exec(ctrl_exec_), ev_mng(timer_factory{task_sched.get_timer_manager(), ctrl_exec}) { @@ -105,7 +105,7 @@ async_task ngap_impl::handle_ng_setup_request(const ngap_n return launch_async(context, ngap_msg, request.max_setup_retries, - ngap_notifier, + tx_pdu_notifier, ev_mng, timer_factory{task_sched.get_timer_manager(), ctrl_exec}, logger); @@ -137,8 +137,6 @@ void ngap_impl::handle_initial_ue_message(const cu_cp_initial_ue_message& msg) return; } - ue_ctxt_list[msg.ue_index].logger.log_debug("Created UE"); - ngap_ue_context& ue_ctxt = ue_ctxt_list[msg.ue_index]; ngap_message ngap_msg = {}; @@ -156,11 +154,11 @@ void ngap_impl::handle_initial_ue_message(const cu_cp_initial_ue_message& msg) }); ue_ctxt.pdu_session_setup_timer.run(); - ue_ctxt.logger.log_debug("Sending InitialUEMessage (PDU session timeout={}ms)", + ue_ctxt.logger.log_debug("Starting PDU session creation timer (timeout={}ms)...", ue_ctxt.pdu_session_setup_timer.duration().count()); // Forward message to AMF - ngap_notifier.on_new_message(ngap_msg); + tx_pdu_notifier.on_new_message(ngap_msg); } void ngap_impl::handle_ul_nas_transport_message(const cu_cp_ul_nas_transport& msg) @@ -194,28 +192,20 @@ void ngap_impl::handle_ul_nas_transport_message(const cu_cp_ul_nas_transport& ms fill_asn1_ul_nas_transport(ul_nas_transport_msg, msg); - ue_ctxt.logger.log_info("Sending UlNasTransportMessage"); - // Schedule transmission of UL NAS transport message to AMF task_sched.schedule_async_task(msg.ue_index, launch_async([this, ngap_msg](coro_context>& ctx) { CORO_BEGIN(ctx); - ngap_notifier.on_new_message(ngap_msg); + tx_pdu_notifier.on_new_message(ngap_msg); CORO_RETURN(); })); } void ngap_impl::handle_message(const ngap_message& msg) { - logger.debug("Received PDU of type \"{}.{}\"", msg.pdu.type().to_string(), get_message_type_str(msg.pdu)); - - if (logger.debug.enabled()) { - asn1::json_writer js; - msg.pdu.to_json(js); - logger.debug("Rx NGAP PDU: {}", js.to_string()); - } - // Run NGAP protocols in Control executor. if (not ctrl_exec.execute([this, msg]() { + log_rx_pdu(msg); + switch (msg.pdu.type().value) { case ngap_pdu_c::types_opts::init_msg: handle_initiating_message(msg.pdu.init_msg()); @@ -273,10 +263,10 @@ void ngap_impl::handle_initiating_message(const init_msg_s& msg) void ngap_impl::handle_dl_nas_transport_message(const asn1::ngap::dl_nas_transport_s& msg) { if (!ue_ctxt_list.contains(uint_to_ran_ue_id(msg->ran_ue_ngap_id))) { - logger.warning("ran_ue_id={} amf_ue_id={}: Dropping DlNasTransportMessage. UE context does not exist", + logger.warning("ran_ue={} amf_ue={}: Dropping DlNasTransportMessage. UE context does not exist", msg->ran_ue_ngap_id, msg->amf_ue_ngap_id); - send_error_indication(ngap_notifier, + send_error_indication(tx_pdu_notifier, logger, {}, uint_to_amf_ue_id(msg->amf_ue_ngap_id), @@ -297,7 +287,7 @@ void ngap_impl::handle_dl_nas_transport_message(const asn1::ngap::dl_nas_transpo auto* ue = ue_manager.find_ngap_ue(ue_ctxt.ue_ids.ue_index); srsran_assert(ue != nullptr, - "ue={} ran_ue_id={} amf_ue_id={}: UE for UE context doesn't exist", + "ue={} ran_ue={} amf_ue={}: UE for UE context doesn't exist", ue_ctxt.ue_ids.ue_index, ue_ctxt.ue_ids.ran_ue_id, ue_ctxt.ue_ids.amf_ue_id); @@ -308,8 +298,6 @@ void ngap_impl::handle_dl_nas_transport_message(const asn1::ngap::dl_nas_transpo ue_ctxt_list.update_amf_ue_id(ue_ctxt.ue_ids.ran_ue_id, uint_to_amf_ue_id(msg->amf_ue_ngap_id)); } - ue_ctxt.logger.log_info("Received DlNasTransportMessage"); - // start routine task_sched.schedule_async_task(ue_ctxt.ue_ids.ue_index, launch_async( @@ -319,10 +307,10 @@ void ngap_impl::handle_dl_nas_transport_message(const asn1::ngap::dl_nas_transpo void ngap_impl::handle_initial_context_setup_request(const asn1::ngap::init_context_setup_request_s& request) { if (!ue_ctxt_list.contains(uint_to_ran_ue_id(request->ran_ue_ngap_id))) { - logger.warning("ran_ue_id={} amf_ue_id={}: Dropping InitialContextSetupRequest. UE context does not exist", + logger.warning("ran_ue={} amf_ue={}: Dropping InitialContextSetupRequest. UE context does not exist", request->ran_ue_ngap_id, request->amf_ue_ngap_id); - send_error_indication(ngap_notifier, logger, {}, {}, ngap_cause_radio_network_t::unknown_local_ue_ngap_id); + send_error_indication(tx_pdu_notifier, logger, {}, {}, ngap_cause_radio_network_t::unknown_local_ue_ngap_id); return; } @@ -339,7 +327,7 @@ void ngap_impl::handle_initial_context_setup_request(const asn1::ngap::init_cont auto* ue = ue_manager.find_ngap_ue(ue_ctxt.ue_ids.ue_index); srsran_assert(ue != nullptr, - "ue={} ran_ue_id={} amf_ue_id={}: UE for UE context doesn't exist", + "ue={} ran_ue={} amf_ue={}: UE for UE context doesn't exist", ue_ctxt.ue_ids.ue_index, ue_ctxt.ue_ids.ran_ue_id, ue_ctxt.ue_ids.amf_ue_id); @@ -349,8 +337,6 @@ void ngap_impl::handle_initial_context_setup_request(const asn1::ngap::init_cont ue_ctxt.pdu_session_setup_timer.stop(); } - ue_ctxt.logger.log_info("Received InitialContextSetupRequest"); - // Update AMF ID and use the one from this Context Setup as per TS 38.413 v16.2 page 38 if (ue_ctxt.ue_ids.amf_ue_id != uint_to_amf_ue_id(request->amf_ue_ngap_id)) { ue_ctxt_list.update_amf_ue_id(ue_ctxt.ue_ids.ran_ue_id, uint_to_amf_ue_id(request->amf_ue_ngap_id)); @@ -361,7 +347,7 @@ void ngap_impl::handle_initial_context_setup_request(const asn1::ngap::init_cont init_ctxt_setup_req.ue_index = ue_ctxt.ue_ids.ue_index; if (!fill_ngap_initial_context_setup_request(init_ctxt_setup_req, request)) { ue_ctxt.logger.log_warning("Conversion of PduSessionResourceSetupRequest failed"); - send_error_indication(ngap_notifier, logger, ue_ctxt.ue_ids.ran_ue_id, ue_ctxt.ue_ids.amf_ue_id); + send_error_indication(tx_pdu_notifier, logger, ue_ctxt.ue_ids.ran_ue_id, ue_ctxt.ue_ids.amf_ue_id); return; } @@ -388,17 +374,17 @@ void ngap_impl::handle_initial_context_setup_request(const asn1::ngap::init_cont ue->get_rrc_ue_control_notifier(), ue->get_rrc_ue_pdu_notifier(), ue->get_du_processor_control_notifier(), - ngap_notifier, + tx_pdu_notifier, ue_ctxt.logger)); } void ngap_impl::handle_pdu_session_resource_setup_request(const asn1::ngap::pdu_session_res_setup_request_s& request) { if (!ue_ctxt_list.contains(uint_to_ran_ue_id(request->ran_ue_ngap_id))) { - logger.warning("ran_ue_id={} amf_ue_id={}: Dropping PduSessionResourceSetupRequest. UE context does not exist", + logger.warning("ran_ue={} amf_ue={}: Dropping PduSessionResourceSetupRequest. UE context does not exist", request->ran_ue_ngap_id, request->amf_ue_ngap_id); - send_error_indication(ngap_notifier, logger, {}, {}, ngap_cause_radio_network_t::unknown_local_ue_ngap_id); + send_error_indication(tx_pdu_notifier, logger, {}, {}, ngap_cause_radio_network_t::unknown_local_ue_ngap_id); return; } @@ -415,7 +401,7 @@ void ngap_impl::handle_pdu_session_resource_setup_request(const asn1::ngap::pdu_ ngap_ue* ue = ue_manager.find_ngap_ue(ue_ctxt.ue_ids.ue_index); srsran_assert(ue != nullptr, - "ue={} ran_ue_id={} amf_ue_id={}: UE for UE context doesn't exist", + "ue={} ran_ue={} amf_ue={}: UE for UE context doesn't exist", ue_ctxt.ue_ids.ue_index, ue_ctxt.ue_ids.ran_ue_id, ue_ctxt.ue_ids.amf_ue_id); @@ -425,12 +411,10 @@ void ngap_impl::handle_pdu_session_resource_setup_request(const asn1::ngap::pdu_ if (!ue->get_rrc_ue_control_notifier().on_security_enabled()) { ue_ctxt.logger.log_warning("Dropping PduSessionResourceSetupRequest. Security context does not exist"); - send_error_indication(ngap_notifier, logger, ue_ctxt.ue_ids.ran_ue_id, ue_ctxt.ue_ids.amf_ue_id, {}); + send_error_indication(tx_pdu_notifier, logger, ue_ctxt.ue_ids.ran_ue_id, ue_ctxt.ue_ids.amf_ue_id, {}); return; } - ue_ctxt.logger.log_info("Received PduSessionResourceSetupRequest"); - // Store information in UE context if (request->ue_aggr_max_bit_rate_present) { ue_ctxt.aggregate_maximum_bit_rate_dl = request->ue_aggr_max_bit_rate.ue_aggr_max_bit_rate_dl; @@ -442,7 +426,7 @@ void ngap_impl::handle_pdu_session_resource_setup_request(const asn1::ngap::pdu_ msg.serving_plmn = context.plmn; if (!fill_cu_cp_pdu_session_resource_setup_request(msg, request->pdu_session_res_setup_list_su_req)) { ue_ctxt.logger.log_warning("Conversion of PduSessionResourceSetupRequest failed"); - send_error_indication(ngap_notifier, logger, ue_ctxt.ue_ids.ran_ue_id, ue_ctxt.ue_ids.amf_ue_id, {}); + send_error_indication(tx_pdu_notifier, logger, ue_ctxt.ue_ids.ran_ue_id, ue_ctxt.ue_ids.amf_ue_id, {}); return; } msg.ue_aggregate_maximum_bit_rate_dl = ue_ctxt.aggregate_maximum_bit_rate_dl; @@ -455,17 +439,17 @@ void ngap_impl::handle_pdu_session_resource_setup_request(const asn1::ngap::pdu_ ue_ctxt.ue_ids, ue->get_rrc_ue_pdu_notifier(), ue->get_du_processor_control_notifier(), - ngap_notifier, + tx_pdu_notifier, ue_ctxt.logger)); } void ngap_impl::handle_pdu_session_resource_modify_request(const asn1::ngap::pdu_session_res_modify_request_s& request) { if (!ue_ctxt_list.contains(uint_to_ran_ue_id(request->ran_ue_ngap_id))) { - logger.warning("ran_ue_id={} amf_ue_id={}: Dropping PduSessionResourceModifyRequest. UE context does not exist", + logger.warning("ran_ue={} amf_ue={}: Dropping PduSessionResourceModifyRequest. UE context does not exist", request->ran_ue_ngap_id, request->amf_ue_ngap_id); - send_error_indication(ngap_notifier, logger, {}, {}, ngap_cause_radio_network_t::unknown_local_ue_ngap_id); + send_error_indication(tx_pdu_notifier, logger, {}, {}, ngap_cause_radio_network_t::unknown_local_ue_ngap_id); return; } @@ -492,13 +476,11 @@ void ngap_impl::handle_pdu_session_resource_modify_request(const asn1::ngap::pdu ngap_ue* ue = ue_manager.find_ngap_ue(ue_ctxt.ue_ids.ue_index); srsran_assert(ue != nullptr, - "ue={} ran_ue_id={} amf_ue_id={}: UE for UE context doesn't exist", + "ue={} ran_ue={} amf_ue={}: UE for UE context doesn't exist", ue_ctxt.ue_ids.ue_index, ue_ctxt.ue_ids.ran_ue_id, ue_ctxt.ue_ids.amf_ue_id); - ue_ctxt.logger.log_info("Received PduSessionResourceModifyRequest"); - if (request->ran_paging_prio_present) { ue_ctxt.logger.log_debug("Not handling RAN paging prio"); } @@ -519,7 +501,7 @@ void ngap_impl::handle_pdu_session_resource_modify_request(const asn1::ngap::pdu request, ue_ctxt.ue_ids, ue->get_du_processor_control_notifier(), - ngap_notifier, + tx_pdu_notifier, get_ngap_control_message_handler(), ue_ctxt.logger)); } @@ -527,10 +509,10 @@ void ngap_impl::handle_pdu_session_resource_modify_request(const asn1::ngap::pdu void ngap_impl::handle_pdu_session_resource_release_command(const asn1::ngap::pdu_session_res_release_cmd_s& command) { if (!ue_ctxt_list.contains(uint_to_ran_ue_id(command->ran_ue_ngap_id))) { - logger.warning("ran_ue_id={} amf_ue_id={}: Dropping PduSessionResourceReleaseCommand. UE context does not exist", + logger.warning("ran_ue={} amf_ue={}: Dropping PduSessionResourceReleaseCommand. UE context does not exist", command->ran_ue_ngap_id, command->amf_ue_ngap_id); - send_error_indication(ngap_notifier, logger, {}, {}, ngap_cause_radio_network_t::unknown_local_ue_ngap_id); + send_error_indication(tx_pdu_notifier, logger, {}, {}, ngap_cause_radio_network_t::unknown_local_ue_ngap_id); return; } @@ -547,7 +529,7 @@ void ngap_impl::handle_pdu_session_resource_release_command(const asn1::ngap::pd ngap_ue* ue = ue_manager.find_ngap_ue(ue_ctxt.ue_ids.ue_index); srsran_assert(ue != nullptr, - "ue={} ran_ue_id={} amf_ue_id={}: UE for UE context doesn't exist", + "ue={} ran_ue={} amf_ue={}: UE for UE context doesn't exist", ue_ctxt.ue_ids.ue_index, ue_ctxt.ue_ids.ran_ue_id, ue_ctxt.ue_ids.amf_ue_id); @@ -563,7 +545,7 @@ void ngap_impl::handle_pdu_session_resource_release_command(const asn1::ngap::pd task_sched.schedule_async_task( ue_ctxt.ue_ids.ue_index, launch_async( - msg, ue_ctxt.ue_ids, ue->get_du_processor_control_notifier(), ngap_notifier, ue_ctxt.logger)); + msg, ue_ctxt.ue_ids, ue->get_du_processor_control_notifier(), tx_pdu_notifier, ue_ctxt.logger)); } void ngap_impl::handle_ue_context_release_command(const asn1::ngap::ue_context_release_cmd_s& cmd) @@ -576,10 +558,11 @@ void ngap_impl::handle_ue_context_release_command(const asn1::ngap::ue_context_r if (!ue_ctxt_list.contains(amf_ue_id)) { // TS 38.413 section 8.3.3 doesn't specify abnormal conditions, so we just drop the message and send an error // indication - logger.warning("{}amf_ue_id={}: Dropping UeContextReleaseCommand. UE does not exist", - ran_ue_id == ran_ue_id_t::invalid ? "" : fmt::format("ran_ue_id={} ", ran_ue_id), + logger.warning("{}amf_ue={}: Dropping UeContextReleaseCommand. UE does not exist", + ran_ue_id == ran_ue_id_t::invalid ? "" : fmt::format("ran_ue={} ", ran_ue_id), amf_ue_id); - send_error_indication(ngap_notifier, logger, {}, amf_ue_id, ngap_cause_radio_network_t::unknown_local_ue_ngap_id); + send_error_indication( + tx_pdu_notifier, logger, {}, amf_ue_id, ngap_cause_radio_network_t::unknown_local_ue_ngap_id); return; } } else if (cmd->ue_ngap_ids.type() == asn1::ngap::ue_ngap_ids_c::types_opts::ue_ngap_id_pair) { @@ -589,9 +572,9 @@ void ngap_impl::handle_ue_context_release_command(const asn1::ngap::ue_context_r if (!ue_ctxt_list.contains(ran_ue_id)) { // TS 38.413 section 8.3.3 doesn't specify abnormal conditions, so we just drop the message and send an error // indication - logger.warning( - "ran_ue_id={} amf_ue_id={}: Dropping UeContextReleaseCommand. UE does not exist", ran_ue_id, amf_ue_id); - send_error_indication(ngap_notifier, logger, {}, amf_ue_id, ngap_cause_radio_network_t::unknown_local_ue_ngap_id); + logger.warning("ran_ue={} amf_ue={}: Dropping UeContextReleaseCommand. UE does not exist", ran_ue_id, amf_ue_id); + send_error_indication( + tx_pdu_notifier, logger, {}, amf_ue_id, ngap_cause_radio_network_t::unknown_local_ue_ngap_id); return; } @@ -626,7 +609,7 @@ void ngap_impl::handle_ue_context_release_command(const asn1::ngap::ue_context_r ngap_ue* ue = ue_manager.find_ngap_ue(ue_ctxt.ue_ids.ue_index); srsran_assert(ue != nullptr, - "ue={} ran_ue_id={} amf_ue_id={}: UE for UE context doesn't exist", + "ue={} ran_ue={} amf_ue={}: UE for UE context doesn't exist", ue_ctxt.ue_ids.ue_index, ue_ctxt.ue_ids.ran_ue_id, ue_ctxt.ue_ids.amf_ue_id); @@ -645,17 +628,15 @@ void ngap_impl::handle_ue_context_release_command(const asn1::ngap::ue_context_r ue_ctxt.ue_ids, stored_error_indications, ue->get_du_processor_control_notifier(), - ngap_notifier, + tx_pdu_notifier, ue_ctxt.logger)); } void ngap_impl::handle_paging(const asn1::ngap::paging_s& msg) { - logger.info("Received Paging"); - if (msg->ue_paging_id.type() != asn1::ngap::ue_paging_id_c::types::five_g_s_tmsi) { logger.warning("Dropping PDU. Unsupported UE Paging ID"); - send_error_indication(ngap_notifier, logger); + send_error_indication(tx_pdu_notifier, logger); return; } @@ -687,7 +668,7 @@ void ngap_impl::handle_handover_request(const asn1::ngap::ho_request_s& msg) ngap_handover_request ho_request; if (!fill_ngap_handover_request(ho_request, msg)) { logger.warning("Sending HandoverFailure. Received invalid HandoverRequest"); - ngap_notifier.on_new_message(generate_handover_failure(msg->amf_ue_ngap_id)); + tx_pdu_notifier.on_new_message(generate_handover_failure(msg->amf_ue_ngap_id)); return; } @@ -700,7 +681,7 @@ void ngap_impl::handle_handover_request(const asn1::ngap::ho_request_s& msg) ho_request.source_to_target_transparent_container.target_cell_id); if (ho_request.ue_index == ue_index_t::invalid) { logger.warning("Sending HandoverFailure. Couldn't allocate UE index"); - ngap_notifier.on_new_message(generate_handover_failure(msg->amf_ue_ngap_id)); + tx_pdu_notifier.on_new_message(generate_handover_failure(msg->amf_ue_ngap_id)); return; } @@ -712,7 +693,7 @@ void ngap_impl::handle_handover_request(const asn1::ngap::ho_request_s& msg) ue_ctxt_list, cu_cp_ue_creation_notifier, cu_cp_du_repository_notifier, - ngap_notifier, + tx_pdu_notifier, task_sched.get_timer_manager(), ctrl_exec, logger)); @@ -732,16 +713,17 @@ void ngap_impl::handle_error_indication(const asn1::ngap::error_ind_s& msg) if (msg->amf_ue_ngap_id_present) { amf_ue_id = uint_to_amf_ue_id(msg->amf_ue_ngap_id); if (!ue_ctxt_list.contains(uint_to_amf_ue_id(msg->amf_ue_ngap_id))) { - logger.warning("amf_ue_id={}: Dropping ErrorIndication. UE context does not exist", msg->amf_ue_ngap_id); - send_error_indication(ngap_notifier, logger, {}, {}, ngap_cause_radio_network_t::inconsistent_remote_ue_ngap_id); + logger.warning("amf_ue={}: Dropping ErrorIndication. UE context does not exist", msg->amf_ue_ngap_id); + send_error_indication( + tx_pdu_notifier, logger, {}, {}, ngap_cause_radio_network_t::inconsistent_remote_ue_ngap_id); return; } ue_index = ue_ctxt_list[amf_ue_id].ue_ids.ue_index; } else if (msg->ran_ue_ngap_id_present) { ran_ue_id = uint_to_ran_ue_id(msg->ran_ue_ngap_id); if (!ue_ctxt_list.contains(uint_to_ran_ue_id(msg->ran_ue_ngap_id))) { - logger.warning("ran_ue_id={}: Dropping ErrorIndication. UE context does not exist", msg->ran_ue_ngap_id); - send_error_indication(ngap_notifier, logger, {}, {}, ngap_cause_radio_network_t::unknown_local_ue_ngap_id); + logger.warning("ran_ue={}: Dropping ErrorIndication. UE context does not exist", msg->ran_ue_ngap_id); + send_error_indication(tx_pdu_notifier, logger, {}, {}, ngap_cause_radio_network_t::unknown_local_ue_ngap_id); return; } ue_index = ue_ctxt_list[ran_ue_id].ue_ids.ue_index; @@ -836,7 +818,6 @@ async_task ngap_impl::handle_ue_context_release_request(const cu_cp_ue_con fill_asn1_ue_context_release_request(ue_context_release_request, msg); // Forward message to AMF - ue_ctxt.logger.log_info("Scheduling transmission of UeContextReleaseRequest"); ue_ctxt.release_requested = true; // Mark UE so retx of request are avoided. // Schedule transmission of UE Context Release Request @@ -847,8 +828,7 @@ async_task ngap_impl::handle_ue_context_release_request(const cu_cp_ue_con logger.warning("ue={}: Dropping scheduled UeContextReleaseRequest. UE context does not exist anymore", msg.ue_index); } else { - ue_ctxt_list[msg.ue_index].logger.log_info("Sending UeContextReleaseRequest"); - ngap_notifier.on_new_message(ngap_msg); + tx_pdu_notifier.on_new_message(ngap_msg); } CORO_RETURN(true); }); @@ -870,7 +850,7 @@ ngap_impl::handle_handover_preparation_request(const ngap_handover_preparation_r ngap_ue* ue = ue_manager.find_ngap_ue(ue_ctxt.ue_ids.ue_index); srsran_assert(ue != nullptr, - "ue={} ran_ue_id={} amf_ue_id={}: UE for UE context doesn't exist", + "ue={} ran_ue={} amf_ue={}: UE for UE context doesn't exist", ue_ctxt.ue_ids.ue_index, ue_ctxt.ue_ids.ran_ue_id, ue_ctxt.ue_ids.amf_ue_id); @@ -880,7 +860,7 @@ ngap_impl::handle_handover_preparation_request(const ngap_handover_preparation_r return launch_async(msg, context, ue_ctxt.ue_ids, - ngap_notifier, + tx_pdu_notifier, ue->get_rrc_ue_control_notifier(), ue->get_up_resource_manager(), ev_mng, @@ -911,7 +891,7 @@ void ngap_impl::handle_inter_cu_ho_rrc_recfg_complete(const ue_index_t // Forward message to AMF ue_ctxt.logger.log_info("Sending HandoverNotify"); - ngap_notifier.on_new_message(ngap_msg); + tx_pdu_notifier.on_new_message(ngap_msg); } void ngap_impl::remove_ue_context(ue_index_t ue_index) @@ -926,13 +906,10 @@ void ngap_impl::remove_ue_context(ue_index_t ue_index) void ngap_impl::schedule_error_indication(ue_index_t ue_index, ngap_cause_t cause, optional amf_ue_id) { - logger.info("{}{}: Scheduling ErrorIndication", - ue_index != ue_index_t::invalid ? fmt::format("ue={}", ue_index) : "", - amf_ue_id.has_value() ? fmt::format(" amf_ue_id={}", amf_ue_id.value()) : ""); task_sched.schedule_async_task( ue_index, launch_async([this, ue_index, cause, amf_ue_id](coro_context>& ctx) { CORO_BEGIN(ctx); - send_error_indication(ngap_notifier, logger, ue_ctxt_list[ue_index].ue_ids.ran_ue_id, amf_ue_id, cause); + send_error_indication(tx_pdu_notifier, logger, ue_ctxt_list[ue_index].ue_ids.ran_ue_id, amf_ue_id, cause); CORO_RETURN(); })); } @@ -949,7 +926,7 @@ void ngap_impl::on_pdu_session_setup_timer_expired(ue_index_t ue_index) auto* ue = ue_manager.find_ngap_ue(ue_ctxt.ue_ids.ue_index); srsran_assert(ue != nullptr, - "ue={} ran_ue_id={} amf_ue_id={}: UE for UE context doesn't exist", + "ue={} ran_ue={} amf_ue={}: UE for UE context doesn't exist", ue_ctxt.ue_ids.ue_index, ue_ctxt.ue_ids.ran_ue_id, ue_ctxt.ue_ids.amf_ue_id); @@ -978,3 +955,37 @@ void ngap_impl::on_pdu_session_setup_timer_expired(ue_index_t ue_index) return; } } + +static auto log_pdu_helper(srslog::basic_logger& logger, + bool json_log, + bool is_rx, + const ngap_ue_context_list& ue_ctxt_list, + const asn1::ngap::ngap_pdu_c& pdu) +{ + if (not logger.info.enabled()) { + return; + } + + optional ran_ue_id = get_ran_ue_id(pdu); + optional ue_idx; + if (ran_ue_id.has_value()) { + auto* ue = ue_ctxt_list.find(ran_ue_id.value()); + if (ue != nullptr) { + ue_idx = ue->ue_ids.ue_index; + } + } + + log_ngap_pdu(logger, json_log, is_rx, ue_idx, pdu); +} + +void ngap_impl::log_rx_pdu(const ngap_message& msg) +{ + log_pdu_helper(logger, logger.debug.enabled(), true, ue_ctxt_list, msg.pdu); +} + +void ngap_impl::tx_pdu_notifier_with_logging::on_new_message(const ngap_message& msg) +{ + log_pdu_helper(parent.logger, parent.logger.debug.enabled(), false, parent.ue_ctxt_list, msg.pdu); + + decorated.on_new_message(msg); +} diff --git a/lib/ngap/ngap_impl.h b/lib/ngap/ngap_impl.h index 50e196ecab..c71ae2760f 100644 --- a/lib/ngap/ngap_impl.h +++ b/lib/ngap/ngap_impl.h @@ -86,6 +86,21 @@ class ngap_impl final : public ngap_interface ngap_ue_context_removal_handler& get_ngap_ue_context_removal_handler() override { return *this; } private: + class tx_pdu_notifier_with_logging final : public ngap_message_notifier + { + public: + tx_pdu_notifier_with_logging(ngap_impl& parent_, ngap_message_notifier& decorated_) : + parent(parent_), decorated(decorated_) + { + } + + void on_new_message(const ngap_message& msg) override; + + private: + ngap_impl& parent; + ngap_message_notifier& decorated; + }; + /// \brief Notify about the reception of an initiating message. /// \param[in] msg The received initiating message. void handle_initiating_message(const asn1::ngap::init_msg_s& msg); @@ -143,6 +158,9 @@ class ngap_impl final : public ngap_interface /// \brief Callback for the PDU Session Setup Timer expiration. Triggers the release of the UE. void on_pdu_session_setup_timer_expired(ue_index_t ue_index); + /// \brief Log NGAP RX PDU. + void log_rx_pdu(const ngap_message& msg); + ngap_context_t context; srslog::basic_logger& logger; @@ -156,7 +174,7 @@ class ngap_impl final : public ngap_interface ngap_cu_cp_du_repository_notifier& cu_cp_du_repository_notifier; ngap_ue_task_scheduler& task_sched; ngap_ue_manager& ue_manager; - ngap_message_notifier& ngap_notifier; + tx_pdu_notifier_with_logging tx_pdu_notifier; task_executor& ctrl_exec; ngap_transaction_manager ev_mng; diff --git a/lib/ngap/procedures/ng_setup_procedure.cpp b/lib/ngap/procedures/ng_setup_procedure.cpp index 9b318b93bc..fc8d237eab 100644 --- a/lib/ngap/procedures/ng_setup_procedure.cpp +++ b/lib/ngap/procedures/ng_setup_procedure.cpp @@ -100,6 +100,7 @@ bool ng_setup_procedure::retry_required() if (ng_setup_retry_no++ >= max_setup_retries) { // Number of retries exceeded, or there is no time to wait. logger.warning("\"{}\": Stopping procedure. Cause: Reached maximum number of NG Setup connection retries ({})", + name(), max_setup_retries); return false; } diff --git a/lib/ngap/procedures/ngap_dl_nas_message_transfer_procedure.cpp b/lib/ngap/procedures/ngap_dl_nas_message_transfer_procedure.cpp index 3abc9feb7f..8c1655e944 100644 --- a/lib/ngap/procedures/ngap_dl_nas_message_transfer_procedure.cpp +++ b/lib/ngap/procedures/ngap_dl_nas_message_transfer_procedure.cpp @@ -50,6 +50,5 @@ void ngap_dl_nas_message_transfer_procedure::operator()(coro_contextamf_ue_ngap_id = amf_ue_id_to_uint(amf_ue_id); ho_request_ack->ran_ue_ngap_id = ran_ue_id_to_uint(ran_ue_id); - logger.info("ue={} ran_ue_id={} amf_ue_id={}: Sending HoRequestAck", ue_index, ran_ue_id, amf_ue_id); amf_notifier.on_new_message(ngap_msg); } @@ -137,6 +136,6 @@ void ngap_handover_resource_allocation_procedure::send_handover_failure() auto& ho_fail = ngap_msg.pdu.unsuccessful_outcome().value.ho_fail(); ho_fail->amf_ue_ngap_id = amf_ue_id_to_uint(amf_ue_id); - logger.info("ue={} amf_ue_id={}: Sending HoFailure", request.ue_index, amf_ue_id); + logger.info("ue={} amf_ue={}: Sending HoFailure", request.ue_index, amf_ue_id); amf_notifier.on_new_message(ngap_msg); } diff --git a/lib/ngap/procedures/ngap_initial_context_setup_procedure.cpp b/lib/ngap/procedures/ngap_initial_context_setup_procedure.cpp index 680758a669..df02ae1756 100644 --- a/lib/ngap/procedures/ngap_initial_context_setup_procedure.cpp +++ b/lib/ngap/procedures/ngap_initial_context_setup_procedure.cpp @@ -138,7 +138,6 @@ void ngap_initial_context_setup_procedure::send_initial_context_setup_response( return; } - logger.log_info("Sending InitialContextSetupResponse"); amf_notifier.on_new_message(ngap_msg); } diff --git a/lib/ngap/procedures/ngap_pdu_session_resource_modify_procedure.cpp b/lib/ngap/procedures/ngap_pdu_session_resource_modify_procedure.cpp index 12b186e228..0b53a311da 100644 --- a/lib/ngap/procedures/ngap_pdu_session_resource_modify_procedure.cpp +++ b/lib/ngap/procedures/ngap_pdu_session_resource_modify_procedure.cpp @@ -152,6 +152,5 @@ void ngap_pdu_session_resource_modify_procedure::send_pdu_session_resource_modif return; } - logger.log_info("Sending PduSessionResourceModifyResponse"); amf_notifier.on_new_message(ngap_msg); } diff --git a/lib/ngap/procedures/ngap_pdu_session_resource_release_procedure.cpp b/lib/ngap/procedures/ngap_pdu_session_resource_release_procedure.cpp index f51611a991..2fef6bd2c7 100644 --- a/lib/ngap/procedures/ngap_pdu_session_resource_release_procedure.cpp +++ b/lib/ngap/procedures/ngap_pdu_session_resource_release_procedure.cpp @@ -169,6 +169,5 @@ void ngap_pdu_session_resource_release_procedure::send_pdu_session_resource_rele return; } - logger.log_info("Sending PduSessionResourceReleaseResponse"); amf_notifier.on_new_message(ngap_msg); } diff --git a/lib/ngap/procedures/ngap_pdu_session_resource_setup_procedure.cpp b/lib/ngap/procedures/ngap_pdu_session_resource_setup_procedure.cpp index 90cbafb228..b7abf5fe94 100644 --- a/lib/ngap/procedures/ngap_pdu_session_resource_setup_procedure.cpp +++ b/lib/ngap/procedures/ngap_pdu_session_resource_setup_procedure.cpp @@ -113,6 +113,5 @@ void ngap_pdu_session_resource_setup_procedure::send_pdu_session_resource_setup_ pdu_session_res_setup_resp->amf_ue_ngap_id = amf_ue_id_to_uint(ue_ids.amf_ue_id); pdu_session_res_setup_resp->ran_ue_ngap_id = ran_ue_id_to_uint(ue_ids.ran_ue_id); - logger.log_info("Sending PduSessionResourceSetupResponse"); amf_notifier.on_new_message(ngap_msg); } diff --git a/lib/ngap/ue_context/ngap_ue_context.h b/lib/ngap/ue_context/ngap_ue_context.h index ebc155521f..c3c54e4a2b 100644 --- a/lib/ngap/ue_context/ngap_ue_context.h +++ b/lib/ngap/ue_context/ngap_ue_context.h @@ -92,7 +92,7 @@ class ngap_ue_context_list ngap_ue_context& operator[](ran_ue_id_t ran_ue_id) { - srsran_assert(ues.find(ran_ue_id) != ues.end(), "ran_ue_id={}: NGAP UE context not found", ran_ue_id); + srsran_assert(ues.find(ran_ue_id) != ues.end(), "ran_ue={}: NGAP UE context not found", ran_ue_id); return ues.at(ran_ue_id); } @@ -101,7 +101,7 @@ class ngap_ue_context_list srsran_assert( ue_index_to_ran_ue_id.find(ue_index) != ue_index_to_ran_ue_id.end(), "ue={}: RAN-UE-ID not found", ue_index); srsran_assert(ues.find(ue_index_to_ran_ue_id.at(ue_index)) != ues.end(), - "ran_ue_id={}: NGAP UE context not found", + "ran_ue={}: NGAP UE context not found", ue_index_to_ran_ue_id.at(ue_index)); return ues.at(ue_index_to_ran_ue_id.at(ue_index)); } @@ -109,20 +109,37 @@ class ngap_ue_context_list ngap_ue_context& operator[](amf_ue_id_t amf_ue_id) { srsran_assert(amf_ue_id_to_ran_ue_id.find(amf_ue_id) != amf_ue_id_to_ran_ue_id.end(), - "amf_ue_id={}: RAN-UE-ID not found", + "amf_ue={}: RAN-UE-ID not found", amf_ue_id); srsran_assert(ues.find(amf_ue_id_to_ran_ue_id.at(amf_ue_id)) != ues.end(), - "ran_ue_id={}: NGAP UE context not found", + "ran_ue={}: NGAP UE context not found", amf_ue_id_to_ran_ue_id.at(amf_ue_id)); return ues.at(amf_ue_id_to_ran_ue_id.at(amf_ue_id)); } + ngap_ue_context* find(ran_ue_id_t ran_ue_id) + { + auto it = ues.find(ran_ue_id); + if (it == ues.end()) { + return nullptr; + } + return &it->second; + } + const ngap_ue_context* find(ran_ue_id_t ran_ue_id) const + { + auto it = ues.find(ran_ue_id); + if (it == ues.end()) { + return nullptr; + } + return &it->second; + } + ngap_ue_context& add_ue(ue_index_t ue_index, ran_ue_id_t ran_ue_id, timer_manager& timers, task_executor& task_exec) { srsran_assert(ue_index != ue_index_t::invalid, "Invalid ue_index={}", ue_index); - srsran_assert(ran_ue_id != ran_ue_id_t::invalid, "Invalid ran_ue_id={}", ran_ue_id); + srsran_assert(ran_ue_id != ran_ue_id_t::invalid, "Invalid ran_ue={}", ran_ue_id); - logger.debug("ue={} ran_ue_id={}: Adding NGAP UE context", ue_index, ran_ue_id); + logger.debug("ue={} ran_ue={}: NGAP UE context created", ue_index, ran_ue_id); ues.emplace(std::piecewise_construct, std::forward_as_tuple(ran_ue_id), std::forward_as_tuple(ue_index, ran_ue_id, timers, task_exec)); @@ -132,9 +149,9 @@ class ngap_ue_context_list void update_amf_ue_id(ran_ue_id_t ran_ue_id, amf_ue_id_t amf_ue_id) { - srsran_assert(amf_ue_id != amf_ue_id_t::invalid, "Invalid amf_ue_id={}", amf_ue_id); - srsran_assert(ran_ue_id != ran_ue_id_t::invalid, "Invalid ran_ue_id={}", ran_ue_id); - srsran_assert(ues.find(ran_ue_id) != ues.end(), "ran_ue_id={}: NGAP UE context not found", ran_ue_id); + srsran_assert(amf_ue_id != amf_ue_id_t::invalid, "Invalid amf_ue={}", amf_ue_id); + srsran_assert(ran_ue_id != ran_ue_id_t::invalid, "Invalid ran_ue={}", ran_ue_id); + srsran_assert(ues.find(ran_ue_id) != ues.end(), "ran_ue={}: NGAP UE context not found", ran_ue_id); auto& ue = ues.at(ran_ue_id); @@ -143,13 +160,13 @@ class ngap_ue_context_list return; } else if (ue.ue_ids.amf_ue_id == amf_ue_id_t::invalid) { // If it was not set before, we add it - ue.logger.log_debug("Adding amf_ue_id={}", amf_ue_id); + ue.logger.log_debug("Setting AMF-UE-NGAP-ID={}", amf_ue_id); ue.ue_ids.amf_ue_id = amf_ue_id; amf_ue_id_to_ran_ue_id.emplace(amf_ue_id, ran_ue_id); } else if (ue.ue_ids.amf_ue_id != amf_ue_id) { // If it was set before, we update it amf_ue_id_t old_amf_ue_id = ue.ue_ids.amf_ue_id; - ue.logger.log_info("Updating AMF-UE-ID. New amf_ue_id={}", amf_ue_id); + ue.logger.log_info("Updating AMF-UE-NGAP-ID={}", amf_ue_id); ue.ue_ids.amf_ue_id = amf_ue_id; amf_ue_id_to_ran_ue_id.emplace(amf_ue_id, ran_ue_id); amf_ue_id_to_ran_ue_id.erase(old_amf_ue_id); @@ -168,7 +185,7 @@ class ngap_ue_context_list ran_ue_id_t ran_ue_id = ue_index_to_ran_ue_id.at(old_ue_index); - srsran_assert(ues.find(ran_ue_id) != ues.end(), "ran_ue_id={}: NGAP UE context not found", ran_ue_id); + srsran_assert(ues.find(ran_ue_id) != ues.end(), "ran_ue={}: NGAP UE context not found", ran_ue_id); // Update UE context ues.at(ran_ue_id).ue_ids.ue_index = new_ue_index; @@ -197,7 +214,7 @@ class ngap_ue_context_list ue_index_to_ran_ue_id.erase(ue_index); if (ues.find(ran_ue_id) == ues.end()) { - logger.warning("ran_ue_id={}: NGAP UE context not found", ran_ue_id); + logger.warning("ran_ue={}: NGAP UE context not found", ran_ue_id); return; } diff --git a/lib/ngap/ue_context/ngap_ue_logger.h b/lib/ngap/ue_context/ngap_ue_logger.h index 096c5e4ded..f331a908c7 100644 --- a/lib/ngap/ue_context/ngap_ue_logger.h +++ b/lib/ngap/ue_context/ngap_ue_logger.h @@ -42,8 +42,8 @@ class ngap_ue_log_prefix fmt::format_to(buffer, "ue={}{}{}: ", ue_index, - ran_ue_id != ran_ue_id_t::invalid ? fmt::format(" ran_ue_id={}", ran_ue_id) : "", - amf_ue_id != amf_ue_id_t::invalid ? fmt::format(" amf_ue_id={}", amf_ue_id) : ""); + ran_ue_id != ran_ue_id_t::invalid ? fmt::format(" ran_ue={}", ran_ue_id) : "", + amf_ue_id != amf_ue_id_t::invalid ? fmt::format(" amf_ue={}", amf_ue_id) : ""); prefix = srsran::to_c_str(buffer); } const char* to_c_str() const { return prefix.c_str(); } diff --git a/lib/ofh/compression/CMakeLists.txt b/lib/ofh/compression/CMakeLists.txt index 80aa149941..23fac660a7 100644 --- a/lib/ofh/compression/CMakeLists.txt +++ b/lib/ofh/compression/CMakeLists.txt @@ -32,8 +32,10 @@ if (${CMAKE_SYSTEM_PROCESSOR} MATCHES "x86_64") list(APPEND SOURCES iq_compression_bfp_avx2.cpp iq_compression_bfp_avx512.cpp - iq_compression_none_avx512.cpp) + iq_compression_none_avx512.cpp + iq_compression_none_avx2.cpp) set_source_files_properties(iq_compression_bfp_avx2.cpp PROPERTIES COMPILE_OPTIONS "-mavx2;") + set_source_files_properties(iq_compression_none_avx2.cpp PROPERTIES COMPILE_OPTIONS "-mavx2;") set_source_files_properties(iq_compression_bfp_avx512.cpp PROPERTIES COMPILE_OPTIONS "-mavx512f;-mavx512bw;-mavx512vl;-mavx512cd;-mavx512dq;") set_source_files_properties(iq_compression_none_avx512.cpp PROPERTIES @@ -42,6 +44,7 @@ endif (${CMAKE_SYSTEM_PROCESSOR} MATCHES "x86_64") if (HAVE_NEON) list(APPEND SOURCES iq_compression_bfp_neon.cpp) + list(APPEND SOURCES iq_compression_none_neon.cpp) endif (HAVE_NEON) add_library(srsran_ofh_compression STATIC ${SOURCES}) diff --git a/lib/ofh/compression/compression_factory.cpp b/lib/ofh/compression/compression_factory.cpp index 4e526f6e71..64b0abeb79 100644 --- a/lib/ofh/compression/compression_factory.cpp +++ b/lib/ofh/compression/compression_factory.cpp @@ -32,11 +32,13 @@ #ifdef __x86_64__ #include "iq_compression_bfp_avx2.h" #include "iq_compression_bfp_avx512.h" +#include "iq_compression_none_avx2.h" #include "iq_compression_none_avx512.h" #endif #ifdef HAVE_NEON #include "iq_compression_bfp_neon.h" +#include "iq_compression_none_neon.h" #endif // HAVE_NEON using namespace srsran; @@ -51,13 +53,22 @@ std::unique_ptr srsran::ofh::create_iq_compressor(compression_typ case compression_type::none: #ifdef __x86_64__ { + bool supports_avx2 = cpu_supports_feature(cpu_feature::avx2); bool supports_avx512 = cpu_supports_feature(cpu_feature::avx512f) && cpu_supports_feature(cpu_feature::avx512vl) && cpu_supports_feature(cpu_feature::avx512bw); if (((impl_type == "avx512") || (impl_type == "auto")) && supports_avx512) { return std::make_unique(logger, iq_scaling); } + if (((impl_type == "avx2") || (impl_type == "auto")) && supports_avx2) { + return std::make_unique(logger, iq_scaling); + } } #endif +#ifdef HAVE_NEON + if ((impl_type == "neon") || (impl_type == "auto")) { + return std::make_unique(logger, iq_scaling); + } +#endif // HAVE_NEON return std::make_unique(logger, iq_scaling); case compression_type::BFP: #ifdef __x86_64__ @@ -103,13 +114,22 @@ srsran::ofh::create_iq_decompressor(compression_type type, srslog::basic_logger& case compression_type::none: #ifdef __x86_64__ { + bool supports_avx2 = cpu_supports_feature(cpu_feature::avx2); bool supports_avx512 = cpu_supports_feature(cpu_feature::avx512f) && cpu_supports_feature(cpu_feature::avx512vl) && cpu_supports_feature(cpu_feature::avx512bw); if (((impl_type == "avx512") || (impl_type == "auto")) && supports_avx512) { return std::make_unique(logger); } + if (((impl_type == "avx2") || (impl_type == "auto")) && supports_avx2) { + return std::make_unique(logger); + } } #endif +#ifdef HAVE_NEON + if ((impl_type == "neon") || (impl_type == "auto")) { + return std::make_unique(logger); + } +#endif // HAVE_NEON return std::make_unique(logger); case compression_type::BFP: #ifdef __x86_64__ diff --git a/lib/ofh/compression/iq_compression_bfp_neon.cpp b/lib/ofh/compression/iq_compression_bfp_neon.cpp index 4d9a9a864a..acf3a76aab 100644 --- a/lib/ofh/compression/iq_compression_bfp_neon.cpp +++ b/lib/ofh/compression/iq_compression_bfp_neon.cpp @@ -105,11 +105,17 @@ void iq_compression_bfp_neon::compress(span int16x8x3_t vec_s16x3_2 = vld1q_s16_x3(&input_quantized[sample_idx + NOF_SAMPLES_PER_PRB * 2]); int16x8x3_t vec_s16x3_3 = vld1q_s16_x3(&input_quantized[sample_idx + NOF_SAMPLES_PER_PRB * 3]); + uint8_t exponent_0 = 0; + uint8_t exponent_1 = 0; + uint8_t exponent_2 = 0; + uint8_t exponent_3 = 0; // Determine exponents. - const uint8_t exponent_0 = neon::determine_bfp_exponent(vec_s16x3_0, params.data_width); - const uint8_t exponent_1 = neon::determine_bfp_exponent(vec_s16x3_1, params.data_width); - const uint8_t exponent_2 = neon::determine_bfp_exponent(vec_s16x3_2, params.data_width); - const uint8_t exponent_3 = neon::determine_bfp_exponent(vec_s16x3_3, params.data_width); + if (SRSRAN_LIKELY(params.data_width != MAX_IQ_WIDTH)) { + exponent_0 = neon::determine_bfp_exponent(vec_s16x3_0, params.data_width); + exponent_1 = neon::determine_bfp_exponent(vec_s16x3_1, params.data_width); + exponent_2 = neon::determine_bfp_exponent(vec_s16x3_2, params.data_width); + exponent_3 = neon::determine_bfp_exponent(vec_s16x3_3, params.data_width); + } // Shift original IQ samples right. int16x8x3_t shifted_data_0, shifted_data_1, shifted_data_2, shifted_data_3; @@ -138,8 +144,12 @@ void iq_compression_bfp_neon::compress(span int16x8x3_t vec_s16x3_1 = vld1q_s16_x3(&input_quantized[sample_idx + NOF_SAMPLES_PER_PRB]); // Determine exponents. - const uint8_t exponent_0 = neon::determine_bfp_exponent(vec_s16x3_0, params.data_width); - const uint8_t exponent_1 = neon::determine_bfp_exponent(vec_s16x3_1, params.data_width); + uint8_t exponent_0 = 0; + uint8_t exponent_1 = 0; + if (SRSRAN_LIKELY(params.data_width != MAX_IQ_WIDTH)) { + exponent_0 = neon::determine_bfp_exponent(vec_s16x3_0, params.data_width); + exponent_1 = neon::determine_bfp_exponent(vec_s16x3_1, params.data_width); + } // Shift original IQ samples right. int16x8x3_t shifted_data_0, shifted_data_1; @@ -163,7 +173,10 @@ void iq_compression_bfp_neon::compress(span int16x8x3_t vec_s16x3 = vld1q_s16_x3(&input_quantized[sample_idx]); // Determine exponent. - const uint8_t exponent = neon::determine_bfp_exponent(vec_s16x3, params.data_width); + uint8_t exponent = 0; + if (SRSRAN_LIKELY(params.data_width != MAX_IQ_WIDTH)) { + exponent = neon::determine_bfp_exponent(vec_s16x3, params.data_width); + } // Shift original IQ samples right. int16x8x3_t shifted_data; diff --git a/lib/ofh/compression/iq_compression_none_avx2.cpp b/lib/ofh/compression/iq_compression_none_avx2.cpp new file mode 100644 index 0000000000..c6caccce62 --- /dev/null +++ b/lib/ofh/compression/iq_compression_none_avx2.cpp @@ -0,0 +1,117 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "iq_compression_none_avx2.h" +#include "avx2_helpers.h" +#include "compressed_prb_packer.h" +#include "packing_utils_avx2.h" +#include "quantizer.h" + +using namespace srsran; +using namespace ofh; + +void iq_compression_none_avx2::compress(span output, + span input, + const srsran::ofh::ru_compression_params& params) +{ + // Number of quantized samples per resource block. + static constexpr size_t NOF_SAMPLES_PER_PRB = 2 * NOF_SUBCARRIERS_PER_RB; + // AVX2 register size in a number of 16bit words. + static constexpr size_t AVX2_REG_SIZE = 16; + + // Quantizer object. + quantizer q(params.data_width); + + // Use generic implementation if AVX512 utils don't support requested bit width. + if (!mm256::iq_width_packing_supported(params.data_width)) { + iq_compression_none_impl::compress(output, input, params); + return; + } + + // Auxiliary arrays used for float to fixed point conversion of the input data. + std::array input_quantized; + + span float_samples_span(reinterpret_cast(input.data()), input.size() * 2U); + span input_quantized_span(input_quantized.data(), float_samples_span.size()); + q.to_fixed_point(input_quantized_span, float_samples_span, iq_scaling); + + log_post_quantization_rms(input_quantized_span); + + unsigned sample_idx = 0; + unsigned rb = 0; + // One AVX2 register stores 8 16bit IQ pairs. We can process 2 PRBs at a time by using 3 AVX2 registers. + for (size_t rb_index_end = (output.size() / 2) * 2; rb != rb_index_end; rb += 2) { + span c_prbs = output.subspan(rb, 2); + + // Load symbols. + const auto* start_it = input_quantized.begin() + sample_idx; + __m256i rb0_epi16 = _mm256_loadu_si256(reinterpret_cast(start_it + 0)); + __m256i rb01_epi16 = _mm256_loadu_si256(reinterpret_cast(start_it + AVX2_REG_SIZE)); + __m256i rb1_epi16 = _mm256_loadu_si256(reinterpret_cast(start_it + AVX2_REG_SIZE * 2)); + + // Pack 2 PRBs using utility function and save compression parameters. + mm256::pack_prbs_big_endian(c_prbs, rb0_epi16, rb01_epi16, rb1_epi16, params.data_width); + + sample_idx += (NOF_SAMPLES_PER_PRB * 2); + } + + // Use generic implementation for the remaining resource blocks. + for (; rb != output.size(); ++rb) { + compressed_prb_packer packer(output[rb]); + + span input_quantized_last_rb(&input_quantized[sample_idx], NOF_SAMPLES_PER_PRB); + packer.pack(input_quantized_last_rb, params.data_width); + + sample_idx += NOF_SAMPLES_PER_PRB; + } +} + +void iq_compression_none_avx2::decompress(span output, + span input, + const srsran::ofh::ru_compression_params& params) +{ + // Use generic implementation if AVX2 utils don't support requested bit width. + if (!mm256::iq_width_packing_supported(params.data_width)) { + iq_compression_none_impl::decompress(output, input, params); + return; + } + + // Quantizer object. + quantizer q(params.data_width); + + unsigned out_idx = 0; + for (const auto& c_prb : input) { + constexpr size_t avx2_size_iqs = 16; + constexpr size_t arr_size = divide_ceil(NOF_SUBCARRIERS_PER_RB * 2, avx2_size_iqs) * avx2_size_iqs; + alignas(64) std::array unpacked_iq_data; + + // Unpack resource block. + mm256::unpack_prb_big_endian(unpacked_iq_data, c_prb.get_packed_data(), params.data_width); + + span output_span = output.subspan(out_idx, NOF_SUBCARRIERS_PER_RB); + span unpacked_span(unpacked_iq_data.data(), NOF_SUBCARRIERS_PER_RB * 2); + + // Convert to complex samples. + q.to_float(output_span, unpacked_span, 1); + out_idx += NOF_SUBCARRIERS_PER_RB; + } +} diff --git a/lib/ofh/compression/iq_compression_none_avx2.h b/lib/ofh/compression/iq_compression_none_avx2.h new file mode 100644 index 0000000000..8a5b893de8 --- /dev/null +++ b/lib/ofh/compression/iq_compression_none_avx2.h @@ -0,0 +1,48 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "iq_compression_none_impl.h" + +namespace srsran { +namespace ofh { + +/// Implementation of no-compression compressor using AVX2 intrinsics. +class iq_compression_none_avx2 : public iq_compression_none_impl +{ +public: + // Constructor. + explicit iq_compression_none_avx2(srslog::basic_logger& logger_, float iq_scaling_ = 1.0) : + iq_compression_none_impl(logger_, iq_scaling_) + { + } + + // See interface for the documentation. + void compress(span output, span input, const ru_compression_params& params) override; + + // See interface for the documentation. + void decompress(span output, span input, const ru_compression_params& params) override; +}; + +} // namespace ofh +} // namespace srsran diff --git a/lib/ofh/compression/iq_compression_none_impl.cpp b/lib/ofh/compression/iq_compression_none_impl.cpp index 209f70b792..3dd3e6ba42 100644 --- a/lib/ofh/compression/iq_compression_none_impl.cpp +++ b/lib/ofh/compression/iq_compression_none_impl.cpp @@ -24,6 +24,7 @@ #include "compressed_prb_packer.h" #include "compressed_prb_unpacker.h" #include "quantizer.h" +#include "srsran/srsvec/dot_prod.h" using namespace srsran; using namespace ofh; @@ -70,3 +71,15 @@ void iq_compression_none_impl::decompress(span output, } } } + +void iq_compression_none_impl::log_post_quantization_rms(span samples) +{ + if (SRSRAN_UNLIKELY(logger.debug.enabled() && !samples.empty())) { + // Calculate and print RMS of quantized samples. + float sum_squares = srsvec::dot_prod(samples, samples, 0); + float rms = std::sqrt(sum_squares / samples.size()); + if (std::isnormal(rms)) { + logger.debug("Quantized IQ samples RMS value of '{}'", rms); + } + } +} diff --git a/lib/ofh/compression/iq_compression_none_impl.h b/lib/ofh/compression/iq_compression_none_impl.h index 8834824137..1d892a9f91 100644 --- a/lib/ofh/compression/iq_compression_none_impl.h +++ b/lib/ofh/compression/iq_compression_none_impl.h @@ -47,6 +47,11 @@ class iq_compression_none_impl : public iq_compressor, public iq_decompressor decompress(span output, span input, const ru_compression_params& params) override; protected: + /// \brief Prints to the log the root mean square (RMS) value of the given samples. + /// + /// \param[in] samples - Quantized samples. + void log_post_quantization_rms(span samples); + srslog::basic_logger& logger; /// Scaling factor applied to IQ data prior to quantization. const float iq_scaling; diff --git a/lib/ofh/compression/iq_compression_none_neon.cpp b/lib/ofh/compression/iq_compression_none_neon.cpp new file mode 100644 index 0000000000..3e1df52cb1 --- /dev/null +++ b/lib/ofh/compression/iq_compression_none_neon.cpp @@ -0,0 +1,133 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "iq_compression_none_neon.h" +#include "compressed_prb_packer.h" +#include "neon_helpers.h" +#include "packing_utils_neon.h" +#include "quantizer.h" + +using namespace srsran; +using namespace ofh; + +void iq_compression_none_neon::compress(span output, + span input, + const srsran::ofh::ru_compression_params& params) +{ + // Number of quantized samples per resource block. + static constexpr size_t NOF_SAMPLES_PER_PRB = 2 * NOF_SUBCARRIERS_PER_RB; + + // Use generic implementation if NEON utils don't support requested bit width. + if (!neon::iq_width_packing_supported(params.data_width)) { + iq_compression_none_impl::compress(output, input, params); + return; + } + + // Quantizer object. + quantizer q(params.data_width); + + // Auxiliary arrays used for float to fixed point conversion of the input data. + std::array input_quantized; + + span float_samples_span(reinterpret_cast(input.data()), input.size() * 2U); + span input_quantized_span(input_quantized.data(), float_samples_span.size()); + q.to_fixed_point(input_quantized_span, float_samples_span, iq_scaling); + + log_post_quantization_rms(input_quantized_span); + + unsigned sample_idx = 0; + unsigned rb = 0; + // One NEON register can store 8 16bit samples. A PRB comprises 24 16bit IQ samples, thus we need three NEON + // registers to process one PRB. + // + // The loop below processes four resource blocks at a time. + for (size_t rb_index_end = (output.size() / 4) * 4; rb != rb_index_end; rb += 4) { + // Load samples. + int16x8x3_t vec_s16x3_0 = vld1q_s16_x3(&input_quantized[sample_idx]); + int16x8x3_t vec_s16x3_1 = vld1q_s16_x3(&input_quantized[sample_idx + NOF_SAMPLES_PER_PRB]); + int16x8x3_t vec_s16x3_2 = vld1q_s16_x3(&input_quantized[sample_idx + NOF_SAMPLES_PER_PRB * 2]); + int16x8x3_t vec_s16x3_3 = vld1q_s16_x3(&input_quantized[sample_idx + NOF_SAMPLES_PER_PRB * 3]); + + // Pack samples of the PRB using utility function. + neon::pack_prb_big_endian(output[rb], vec_s16x3_0, params.data_width); + neon::pack_prb_big_endian(output[rb + 1], vec_s16x3_1, params.data_width); + neon::pack_prb_big_endian(output[rb + 2], vec_s16x3_2, params.data_width); + neon::pack_prb_big_endian(output[rb + 3], vec_s16x3_3, params.data_width); + + sample_idx += (4 * NOF_SAMPLES_PER_PRB); + } + + // The loop below processes two resource blocks at a time. + for (size_t rb_index_end = (output.size() / 2) * 2; rb != rb_index_end; rb += 2) { + // Load samples. + int16x8x3_t vec_s16x3_0 = vld1q_s16_x3(&input_quantized[sample_idx]); + int16x8x3_t vec_s16x3_1 = vld1q_s16_x3(&input_quantized[sample_idx + NOF_SAMPLES_PER_PRB]); + // Pack samples of the PRB using utility function. + neon::pack_prb_big_endian(output[rb], vec_s16x3_0, params.data_width); + neon::pack_prb_big_endian(output[rb + 1], vec_s16x3_1, params.data_width); + sample_idx += (2 * NOF_SAMPLES_PER_PRB); + } + + // Process the last resource block. + if (rb != output.size()) { + // Load samples. + int16x8x3_t vec_s16x3 = vld1q_s16_x3(&input_quantized[sample_idx]); + + // Pack samples of the PRB using utility function. + neon::pack_prb_big_endian(output[rb], vec_s16x3, params.data_width); + + sample_idx += NOF_SAMPLES_PER_PRB; + ++rb; + } + + // Use generic implementation if the requested compression width is not supported by neon utils. + for (; rb != output.size(); ++rb) { + compressed_prb_packer packer(output[rb]); + packer.pack({&input_quantized[sample_idx], NOF_SAMPLES_PER_PRB}, params.data_width); + sample_idx += NOF_SAMPLES_PER_PRB; + } +} + +void iq_compression_none_neon::decompress(span output, + span input, + const srsran::ofh::ru_compression_params& params) +{ + // Use generic implementation if NEON utils don't support requested bit width. + if (!neon::iq_width_packing_supported(params.data_width)) { + iq_compression_none_impl::decompress(output, input, params); + return; + } + + quantizer q_out(params.data_width); + + unsigned out_idx = 0; + for (const compressed_prb& c_prb : input) { + // Unpack resource block. + std::array unpacked_iq_data; + neon::unpack_prb_big_endian(unpacked_iq_data, c_prb.get_packed_data(), params.data_width); + + span output_span = output.subspan(out_idx, NOF_SUBCARRIERS_PER_RB); + // Convert to complex samples. + q_out.to_float(output_span, unpacked_iq_data, 1); + out_idx += NOF_SUBCARRIERS_PER_RB; + } +} diff --git a/lib/ofh/compression/iq_compression_none_neon.h b/lib/ofh/compression/iq_compression_none_neon.h new file mode 100644 index 0000000000..255e07af87 --- /dev/null +++ b/lib/ofh/compression/iq_compression_none_neon.h @@ -0,0 +1,48 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "iq_compression_none_impl.h" + +namespace srsran { +namespace ofh { + +/// Implementation of no-compression compressor using NEON intrinsics. +class iq_compression_none_neon : public iq_compression_none_impl +{ +public: + // Constructor. + explicit iq_compression_none_neon(srslog::basic_logger& logger_, float iq_scaling_ = 1.0) : + iq_compression_none_impl(logger_, iq_scaling_) + { + } + + // See interface for the documentation. + void compress(span output, span input, const ru_compression_params& params) override; + + // See interface for the documentation. + void decompress(span output, span input, const ru_compression_params& params) override; +}; + +} // namespace ofh +} // namespace srsran diff --git a/lib/ofh/compression/packing_utils_avx2.h b/lib/ofh/compression/packing_utils_avx2.h index 7e5976f125..2fde369300 100644 --- a/lib/ofh/compression/packing_utils_avx2.h +++ b/lib/ofh/compression/packing_utils_avx2.h @@ -83,7 +83,7 @@ inline __m256i pack_avx2_register_9b_be(__m256i cmp_data_epi16) /// \brief Packs 16bit IQ values of the two input RBs using 9bit big-endian format. /// /// The following diagram shows the input format. Here RBx stands for one unique RE (pair of IQ samples, 32 bits long) -/// pertaining to a respective RB): +/// pertaining to a respective RB: /// | | | | | | /// | ----- | ------- | ------- | ------- | ------- | /// | \c r0: | RB0 RB0 | RB0 RB0 | RB0 RB0 | RB0 RB0 | @@ -162,6 +162,50 @@ inline void avx2_pack_prbs_9b_big_endian(span c_prbs, __m256i r0 c_prbs[1].set_stored_size(BYTES_PER_PRB_9BIT_COMPRESSION); } +/// \brief Packs 16bit IQ values of the two input RBs using 16bit big-endian format. +/// +/// The following diagram shows the input format. Here RBx stands for one unique RE (pair of IQ samples, 32 bits long) +/// pertaining to a respective RB: +/// | | | | | | +/// | ----- | ------- | ------- | ------- | ------- | +/// | \c r0: | RB0 RB0 | RB0 RB0 | RB0 RB0 | RB0 RB0 | +/// | \c r1: | RB0 RB0 | RB0 RB0 | RB1 RB1 | RB1 RB1 | +/// | \c r2: | RB1 RB1 | RB1 RB1 | RB1 RB1 | RB1 RB1 | +/// +/// \param[out] c_prbs Span of two compressed PRBs. +/// \param[in] r0 AVX2 register storing 16bit IQ samples of the first RB. +/// \param[in] r1 AVX2 register storing 16bit IQ samples of the first and second RB. +/// \param[in] r2 AVX2 register storing 16bit IQ samples of the second RB. +inline void avx2_pack_prbs_16b_big_endian(span c_prbs, __m256i r0, __m256i r1, __m256i r2) +{ + srsran_assert(c_prbs.size() == 2, "Output span must contain 2 resource blocks"); + + static constexpr unsigned BYTES_PER_PRB_NO_COMPRESSION = 48; + + // Swap bytes to convert from big-endian format and write them directly to the output memory. + const __m256i shuffle_mask_epi8 = + _mm256_setr_epi64x(0x0607040502030001, 0x0e0f0c0d0a0b0809, 0x0607040502030001, 0x0e0f0c0d0a0b0809); + + __m256i reg0_swp_epi16 = _mm256_shuffle_epi8(r0, shuffle_mask_epi8); + __m256i reg1_swp_epi16 = _mm256_shuffle_epi8(r1, shuffle_mask_epi8); + __m256i reg2_swp_epi16 = _mm256_shuffle_epi8(r2, shuffle_mask_epi8); + + uint8_t* data = c_prbs[0].get_byte_buffer().data(); + _mm256_storeu_si256(reinterpret_cast<__m256i*>(data), reg0_swp_epi16); + _mm256_maskstore_epi32(reinterpret_cast(data + 32), + _mm256_setr_epi32(0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0, 0, 0, 0), + reg1_swp_epi16); + + data = c_prbs[1].get_byte_buffer().data(); + _mm_maskstore_epi32(reinterpret_cast(data), + _mm_set_epi32(0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff), + _mm256_extracti128_si256(reg1_swp_epi16, 0x1)); + _mm256_storeu_si256(reinterpret_cast<__m256i*>(data + 16), reg2_swp_epi16); + + c_prbs[0].set_stored_size(BYTES_PER_PRB_NO_COMPRESSION); + c_prbs[1].set_stored_size(BYTES_PER_PRB_NO_COMPRESSION); +} + /// \brief Packs 16bit IQ values of the two RBs using the specified width and big-endian format. /// /// The following diagram shows the input format. Here RBx stands for one unique RE (pair of IQ samples, 32 bits long) @@ -182,8 +226,9 @@ pack_prbs_big_endian(span c_prbs, __m256i r0, __m256i r1, _ { switch (iq_width) { case 9: - avx2_pack_prbs_9b_big_endian(c_prbs, r0, r1, r2); - break; + return avx2_pack_prbs_9b_big_endian(c_prbs, r0, r1, r2); + case 16: + return avx2_pack_prbs_16b_big_endian(c_prbs, r0, r1, r2); default: report_fatal_error("Unsupported bit width"); } @@ -196,7 +241,7 @@ pack_prbs_big_endian(span c_prbs, __m256i r0, __m256i r1, _ /// /// \note The \c unpacked_iq_data parameter should be sized to store 32 output IQ samples: it is 24 IQ samples of one RB /// rounded up to 32-byte boundary required by AVX2 intrinsics. -inline void unpack_prb_9b_be(span unpacked_iq_data, span packed_data) +inline void avx2_unpack_prb_9b_be(span unpacked_iq_data, span packed_data) { constexpr size_t avx2_size_short_words = 16; srsran_assert(unpacked_iq_data.size() >= avx2_size_short_words * 2, "Wrong unpacked data span size"); @@ -244,6 +289,31 @@ inline void unpack_prb_9b_be(span unpacked_iq_data, span _mm256_storeu_si256(reinterpret_cast<__m256i*>(unpacked_iq_data.subspan(16, 8).data()), unpacked_data_1_epi16); } +/// \brief Unpacks packed 16bit IQ samples stored as bytes in big-endian format to an array of 16bit signed values. +/// +/// \param[out] unpacked_iq_data A span of 16bit integers, corresponding to \c NOF_CARRIERS_PER_RB unpacked IQ pairs. +/// \param[in] packed_data A span of 48 packed bytes. +inline void avx2_unpack_prb_16b_be(span unpacked_iq_data, span packed_data) +{ + // Load input, 48 bytes (need two AVX2 registers). + const __m256i rd_mask_last_bytes = _mm256_setr_epi32(0x80000000, 0x80000000, 0x80000000, 0x80000000, 0, 0, 0, 0); + + __m256i reg0_epi16 = _mm256_loadu_si256(reinterpret_cast(packed_data.data())); + __m256i reg1_epi16 = _mm256_maskload_epi32(reinterpret_cast(packed_data.data() + 32), rd_mask_last_bytes); + + // Swap bytes for Little-endian representation. + const __m256i shuffle_mask_epi8 = + _mm256_setr_epi64x(0x0607040502030001, 0x0e0f0c0d0a0b0809, 0x0607040502030001, 0x0e0f0c0d0a0b0809); + + __m256i reg0_unpacked_data_epi16 = _mm256_shuffle_epi8(reg0_epi16, shuffle_mask_epi8); + __m256i reg1_unpacked_data_epi16 = _mm256_shuffle_epi8(reg1_epi16, shuffle_mask_epi8); + + _mm256_storeu_si256(reinterpret_cast<__m256i*>(unpacked_iq_data.data()), reg0_unpacked_data_epi16); + _mm256_maskstore_epi32(reinterpret_cast(unpacked_iq_data.data() + 16), + _mm256_setr_epi32(0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0, 0, 0, 0), + reg1_unpacked_data_epi16); +} + /// \brief Unpacks packed IQ samples stored as bytes in big-endian format to an array of 16bit signed values. /// /// \param[out] unpacked_iq_data A span of 32 16bit integers, corresponding to \c NOF_CARRIERS_PER_RB unpacked IQ pairs. @@ -252,12 +322,13 @@ inline void unpack_prb_9b_be(span unpacked_iq_data, span /// /// \note The \c unpacked_iq_data parameter should be sized to store 32 output IQ samples: it is 24 IQ samples of one RB /// rounded up to 32-byte boundary required by AVX2 intrinsics. -void unpack_prb_big_endian(span unpacked_iq_data, span packed_data, unsigned iq_width) +inline void unpack_prb_big_endian(span unpacked_iq_data, span packed_data, unsigned iq_width) { switch (iq_width) { case 9: - unpack_prb_9b_be(unpacked_iq_data, packed_data); - break; + return avx2_unpack_prb_9b_be(unpacked_iq_data, packed_data); + case 16: + return avx2_unpack_prb_16b_be(unpacked_iq_data, packed_data); default: report_fatal_error("Unsupported bit width"); } @@ -269,7 +340,7 @@ void unpack_prb_big_endian(span unpacked_iq_data, span p /// \return True in case packing/unpacking with the requested bit width is supported inline bool iq_width_packing_supported(unsigned iq_width) { - return iq_width == 9; + return ((iq_width == 9) || (iq_width == 16)); } } // namespace mm256 diff --git a/lib/ofh/compression/packing_utils_avx512.h b/lib/ofh/compression/packing_utils_avx512.h index d71229fd57..c9c08d9aaa 100644 --- a/lib/ofh/compression/packing_utils_avx512.h +++ b/lib/ofh/compression/packing_utils_avx512.h @@ -229,10 +229,7 @@ inline void unpack_prb_big_endian(span unpacked_iq_data, span(c_prb.get_byte_buffer().data()); + vst1q_s8_x3(data, regs_shuffled_s16); + c_prb.set_stored_size(BYTES_PER_PRB_16BIT_COMPRESSION); +} + /// \brief Packs 16bit IQ values of a resource block using the specified width and big-endian format. /// /// \param[out] c_prb Output PRB storing compressed packed bytes. /// \param[in] reg Vector of three NEON registers storing 16bit IQ pairs of the PRB. /// \param[in] iq_width Bit width of the resulting packed IQ samples. -void pack_prb_big_endian(ofh::compressed_prb& c_prb, int16x8x3_t regs, unsigned iq_width) +inline void pack_prb_big_endian(ofh::compressed_prb& c_prb, int16x8x3_t regs, unsigned iq_width) { if (iq_width == 9) { return pack_prb_9b_big_endian(c_prb, regs); } + if (iq_width == 16) { + return pack_prb_16b_big_endian(c_prb, regs); + } report_fatal_error("Unsupported bit width"); } @@ -153,6 +178,28 @@ inline void unpack_prb_9b_big_endian(span unpacked_iq_data, span unpacked_iq_data, span packed_data) +{ + static const uint8x16_t shuffle_mask_u8 = vcombine_u8(vcreate_u8(0x0607040502030001), vcreate_u8(0x0e0f0c0d0a0b0809)); + + // Load input (we need three NEON register to load 48 bytes). + uint8x16x3_t packed_vec_u8x3 = vld1q_u8_x3(packed_data.data()); + + uint8x16x3_t packed_shuffled_s16; + packed_shuffled_s16.val[0] = vqtbl1q_u8(packed_vec_u8x3.val[0], shuffle_mask_u8); + packed_shuffled_s16.val[1] = vqtbl1q_u8(packed_vec_u8x3.val[1], shuffle_mask_u8); + packed_shuffled_s16.val[2] = vqtbl1q_u8(packed_vec_u8x3.val[2], shuffle_mask_u8); + + // Write results to the output buffer. + vst1q_s16(unpacked_iq_data.data(), vreinterpretq_s16_u8(packed_shuffled_s16.val[0])); + vst1q_s16(unpacked_iq_data.data() + 8, vreinterpretq_s16_u8(packed_shuffled_s16.val[1])); + vst1q_s16(unpacked_iq_data.data() + 16, vreinterpretq_s16_u8(packed_shuffled_s16.val[2])); +} + /// \brief Unpacks packed IQ samples stored as bytes in big-endian format to an array of 16bit signed values. /// /// \param[out] unpacked_iq_data A sequence of 24 integers, corresponding to \c NOF_CARRIERS_PER_RB unpacked IQ pairs. @@ -163,6 +210,9 @@ inline void unpack_prb_big_endian(span unpacked_iq_data, span unpacked_iq_data, span +static double to_seconds(IndexType index, unsigned dft_size, subcarrier_spacing scs) { // Calculate DFT sampling rate. unsigned sampling_rate = dft_size * scs_to_khz(scs) * 1000; @@ -43,7 +44,8 @@ static double to_seconds(unsigned index, unsigned dft_size, subcarrier_spacing s time_alignment_measurement time_alignment_estimator_dft_impl::estimate(span pilots_lse, bounded_bitset re_mask, - subcarrier_spacing scs) + subcarrier_spacing scs, + double max_ta) { span channel_observed_freq = idft->get_input(); srsvec::zero(channel_observed_freq); @@ -53,21 +55,26 @@ time_alignment_measurement time_alignment_estimator_dft_impl::estimate(span channel_observed_time = idft->run(); - static constexpr unsigned HALF_CP_LENGTH = ((144 / 2) * dft_size) / 2048; - std::pair observed_max_delay = srsvec::max_abs_element(channel_observed_time.first(HALF_CP_LENGTH)); - std::pair observed_max_advance = srsvec::max_abs_element(channel_observed_time.last(HALF_CP_LENGTH)); + unsigned max_ta_samples = ((144 / 2) * dft_size) / 2048; + if (std::isnormal(max_ta)) { + max_ta_samples = static_cast(std::floor(max_ta * static_cast(scs_to_khz(scs) * 1000 * dft_size))); + } + + std::pair observed_max_delay = srsvec::max_abs_element(channel_observed_time.first(max_ta_samples)); + std::pair observed_max_advance = srsvec::max_abs_element(channel_observed_time.last(max_ta_samples)); double t_align_seconds; if (observed_max_delay.second >= observed_max_advance.second) { t_align_seconds = to_seconds(observed_max_delay.first, dft_size, scs); } else { - t_align_seconds = -to_seconds(HALF_CP_LENGTH - observed_max_advance.first, dft_size, scs); + t_align_seconds = -to_seconds(max_ta_samples - observed_max_advance.first, dft_size, scs); } + // Fill results. time_alignment_measurement result; result.time_alignment = t_align_seconds; - result.min = -to_seconds(HALF_CP_LENGTH, dft_size, scs); - result.max = to_seconds(HALF_CP_LENGTH, dft_size, scs); + result.min = -to_seconds(max_ta_samples, dft_size, scs); + result.max = to_seconds(max_ta_samples, dft_size, scs); result.resolution = to_seconds(1, dft_size, scs); return result; } diff --git a/lib/phy/support/time_alignment_estimator/time_alignment_estimator_dft_impl.h b/lib/phy/support/time_alignment_estimator/time_alignment_estimator_dft_impl.h index 9d0f75776d..5f5dced0e1 100644 --- a/lib/phy/support/time_alignment_estimator/time_alignment_estimator_dft_impl.h +++ b/lib/phy/support/time_alignment_estimator/time_alignment_estimator_dft_impl.h @@ -47,8 +47,10 @@ class time_alignment_estimator_dft_impl : public time_alignment_estimator } // See interface for documentation. - time_alignment_measurement - estimate(span symbols, bounded_bitset mask, subcarrier_spacing scs) override; + time_alignment_measurement estimate(span symbols, + bounded_bitset mask, + subcarrier_spacing scs, + double max_ta) override; private: /// DFT processor for converting frequency domain to time domain. diff --git a/lib/phy/upper/channel_processors/pucch_detector_impl.cpp b/lib/phy/upper/channel_processors/pucch_detector_impl.cpp index 9654921d86..c3688b43bf 100644 --- a/lib/phy/upper/channel_processors/pucch_detector_impl.cpp +++ b/lib/phy/upper/channel_processors/pucch_detector_impl.cpp @@ -198,13 +198,13 @@ static float detect_bits(span out_bits, cf_t detected_symbol, float eq_ if ((nof_bits > 1) && (detection_metric2 > detection_metric)) { out_bits[0] = (bits2 & 1U); out_bits[1] = ((bits2 >> 1U) & 1U); - return (detection_metric2 / std::sqrt(eq_noise_var)); + return (std::norm(detected_symbol) / eq_noise_var); } out_bits[0] = (bits & 1U); if (nof_bits > 1) { out_bits[1] = ((bits >> 1U) & 1U); } - return detection_metric / std::sqrt(eq_noise_var); + return (std::norm(detected_symbol) / eq_noise_var); } pucch_detector::pucch_detection_result pucch_detector_impl::detect(const resource_grid_reader& grid, @@ -214,9 +214,10 @@ pucch_detector::pucch_detection_result pucch_detector_impl::detect(const resourc validate_config(config); // Total number of REs used for PUCCH data (recall that positive integer division implies taking the floor). - unsigned nof_res = (config.nof_symbols / 2) * NRE; - time_spread_sequence.resize({nof_res, 1}); - ch_estimates.resize({nof_res, 1, 1}); + unsigned nof_res = (config.nof_symbols / 2) * NRE; + unsigned nof_ports = config.ports.size(); + time_spread_sequence.resize({nof_res, nof_ports}); + ch_estimates.resize({nof_res, nof_ports, 1}); eq_time_spread_sequence.resize({nof_res, 1}); eq_time_spread_noise_var.resize({nof_res, 1}); @@ -230,14 +231,17 @@ pucch_detector::pucch_detection_result pucch_detector_impl::detect(const resourc alpha_indices = span(alpha_buffer).first(nof_data_symbols); extract_data_and_estimates( - grid, estimates, config.start_symbol_index, config.starting_prb, config.second_hop_prb, config.port); + grid, estimates, config.start_symbol_index, config.starting_prb, config.second_hop_prb, config.ports); - float port_noise_var = estimates.get_noise_variance(config.port); + span noise_var_all_ports = span(noise_var_buffer).first(nof_ports); + for (unsigned i_port = 0; i_port != nof_ports; ++i_port) { + noise_var_all_ports[i_port] = estimates.get_noise_variance(config.ports[i_port]); + } equalizer->equalize(eq_time_spread_sequence, eq_time_spread_noise_var, time_spread_sequence, ch_estimates, - span(&port_noise_var, 1), + noise_var_all_ports, config.beta_pucch); marginalize_w_and_r_out(config); @@ -271,7 +275,7 @@ pucch_detector::pucch_detection_result pucch_detector_impl::detect(const resourc // either 0 (when there is no PUCCH) or larger than 0 (when there is a PUCCH). Therefore, one can target a constant // probability of false alarm of 1% by setting the detection threshold T such that Q(T) = 0.01, where the Q-function // is the tail distribution function of the standard normal distribution. - constexpr float THRESHOLD = 3.3F; + constexpr float THRESHOLD = 4.0F; bool is_msg_ok = (detection_metric > THRESHOLD); output.detection_metric = detection_metric / THRESHOLD; @@ -296,36 +300,40 @@ pucch_detector::pucch_detection_result pucch_detector_impl::detect(const resourc return output; } -void pucch_detector_impl::extract_data_and_estimates(const resource_grid_reader& grid, - const channel_estimate& estimates, - unsigned first_symbol, - unsigned first_prb, - optional second_prb, - unsigned port) +void pucch_detector_impl::extract_data_and_estimates(const resource_grid_reader& grid, + const channel_estimate& estimates, + unsigned first_symbol, + unsigned first_prb, + optional second_prb, + const static_vector& antenna_ports) { - unsigned i_symbol = 0; - unsigned skip = 0; - unsigned symbol_index = first_symbol + 1; - for (; i_symbol != nof_data_symbols_pre_hop; ++i_symbol, skip += NRE, symbol_index += 2) { - // Index of the first subcarrier assigned to PUCCH, before hopping. - unsigned k_init = NRE * first_prb; - span sequence_chunk = time_spread_sequence.get_data().subspan(skip, NRE); - grid.get(sequence_chunk, port, symbol_index, k_init); - - span tmp = estimates.get_symbol_ch_estimate(symbol_index, port); - srsvec::copy(ch_estimates.get_data().subspan(skip, NRE), tmp.subspan(k_init, NRE)); - } + for (uint8_t port : antenna_ports) { + unsigned i_symbol = 0; + unsigned skip = 0; + unsigned symbol_index = first_symbol + 1; + span sequence_slice = time_spread_sequence.get_view({port}); + span estimate_slice = ch_estimates.get_view({port}); + for (; i_symbol != nof_data_symbols_pre_hop; ++i_symbol, skip += NRE, symbol_index += 2) { + // Index of the first subcarrier assigned to PUCCH, before hopping. + unsigned k_init = NRE * first_prb; + span sequence_chunk = sequence_slice.subspan(skip, NRE); + grid.get(sequence_chunk, port, symbol_index, k_init); + + span tmp = estimates.get_symbol_ch_estimate(symbol_index, port); + srsvec::copy(estimate_slice.subspan(skip, NRE), tmp.subspan(k_init, NRE)); + } - for (; i_symbol != nof_data_symbols; ++i_symbol, skip += NRE, symbol_index += 2) { - // Index of the first subcarrier assigned to PUCCH, after hopping. Note that we only enter this loop if - // second_prb.has_value(). - unsigned k_init = NRE * second_prb.value(); - span sequence_chunk = time_spread_sequence.get_data().subspan(skip, NRE); - grid.get(sequence_chunk, port, symbol_index, k_init); + for (; i_symbol != nof_data_symbols; ++i_symbol, skip += NRE, symbol_index += 2) { + // Index of the first subcarrier assigned to PUCCH, after hopping. Note that we only enter this loop if + // second_prb.has_value(). + unsigned k_init = NRE * second_prb.value(); + span sequence_chunk = sequence_slice.subspan(skip, NRE); + grid.get(sequence_chunk, port, symbol_index, k_init); - span tmp_in = estimates.get_symbol_ch_estimate(symbol_index, port).subspan(k_init, NRE); - span tmp_out = ch_estimates.get_data().subspan(skip, NRE); - srsvec::copy(tmp_out, tmp_in); + span tmp_in = estimates.get_symbol_ch_estimate(symbol_index, port).subspan(k_init, NRE); + span tmp_out = estimate_slice.subspan(skip, NRE); + srsvec::copy(tmp_out, tmp_in); + } } } @@ -417,4 +425,4 @@ void pucch_detector_impl::marginalize_w_and_r_out(const format1_configuration& c // For the noise variance, we have to compute the sum of all variances and divide by the square of their number: same // as computing the mean and dividing again buy their number. eq_noise_var = srsvec::mean(eq_time_spread_noise_var.get_data()) / n_repetitions; -} \ No newline at end of file +} diff --git a/lib/phy/upper/channel_processors/pucch_detector_impl.h b/lib/phy/upper/channel_processors/pucch_detector_impl.h index ff33f97be6..1129bd5c6f 100644 --- a/lib/phy/upper/channel_processors/pucch_detector_impl.h +++ b/lib/phy/upper/channel_processors/pucch_detector_impl.h @@ -74,18 +74,19 @@ class pucch_detector_impl : public pucch_detector /// \brief Extracts PUCCH data and channel coefficients. /// /// This method extracts the PUCCH data REs from the resource grid as well as the corresponding channel estimates. - void extract_data_and_estimates(const resource_grid_reader& grid, - const channel_estimate& estimates, - unsigned first_symbol, - unsigned first_prb, - optional second_prb, - unsigned port); + void extract_data_and_estimates(const resource_grid_reader& grid, + const channel_estimate& estimates, + unsigned first_symbol, + unsigned first_prb, + optional second_prb, + const static_vector& antenna_ports); /// \brief Marginalizes the spreading sequences out. /// - /// A PUCCH Format 1 consists of a single modulation symbol spread over all time and frequency allocated resources. - /// This method combines all the replicas into a single estimate of the modulation symbol and it computes the - /// equivalent noise variance. The PUCCH configuration is needed to build the proper spreading sequences. + /// A PUCCH Format 1 consists of a single modulation symbol spread over all time and frequency allocated + /// resources. This method combines all the replicas into a single estimate of the modulation symbol and it + /// computes the equivalent noise variance. The PUCCH configuration is needed to build the proper spreading + /// sequences. void marginalize_w_and_r_out(const format1_configuration& config); /// Collection of low-PAPR sequences. @@ -98,7 +99,7 @@ class pucch_detector_impl : public pucch_detector /// \remark Only half of the allocated symbols contain data, the other half being used for DM-RS. static_tensor(channel_equalizer::re_list::dims::nof_dims), cf_t, - MAX_ALLOCATED_RE_F1 / 2, + MAX_ALLOCATED_RE_F1 * MAX_PORTS / 2, channel_equalizer::re_list::dims> time_spread_sequence; /// \brief Tensor for storing the channel estimates corresponding to the spread data sequence. @@ -106,7 +107,7 @@ class pucch_detector_impl : public pucch_detector static_tensor( channel_equalizer::ch_est_list::dims::nof_dims), cf_t, - MAX_ALLOCATED_RE_F1 / 2, + MAX_ALLOCATED_RE_F1 * MAX_PORTS / 2, channel_equalizer::ch_est_list::dims> ch_estimates; /// \brief Buffer for storing the spread data sequence after equalization. @@ -124,6 +125,8 @@ class pucch_detector_impl : public pucch_detector MAX_ALLOCATED_RE_F1 / 2, channel_equalizer::re_list::dims> eq_time_spread_noise_var; + /// Buffer for noise variances. + std::array noise_var_buffer; /// Buffer for storing alpha indices. std::array alpha_buffer; /// View of the alpha indices buffer. diff --git a/lib/phy/upper/channel_processors/pucch_processor_impl.cpp b/lib/phy/upper/channel_processors/pucch_processor_impl.cpp index d6388ea4e5..1490be9f2a 100644 --- a/lib/phy/upper/channel_processors/pucch_processor_impl.cpp +++ b/lib/phy/upper/channel_processors/pucch_processor_impl.cpp @@ -78,7 +78,7 @@ pucch_processor_result pucch_processor_impl::process(const resource_grid_reader& detector_config.start_symbol_index = config.start_symbol_index; detector_config.nof_symbols = config.nof_symbols; detector_config.group_hopping = pucch_group_hopping::NEITHER; - detector_config.port = config.ports.front(); + detector_config.ports = config.ports; detector_config.beta_pucch = 1.0F; detector_config.time_domain_occ = config.time_domain_occ; detector_config.initial_cyclic_shift = config.initial_cyclic_shift; diff --git a/lib/phy/upper/signal_processors/srs/srs_estimator_factory.cpp b/lib/phy/upper/signal_processors/srs/srs_estimator_factory.cpp index 60568df84b..d083259167 100644 --- a/lib/phy/upper/signal_processors/srs/srs_estimator_factory.cpp +++ b/lib/phy/upper/signal_processors/srs/srs_estimator_factory.cpp @@ -24,6 +24,7 @@ #include "logging_srs_estimator_decorator.h" #include "srs_estimator_generic_impl.h" #include "srs_validator_generic_impl.h" +#include "srsran/phy/support/time_alignment_estimator/time_alignment_estimator_factories.h" using namespace srsran; @@ -32,16 +33,20 @@ namespace { class srs_estimator_factory_generic : public srs_estimator_factory { public: - srs_estimator_factory_generic(std::shared_ptr sequence_generator_factory_) : - sequence_generator_factory(std::move(sequence_generator_factory_)) + srs_estimator_factory_generic(std::shared_ptr sequence_generator_factory_, + std::shared_ptr ta_estimator_factory_) : + sequence_generator_factory(std::move(sequence_generator_factory_)), + ta_estimator_factory(std::move(ta_estimator_factory_)) { srsran_assert(sequence_generator_factory, "Invalid sequence generator factory."); + srsran_assert(ta_estimator_factory, "Invalid TA estimator factory."); } std::unique_ptr create() override { srs_estimator_generic_impl::dependencies deps; deps.sequence_generator = sequence_generator_factory->create(); + deps.ta_estimator = ta_estimator_factory->create(); return std::make_unique(std::move(deps)); } @@ -53,14 +58,17 @@ class srs_estimator_factory_generic : public srs_estimator_factory private: std::shared_ptr sequence_generator_factory; + std::shared_ptr ta_estimator_factory; }; } // namespace std::shared_ptr srsran::create_srs_estimator_generic_factory( - std::shared_ptr sequence_generator_factory) + std::shared_ptr sequence_generator_factory, + std::shared_ptr ta_estimator_factory) { - return std::make_shared(std::move(sequence_generator_factory)); + return std::make_shared(std::move(sequence_generator_factory), + std::move(ta_estimator_factory)); } std::unique_ptr srs_estimator_factory::create(srslog::basic_logger& logger) diff --git a/lib/phy/upper/signal_processors/srs/srs_estimator_generic_impl.cpp b/lib/phy/upper/signal_processors/srs/srs_estimator_generic_impl.cpp index 839eb30d51..96d4e50868 100644 --- a/lib/phy/upper/signal_processors/srs/srs_estimator_generic_impl.cpp +++ b/lib/phy/upper/signal_processors/srs/srs_estimator_generic_impl.cpp @@ -21,31 +21,35 @@ */ #include "srs_estimator_generic_impl.h" +#include "srsran/adt/complex.h" +#include "srsran/adt/static_vector.h" +#include "srsran/adt/tensor.h" #include "srsran/phy/constants.h" #include "srsran/phy/support/resource_grid_reader.h" #include "srsran/phy/upper/signal_processors/srs/srs_estimator_configuration.h" #include "srsran/phy/upper/signal_processors/srs/srs_estimator_result.h" #include "srsran/ran/cyclic_prefix.h" -#include "srsran/ran/srs/srs_bandwidth_configuration.h" +#include "srsran/ran/srs/srs_constants.h" #include "srsran/ran/srs/srs_information.h" -#include "srsran/srsvec/dot_prod.h" +#include "srsran/srsvec/add.h" +#include "srsran/srsvec/convolution.h" +#include "srsran/srsvec/mean.h" +#include "srsran/srsvec/prod.h" using namespace srsran; -void srs_estimator_generic_impl::extract_sequence(span sequence, - const resource_grid_reader& grid, - unsigned i_rx_port, - unsigned i_symbol, - unsigned initial_subcarrier, - unsigned comb_size) +bounded_bitset +srs_estimator_generic_impl::generate_mask(unsigned comb_size, unsigned sequence_length) { - bounded_bitset mask(sequence.size() * comb_size); - - for (unsigned i = 0, i_end = sequence.size(); i != i_end; ++i) { - mask.set(comb_size * i); + bounded_bitset re_mask(NRE); + for (unsigned k = 0; k != NRE; k += comb_size) { + re_mask.set(k); } - grid.get(sequence, i_rx_port, i_symbol, initial_subcarrier, mask); + // Make RB mask with only active RB. + bounded_bitset rb_mask = ~bounded_bitset((sequence_length * comb_size) / NRE); + + return rb_mask.kronecker_product(re_mask); } srs_estimator_result srs_estimator_generic_impl::estimate(const resource_grid_reader& grid, @@ -55,6 +59,8 @@ srs_estimator_result srs_estimator_generic_impl::estimate(const resource_grid_re srsran_assert(config.resource.is_valid(), "Invalid SRS resource."); srsran_assert(!config.ports.empty(), "Receive port list is empty."); + unsigned nof_rx_ports = config.ports.size(); + unsigned nof_antenna_ports = static_cast(config.resource.nof_antenna_ports); unsigned nof_symbols = static_cast(config.resource.nof_symbols); unsigned nof_symbols_per_slot = get_nsymb_per_slot(cyclic_prefix::NORMAL); srsran_assert(config.resource.start_symbol.value() + nof_symbols <= nof_symbols_per_slot, @@ -64,35 +70,123 @@ srs_estimator_result srs_estimator_generic_impl::estimate(const resource_grid_re nof_symbols, nof_symbols_per_slot); + // Extract subcarrier spacing. + subcarrier_spacing scs = to_subcarrier_spacing(config.slot.numerology()); + + // Extract comb size. + unsigned comb_size = static_cast(config.resource.comb_size); + + srs_information common_info = get_srs_information(config.resource, 0); + + // Sequence length is common for all ports and symbols. + unsigned sequence_length = common_info.sequence_length; + + // Maximum measurable delay due to cyclic shift. + double max_ta = 1.0 / static_cast(common_info.n_cs_max * scs_to_khz(scs) * 1000 * comb_size); + + // Prepare results. srs_estimator_result result; - result.channel_matrix = - srs_channel_matrix(config.ports.size(), static_cast(config.resource.nof_antenna_ports)); - - for (uint8_t i_rx_port : config.ports) { - for (unsigned i_antenna_port = 0, i_antenna_port_end = static_cast(config.resource.nof_antenna_ports); - i_antenna_port != i_antenna_port_end; - ++i_antenna_port) { - cf_t coefficient = 0; + result.time_alignment.time_alignment = 0; + result.time_alignment.resolution = 0; + result.time_alignment.min = std::numeric_limits::min(); + result.time_alignment.max = std::numeric_limits::max(); + result.channel_matrix = srs_channel_matrix(nof_rx_ports, nof_antenna_ports); + + // Temporary LSE. + static_tensor<3, cf_t, max_seq_length * srs_constants::max_nof_rx_ports * srs_constants::max_nof_tx_ports> temp_lse( + {sequence_length, nof_rx_ports, nof_antenna_ports}); + + // Iterate transmit ports. + for (unsigned i_antenna_port = 0; i_antenna_port != nof_antenna_ports; ++i_antenna_port) { + // Obtain SRS information for a given SRS antenna port. + srs_information info = get_srs_information(config.resource, i_antenna_port); + + // Generate sequence. + static_vector sequence(info.sequence_length); + deps.sequence_generator->generate(sequence, info.sequence_group, info.sequence_number, info.n_cs, info.n_cs_max); + + // Generate allocation mask. + bounded_bitset mask = generate_mask(info.comb_size, info.sequence_length); + + // Iterate receive ports. + for (unsigned i_rx_port_index = 0; i_rx_port_index != nof_rx_ports; ++i_rx_port_index) { + unsigned i_rx_port = config.ports[i_rx_port_index]; + + // View to the mean LSE for a port combination. + span mean_lse = temp_lse.get_view({i_rx_port_index, i_antenna_port}); + + // Extract sequence for all symbols and average LSE. for (unsigned i_symbol = config.resource.start_symbol.value(), i_symbol_end = config.resource.start_symbol.value() + nof_symbols; i_symbol != i_symbol_end; ++i_symbol) { - // Generate SRS information for a given SRS antenna port and symbol. - srs_information info = get_srs_information(config.resource, i_antenna_port, i_symbol); + // Extract received sequence. + static_vector rx_sequence(info.sequence_length); + grid.get(rx_sequence, i_rx_port, i_symbol, info.mapping_initial_subcarrier, mask); - // Generate sequence. - static_vector sequence(info.sequence_length); - deps.sequence_generator->generate( - sequence, info.sequence_group, info.sequence_number, info.n_cs, info.n_cs_max); + // Temporary LSE. + span lse = rx_sequence; - static_vector rx_sequence(info.sequence_length); - extract_sequence(rx_sequence, grid, i_rx_port, i_symbol, info.mapping_initial_subcarrier, info.comb_size); + // Avoid accumulation for the first symbol containing SRS. + if (i_symbol == config.resource.start_symbol.value()) { + lse = mean_lse; + } - coefficient += srsvec::dot_prod(rx_sequence, sequence) / static_cast(sequence.size()); + // Calculate LSE. + srsvec::prod_conj(rx_sequence, sequence, lse); + + // Accumulate LSE for the averaging. + if (lse.data() != mean_lse.data()) { + srsvec::add(mean_lse, lse, mean_lse); + } } - coefficient /= static_cast(nof_symbols); + // Scale accumulated LSE. + if (nof_symbols > 1) { + srsvec::sc_prod(mean_lse, 1.0 / static_cast(nof_symbols), mean_lse); + } + + // Estimate TA. + time_alignment_measurement ta_meas = deps.ta_estimator->estimate(mean_lse, mask, scs, max_ta); + + // Combine time alignment measurement. + result.time_alignment.time_alignment += ta_meas.time_alignment; + result.time_alignment.min = std::max(result.time_alignment.min, ta_meas.min); + result.time_alignment.max = std::min(result.time_alignment.max, ta_meas.max); + result.time_alignment.resolution = std::max(result.time_alignment.resolution, ta_meas.resolution); + } + } + + // Average time alignment across all paths. + result.time_alignment.time_alignment /= nof_antenna_ports * nof_rx_ports; + + // Compensate time alignment and estimate channel coefficients. + for (unsigned i_antenna_port = 0; i_antenna_port != nof_antenna_ports; ++i_antenna_port) { + for (unsigned i_rx_port = 0; i_rx_port != nof_rx_ports; ++i_rx_port) { + // View to the mean LSE for a port combination. + span mean_lse = temp_lse.get_view({i_rx_port, i_antenna_port}); + + // Get sequence information. + srs_information info = get_srs_information(config.resource, i_antenna_port); + + // Calculate subcarrier phase shift in radians. + float phase_shift_subcarrier = + static_cast(2.0F * M_PI * result.time_alignment.time_alignment * scs_to_khz(scs) * 1000 * comb_size); + + // Calculate the initial phase shift in radians. + float phase_shift_offset = phase_shift_subcarrier * (info.mapping_initial_subcarrier % comb_size) / comb_size; + + // Apply phase shift in frequency domain. + std::transform(mean_lse.begin(), + mean_lse.end(), + mean_lse.begin(), + [phase_shift_subcarrier, phase_shift_offset, n = 0](cf_t in) mutable { + return in * + std::polar(1.0F, phase_shift_subcarrier * static_cast(n++) + phase_shift_offset); + }); + // Calculate channel wideband coefficient. + cf_t coefficient = srsvec::mean(mean_lse); result.channel_matrix.set_coefficient(coefficient, i_rx_port, i_antenna_port); } } diff --git a/lib/phy/upper/signal_processors/srs/srs_estimator_generic_impl.h b/lib/phy/upper/signal_processors/srs/srs_estimator_generic_impl.h index 24739d628e..341babceb0 100644 --- a/lib/phy/upper/signal_processors/srs/srs_estimator_generic_impl.h +++ b/lib/phy/upper/signal_processors/srs/srs_estimator_generic_impl.h @@ -23,6 +23,7 @@ #pragma once #include "srsran/phy/constants.h" +#include "srsran/phy/support/time_alignment_estimator/time_alignment_estimator.h" #include "srsran/phy/upper/sequence_generators/low_papr_sequence_generator.h" #include "srsran/phy/upper/signal_processors/srs/srs_estimator.h" #include @@ -37,11 +38,14 @@ class srs_estimator_generic_impl : public srs_estimator struct dependencies { /// Sequence generator. std::unique_ptr sequence_generator; + /// Time alignment estimator. + std::unique_ptr ta_estimator; }; srs_estimator_generic_impl(dependencies deps_) : deps(std::move(deps_)) { srsran_assert(deps.sequence_generator, "Invalid sequence generator."); + srsran_assert(deps.ta_estimator, "Invalid TA estimator."); } srs_estimator_result estimate(const resource_grid_reader& grid, const srs_estimator_configuration& config) override; @@ -52,23 +56,16 @@ class srs_estimator_generic_impl : public srs_estimator /// It is given by the maximum value of \f$m_{SRS,0}\f$ in TS38.211 Table 6.4.1.4.3-1 and a comb size of 2. static constexpr unsigned max_seq_length = 272 * NRE / 2; - /// \brief Extract the received Sounding Reference Signals sequence from a resource grid. + static constexpr unsigned max_symbol_size = MAX_RB * NRE; + + /// \brief Generates the SRS sequence allocation mask. /// - /// The sequence mapping is given in TS38.211 Section 6.4.1.4.3. + /// The mask starts at the first subcarrier used for mapping the SRS sequence. /// - /// \param[out] sequence Extracted sequence. - /// \param[in] grid Received resource grid. - /// \param[in] i_rx_port Receive port index. - /// \param[in] i_symbol OFDM symbol index within the slot to extract. - /// \param[in] initial_subcarrier Initial subcarrier index relative to Point A. Parameter \f$k_0^{(p_i)}\f$. - /// \param[in] comb_size Subcarrier stride. Parameter \f$K_{TC}\f$. - /// \return A vector containing the sequence. - void extract_sequence(span sequence, - const resource_grid_reader& grid, - unsigned i_rx_port, - unsigned i_symbol, - unsigned initial_subcarrier, - unsigned comb_size); + /// \param comb_size Comb size, parameter \f$K_{TC}\f$. + /// \param sequence_length Sequence length, parameter \f$M_{sc,b}^{SRS}\f$. + /// \return The SRS sequence allocation mask. + static bounded_bitset generate_mask(unsigned comb_size, unsigned sequence_length); /// Dependencies collection. dependencies deps; diff --git a/lib/phy/upper/upper_phy_factories.cpp b/lib/phy/upper/upper_phy_factories.cpp index d47913780d..6f96ec4457 100644 --- a/lib/phy/upper/upper_phy_factories.cpp +++ b/lib/phy/upper/upper_phy_factories.cpp @@ -356,6 +356,10 @@ static std::shared_ptr create_ul_processor_factory(con report_fatal_error_if_not(dft_factory, "Invalid DFT factory."); } + std::shared_ptr ta_est_factory = + create_time_alignment_estimator_dft_factory(dft_factory); + report_fatal_error_if_not(ta_est_factory, "Invalid TA estimator factory."); + std::shared_ptr prach_gen_factory = create_prach_generator_factory_sw(); report_fatal_error_if_not(prach_gen_factory, "Invalid PRACH generator factory."); @@ -363,7 +367,8 @@ static std::shared_ptr create_ul_processor_factory(con create_prach_detector_factory_sw(dft_factory, prach_gen_factory); report_fatal_error_if_not(prach_factory, "Invalid PRACH detector factory."); - std::shared_ptr srs_factory = create_srs_estimator_generic_factory(sequence_factory); + std::shared_ptr srs_factory = + create_srs_estimator_generic_factory(sequence_factory, ta_est_factory); report_fatal_error_if_not(srs_factory, "Invalid SRS estimator factory."); // Create PRACH detector pool factory if more than one thread is used. diff --git a/lib/ran/srs/srs_information.cpp b/lib/ran/srs/srs_information.cpp index 3effb98543..df5e884360 100644 --- a/lib/ran/srs/srs_information.cpp +++ b/lib/ran/srs/srs_information.cpp @@ -36,8 +36,7 @@ static constexpr unsigned get_sequence_length(unsigned m_srs_b, srs_resource_con return (m_srs_b * N_RB_SC) / static_cast(comb_size); } -srs_information -srsran::get_srs_information(const srs_resource_configuration& resource, unsigned i_antenna_port, unsigned i_symbol) +srs_information srsran::get_srs_information(const srs_resource_configuration& resource, unsigned i_antenna_port) { // Select BW configuration. optional srs_bw_config = @@ -94,6 +93,7 @@ srsran::get_srs_information(const srs_resource_configuration& resource, unsigned // Fill derived parameters. srs_information info; + info.n_cs_max = n_cs_max; info.sequence_length = sequence_length; info.sequence_group = u; info.sequence_number = v; diff --git a/lib/rlc/rlc_sdu_queue_lockfree.h b/lib/rlc/rlc_sdu_queue_lockfree.h index bc5b715622..613f15227d 100644 --- a/lib/rlc/rlc_sdu_queue_lockfree.h +++ b/lib/rlc/rlc_sdu_queue_lockfree.h @@ -105,8 +105,7 @@ class rlc_sdu_queue_lockfree } // update totals - n_sdus.fetch_add(1, std::memory_order_relaxed); - n_bytes.fetch_add(sdu_size, std::memory_order_relaxed); + state_add(sdu_size); return true; } @@ -151,8 +150,7 @@ class rlc_sdu_queue_lockfree } // update totals - n_sdus.fetch_sub(1, std::memory_order_relaxed); - n_bytes.fetch_sub(sdu_size, std::memory_order_relaxed); + state_sub(sdu_size); return true; } @@ -189,30 +187,15 @@ class rlc_sdu_queue_lockfree sdu_is_valid = true; // update totals - n_sdus.fetch_sub(1, std::memory_order_relaxed); - n_bytes.fetch_sub(sdu.buf.length(), std::memory_order_relaxed); + state_sub(sdu.buf.length()); } // try again if SDU is not valid } while (not sdu_is_valid); return true; } - /// \brief Reads the number of buffered SDUs that are not marked as discarded. - /// - /// This function may be called by any thread. - /// - /// \return The number of buffered SDUs that are not marked as discarded. - uint32_t size_sdus() const { return n_sdus.load(std::memory_order_relaxed); } - - /// \brief Reads the number of buffered SDU bytes that are not marked as discarded. - /// - /// This function may be called by any thread. - /// - /// \return The number of buffered SDU bytes that are not marked as discarded. - uint32_t size_bytes() const { return n_bytes.load(std::memory_order_relaxed); } - /// \brief Container for return value of \c get_state function. - struct state { + struct state_t { uint32_t n_sdus; ///< Number of buffered SDUs that are not marked as discarded. uint32_t n_bytes; ///< Number of buffered bytes that are not marked as discarded. }; @@ -222,7 +205,14 @@ class rlc_sdu_queue_lockfree /// This function may be called by any thread. /// /// \return Current state of the queue. - state get_state() const { return {size_sdus(), size_bytes()}; } + state_t get_state() const + { + uint64_t packed = state.load(std::memory_order_relaxed); + state_t result; + result.n_bytes = packed & 0xffffffff; + result.n_sdus = packed >> 32; + return result; + } /// \brief Checks if the internal queue is empty. /// @@ -280,8 +270,7 @@ class rlc_sdu_queue_lockfree } // update totals - n_sdus.fetch_sub(1, std::memory_order_relaxed); - n_bytes.fetch_sub(sdu_size, std::memory_order_relaxed); + state_sub(sdu_size); return sdu_is_valid; } @@ -290,9 +279,11 @@ class rlc_sdu_queue_lockfree rlc_bearer_logger& logger; - uint16_t capacity; - std::atomic n_bytes = {0}; - std::atomic n_sdus = {0}; + uint16_t capacity; + + /// Combined atomic state of the queue reflecting the number of SDUs and the number of bytes. + /// Upper 32 bit: n_sdus; Lower 32 bit: n_bytes + std::atomic state = {0}; std::unique_ptr< concurrent_queue> @@ -300,13 +291,29 @@ class rlc_sdu_queue_lockfree std::unique_ptr[]> sdu_states; std::unique_ptr[]> sdu_sizes; + + /// \brief Adds one SDU of given size to the atomic queue state. + /// \param sdu_size The size of the SDU. + void state_add(uint32_t sdu_size) + { + uint64_t state_change = static_cast(1U) << 32U | sdu_size; + state.fetch_add(state_change, std::memory_order_relaxed); + } + + /// \brief Subtracts one SDU of given size from the atomic queue state. + /// \param sdu_size The size of the SDU. + void state_sub(uint32_t sdu_size) + { + uint64_t state_change = static_cast(1U) << 32U | sdu_size; + state.fetch_sub(state_change, std::memory_order_relaxed); + } }; } // namespace srsran namespace fmt { template <> -struct formatter { +struct formatter { template auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { @@ -314,7 +321,7 @@ struct formatter { } template - auto format(const srsran::rlc_sdu_queue_lockfree::state& state, FormatContext& ctx) + auto format(const srsran::rlc_sdu_queue_lockfree::state_t& state, FormatContext& ctx) -> decltype(std::declval().out()) { return format_to(ctx.out(), "queued_sdus={} queued_bytes={}", state.n_sdus, state.n_bytes); diff --git a/lib/rlc/rlc_tx_am_entity.cpp b/lib/rlc/rlc_tx_am_entity.cpp index 30d6b78894..f807303ef3 100644 --- a/lib/rlc/rlc_tx_am_entity.cpp +++ b/lib/rlc/rlc_tx_am_entity.cpp @@ -597,7 +597,7 @@ void rlc_tx_am_entity::handle_status_pdu(rlc_am_status_pdu status) if (tx_mod_base(status.ack_sn) > tx_mod_base(st.tx_next + 1)) { logger.log_error("Ignoring status report with ack_sn={} > tx_next. {}", status.ack_sn, st); - if (ue_executor.defer([this]() { upper_cn.on_protocol_failure(); })) { + if (not ue_executor.defer([this]() { upper_cn.on_protocol_failure(); })) { logger.log_error("Could not trigger protocol failure on invalid ACK_SN"); } return; @@ -920,7 +920,8 @@ uint32_t rlc_tx_am_entity::get_buffer_state() uint32_t rlc_tx_am_entity::get_buffer_state_nolock() { // minimum bytes needed to tx all queued SDUs + each header - uint32_t queue_bytes = sdu_queue.size_bytes() + sdu_queue.size_sdus() * head_min_size; + rlc_sdu_queue_lockfree::state_t queue_state = sdu_queue.get_state(); + uint32_t queue_bytes = queue_state.n_bytes + queue_state.n_sdus * head_min_size; // minimum bytes needed to tx SDU under segmentation + header (if applicable) uint32_t segment_bytes = 0; diff --git a/lib/rlc/rlc_tx_tm_entity.cpp b/lib/rlc/rlc_tx_tm_entity.cpp index eace43776e..f4c25ed3a1 100644 --- a/lib/rlc/rlc_tx_tm_entity.cpp +++ b/lib/rlc/rlc_tx_tm_entity.cpp @@ -136,5 +136,5 @@ void rlc_tx_tm_entity::update_mac_buffer_state() uint32_t rlc_tx_tm_entity::get_buffer_state() { - return sdu_queue.size_bytes() + sdu.buf.length(); + return sdu_queue.get_state().n_bytes + sdu.buf.length(); } diff --git a/lib/rlc/rlc_tx_um_entity.cpp b/lib/rlc/rlc_tx_um_entity.cpp index 5c8cc25493..f7276f69a7 100644 --- a/lib/rlc/rlc_tx_um_entity.cpp +++ b/lib/rlc/rlc_tx_um_entity.cpp @@ -269,7 +269,8 @@ uint32_t rlc_tx_um_entity::get_buffer_state() std::lock_guard lock(mutex); // minimum bytes needed to tx all queued SDUs + each header - uint32_t queue_bytes = sdu_queue.size_bytes() + sdu_queue.size_sdus() * head_len_full; + rlc_sdu_queue_lockfree::state_t queue_state = sdu_queue.get_state(); + uint32_t queue_bytes = queue_state.n_bytes + queue_state.n_sdus * head_len_full; // minimum bytes needed to tx SDU under segmentation + header (if applicable) uint32_t segment_bytes = 0; diff --git a/lib/rrc/ue/procedures/rrc_reconfiguration_procedure.cpp b/lib/rrc/ue/procedures/rrc_reconfiguration_procedure.cpp index a3a47c1fd8..16a908730e 100644 --- a/lib/rrc/ue/procedures/rrc_reconfiguration_procedure.cpp +++ b/lib/rrc/ue/procedures/rrc_reconfiguration_procedure.cpp @@ -31,16 +31,14 @@ using namespace asn1::rrc_nr; rrc_reconfiguration_procedure::rrc_reconfiguration_procedure(rrc_ue_context_t& context_, const rrc_reconfiguration_procedure_request& args_, rrc_ue_reconfiguration_proc_notifier& rrc_ue_notifier_, - rrc_ue_control_notifier& ngap_ctrl_notifier_, - rrc_ue_du_processor_notifier& du_proc_notifier_, + rrc_ue_context_update_notifier& cu_cp_notifier_, rrc_ue_event_manager& event_mng_, rrc_ue_srb_handler& srb_notifier_, rrc_ue_logger& logger_) : context(context_), args(args_), rrc_ue(rrc_ue_notifier_), - ngap_ctrl_notifier(ngap_ctrl_notifier_), - du_processor_notifier(du_proc_notifier_), + cu_cp_notifier(cu_cp_notifier_), event_mng(event_mng_), srb_notifier(srb_notifier_), logger(logger_) @@ -79,14 +77,8 @@ void rrc_reconfiguration_procedure::operator()(coro_context>& c } else { logger.log_warning("\"{}\" timed out after {}ms", name(), context.cfg.rrc_procedure_timeout_ms); // Notify NGAP to request UE context release from AMF - CORO_AWAIT_VALUE(release_request_sent, - ngap_ctrl_notifier.on_ue_context_release_request( - {context.ue_index, {}, ngap_cause_radio_network_t::release_due_to_ngran_generated_reason})); - if (!release_request_sent) { - // If NGAP release request was not sent to AMF, release UE from DU processor, RRC and F1AP - CORO_AWAIT(du_processor_notifier.on_ue_context_release_command( - {context.ue_index, ngap_cause_radio_network_t::unspecified})); - } + CORO_AWAIT(cu_cp_notifier.on_ue_release_required( + {context.ue_index, {}, ngap_cause_radio_network_t::release_due_to_ngran_generated_reason})); } logger.log_debug("\"{}\" finalized", name()); diff --git a/lib/rrc/ue/procedures/rrc_reconfiguration_procedure.h b/lib/rrc/ue/procedures/rrc_reconfiguration_procedure.h index 9111f437f4..d9eb3c04e4 100644 --- a/lib/rrc/ue/procedures/rrc_reconfiguration_procedure.h +++ b/lib/rrc/ue/procedures/rrc_reconfiguration_procedure.h @@ -40,8 +40,7 @@ class rrc_reconfiguration_procedure rrc_reconfiguration_procedure(rrc_ue_context_t& context_, const rrc_reconfiguration_procedure_request& args_, rrc_ue_reconfiguration_proc_notifier& rrc_ue_notifier_, - rrc_ue_control_notifier& ngap_ctrl_notifier_, - rrc_ue_du_processor_notifier& du_proc_notifier_, + rrc_ue_context_update_notifier& cu_cp_notifier_, rrc_ue_event_manager& ev_mng_, rrc_ue_srb_handler& srb_notifier_, rrc_ue_logger& logger_); @@ -57,18 +56,16 @@ class rrc_reconfiguration_procedure rrc_ue_context_t& context; const rrc_reconfiguration_procedure_request args; - rrc_ue_reconfiguration_proc_notifier& rrc_ue; // handler to the parent RRC UE object - rrc_ue_control_notifier& ngap_ctrl_notifier; - rrc_ue_du_processor_notifier& du_processor_notifier; // to release the UE if it couldn't be found in the NGAP - rrc_ue_event_manager& event_mng; // event manager for the RRC UE entity - rrc_ue_srb_handler& srb_notifier; // For creating SRBs + rrc_ue_reconfiguration_proc_notifier& rrc_ue; // handler to the parent RRC UE object + rrc_ue_context_update_notifier& cu_cp_notifier; // to release the UE if the reconfiguration fails + rrc_ue_event_manager& event_mng; // event manager for the RRC UE entity + rrc_ue_srb_handler& srb_notifier; // For creating SRBs rrc_ue_logger& logger; rrc_transaction transaction; eager_async_task task; - bool release_request_sent = false; - bool procedure_result = false; + bool procedure_result = false; }; } // namespace srs_cu_cp diff --git a/lib/rrc/ue/procedures/rrc_reestablishment_procedure.cpp b/lib/rrc/ue/procedures/rrc_reestablishment_procedure.cpp index fbef1dc431..0286b6512e 100644 --- a/lib/rrc/ue/procedures/rrc_reestablishment_procedure.cpp +++ b/lib/rrc/ue/procedures/rrc_reestablishment_procedure.cpp @@ -40,7 +40,6 @@ rrc_reestablishment_procedure::rrc_reestablishment_procedure( rrc_ue_srb_handler& srb_notifier_, rrc_ue_du_processor_notifier& du_processor_notifier_, rrc_ue_context_update_notifier& cu_cp_notifier_, - rrc_ue_control_notifier& ngap_ctrl_notifier_, rrc_ue_nas_notifier& nas_notifier_, rrc_ue_event_manager& event_mng_, rrc_ue_logger& logger_) : @@ -53,7 +52,6 @@ rrc_reestablishment_procedure::rrc_reestablishment_procedure( srb_notifier(srb_notifier_), du_processor_notifier(du_processor_notifier_), cu_cp_notifier(cu_cp_notifier_), - ngap_ctrl_notifier(ngap_ctrl_notifier_), nas_notifier(nas_notifier_), event_mng(event_mng_), logger(logger_) @@ -77,8 +75,7 @@ void rrc_reestablishment_procedure::operator()(coro_context>& c } // Transfer old UE context to new UE context. If it fails, resort to fallback. - CORO_AWAIT_VALUE(context_transfer_success, - cu_cp_notifier.on_ue_transfer_required(context.ue_index, old_ue_reest_context.ue_index)); + CORO_AWAIT_VALUE(context_transfer_success, cu_cp_notifier.on_ue_transfer_required(old_ue_reest_context.ue_index)); if (not context_transfer_success) { CORO_AWAIT(handle_rrc_reestablishment_fallback()); logger.log_debug("\"{}\" for old_ue={} finalized", name(), old_ue_reest_context.ue_index); @@ -118,7 +115,7 @@ void rrc_reestablishment_procedure::operator()(coro_context>& c // Release the old UE ue_context_release_request.ue_index = context.ue_index; ue_context_release_request.cause = ngap_cause_radio_network_t::unspecified; - CORO_AWAIT(ngap_ctrl_notifier.on_ue_context_release_request(ue_context_release_request)); + CORO_AWAIT(cu_cp_notifier.on_ue_release_required(ue_context_release_request)); } else { logger.log_debug("\"{}\" for old_ue={} finalized", name(), old_ue_reest_context.ue_index); } @@ -132,7 +129,7 @@ void rrc_reestablishment_procedure::operator()(coro_context>& c } // Notify CU-CP to remove the old UE - CORO_AWAIT(cu_cp_notifier.on_ue_removal_required(old_ue_reest_context.ue_index)); + cu_cp_notifier.on_rrc_reestablishment_complete(old_ue_reest_context.ue_index); // Note: From this point the UE is removed and only the stored context can be accessed. @@ -141,7 +138,7 @@ void rrc_reestablishment_procedure::operator()(coro_context>& c async_task rrc_reestablishment_procedure::handle_rrc_reestablishment_fallback() { - return launch_async([this, result = false](coro_context>& ctx) mutable { + return launch_async([this](coro_context>& ctx) mutable { CORO_BEGIN(ctx); // Reject RRC Reestablishment Request by sending RRC Setup @@ -160,15 +157,7 @@ async_task rrc_reestablishment_procedure::handle_rrc_reestablishment_fallb old_ue_reest_context.ue_index); ue_context_release_request.ue_index = old_ue_reest_context.ue_index; ue_context_release_request.cause = ngap_cause_radio_network_t::unspecified; - CORO_AWAIT_VALUE(result, ngap_ctrl_notifier.on_ue_context_release_request(ue_context_release_request)); - if (!result) { - // If NGAP release request was not sent to AMF, release UE from DU processor, RRC and F1AP - CORO_AWAIT(du_processor_notifier.on_ue_context_release_command( - {old_ue_reest_context.ue_index, ngap_cause_radio_network_t::unspecified})); - } - if (not result) { - // The old UE did not have an NGAP UE context. - } + cu_cp_notifier.on_rrc_reestablishment_failure(ue_context_release_request); } CORO_RETURN(); @@ -190,8 +179,7 @@ bool rrc_reestablishment_procedure::is_reestablishment_accepted() // Request from the CU-CP the old RRC UE context. old_ue_reest_context = cu_cp_notifier.on_rrc_reestablishment_request(reestablishment_request.rrc_reest_request.ue_id.pci, - to_rnti(reestablishment_request.rrc_reest_request.ue_id.c_rnti), - context.ue_index); + to_rnti(reestablishment_request.rrc_reest_request.ue_id.c_rnti)); // check if an old UE context with matching C-RNTI, PCI exists. if (old_ue_reest_context.ue_index == ue_index_t::invalid) { diff --git a/lib/rrc/ue/procedures/rrc_reestablishment_procedure.h b/lib/rrc/ue/procedures/rrc_reestablishment_procedure.h index 55e67a55b0..14d5475601 100644 --- a/lib/rrc/ue/procedures/rrc_reestablishment_procedure.h +++ b/lib/rrc/ue/procedures/rrc_reestablishment_procedure.h @@ -44,7 +44,6 @@ class rrc_reestablishment_procedure rrc_ue_srb_handler& srb_notifier_, rrc_ue_du_processor_notifier& du_processor_notifier_, rrc_ue_context_update_notifier& cu_cp_notifier_, - rrc_ue_control_notifier& ngap_ctrl_notifier_, rrc_ue_nas_notifier& nas_notifier_, rrc_ue_event_manager& event_mng_, rrc_ue_logger& logger_); @@ -82,7 +81,6 @@ class rrc_reestablishment_procedure rrc_ue_srb_handler& srb_notifier; // for creating SRBs rrc_ue_du_processor_notifier& du_processor_notifier; // notifier to the DU processor rrc_ue_context_update_notifier& cu_cp_notifier; // notifier to the CU-CP - rrc_ue_control_notifier& ngap_ctrl_notifier; // Control message notifier to the NGAP rrc_ue_nas_notifier& nas_notifier; // notifier to the NGAP rrc_ue_event_manager& event_mng; // event manager for the RRC UE entity rrc_ue_logger& logger; diff --git a/lib/rrc/ue/rrc_ue_impl.cpp b/lib/rrc/ue/rrc_ue_impl.cpp index 96bb0cff8c..fb7a59d76b 100644 --- a/lib/rrc/ue/rrc_ue_impl.cpp +++ b/lib/rrc/ue/rrc_ue_impl.cpp @@ -175,17 +175,11 @@ byte_buffer rrc_ue_impl::get_packed_handover_preparation_message() void rrc_ue_impl::on_ue_release_required(const ngap_cause_t& cause) { - task_sched.schedule_async_task( - launch_async([this, ngap_release_result = bool{false}, cause](coro_context>& ctx) mutable { - CORO_BEGIN(ctx); - - CORO_AWAIT_VALUE(ngap_release_result, - ngap_ctrl_notifier.on_ue_context_release_request({context.ue_index, {}, cause})); - if (!ngap_release_result) { - // If NGAP release request was not sent to AMF, release UE from DU processor, RRC and F1AP - CORO_AWAIT(du_processor_notifier.on_ue_context_release_command({context.ue_index, cause})); - } - - CORO_RETURN(); - })); + task_sched.schedule_async_task(launch_async([this, cause](coro_context>& ctx) mutable { + CORO_BEGIN(ctx); + + CORO_AWAIT(cu_cp_notifier.on_ue_release_required({context.ue_index, {}, cause})); + + CORO_RETURN(); + })); } diff --git a/lib/rrc/ue/rrc_ue_message_handlers.cpp b/lib/rrc/ue/rrc_ue_message_handlers.cpp index 1116349d38..2331a3ef31 100644 --- a/lib/rrc/ue/rrc_ue_message_handlers.cpp +++ b/lib/rrc/ue/rrc_ue_message_handlers.cpp @@ -130,7 +130,6 @@ void rrc_ue_impl::handle_rrc_reest_request(const asn1::rrc_nr::rrc_reest_request get_rrc_ue_srb_handler(), du_processor_notifier, cu_cp_notifier, - ngap_ctrl_notifier, nas_notifier, *event_mng, logger)); @@ -233,13 +232,11 @@ void rrc_ue_impl::handle_measurement_report(const asn1::rrc_nr::meas_report_s& m // convert asn1 to common type rrc_meas_results meas_results = asn1_to_measurement_results(msg.crit_exts.meas_report().meas_results); // send measurement results to cell measurement manager - measurement_notifier.on_measurement_report(context.ue_index, meas_results); + measurement_notifier.on_measurement_report(meas_results); } void rrc_ue_impl::handle_dl_nas_transport_message(byte_buffer nas_pdu) { - logger.log_debug("Received DlNasTransportMessage ({} B)", nas_pdu.length()); - dl_dcch_msg_s dl_dcch_msg; dl_info_transfer_ies_s& dl_info_transfer = dl_dcch_msg.msg.set_c1().set_dl_info_transfer().crit_exts.set_dl_info_transfer(); @@ -265,7 +262,7 @@ void rrc_ue_impl::handle_rrc_transaction_complete(const ul_dcch_msg_s& msg, uint async_task rrc_ue_impl::handle_rrc_reconfiguration_request(const rrc_reconfiguration_procedure_request& msg) { return launch_async( - context, msg, *this, ngap_ctrl_notifier, du_processor_notifier, *event_mng, get_rrc_ue_srb_handler(), logger); + context, msg, *this, cu_cp_notifier, *event_mng, get_rrc_ue_srb_handler(), logger); } uint8_t rrc_ue_impl::handle_handover_reconfiguration_request(const rrc_reconfiguration_procedure_request& msg) @@ -386,8 +383,7 @@ rrc_ue_release_context rrc_ue_impl::get_rrc_ue_release_context(bool requires_rrc optional rrc_ue_impl::generate_meas_config(optional current_meas_config) { // (Re-)generate measurement config and return result. - context.meas_cfg = - measurement_notifier.on_measurement_config_request(context.ue_index, context.cell.cgi.nci, current_meas_config); + context.meas_cfg = measurement_notifier.on_measurement_config_request(context.cell.cgi.nci, current_meas_config); return context.meas_cfg; } diff --git a/lib/scheduler/logging/scheduler_result_logger.cpp b/lib/scheduler/logging/scheduler_result_logger.cpp index a27472b503..be55e7386c 100644 --- a/lib/scheduler/logging/scheduler_result_logger.cpp +++ b/lib/scheduler/logging/scheduler_result_logger.cpp @@ -57,29 +57,32 @@ void scheduler_result_logger::log_debug(const sched_result& result) case dci_dl_rnti_config_type::c_rnti_f1_0: { const auto& dci = pdcch.dci.c_rnti_f1_0; fmt::format_to(fmtbuf, - " dci: h_id={} ndi={} rv={} mcs={}", + " dci: h_id={} ndi={} rv={} mcs={} res_ind={}", dci.harq_process_number, dci.new_data_indicator, dci.redundancy_version, - dci.modulation_coding_scheme); + dci.modulation_coding_scheme, + dci.pucch_resource_indicator); } break; case dci_dl_rnti_config_type::tc_rnti_f1_0: { const auto& dci = pdcch.dci.tc_rnti_f1_0; fmt::format_to(fmtbuf, - " dci: h_id={} ndi={} rv={} mcs={}", + " dci: h_id={} ndi={} rv={} mcs={} res_ind={}", dci.harq_process_number, dci.new_data_indicator, dci.redundancy_version, - dci.modulation_coding_scheme); + dci.modulation_coding_scheme, + dci.pucch_resource_indicator); } break; case dci_dl_rnti_config_type::c_rnti_f1_1: { const auto& dci = pdcch.dci.c_rnti_f1_1; fmt::format_to(fmtbuf, - " dci: h_id={} ndi={} rv={} mcs={}", + " dci: h_id={} ndi={} rv={} mcs={} res_ind={}", dci.harq_process_number, dci.tb1_new_data_indicator, dci.tb1_redundancy_version, - dci.tb1_modulation_coding_scheme); + dci.tb1_modulation_coding_scheme, + dci.pucch_resource_indicator); if (dci.downlink_assignment_index.has_value()) { fmt::format_to(fmtbuf, " dai={}", *dci.downlink_assignment_index); } diff --git a/lib/scheduler/policy/scheduler_time_rr.cpp b/lib/scheduler/policy/scheduler_time_rr.cpp index fb9e684076..f4b71e3681 100644 --- a/lib/scheduler/policy/scheduler_time_rr.cpp +++ b/lib/scheduler/policy/scheduler_time_rr.cpp @@ -164,13 +164,14 @@ du_ue_index_t round_robin_apply(const ue_repository& ue_db, du_ue_index_t next_u } /// Allocate UE PDSCH grant. -static alloc_outcome alloc_dl_ue(const ue& u, - const ue_resource_grid_view& res_grid, - ue_pdsch_allocator& pdsch_alloc, - bool is_retx, - bool ue_with_srb_data_only, - srslog::basic_logger& logger, - optional dl_new_tx_max_nof_rbs_per_ue_per_slot = {}) +static alloc_outcome alloc_dl_ue(const ue& u, + const ue_resource_grid_view& res_grid, + ue_pdsch_allocator& pdsch_alloc, + bool is_retx, + bool ue_with_srb_data_only, + srslog::basic_logger& logger, + const scheduler_ue_expert_config& ue_expert_cfg, + optional dl_new_tx_max_nof_rbs_per_ue_per_slot = {}) { if (not is_retx) { if (not u.has_pending_dl_newtx_bytes()) { @@ -212,7 +213,19 @@ static alloc_outcome alloc_dl_ue(const ue& u, const dl_harq_process& h = param_candidate.harq(); const dci_dl_rnti_config_type dci_type = param_candidate.dci_dl_rnti_cfg_type(); const cell_slot_resource_grid& grid = res_grid.get_pdsch_grid(ue_cc.cell_index, pdsch.k0); - const crb_bitmap used_crbs = grid.used_crbs(ss.bwp->dl_common->generic_params.scs, ss.dl_crb_lims, pdsch.symbols); + // Apply RB allocation limits. + const unsigned start_rb = std::max(ue_expert_cfg.pdsch_crb_limits.start(), ss.dl_crb_lims.start()); + const unsigned end_rb = std::min(ue_expert_cfg.pdsch_crb_limits.stop(), ss.dl_crb_lims.stop()); + if (start_rb >= end_rb) { + logger.debug("ue={} rnti={} PDSCH allocation skipped. Cause: Invalid RB allocation range [{}, {})", + ue_cc.ue_index, + ue_cc.rnti(), + start_rb, + end_rb); + continue; + } + const crb_interval dl_crb_lims = {start_rb, end_rb}; + const crb_bitmap used_crbs = grid.used_crbs(ss.bwp->dl_common->generic_params.scs, dl_crb_lims, pdsch.symbols); if (used_crbs.all()) { logger.debug( "ue={} rnti={} PDSCH allocation skipped. Cause: No more RBs available", ue_cc.ue_index, ue_cc.rnti()); @@ -282,14 +295,15 @@ static alloc_outcome alloc_dl_ue(const ue& u, } /// Allocate UE PUSCH grant. -static alloc_outcome alloc_ul_ue(const ue& u, - const ue_resource_grid_view& res_grid, - ue_pusch_allocator& pusch_alloc, - bool is_retx, - bool schedule_sr_only, - srslog::basic_logger& logger, - unsigned pusch_td_res_idx, - optional ul_new_tx_max_nof_rbs_per_ue_per_slot = {}) +static alloc_outcome alloc_ul_ue(const ue& u, + const ue_resource_grid_view& res_grid, + ue_pusch_allocator& pusch_alloc, + bool is_retx, + bool schedule_sr_only, + srslog::basic_logger& logger, + unsigned pusch_td_res_idx, + const scheduler_ue_expert_config& ue_expert_cfg, + optional ul_new_tx_max_nof_rbs_per_ue_per_slot = {}) { unsigned pending_newtx_bytes = 0; if (not is_retx) { @@ -372,8 +386,20 @@ static alloc_outcome alloc_ul_ue(const ue& u, const cell_slot_resource_grid& grid = res_grid.get_pusch_grid(ue_cc.cell_index, pusch_td.k2 + cell_cfg_common.ntn_cs_koffset); - const prb_bitmap used_crbs = - grid.used_crbs(ss->bwp->ul_common->generic_params.scs, ss->ul_crb_lims, pusch_td.symbols); + // Apply RB allocation limits. + const unsigned start_rb = std::max(ue_expert_cfg.pusch_crb_limits.start(), ss->ul_crb_lims.start()); + const unsigned end_rb = std::min(ue_expert_cfg.pusch_crb_limits.stop(), ss->ul_crb_lims.stop()); + if (start_rb >= end_rb) { + logger.debug("ue={} rnti={} PUSCH allocation skipped. Cause: Invalid RB allocation range [{}, {})", + ue_cc.ue_index, + ue_cc.rnti(), + start_rb, + end_rb); + continue; + } + const crb_interval ul_crb_lims = {start_rb, end_rb}; + const prb_bitmap used_crbs = + grid.used_crbs(ss->bwp->ul_common->generic_params.scs, ul_crb_lims, pusch_td.symbols); if (used_crbs.all()) { logger.debug( "ue={} rnti={} PUSCH allocation skipped. Cause: No more RBs available", ue_cc.ue_index, ue_cc.rnti()); @@ -472,7 +498,7 @@ void scheduler_time_rr::dl_sched(ue_pdsch_allocator& pdsch_alloc, { // First schedule re-transmissions. auto retx_ue_function = [this, &res_grid, &pdsch_alloc](const ue& u) { - return alloc_dl_ue(u, res_grid, pdsch_alloc, true, false, logger); + return alloc_dl_ue(u, res_grid, pdsch_alloc, true, false, logger, expert_cfg); }; next_dl_ue_index = round_robin_apply(ues, next_dl_ue_index, retx_ue_function); @@ -481,13 +507,15 @@ void scheduler_time_rr::dl_sched(ue_pdsch_allocator& pdsch_alloc, if (dl_new_tx_max_nof_rbs_per_ue_per_slot > 0) { // Second, schedule UEs with SRB data. auto srb_newtx_ue_function = [this, &res_grid, &pdsch_alloc, dl_new_tx_max_nof_rbs_per_ue_per_slot](const ue& u) { - return alloc_dl_ue(u, res_grid, pdsch_alloc, false, true, logger, dl_new_tx_max_nof_rbs_per_ue_per_slot); + return alloc_dl_ue( + u, res_grid, pdsch_alloc, false, true, logger, expert_cfg, dl_new_tx_max_nof_rbs_per_ue_per_slot); }; next_dl_ue_index = round_robin_apply(ues, next_dl_ue_index, srb_newtx_ue_function); // Then, schedule new transmissions. auto tx_ue_function = [this, &res_grid, &pdsch_alloc, dl_new_tx_max_nof_rbs_per_ue_per_slot](const ue& u) { - return alloc_dl_ue(u, res_grid, pdsch_alloc, false, false, logger, dl_new_tx_max_nof_rbs_per_ue_per_slot); + return alloc_dl_ue( + u, res_grid, pdsch_alloc, false, false, logger, expert_cfg, dl_new_tx_max_nof_rbs_per_ue_per_slot); }; next_dl_ue_index = round_robin_apply(ues, next_dl_ue_index, tx_ue_function); } @@ -513,7 +541,7 @@ void scheduler_time_rr::ul_sched(ue_pusch_allocator& pusch_alloc, // First schedule UL data re-transmissions. for (unsigned pusch_td_res_idx : pusch_td_res_index_list) { auto data_retx_ue_function = [this, &res_grid, &pusch_alloc, pusch_td_res_idx](const ue& u) { - return alloc_ul_ue(u, res_grid, pusch_alloc, true, false, logger, pusch_td_res_idx); + return alloc_ul_ue(u, res_grid, pusch_alloc, true, false, logger, pusch_td_res_idx, expert_cfg); }; next_ul_ue_index = round_robin_apply(ues, next_ul_ue_index, data_retx_ue_function); } @@ -521,7 +549,7 @@ void scheduler_time_rr::ul_sched(ue_pusch_allocator& pusch_alloc, // Then, schedule all pending SR. for (unsigned pusch_td_res_idx : pusch_td_res_index_list) { auto sr_ue_function = [this, &res_grid, &pusch_alloc, pusch_td_res_idx](const ue& u) { - return alloc_ul_ue(u, res_grid, pusch_alloc, false, true, logger, pusch_td_res_idx); + return alloc_ul_ue(u, res_grid, pusch_alloc, false, true, logger, pusch_td_res_idx, expert_cfg); }; next_ul_ue_index = round_robin_apply(ues, next_ul_ue_index, sr_ue_function); } @@ -531,14 +559,18 @@ void scheduler_time_rr::ul_sched(ue_pusch_allocator& pusch_alloc, compute_max_nof_rbs_per_ue_per_slot(ues, false, res_grid, expert_cfg); if (ul_new_tx_max_nof_rbs_per_ue_per_slot > 0) { for (unsigned pusch_td_res_idx : pusch_td_res_index_list) { - auto data_tx_ue_function = [this, - &res_grid, - &pusch_alloc, - ul_new_tx_max_nof_rbs_per_ue_per_slot, - pusch_td_res_idx](const ue& u) { - return alloc_ul_ue( - u, res_grid, pusch_alloc, false, false, logger, pusch_td_res_idx, ul_new_tx_max_nof_rbs_per_ue_per_slot); - }; + auto data_tx_ue_function = + [this, &res_grid, &pusch_alloc, ul_new_tx_max_nof_rbs_per_ue_per_slot, pusch_td_res_idx](const ue& u) { + return alloc_ul_ue(u, + res_grid, + pusch_alloc, + false, + false, + logger, + pusch_td_res_idx, + expert_cfg, + ul_new_tx_max_nof_rbs_per_ue_per_slot); + }; next_ul_ue_index = round_robin_apply(ues, next_ul_ue_index, data_tx_ue_function); } } diff --git a/lib/scheduler/pucch_scheduling/pucch_allocator.h b/lib/scheduler/pucch_scheduling/pucch_allocator.h index 6896f723b0..2e948bb3bd 100644 --- a/lib/scheduler/pucch_scheduling/pucch_allocator.h +++ b/lib/scheduler/pucch_scheduling/pucch_allocator.h @@ -50,7 +50,7 @@ class pucch_allocator virtual void slot_indication(slot_point sl_tx) = 0; /// Allocate the common PUCCH resource for HARQ-ACK for a given UE. - /// \param[out,in] slot_alloc struct with scheduling results. + /// \param[out,in] res_alloc struct with scheduling results. /// \param[in] tcrnti temporary RNTI of the UE. /// \param[in] k0 k0 value, or delay (in slots) of PDSCH slot vs the corresponding PDCCH slot. /// \param[in] k1 delay in slots of the UE's PUCCH HARQ-ACK report with respect to the PDSCH. @@ -65,8 +65,26 @@ class pucch_allocator unsigned k1, const pdcch_dl_information& dci_info) = 0; + /// Allocate the common PUCCH resource for HARQ-ACK for a given UE. + /// \param[out,in] res_alloc struct with scheduling results. + /// \param[in] rnti RNTI of the UE. + /// \param[in] ue_cell_cfg user configuration. + /// \param[in] k0 k0 value, or delay (in slots) of PDSCH slot vs the corresponding PDCCH slot. + /// \param[in] k1 delay in slots of the UE's PUCCH HARQ-ACK report with respect to the PDSCH. + /// \param[in] dci_info information with DL DCI, needed for HARQ-(N)-ACK scheduling info. + /// \return The PUCCH resource indicator, if the allocation is successful; a \c nullopt otherwise. + /// \remark \c pucch_res_indicator, or \f$\Delta_{PRI}\f$, is the PUCCH resource indicator<\em> field for DCI 1_0 + /// and 1_1 as per TS 38.213, Section 9.2.1. It indicates the UE which PUCCH resource should be used for HACK-(N)ACK + /// reporting. + virtual optional alloc_common_and_ded_harq_res(cell_resource_allocator& res_alloc, + rnti_t rnti, + const ue_cell_configuration& ue_cell_cfg, + unsigned k0, + unsigned k1, + const pdcch_dl_information& dci_info) = 0; + /// Allocate the PUCCH resource for a UE's SR opportunity. - /// \param[out,in] slot_alloc struct with scheduling results. + /// \param[out,in] pucch_slot_alloc struct with scheduling results. /// \param[in] crnti C-RNTI of the UE. /// \param[in] ue_cell_cfg user configuration. virtual void pucch_allocate_sr_opportunity(cell_slot_resource_allocator& pucch_slot_alloc, @@ -91,11 +109,10 @@ class pucch_allocator unsigned k1) = 0; /// Allocate the PUCCH grant for a UE's CSI opportunity. - /// \param[out,in] slot_alloc struct with scheduling results. + /// \param[out,in] pucch_slot_alloc struct with scheduling results. /// \param[in] crnti C-RNTI of the UE. /// \param[in] ue_cell_cfg user configuration. /// \param[in] csi_part1_nof_bits Number of CSI Part 1 bits that need to be reported. - /// \param[in] is_fallback_mode Indicates whether the UE is in fallback mode. virtual void pucch_allocate_csi_opportunity(cell_slot_resource_allocator& pucch_slot_alloc, rnti_t crnti, const ue_cell_configuration& ue_cell_cfg, @@ -104,7 +121,7 @@ class pucch_allocator /// Remove UCI allocations on PUCCH for a given UE. /// \param[out,in] slot_alloc struct with scheduling results. /// \param[in] crnti RNTI of the UE. - /// \param[in] pucch_cfg User's PUCCH configuration. + /// \param[in] ue_cell_cfg User configuration. /// \return struct with the number of HARQ-ACK and CSI info bits from the removed PUCCH grants. If there was no PUCCH /// to be removed, return 0 for both HARQ-ACK and CSI info bits. virtual pucch_uci_bits remove_ue_uci_from_pucch(cell_slot_resource_allocator& slot_alloc, diff --git a/lib/scheduler/pucch_scheduling/pucch_allocator_impl.cpp b/lib/scheduler/pucch_scheduling/pucch_allocator_impl.cpp index c1ca9d08e0..c0e705ea90 100644 --- a/lib/scheduler/pucch_scheduling/pucch_allocator_impl.cpp +++ b/lib/scheduler/pucch_scheduling/pucch_allocator_impl.cpp @@ -23,7 +23,6 @@ #include "pucch_allocator_impl.h" #include "../support/csi_report_helpers.h" #include "../support/pucch/pucch_default_resource.h" -#include "../support/sr_helper.h" #include "srsran/ran/csi_report/csi_report_config_helpers.h" #include "srsran/ran/csi_report/csi_report_on_pucch_helpers.h" #include "srsran/ran/pucch/pucch_info.h" @@ -123,27 +122,25 @@ optional pucch_allocator_impl::alloc_common_pucch_harq_ack_ue(cell_res return nullopt; } - if (has_common_pucch_f1_grant(tcrnti, pucch_slot_alloc.slot)) { - logger.debug("tc-rnti={}: PUCCH common not allocated for slot={}. Cause: a grant for this UE already exists in the " - "same slot", + // If there are existing PUCCH grants that are either F2 for CSI or F1 for SR, allocate the PUCCH common grant anyway + // without multiplexing it with the existing one. Otherwise, if the existing grant is F1 for HARQ-ACK, do not allocate + // on the same slot. + const bool has_existing_pucch_f1_grants = std::find_if(pucch_slot_alloc.result.ul.pucchs.begin(), + pucch_slot_alloc.result.ul.pucchs.end(), + [tcrnti](const pucch_info& pucch) { + return tcrnti == pucch.crnti and + pucch.format == pucch_format::FORMAT_1 and + pucch.format_1.harq_ack_nof_bits != 0; + }) != pucch_slot_alloc.result.ul.pucchs.end(); + if (has_existing_pucch_f1_grants or has_common_pucch_f1_grant(tcrnti, pucch_slot_alloc.slot)) { + logger.debug("tc-rnti={}: PUCCH common not allocated for slot={}. Cause: a PUCCH F1 grant with HARQ-ACK bits " + "already exists in the same slot", tcrnti, pucch_slot_alloc.slot); return nullopt; } - if (std::find_if(pucch_slot_alloc.result.ul.pucchs.begin(), - pucch_slot_alloc.result.ul.pucchs.end(), - [tcrnti](const pucch_info& pucch) { return tcrnti == pucch.crnti; }) != - pucch_slot_alloc.result.ul.pucchs.end()) { - logger.debug( - "tc-rnti={}: PUCCH common not allocated for slot={}. Cause: a PUCCH grant for this UE already exists in the " - "same slot", - tcrnti, - pucch_slot_alloc.slot); - return nullopt; - } - - // Get the PUCCH resources, either from default tables. + // Get the PUCCH resources from default tables. optional pucch_res = alloc_pucch_common_res_harq(pucch_slot_alloc, dci_info.ctx); // No resources available for PUCCH. @@ -163,14 +160,95 @@ optional pucch_allocator_impl::alloc_common_pucch_harq_ack_ue(cell_res pucch_common_alloc_grid[slot_alloc[k0 + k1 + slot_alloc.cfg.ntn_cs_koffset].slot.to_uint()].emplace_back(tcrnti); - logger.debug("tc-rnti={}: PUCCH HARQ-ACK common with res_ind={} allocated for slot={}", - tcrnti, - pucch_res_indicator, - pucch_slot_alloc.slot); - return pucch_res_indicator; } +optional pucch_allocator_impl::alloc_common_and_ded_harq_res(cell_resource_allocator& res_alloc, + rnti_t rnti, + const ue_cell_configuration& ue_cell_cfg, + unsigned k0, + unsigned k1, + const pdcch_dl_information& dci_info) +{ + // Get the slot allocation grid considering the PDSCH delay (k0) and the PUCCH delay wrt PDSCH (k1). + cell_slot_resource_allocator& pucch_slot_alloc = res_alloc[k0 + k1 + res_alloc.cfg.ntn_cs_koffset]; + slot_point pucch_slot = pucch_slot_alloc.slot; + + if (not cell_cfg.is_fully_ul_enabled(pucch_slot)) { + return nullopt; + } + + existing_pucch_grants existing_grants = + get_existing_pucch_grants(pucch_slot_alloc.result.ul.pucchs, rnti, pucch_slot); + + // NOTE: this function is called by the UE fallback scheduler, which iterates over several PDCCH slots and different + // k1 values. It can happen that the UE fallback scheduler attempts to allocate a grant on a slot where it previously + // allocated another grant. If that is the case, quit the PUCCH allocation. + if (existing_grants.format1_harq_common_grant != nullptr or existing_grants.format1_harq_grant != nullptr) { + logger.debug("rnti={}: PUCCH HARQ-ACK for slot={} not allocated. Cause: another F1 PUCCH grant with HARQ-ACK bits " + "already exists for the same UE for the same slot", + rnti, + pucch_slot); + return nullopt; + } + + srsran_assert(not(existing_grants.format1_sr_grant != nullptr and existing_grants.format2_grant != nullptr), + "It is expected that there are either no grants, or at most 1 PUCCH grant (F1 SR and F2 for CSI)"); + + // If a F2 PUCCH grant with HARQ-ACK bits exists, then there must be as well a common PUCCH F1 grant (with 1 HARQ-ACK + // bit); in that case, the function should have returned already in the previous "if" check. + srsran_assert(existing_grants.format2_grant == nullptr or + existing_grants.format2_grant->format_2.harq_ack_nof_bits == 0, + "If the existing PUCCH grant is F2, it must be for CSI or CSI/SR reporting only"); + + pucch_info* current_existing_grant = nullptr; + // If there are no existing grants or if the existing one is F1 (for SR), we need to add 2 additional PUCCH grants: 1 + // on common resources and 1 on dedicated resources (for HARQ-ACK bit). + unsigned extra_pucch_grants_to_allocate = 2; + if (existing_grants.format1_sr_grant != nullptr) { + current_existing_grant = existing_grants.format1_sr_grant; + } else if (existing_grants.format2_grant != nullptr) { + current_existing_grant = existing_grants.format2_grant; + // If the existing PUCCH grant is F2 (for CSI), we need to allocate only 1 additional PUCCH grant on common + // resource; the CSI + additional HARQ-ACK bit will be allocated on a F2 resource which will replace the existing F2 + // resource for CSI. + extra_pucch_grants_to_allocate = 1; + } + + // [Implementation-defined] We only allow a max number of PUCCH + PUSCH grants per slot. + if (pucch_slot_alloc.result.ul.pucchs.size() + extra_pucch_grants_to_allocate > + pucch_slot_alloc.result.ul.pucchs.capacity() or + pucch_slot_alloc.result.ul.pucchs.size() + extra_pucch_grants_to_allocate > + get_max_pucch_grants(static_cast(pucch_slot_alloc.result.ul.puschs.size())) or + pucch_common_alloc_grid[pucch_slot.to_uint()].full()) { + return nullopt; + } + + // Find a couple of PUCCH resources (1 common, 1 dedicated) that are (i) are available and that (ii) have the same + // PUCCH resource indicator. + optional harq_res_cfgs = + find_common_and_ded_harq_res_available(pucch_slot_alloc, current_existing_grant, rnti, ue_cell_cfg, dci_info.ctx); + + // If both PUCCH common and dedicated resources are available, allocate them. If it is not possible to allocate the + // dedicated resource (this can only happen if the UCI bits exceeds the PUCCH F2 capacity), then the function will + // abort the allocation. The caller will attempt a new allocation in the next UL slot. + if (harq_res_cfgs.has_value()) { + return exec_common_and_ded_res_alloc(pucch_slot_alloc, + current_existing_grant, + rnti, + ue_cell_cfg, + harq_res_cfgs.value().pucch_common_info, + harq_res_cfgs.value().pucch_ded_cfg); + } + + logger.debug( + "rnti={}: PUCCH HARQ-ACK for slot={} not allocated. Cause: no res_indicator available for both common and " + "ded. PUCCH resources", + rnti, + pucch_slot_alloc.slot); + return nullopt; +} + optional pucch_allocator_impl::alloc_ded_pucch_harq_ack_ue(cell_resource_allocator& res_alloc, rnti_t crnti, const ue_cell_configuration& ue_cell_cfg, @@ -218,7 +296,7 @@ optional pucch_allocator_impl::alloc_ded_pucch_harq_ack_ue(cell_resour if (existing_grants.format2_grant->format_2.harq_ack_nof_bits == 0 and existing_grants.format2_grant->format_2.csi_part1_bits > 0) { return change_format2_resource( - pucch_slot_alloc, *existing_grants.format2_grant, crnti, ue_cell_cfg, harq_ack_bits_increment); + pucch_slot_alloc, *existing_grants.format2_grant, crnti, ue_cell_cfg, harq_ack_bits_increment, {}); } // Case 1-B) If the allocated resource is for HARQ too, just update the resource. @@ -500,6 +578,61 @@ pucch_allocator_impl::alloc_pucch_common_res_harq(cell_slot_resource_allocator& return nullopt; } +optional pucch_allocator_impl::exec_common_and_ded_res_alloc(cell_slot_resource_allocator& pucch_alloc, + pucch_info* existing_grant, + rnti_t rnti, + const ue_cell_configuration& ue_cell_cfg, + pucch_res_alloc_cfg common_res_cfg, + const pucch_resource& ded_res_cfg) +{ + const unsigned pucch_res_indicator = common_res_cfg.pucch_res_indicator; + const unsigned HARQ_BITS_IN_NEW_PUCCH_GRANT = 1; + + // Allocate the dedicated PUCCH HARQ-ACK resource first; this is because the allocation can may and, in that case, we + // can quit the allocation without the need to remove the PUCCH common grant. + if (existing_grant == nullptr) { + pucch_info& pucch_pdu = pucch_alloc.result.ul.pucchs.emplace_back(); + fill_pucch_ded_format1_grant(pucch_pdu, rnti, ded_res_cfg, HARQ_BITS_IN_NEW_PUCCH_GRANT, sr_nof_bits::no_sr); + } else if (existing_grant->format == pucch_format::FORMAT_1) { + // Update the HARQ-ACK bits in the PUCCH resource for SR. + ++existing_grant->format_1.harq_ack_nof_bits; + + // Allocate the new grant on ded. PUCCH F1 resource for HARQ-ACK. + pucch_info& pucch_pdu = pucch_alloc.result.ul.pucchs.emplace_back(); + fill_pucch_ded_format1_grant(pucch_pdu, rnti, ded_res_cfg, HARQ_BITS_IN_NEW_PUCCH_GRANT, sr_nof_bits::no_sr); + } else { + // Change the existing PUCCH F2 grant for CSI into one for HARQ-ACK bits, if available. + optional result = change_format2_resource( + pucch_alloc, + *existing_grant, + rnti, + ue_cell_cfg, + HARQ_BITS_IN_NEW_PUCCH_GRANT, + pucch_harq_resource_alloc_record{.pucch_res = &ded_res_cfg, .pucch_res_indicator = pucch_res_indicator}); + if (not result.has_value()) { + return nullopt; + } + } + + // Fill scheduler output. + pucch_info& pucch_info = pucch_alloc.result.ul.pucchs.emplace_back(); + fill_pucch_harq_common_grant(pucch_info, rnti, common_res_cfg); + + // Allocate common HARQ-ACK resource. + pucch_alloc.ul_res_grid.fill(common_res_cfg.first_hop_res); + pucch_alloc.ul_res_grid.fill(common_res_cfg.second_hop_res); + + pucch_common_alloc_grid[pucch_alloc.slot.to_uint()].emplace_back(rnti); + + logger.debug("rnti={}: PUCCH on common {}resource with res_ind={} allocated for slot={}", + rnti, + existing_grant != nullptr and existing_grant->format == pucch_format::FORMAT_2 ? "" : "and ded. ", + pucch_res_indicator, + pucch_alloc.slot); + + return pucch_res_indicator; +} + void pucch_allocator_impl::fill_pucch_harq_common_grant(pucch_info& pucch_info, rnti_t rnti, const pucch_res_alloc_cfg& pucch_res) @@ -549,6 +682,86 @@ void pucch_allocator_impl::fill_pucch_harq_common_grant(pucch_info& } } +// The function returns an available common PUCCH resource (i.e., not used by other UEs); it returns a null optional +// if no resource is available. +optional +pucch_allocator_impl::find_common_and_ded_harq_res_available(cell_slot_resource_allocator& pucch_alloc, + pucch_info* existing_grant, + rnti_t rnti, + const ue_cell_configuration& ue_cell_cfg, + const dci_context_information& dci_info) +{ + // Get the parameter N_bwp_size, which is the Initial UL BWP size in PRBs, as per TS 38.213, Section 9.2.1. + const unsigned size_ul_bwp = cell_cfg.ul_cfg_common.init_ul_bwp.generic_params.crbs.length(); + + // Get PUCCH common resource config from Table 9.2.1-1, TS 38.213. + pucch_default_resource pucch_res = get_pucch_default_resource( + cell_cfg.ul_cfg_common.init_ul_bwp.pucch_cfg_common->pucch_resource_common, size_ul_bwp); + + // Get N_CCE (nof_coreset_cces) and n_{CCE,0} (start_cce_idx), as per TS 38.213, Section 9.2.1. + const unsigned nof_coreset_cces = dci_info.coreset_cfg->get_nof_cces(); + const unsigned start_cce_idx = dci_info.cces.ncce; + + // As per Section 9.2.1, TS 38.213, this is the max value of \f$\Delta_{PRI}\f$, which is a 3-bit unsigned. + const unsigned max_d_pri = 7; + for (unsigned d_pri = 0; d_pri != max_d_pri + 1; ++d_pri) { + // r_PUCCH, as per Section 9.2.1, TS 38.213. + const unsigned r_pucch = get_pucch_default_resource_index(start_cce_idx, nof_coreset_cces, d_pri); + srsran_assert(r_pucch < 16, "r_PUCCH must be less than 16"); + + // Look for an available PUCCH common resource. + if (not resource_manager.is_common_resource_available(pucch_alloc.slot, r_pucch)) { + continue; + } + + // Look for an available PUCCH dedicated resource with the same PUCCH resource indicator as the common's. + const pucch_config& pucch_cfg = ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value(); + const pucch_resource* ded_resource = + existing_grant == nullptr or existing_grant->format == pucch_format::FORMAT_1 + ? resource_manager.reserve_f1_res_by_res_indicator(pucch_alloc.slot, rnti, d_pri, pucch_cfg) + : resource_manager.reserve_f2_res_by_res_indicator(pucch_alloc.slot, rnti, d_pri, pucch_cfg); + if (ded_resource == nullptr) { + continue; + } + + // Compute the PUCCH resource common configuration parameters. + + // As per TS 38.211, Section 6.3.2.1, the first floor(N_symb_PUCCH/2) are for the first hop, the remaining ones for + // the second hop. + const ofdm_symbol_range first_hop_symbols{pucch_res.first_symbol_index, + pucch_res.first_symbol_index + pucch_res.nof_symbols / 2}; + const ofdm_symbol_range second_hop_symbols{pucch_res.first_symbol_index + pucch_res.nof_symbols / 2, + pucch_res.first_symbol_index + pucch_res.nof_symbols}; + + const bwp_configuration& init_ul_bwp_param = cell_cfg.ul_cfg_common.init_ul_bwp.generic_params; + + // Compute PRB_first_hop and PRB_second_hop as per Section 9.2.1, TS 38.213. + auto prbs = get_pucch_default_prb_index(r_pucch, pucch_res.rb_bwp_offset, pucch_res.cs_indexes.size(), size_ul_bwp); + + // With the default PUCCH resource configs, Format is either 0 or 1, which only occupy 1 RB. + const unsigned crb_first_hop = prb_to_crb(init_ul_bwp_param, prbs.first); + const grant_info first_hop_grant{ + init_ul_bwp_param.scs, first_hop_symbols, crb_interval{crb_first_hop, crb_first_hop + 1}}; + const unsigned crb_second_hop = prb_to_crb(init_ul_bwp_param, prbs.second); + const grant_info second_hop_grant{ + init_ul_bwp_param.scs, second_hop_symbols, crb_interval{crb_second_hop, crb_second_hop + 1}}; + + // Compute CS index as per Section 9.2.1, TS 38.213. + const size_t cs_idx = r_pucch < 8 ? static_cast(r_pucch) % pucch_res.cs_indexes.size() + : static_cast(r_pucch - 8) % pucch_res.cs_indexes.size(); + srsran_assert(cs_idx < pucch_res.cs_indexes.size(), "CS index exceeds static vector size"); + const uint8_t cyclic_shift = pucch_res.cs_indexes[cs_idx]; + + return pucch_com_ded_res{pucch_res_alloc_cfg{.pucch_res_indicator = d_pri, + .first_hop_res = first_hop_grant, + .second_hop_res = second_hop_grant, + .cs = cyclic_shift, + .format = pucch_res.format}, + *ded_resource}; + }; + return nullopt; +} + optional pucch_allocator_impl::allocate_new_format1_harq_grant(cell_slot_resource_allocator& pucch_slot_alloc, rnti_t crnti, const ue_cell_configuration& ue_cell_cfg, @@ -572,22 +785,19 @@ optional pucch_allocator_impl::allocate_new_format1_harq_grant(cell_sl return nullopt; } + // Update the number of HARQ-ACK bits in the SR grant, if present. if (existing_sr_grant != nullptr) { srsran_sanity_check(existing_sr_grant->format == pucch_format::FORMAT_1, "Only PUCCH format 1 expected for SR"); existing_sr_grant->format_1.harq_ack_nof_bits++; } - // Allocate PUCCH SR grant only, as HARQ-ACK grant has been allocated earlier. + // Allocate the new grant on PUCCH F1 resources for HARQ-ACK bits (without SR). pucch_info& pucch_pdu = pucch_slot_alloc.result.ul.pucchs.emplace_back(); const unsigned HARQ_BITS_IN_NEW_PUCCH_GRANT = 1; fill_pucch_ded_format1_grant( pucch_pdu, crnti, *pucch_harq_res_info.pucch_res, HARQ_BITS_IN_NEW_PUCCH_GRANT, sr_nof_bits::no_sr); - const unsigned pucch_res_indicator = static_cast(pucch_harq_res_info.pucch_res_indicator); + const auto pucch_res_indicator = static_cast(pucch_harq_res_info.pucch_res_indicator); - logger.debug("rnti={}: PUCCH HARQ-ACK allocation on F1 with res_ind={} for slot={} completed", - crnti, - pucch_res_indicator, - pucch_slot_alloc.slot); return pucch_res_indicator; } @@ -652,7 +862,6 @@ void pucch_allocator_impl::convert_to_format2_csi(cell_slot_resource_allocator& return; } - // Allocate PUCCH SR grant only, as HARQ-ACK grant has been allocated earlier. pucch_info& pucch_pdu = pucch_slot_alloc.result.ul.pucchs.emplace_back(); const unsigned harq_bits_only_csi = 0U; fill_pucch_format2_grant(pucch_pdu, @@ -733,7 +942,6 @@ optional pucch_allocator_impl::convert_to_format2_harq(cell_slot_resou return nullopt; } - // Allocate PUCCH SR grant only, as HARQ-ACK grant has been allocated earlier. pucch_info& pucch_pdu = pucch_slot_alloc.result.ul.pucchs.emplace_back(); const unsigned csi1_nof_bits_only_harq = 0U; fill_pucch_format2_grant(pucch_pdu, @@ -745,17 +953,6 @@ optional pucch_allocator_impl::convert_to_format2_harq(cell_slot_resou sr_bits, csi1_nof_bits_only_harq); - logger.debug("rnti={}: PUCCH Format 2 grant allocation with {} H-ACK, {} SR, {} CSI bits with res_ind={} for " - "slot={} completed", - rnti, - curr_harq_bits + harq_ack_bits_increment, - sr_nof_bits_to_uint(sr_bits), - csi1_nof_bits_only_harq, - format2_res.pucch_res_indicator, - pucch_slot_alloc.slot - - ); - return format2_res.pucch_res_indicator; } @@ -763,11 +960,14 @@ optional pucch_allocator_impl::change_format2_resource(cell_slot_resou pucch_info& existing_grant, rnti_t rnti, const ue_cell_configuration& ue_cell_cfg, - unsigned harq_ack_bits_increment) + unsigned harq_ack_bits_increment, + optional harq_f2_res) { const pucch_config& pucch_cfg = ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value(); const pucch_harq_resource_alloc_record format2_res = - resource_manager.reserve_next_f2_harq_res_available(pucch_slot_alloc.slot, rnti, pucch_cfg); + harq_f2_res.has_value() + ? harq_f2_res.value() + : resource_manager.reserve_next_f2_harq_res_available(pucch_slot_alloc.slot, rnti, pucch_cfg); if (format2_res.pucch_res == nullptr) { logger.debug("rnti={}: HARQ-ACK could not be allocated on PUCCH Format2 for slot={}. Cause: PUCCH F2 resource " @@ -779,7 +979,7 @@ optional pucch_allocator_impl::change_format2_resource(cell_slot_resou // This function would only be called in case CSI and SR gets allocated before the HARQ. In that case, if there are // SR bits or CSI bits to be carried by the PUCCH F2 grant, they would have already been allocated and there is no - // need to check if the slot is an CSI or SR opportunity. + // need to check if the slot is a CSI or SR opportunity. const sr_nof_bits sr_bits_to_report = existing_grant.format_2.sr_bits; const unsigned csi_bits_to_report = existing_grant.format_2.csi_part1_bits; @@ -838,12 +1038,6 @@ optional pucch_allocator_impl::change_format2_resource(cell_slot_resou harq_ack_bits_increment, sr_bits_to_report, csi_bits_to_report); - logger.debug("rnti={}: PUCCH Format 2 grant allocation with {} H-ACK, {} SR, {} CSI bits for slot={} completed", - rnti, - harq_ack_bits_increment, - sr_nof_bits_to_uint(sr_bits_to_report), - csi_bits_to_report, - pucch_slot_alloc.slot); return static_cast(format2_res.pucch_res_indicator); } @@ -867,10 +1061,8 @@ optional pucch_allocator_impl::add_harq_ack_bit_to_format1_grant(pucch } // Update the HARQ, if present. existing_harq_grant.format_1.harq_ack_nof_bits++; - const unsigned pucch_res_indicator = static_cast(pucch_res_idx); + const auto pucch_res_indicator = static_cast(pucch_res_idx); - logger.debug( - "rnti={}: HARQ-ACK mltplxd on existing PUCCH F1 with res_ind={} for slot={}", rnti, pucch_res_indicator, sl_tx); return pucch_res_indicator; } @@ -1051,10 +1243,6 @@ optional pucch_allocator_impl::add_harq_bits_to_harq_f2_grant(pucch_in } existing_f2_grant.format_2.harq_ack_nof_bits += harq_ack_bits_increment; - logger.debug("rnti={}: HARQ-ACK multiplexed on existing PUCCH F2 with res_ind={} for slot={}", - existing_f2_grant.crnti, - pucch_f2_harq_cfg.pucch_res_indicator, - sl_tx); return pucch_f2_harq_cfg.pucch_res_indicator; } @@ -1071,7 +1259,7 @@ void pucch_allocator_impl::fill_pucch_ded_format1_grant(pucch_info& pu // Set PRBs and symbols, first.º // The number of PRBs is not explicitly stated in the TS, but it can be inferred it's 1. - const pucch_format_1_cfg& res_f1 = variant_get(pucch_ded_res_cfg.format_params); + const auto& res_f1 = variant_get(pucch_ded_res_cfg.format_params); pucch_grant.resources.prbs.set(pucch_ded_res_cfg.starting_prb, pucch_ded_res_cfg.starting_prb + PUCCH_FORMAT_1_NOF_PRBS); pucch_grant.resources.symbols.set(res_f1.starting_sym_idx, res_f1.starting_sym_idx + res_f1.nof_symbols); @@ -1109,7 +1297,7 @@ void pucch_allocator_impl::fill_pucch_format2_grant(pucch_info& // Set PRBs and symbols, first.º // The number of PRBs is not explicitly stated in the TS, but it can be inferred it's 1. pucch_grant.resources.prbs.set(pucch_ded_res_cfg.starting_prb, pucch_ded_res_cfg.starting_prb + nof_prbs); - const pucch_format_2_3_cfg& res_f2 = variant_get(pucch_ded_res_cfg.format_params); + const auto& res_f2 = variant_get(pucch_ded_res_cfg.format_params); pucch_grant.resources.symbols.set(res_f2.starting_sym_idx, res_f2.starting_sym_idx + res_f2.nof_symbols); if (pucch_ded_res_cfg.second_hop_prb.has_value()) { pucch_grant.resources.second_hop_prbs.set(pucch_ded_res_cfg.second_hop_prb.value(), diff --git a/lib/scheduler/pucch_scheduling/pucch_allocator_impl.h b/lib/scheduler/pucch_scheduling/pucch_allocator_impl.h index bc9f3b2efc..71cdfdb8d2 100644 --- a/lib/scheduler/pucch_scheduling/pucch_allocator_impl.h +++ b/lib/scheduler/pucch_scheduling/pucch_allocator_impl.h @@ -49,6 +49,13 @@ class pucch_allocator_impl final : public pucch_allocator unsigned k1, const pdcch_dl_information& dci_info) override; + optional alloc_common_and_ded_harq_res(cell_resource_allocator& res_alloc, + rnti_t rnti, + const ue_cell_configuration& ue_cell_cfg, + unsigned k0, + unsigned k1, + const pdcch_dl_information& dci_info) override; + optional alloc_ded_pucch_harq_ack_ue(cell_resource_allocator& res_alloc, rnti_t crnti, const ue_cell_configuration& ue_cell_cfg, @@ -96,6 +103,13 @@ class pucch_allocator_impl final : public pucch_allocator optional alloc_pucch_common_res_harq(cell_slot_resource_allocator& pucch_alloc, const dci_context_information& dci_info); + optional exec_common_and_ded_res_alloc(cell_slot_resource_allocator& pucch_alloc, + pucch_info* existing_grant, + rnti_t rnti, + const ue_cell_configuration& ue_cell_cfg, + pucch_res_alloc_cfg common_res_cfg, + const pucch_resource& ded_res_cfg); + // Helper that allocates a NEW PUCCH HARQ grant (Format 1). optional allocate_new_format1_harq_grant(cell_slot_resource_allocator& pucch_slot_alloc, rnti_t crnti, @@ -132,11 +146,12 @@ class pucch_allocator_impl final : public pucch_allocator // Helper that changes the current PUCCH Format 2 grant (specifically used for CSI reporting) into a PUCCH Format 2 // resource for the HARQ-ACK + CSI. - optional change_format2_resource(cell_slot_resource_allocator& pucch_slot_alloc, - pucch_info& existing_grant, - rnti_t rnti, - const ue_cell_configuration& ue_cell_cfg, - unsigned harq_ack_bits_increment); + optional change_format2_resource(cell_slot_resource_allocator& pucch_slot_alloc, + pucch_info& existing_grant, + rnti_t rnti, + const ue_cell_configuration& ue_cell_cfg, + unsigned harq_ack_bits_increment, + optional harq_f2_res); // Helper that adds HARQ-ACK bits to a PUCCH Format 2 grant for HARQ-ACK. optional add_harq_bits_to_harq_f2_grant(pucch_info& existing_f2_grant, @@ -145,6 +160,17 @@ class pucch_allocator_impl final : public pucch_allocator const ue_cell_configuration& ue_cell_cfg, unsigned harq_ack_bits_increment); + struct pucch_com_ded_res { + pucch_res_alloc_cfg pucch_common_info; + const pucch_resource& pucch_ded_cfg; + }; + + optional find_common_and_ded_harq_res_available(cell_slot_resource_allocator& pucch_alloc, + pucch_info* existing_grant, + rnti_t rnti, + const ue_cell_configuration& ue_cell_cfg, + const dci_context_information& dci_info); + // Helper that removes the existing PUCCH Format 1 grants (both HARQ-ACK and SR). void remove_pucch_format1_from_grants(cell_slot_resource_allocator& slot_alloc, rnti_t crnti, diff --git a/lib/scheduler/pucch_scheduling/pucch_resource_manager.cpp b/lib/scheduler/pucch_scheduling/pucch_resource_manager.cpp index 900212f828..34493e9a40 100644 --- a/lib/scheduler/pucch_scheduling/pucch_resource_manager.cpp +++ b/lib/scheduler/pucch_scheduling/pucch_resource_manager.cpp @@ -116,47 +116,20 @@ pucch_resource_manager::reserve_next_f2_harq_res_available(slot_point s return reserve_next_harq_res_available(slot_harq, crnti, pucch_cfg, pucch_format::FORMAT_2); }; -const pucch_resource* pucch_resource_manager::reserve_specific_format2_res(slot_point slot_harq, - rnti_t crnti, - unsigned res_indicator, - const pucch_config& pucch_cfg) +const pucch_resource* pucch_resource_manager::reserve_f1_res_by_res_indicator(slot_point slot_harq, + rnti_t crnti, + unsigned res_indicator, + const pucch_config& pucch_cfg) { - srsran_sanity_check(slot_harq < last_sl_ind + RES_MANAGER_RING_BUFFER_SIZE, - "PUCCH being allocated to far into the future"); - - // Get resource list of wanted slot. - rnti_pucch_res_id_slot_record& res_counter = get_slot_resource_counter(slot_harq); - // Retrieve the PUCCH resource set. - const auto& ue_res_id_set_for_harq_f2 = pucch_cfg.pucch_res_set[PUCCH_HARQ_F2_RES_SET_ID].pucch_res_id_list; - - // Make sure the resource indicator points to a valid resource. - if (res_indicator >= ue_res_id_set_for_harq_f2.size()) { - return nullptr; - } - - // Get PUCCH resource ID from the PUCCH resource set. - const unsigned pucch_res_id = ue_res_id_set_for_harq_f2[res_indicator].cell_res_id; - // Get the PUCCH resource tracker in the PUCCH resource manager. - auto& pucch_res_tracker = res_counter.ues_using_pucch_res[pucch_res_id]; - const auto& pucch_res_list = pucch_cfg.pucch_res_list; - - // Check first if the wanted PUCCH resource is available. - if (pucch_res_tracker.rnti == rnti_t::INVALID_RNTI) { - // Search for the PUCCH resource with the correct PUCCH resource ID from the PUCCH resource list. - const auto* res_cfg = - std::find_if(pucch_res_list.begin(), pucch_res_list.end(), [pucch_res_id](const pucch_resource& res) { - return res.res_id.cell_res_id == pucch_res_id; - }); - - // If the PUCCH res with correct ID is found, then allocate it to the user. - if (res_cfg != pucch_res_list.end()) { - pucch_res_tracker.rnti = crnti; - pucch_res_tracker.resource_usage = pucch_resource_usage::HARQ_F2; - return &(*res_cfg); - } - } + return reserve_harq_res_by_res_indicator(slot_harq, crnti, res_indicator, pucch_cfg, pucch_format::FORMAT_1); +} - return nullptr; +const pucch_resource* pucch_resource_manager::reserve_f2_res_by_res_indicator(slot_point slot_harq, + rnti_t crnti, + unsigned res_indicator, + const pucch_config& pucch_cfg) +{ + return reserve_harq_res_by_res_indicator(slot_harq, crnti, res_indicator, pucch_cfg, pucch_format::FORMAT_2); } const pucch_resource* pucch_resource_manager::reserve_csi_resource(slot_point slot_csi, @@ -433,6 +406,55 @@ pucch_harq_resource_alloc_record pucch_resource_manager::reserve_next_harq_res_a return pucch_harq_resource_alloc_record{.pucch_res = nullptr}; }; +const pucch_resource* pucch_resource_manager::reserve_harq_res_by_res_indicator(slot_point slot_harq, + rnti_t crnti, + unsigned res_indicator, + const pucch_config& pucch_cfg, + pucch_format format) +{ + srsran_sanity_check(slot_harq < last_sl_ind + RES_MANAGER_RING_BUFFER_SIZE, + "PUCCH being allocated to far into the future"); + srsran_assert(format == pucch_format::FORMAT_1 or format == pucch_format::FORMAT_2, + "Only PUCCH Format 1 and Format 2 are currently supported"); + + const unsigned res_set_idx = format == pucch_format::FORMAT_1 ? PUCCH_HARQ_F1_RES_SET_ID : PUCCH_HARQ_F2_RES_SET_ID; + + // Get resource list of wanted slot. + rnti_pucch_res_id_slot_record& res_counter = get_slot_resource_counter(slot_harq); + // Retrieve the PUCCH resource set. + const auto& ue_res_id_set_for_harq = pucch_cfg.pucch_res_set[res_set_idx].pucch_res_id_list; + + // Make sure the resource indicator points to a valid resource. + if (res_indicator >= ue_res_id_set_for_harq.size()) { + return nullptr; + } + + // Get PUCCH resource ID from the PUCCH resource set. + const unsigned pucch_res_id = ue_res_id_set_for_harq[res_indicator].cell_res_id; + // Get the PUCCH resource tracker in the PUCCH resource manager. + auto& pucch_res_tracker = res_counter.ues_using_pucch_res[pucch_res_id]; + const auto& pucch_res_list = pucch_cfg.pucch_res_list; + + // Check first if the wanted PUCCH resource is available. + if (pucch_res_tracker.rnti == rnti_t::INVALID_RNTI) { + // Search for the PUCCH resource with the correct PUCCH resource ID from the PUCCH resource list. + const auto* res_cfg = + std::find_if(pucch_res_list.begin(), pucch_res_list.end(), [pucch_res_id](const pucch_resource& res) { + return res.res_id.cell_res_id == pucch_res_id; + }); + + // If the PUCCH res with correct ID is found, then allocate it to the user. + if (res_cfg != pucch_res_list.end()) { + pucch_res_tracker.rnti = crnti; + pucch_res_tracker.resource_usage = + format == pucch_format::FORMAT_1 ? pucch_resource_usage::HARQ_F1 : pucch_resource_usage::HARQ_F2; + return &(*res_cfg); + } + } + + return nullptr; +} + bool pucch_resource_manager::release_harq_resource(slot_point slot_harq, rnti_t crnti, const pucch_config& pucch_cfg, diff --git a/lib/scheduler/pucch_scheduling/pucch_resource_manager.h b/lib/scheduler/pucch_scheduling/pucch_resource_manager.h index 39b35fc95d..56535aadb3 100644 --- a/lib/scheduler/pucch_scheduling/pucch_resource_manager.h +++ b/lib/scheduler/pucch_scheduling/pucch_resource_manager.h @@ -77,14 +77,22 @@ class pucch_resource_manager pucch_harq_resource_alloc_record reserve_next_f2_harq_res_available(slot_point slot_harq, rnti_t crnti, const pucch_config& pucch_cfg); + /// \brief Returns a specific PUCCH format 1 resource (identified by the res. indicator) to be used for HARQ-ACK. + /// \return If the specific PUCCH resource is available, it returns the pointer to the configuration. Else, it returns + /// \c nullptr. + const pucch_resource* reserve_f1_res_by_res_indicator(slot_point slot_harq, + rnti_t crnti, + unsigned res_indicator, + const pucch_config& pucch_cfg); + /// \brief Returns a specific PUCCH format 2 resource (identified by the res. indicator) to be used for HARQ-ACK. /// \remark If SR and CSI multiplexing is enabled, this resource can be used for HARQ-ACK + SR and/or CSI. /// \return If the specific PUCCH resource is available, it returns the pointer to the configuration. Else, it returns /// \c nullptr. - const pucch_resource* reserve_specific_format2_res(slot_point slot_harq, - rnti_t crnti, - unsigned res_indicator, - const pucch_config& pucch_cfg); + const pucch_resource* reserve_f2_res_by_res_indicator(slot_point slot_harq, + rnti_t crnti, + unsigned res_indicator, + const pucch_config& pucch_cfg); /// \brief Returns the specific PUCCH format 2 resource config to be used for CSI, if available. /// \remark If SR multiplexing is enabled, this resource can be used for CSI + SR. @@ -190,6 +198,12 @@ class pucch_resource_manager const pucch_config& pucch_cfg, pucch_format format); + const pucch_resource* reserve_harq_res_by_res_indicator(slot_point slot_harq, + rnti_t crnti, + unsigned res_indicator, + const pucch_config& pucch_cfg, + pucch_format format); + bool release_harq_resource(slot_point slot_harq, rnti_t crnti, const pucch_config& pucch_cfg, pucch_format format); int fetch_pucch_res_indic(slot_point slot_tx, rnti_t crnti, const pucch_config& pucch_cfg, pucch_format format); diff --git a/lib/scheduler/uci_scheduling/uci_allocator_impl.cpp b/lib/scheduler/uci_scheduling/uci_allocator_impl.cpp index 88db3bedfe..9b9c1e8417 100644 --- a/lib/scheduler/uci_scheduling/uci_allocator_impl.cpp +++ b/lib/scheduler/uci_scheduling/uci_allocator_impl.cpp @@ -290,8 +290,6 @@ void uci_allocator_impl::multiplex_uci_on_pusch(ul_sched_info& pu update_uci_on_pusch_harq_offsets( uci.harq.value(), ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pusch_cfg.value().uci_cfg.value()); } - - logger.debug("rnti={}: UCI mltplxd on PUSCH for slot={}", crnti, slot_alloc.slot); } void uci_allocator_impl::uci_allocate_sr_opportunity(cell_slot_resource_allocator& slot_alloc, @@ -337,12 +335,6 @@ void uci_allocator_impl::uci_allocate_csi_opportunity(cell_slot_resource_allocat ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pusch_cfg.value().uci_cfg.value().scaling; add_csi_to_uci_on_pusch(existing_pusch->uci.value().csi.emplace(), ue_cell_cfg); - - logger.debug("rnti={} UCI with 0 H-ACK, {} CSI-p1 and {} CSI-p2 bits for slot={} allocated on PUSCH", - crnti, - existing_pusch->uci.value().csi.value().csi_part1_nof_bits, - existing_pusch->uci.value().csi.value().beta_offset_csi_2.has_value() ? "up to 11" : "0", - slot_alloc.slot); return; } diff --git a/lib/scheduler/uci_scheduling/uci_scheduler_impl.cpp b/lib/scheduler/uci_scheduling/uci_scheduler_impl.cpp index 0d68645b98..d988eb4331 100644 --- a/lib/scheduler/uci_scheduling/uci_scheduler_impl.cpp +++ b/lib/scheduler/uci_scheduling/uci_scheduler_impl.cpp @@ -260,6 +260,22 @@ void uci_scheduler_impl::schedule_updated_ues_ucis(cell_resource_allocator& cell for (unsigned n = 0; n != cell_alloc.max_ul_slot_alloc_delay; ++n) { auto& slot_ucis = periodic_uci_slot_wheel[(cell_alloc.slot_tx() + n).to_uint() % periodic_uci_slot_wheel.size()]; + // Skip UCI scheduling for this UE and slot, if the maximum number of PUCCHs has been reached. + if (cell_alloc[n].result.ul.pucchs.full()) { + if (logger.debug.enabled()) { + // If we want more detailed logs on the skipped allocations. + for (const periodic_uci_info& uci_info : slot_ucis) { + if (uci_info.rnti == rnti) { + logger.debug("cell={} c-rnti={}: Skipped UCI scheduling for slot={}. Cause: Max PUCCHs has been reached", + cell_cfg.cell_index, + rnti, + cell_alloc[n].slot); + } + } + } + continue; + } + for (const periodic_uci_info& uci_info : slot_ucis) { if (uci_info.rnti == rnti) { // Schedule SR PUCCHs first. diff --git a/lib/scheduler/ue_scheduling/ue.cpp b/lib/scheduler/ue_scheduling/ue.cpp index 5c9604f6e3..d26bc6b71a 100644 --- a/lib/scheduler/ue_scheduling/ue.cpp +++ b/lib/scheduler/ue_scheduling/ue.cpp @@ -41,7 +41,8 @@ ue::ue(const ue_creation_command& cmd) : for (auto& cell : ue_du_cells) { if (cell != nullptr) { - cell->set_fallback_state(cmd.starts_in_fallback); + cell->set_fallback_state(cmd.starts_in_fallback ? ue_cell::fallback_state::fallback + : ue_cell::fallback_state::normal); } } } diff --git a/lib/scheduler/ue_scheduling/ue.h b/lib/scheduler/ue_scheduling/ue.h index ef35c0d052..e564c9f341 100644 --- a/lib/scheduler/ue_scheduling/ue.h +++ b/lib/scheduler/ue_scheduling/ue.h @@ -96,8 +96,11 @@ class ue /// \brief Handle received SR indication. void handle_sr_indication() { - // Reception of SR means that the UE has applied its dedicated configuration. - ue_cells[0]->set_fallback_state(false); + // Reception of the SR means that the UE has applied its dedicated configuration; this triggers the new state + // transition to \ref fallback_state::sr_csi_received. + if (ue_cells[0]->is_in_fallback_mode()) { + ue_cells[0]->set_fallback_state(ue_cell::fallback_state::sr_csi_received); + } ul_lc_ch_mgr.handle_sr_indication(); } diff --git a/lib/scheduler/ue_scheduling/ue_cell.cpp b/lib/scheduler/ue_scheduling/ue_cell.cpp index 64c625dd28..44a6516e1c 100644 --- a/lib/scheduler/ue_scheduling/ue_cell.cpp +++ b/lib/scheduler/ue_scheduling/ue_cell.cpp @@ -180,6 +180,8 @@ grant_prbs_mcs ue_cell::required_ul_prbs(const pusch_time_domain_resource_alloca unsigned nof_prbs = std::min(prbs_tbs.nof_prbs, bwp_ul_cmn.generic_params.crbs.length()); // Apply grant size limits specified in the config. + nof_prbs = std::max(std::min(nof_prbs, cell_cfg.expert_cfg.ue.pusch_nof_rbs.stop()), + cell_cfg.expert_cfg.ue.pusch_nof_rbs.start()); nof_prbs = std::max(std::min(nof_prbs, ue_cfg->rrm_cfg().pusch_grant_size_limits.stop()), ue_cfg->rrm_cfg().pusch_grant_size_limits.start()); @@ -190,9 +192,21 @@ int ue_cell::handle_crc_pdu(slot_point pusch_slot, const ul_crc_pdu_indication& { // Update UL HARQ state. int tbs = harqs.ul_crc_info(crc_pdu.harq_id, crc_pdu.tb_crc_success, pusch_slot); + if (tbs >= 0) { // HARQ with matching ID and UCI slot was found. + // Implements the condition for the UE to exit fallback mode: the GNB must receive 2 CRC=OK after receiving either a + // SR or CSI. + if (fallback_mode_current == fallback_state::sr_csi_received and crc_pdu.tb_crc_success) { + ++fallback_crc_cnt; + const unsigned min_crc_ok_for_leaving_fallback = 2U; + if (fallback_crc_cnt >= min_crc_ok_for_leaving_fallback) { + set_fallback_state(fallback_state::normal); + fallback_crc_cnt = 0; + } + } + // Update link adaptation controller. const ul_harq_process& h_ul = harqs.ul_harq(crc_pdu.harq_id); ue_mcs_calculator.handle_ul_crc_info( @@ -212,7 +226,9 @@ int ue_cell::handle_crc_pdu(slot_point pusch_slot, const ul_crc_pdu_indication& void ue_cell::handle_csi_report(const csi_report_data& csi_report) { - set_fallback_state(false); + if (fallback_mode_current == fallback_state::fallback) { + set_fallback_state(fallback_state::sr_csi_received); + } apply_link_adaptation_procedures(csi_report); if (not channel_state.handle_csi_report(csi_report)) { logger.warning("ue={} rnti={}: Invalid CSI report received", ue_index, rnti()); @@ -260,7 +276,7 @@ ue_cell::get_active_dl_search_spaces(slot_point pdcch_slo } // In fallback mode state, only use search spaces configured in CellConfigCommon. - if (is_fallback_mode) { + if (is_in_fallback_mode()) { srsran_assert(not required_dci_rnti_type.has_value() or required_dci_rnti_type == dci_dl_rnti_config_type::c_rnti_f1_0, "Invalid required dci-rnti parameter"); @@ -330,7 +346,7 @@ ue_cell::get_active_ul_search_spaces(slot_point pdcch_slo optional required_dci_rnti_type) const { // In fallback mode state, only use search spaces configured in CellConfigCommon. - if (is_fallback_mode) { + if (is_in_fallback_mode()) { static_vector active_search_spaces; srsran_assert(not required_dci_rnti_type.has_value() or required_dci_rnti_type == dci_ul_rnti_config_type::c_rnti_f0_0, @@ -426,6 +442,35 @@ aggregation_level ue_cell::get_aggregation_level(cqi_value cqi, const search_spa return map_cqi_to_aggregation_level(cqi, cqi_table, ss_info.cfg->get_nof_candidates(), dci_size); } +void ue_cell::set_fallback_state(fallback_state fallback_mode_new) +{ + // Allowed state-transition: + // - fallback => sr_csi_received. + // - sr_csi_received => normal. + // - normal => fallback. + // - transitions to same state. + if (fallback_mode_new == fallback_state::normal) { + fallback_crc_cnt = 0; + if (fallback_mode_current == fallback_state::sr_csi_received) { + logger.debug("ue={} rnti={}: Leaving fallback mode", ue_index, rnti()); + } else if (fallback_mode_current == fallback_state::fallback) { + logger.error("ue={} rnti={}: Detected wrong transition from fallback to normal mode", ue_index, rnti()); + } + } else if (fallback_mode_new == fallback_state::fallback) { + if (fallback_mode_current == fallback_state::normal) { + logger.debug("ue={} rnti={}: Entering fallback mode", ue_index, rnti()); + } else if (fallback_mode_current == fallback_state::sr_csi_received) { + logger.error("ue={} rnti={}: Detected wrong transition from sr_csi_received to fallback mode", ue_index, rnti()); + } + } else { + if (fallback_mode_current == fallback_state::normal) { + logger.error("ue={} rnti={}: Detected wrong transition from normal to sr_csi_received", ue_index, rnti()); + } + } + + fallback_mode_current = fallback_mode_new; +} + void ue_cell::apply_link_adaptation_procedures(const csi_report_data& csi_report) { // Early return if no decrease in CQI and RI. diff --git a/lib/scheduler/ue_scheduling/ue_cell.h b/lib/scheduler/ue_scheduling/ue_cell.h index 9e45330c3f..1dae1e0a6e 100644 --- a/lib/scheduler/ue_scheduling/ue_cell.h +++ b/lib/scheduler/ue_scheduling/ue_cell.h @@ -51,6 +51,8 @@ class ue_cell unsigned consecutive_pusch_kos = 0; }; + bool is_in_fallback_mode() const { return fallback_mode_current != fallback_state::normal; } + ue_cell(du_ue_index_t ue_index_, rnti_t crnti_val, const ue_cell_configuration& ue_cell_cfg_, @@ -101,8 +103,6 @@ class ue_cell .pusch_rv_sequence[h_ul.tb().nof_retxs % cell_cfg.expert_cfg.ue.pusch_rv_sequence.size()]; } - bool is_in_fallback_mode() const { return is_fallback_mode; } - /// \brief Handle CRC PDU indication. int handle_crc_pdu(slot_point pusch_slot, const ul_crc_pdu_indication& crc_pdu); @@ -128,14 +128,14 @@ class ue_cell get_active_ul_search_spaces(slot_point pdcch_slot, optional required_dci_rnti_type = {}) const; + /// \brief Defines the fallback state of the ue_cell. + /// Transitions can be fallback => sr_csi_received => normal => fallback. The fallback => sr_csi_received transition + /// is triggered by the reception of SR or CSI, the sr_csi_received => normal is triggered by the reception of 2 + /// CRC=OK after the first SR or CSI is received. + enum class fallback_state { fallback, sr_csi_received, normal }; + /// \brief Set UE fallback state. - void set_fallback_state(bool fallback_state_) - { - if (fallback_state_ != is_fallback_mode) { - logger.debug("ue={} rnti={}: {} fallback mode", ue_index, rnti(), fallback_state_ ? "Entering" : "Leaving"); - } - is_fallback_mode = fallback_state_; - } + void set_fallback_state(fallback_state fallback_mode_new); /// \brief Get UE channel state handler. ue_channel_state_manager& channel_state_manager() { return channel_state; } @@ -161,10 +161,11 @@ class ue_cell /// \brief Whether cell is currently active. bool active = true; - /// \brief Fallback state of the UE. When in "fallback" mode, only the search spaces of cellConfigCommon are used. - /// The UE should automatically leave this mode, when a SR/CSI is received, since, in order to send SR/CSI the UE must - /// already have applied a dedicated config. - bool is_fallback_mode = false; + /// \brief Fallback state of the UE. When in "fallback" mode, only the search spaces and the configuration of + /// cellConfigCommon are used. + fallback_state fallback_mode_current = fallback_state::normal; + /// Counter of received CRC=OK after the first SR or CSI has been received. + unsigned fallback_crc_cnt = 0; metrics ue_metrics; diff --git a/lib/scheduler/ue_scheduling/ue_cell_grid_allocator.cpp b/lib/scheduler/ue_scheduling/ue_cell_grid_allocator.cpp index f2578f6dc1..b29dc06580 100644 --- a/lib/scheduler/ue_scheduling/ue_cell_grid_allocator.cpp +++ b/lib/scheduler/ue_scheduling/ue_cell_grid_allocator.cpp @@ -234,8 +234,10 @@ alloc_outcome ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& gr if (h_dl.empty() and ((ul_alloc.result.ul.pucchs.size() > (expert_cfg.max_pucchs_per_slot - 1)) or ((ul_alloc.result.ul.pucchs.size() + ul_alloc.result.ul.puschs.size()) > (expert_cfg.max_ul_grants_per_slot - 1)))) { - const crb_bitmap used_crbs = - pdsch_alloc.dl_res_grid.used_crbs(bwp_dl_cmn.generic_params.scs, ss_info->dl_crb_lims, pdsch_td_cfg.symbols); + const crb_interval dl_crb_lims = {std::max(expert_cfg.pdsch_crb_limits.start(), ss_info->dl_crb_lims.start()), + std::min(expert_cfg.pdsch_crb_limits.stop(), ss_info->dl_crb_lims.stop())}; + const crb_bitmap used_crbs = + pdsch_alloc.dl_res_grid.used_crbs(bwp_dl_cmn.generic_params.scs, dl_crb_lims, pdsch_td_cfg.symbols); adjusted_crbs = rb_helper::find_empty_interval_of_length(used_crbs, used_crbs.size(), 0); } @@ -600,8 +602,10 @@ alloc_outcome ue_cell_grid_allocator::allocate_ul_grant(const ue_pusch_grant& gr if (h_ul.empty() and (pusch_alloc.result.ul.puschs.size() >= expert_cfg.max_ul_grants_per_slot - (static_cast(pusch_alloc.result.ul.pucchs.size()) - nof_pucch_grants) - 1)) { - const crb_bitmap used_crbs = - pusch_alloc.ul_res_grid.used_crbs(bwp_ul_cmn.generic_params.scs, ss_info->ul_crb_lims, pusch_td_cfg.symbols); + const crb_interval ul_crb_lims = {std::max(expert_cfg.pusch_crb_limits.start(), ss_info->ul_crb_lims.start()), + std::min(expert_cfg.pusch_crb_limits.stop(), ss_info->ul_crb_lims.stop())}; + const crb_bitmap used_crbs = + pusch_alloc.ul_res_grid.used_crbs(bwp_ul_cmn.generic_params.scs, ul_crb_lims, pusch_td_cfg.symbols); adjusted_crbs = rb_helper::find_empty_interval_of_length(used_crbs, used_crbs.size(), 0); } diff --git a/lib/scheduler/ue_scheduling/ue_event_manager.cpp b/lib/scheduler/ue_scheduling/ue_event_manager.cpp index ae54953ceb..867ab36ef2 100644 --- a/lib/scheduler/ue_scheduling/ue_event_manager.cpp +++ b/lib/scheduler/ue_scheduling/ue_event_manager.cpp @@ -381,6 +381,7 @@ void ue_event_manager::handle_uci_indication(const uci_indication& ind) if (pdu.sr_detected) { // Handle SR indication. ue_db[ue_cc.ue_index].handle_sr_indication(); + du_cells[ue_cc.cell_index].fallback_sched->handle_sr_indication(ue_cc.ue_index); // Log SR event. ev_logger.enqueue(scheduler_event_logger::sr_event{ue_cc.ue_index, ue_cc.rnti()}); diff --git a/lib/scheduler/ue_scheduling/ue_fallback_scheduler.cpp b/lib/scheduler/ue_scheduling/ue_fallback_scheduler.cpp index aaf4b80e2e..3f98b5f2cc 100644 --- a/lib/scheduler/ue_scheduling/ue_fallback_scheduler.cpp +++ b/lib/scheduler/ue_scheduling/ue_fallback_scheduler.cpp @@ -158,6 +158,20 @@ void ue_fallback_scheduler::handle_ul_bsr_indication(du_ue_index_t ue_index, con } } +void ue_fallback_scheduler::handle_sr_indication(du_ue_index_t ue_index) +{ + if (not ues.contains(ue_index)) { + logger.error("ue_index={} not found in the scheduler", ue_index); + return; + } + + auto ue_it = std::find(pending_ul_ues.begin(), pending_ul_ues.end(), ue_index); + + if (ue_it == pending_ul_ues.end()) { + pending_ul_ues.emplace_back(ue_index); + } +} + bool ue_fallback_scheduler::schedule_dl_retx(cell_resource_allocator& res_alloc) { for (auto& next_ue_harq_retx : ongoing_ues_ack_retxs) { @@ -166,9 +180,9 @@ bool ue_fallback_scheduler::schedule_dl_retx(cell_resource_allocator& res_alloc) if (h_dl->has_pending_retx()) { optional most_recent_tx_ack = get_most_recent_slot_tx(u.ue_index); - sched_outcome outcome = schedule_dl_srb(res_alloc, u, next_ue_harq_retx.is_srb0, h_dl, most_recent_tx_ack); + dl_sched_outcome outcome = schedule_dl_srb(res_alloc, u, next_ue_harq_retx.is_srb0, h_dl, most_recent_tx_ack); // This is the case of the scheduler reaching the maximum number of sched attempts. - if (outcome == sched_outcome::exit_scheduler) { + if (outcome == dl_sched_outcome::stop_dl_scheduling) { return false; } } @@ -187,7 +201,11 @@ void ue_fallback_scheduler::schedule_ul_new_tx_and_retx(cell_resource_allocator& ++next_ue; continue; } - schedule_ul_ue(res_alloc, u, h_ul_retx); + ul_srb_sched_outcome outcome = schedule_ul_ue(res_alloc, u, h_ul_retx); + if (outcome == ul_srb_sched_outcome::stop_ul_scheduling) { + // If there is no PDCCH space, then stop the scheduling for all UL UEs. + return; + } ++next_ue; } } @@ -213,14 +231,14 @@ bool ue_fallback_scheduler::schedule_dl_new_tx_srb0(cell_resource_allocator& res continue; } - sched_outcome outcome = schedule_dl_srb(res_alloc, u, true, nullptr, most_recent_tx_ack); - if (outcome == sched_outcome::success) { + dl_sched_outcome outcome = schedule_dl_srb(res_alloc, u, true, nullptr, most_recent_tx_ack); + if (outcome == dl_sched_outcome::success) { next_ue = pending_dl_ues_new_tx.erase(next_ue); - } else if (outcome == sched_outcome::next_ue) { + } else if (outcome == dl_sched_outcome::next_ue) { ++next_ue; } else { // This is the case the DL fallback scheduler has reached the maximum number of scheduling attempts and the fnc - // returns \ref exit_scheduler. + // returns \ref stop_dl_scheduling. return false; } } @@ -252,8 +270,8 @@ void ue_fallback_scheduler::schedule_dl_new_tx_srb1(cell_resource_allocator& res continue; } - sched_outcome outcome = schedule_dl_srb(res_alloc, u, false, nullptr, most_recent_tx_ack); - if (outcome == sched_outcome::success) { + dl_sched_outcome outcome = schedule_dl_srb(res_alloc, u, false, nullptr, most_recent_tx_ack); + if (outcome == dl_sched_outcome::success) { // Move to the next UE ONLY IF the UE has no more pending bytes for SRB1. This is to give priority to the same UE, // if there are still some SRB1 bytes left in the buffer. At the next iteration, the scheduler will try again with // the same scheduler, but starting from the next available slot. @@ -261,11 +279,11 @@ void ue_fallback_scheduler::schedule_dl_new_tx_srb1(cell_resource_allocator& res ++next_ue; } - } else if (outcome == sched_outcome::next_ue) { + } else if (outcome == dl_sched_outcome::next_ue) { ++next_ue; } else { // This is the case the DL fallback scheduler has reached the maximum number of scheduling attempts and the fnc - // returns \ref exit_scheduler. + // returns \ref stop_dl_scheduling. return; } } @@ -287,7 +305,7 @@ static slot_point get_next_srb_slot(const cell_configuration& cell_cfg, slot_poi return next_candidate_slot; } -ue_fallback_scheduler::sched_outcome +ue_fallback_scheduler::dl_sched_outcome ue_fallback_scheduler::schedule_dl_srb(cell_resource_allocator& res_alloc, ue& u, bool is_srb0, @@ -311,7 +329,7 @@ ue_fallback_scheduler::schedule_dl_srb(cell_resource_allocator& res_alloc, // scheduler attempt to allocate a new TX on the same slot. if (most_recent_tx_ack_slots.has_value() and sched_ref_slot + max_dl_slots_ahead_sched <= most_recent_tx_ack_slots.value().most_recent_tx_slot) { - return sched_outcome::next_ue; + return dl_sched_outcome::next_ue; } // \ref starting_slot is the slot from which the SRB0 starts scheduling this given UE. Assuming the UE was assigned @@ -327,7 +345,7 @@ ue_fallback_scheduler::schedule_dl_srb(cell_resource_allocator& res_alloc, for (slot_point next_slot = starting_slot; next_slot <= sched_ref_slot + max_dl_slots_ahead_sched; next_slot = get_next_srb_slot(cell_cfg, next_slot)) { if (sched_attempts_cnt >= max_dl_sched_attempts) { - return sched_outcome::exit_scheduler; + return dl_sched_outcome::stop_dl_scheduling; } if (slots_with_no_pdxch_space[next_slot.to_uint() % FALLBACK_SCHED_RING_BUFFER_SIZE]) { @@ -397,7 +415,7 @@ ue_fallback_scheduler::schedule_dl_srb(cell_resource_allocator& res_alloc, // The srb1_payload_bytes is meaningful only for SRB1. store_harq_tx(u.ue_index, sched_res.h_dl, is_srb0, sched_res.nof_srb1_scheduled_bytes); } - return sched_outcome::success; + return dl_sched_outcome::success; } ++sched_attempts_cnt; @@ -410,7 +428,7 @@ ue_fallback_scheduler::schedule_dl_srb(cell_resource_allocator& res_alloc, is_srb0 ? "SRB0" : "SRB1", pdcch_slot, pdcch_slot + max_dl_slots_ahead_sched + 1); - return sched_outcome::next_ue; + return dl_sched_outcome::next_ue; } ue_fallback_scheduler::sched_srb_results ue_fallback_scheduler::schedule_dl_srb0(ue& u, @@ -531,8 +549,11 @@ ue_fallback_scheduler::sched_srb_results ue_fallback_scheduler::schedule_dl_srb0 if (pdsch_alloc.slot + k1_candidate <= most_recent_ack_slot) { continue; } - pucch_res_indicator = pucch_alloc.alloc_common_pucch_harq_ack_ue( - res_alloc, u.crnti, slot_offset + pdsch_td_cfg.k0, k1_candidate, *pdcch); + pucch_res_indicator = + is_retx ? pucch_alloc.alloc_common_and_ded_harq_res( + res_alloc, u.crnti, u.get_pcell().cfg(), slot_offset + pdsch_td_cfg.k0, k1_candidate, *pdcch) + : pucch_alloc.alloc_common_pucch_harq_ack_ue( + res_alloc, u.crnti, slot_offset + pdsch_td_cfg.k0, k1_candidate, *pdcch); if (pucch_res_indicator.has_value()) { k1 = k1_candidate; break; @@ -702,13 +723,18 @@ ue_fallback_scheduler::sched_srb_results ue_fallback_scheduler::schedule_dl_srb1 unsigned k1 = dci_1_0_k1_values.front(); // Minimum k1 value supported is 4. optional pucch_res_indicator; + const bool allocate_common_and_ded_pucch = dci_type != dci_dl_rnti_config_type::tc_rnti_f1_0 or is_retx; for (const auto k1_candidate : dci_1_0_k1_values) { // Skip the k1 values that would result in a PUCCH allocation that would overlap with the most recent ACK slot. if (pdsch_alloc.slot + k1_candidate <= most_recent_ack_slot) { continue; } - pucch_res_indicator = pucch_alloc.alloc_common_pucch_harq_ack_ue( - res_alloc, u.crnti, slot_offset + pdsch_td_cfg.k0, k1_candidate, *pdcch); + pucch_res_indicator = + allocate_common_and_ded_pucch + ? pucch_alloc.alloc_common_and_ded_harq_res( + res_alloc, u.crnti, u.get_pcell().cfg(), slot_offset + pdsch_td_cfg.k0, k1_candidate, *pdcch) + : pucch_alloc.alloc_common_pucch_harq_ack_ue( + res_alloc, u.crnti, slot_offset + pdsch_td_cfg.k0, k1_candidate, *pdcch); if (pucch_res_indicator.has_value()) { k1 = k1_candidate; break; @@ -884,7 +910,8 @@ unsigned ue_fallback_scheduler::fill_dl_srb_grant(ue& u, return srb1_bytes_allocated; } -void ue_fallback_scheduler::schedule_ul_ue(cell_resource_allocator& res_alloc, ue& u, ul_harq_process* h_ul_retx) +ue_fallback_scheduler::ul_srb_sched_outcome +ue_fallback_scheduler::schedule_ul_ue(cell_resource_allocator& res_alloc, ue& u, ul_harq_process* h_ul_retx) { // The caller ensures the slot is Ul enabled. const cell_slot_resource_allocator& pdcch_alloc = res_alloc[0]; @@ -905,10 +932,6 @@ void ue_fallback_scheduler::schedule_ul_ue(cell_resource_allocator& res_alloc, u for (const auto* ss : search_spaces) { for (unsigned pusch_td_res_idx : pusch_td_res_index_list) { - if (ss->cfg->is_search_space0()) { - continue; - } - const pusch_time_domain_resource_allocation& pusch_td = ss->pusch_time_domain_list[pusch_td_res_idx]; const cell_slot_resource_allocator& pusch_alloc = res_alloc[pusch_td.k2 + cell_cfg.ntn_cs_koffset]; const slot_point pusch_slot = pusch_alloc.slot; @@ -973,19 +996,21 @@ void ue_fallback_scheduler::schedule_ul_ue(cell_resource_allocator& res_alloc, u continue; } - // If the function return true, the PUSCH allocation was successful. We then move the next UE. - if (schedule_ul_srb(u, res_alloc, pusch_td_res_idx, pusch_td, h_ul_retx)) { - return; + ul_srb_sched_outcome outcome = schedule_ul_srb(u, res_alloc, pusch_td_res_idx, pusch_td, h_ul_retx); + if (outcome != ul_srb_sched_outcome::next_slot) { + return outcome; } } } + return ul_srb_sched_outcome::next_ue; } -bool ue_fallback_scheduler::schedule_ul_srb(ue& u, - cell_resource_allocator& res_alloc, - unsigned pusch_time_res, - const pusch_time_domain_resource_allocation& pusch_td, - ul_harq_process* h_ul_retx) +ue_fallback_scheduler::ul_srb_sched_outcome +ue_fallback_scheduler::schedule_ul_srb(ue& u, + cell_resource_allocator& res_alloc, + unsigned pusch_time_res, + const pusch_time_domain_resource_allocation& pusch_td, + ul_harq_process* h_ul_retx) { ue_cell& ue_pcell = u.get_pcell(); cell_slot_resource_allocator& pdcch_alloc = res_alloc[0]; @@ -1002,12 +1027,12 @@ bool ue_fallback_scheduler::schedule_ul_srb(ue& ul_harq_process* h_ul = is_retx ? h_ul_retx : ue_pcell.harqs.find_empty_ul_harq(); if (h_ul == nullptr) { logger.debug("ue={} rnti={} PUSCH allocation skipped. Cause: no HARQ available", u.ue_index, u.crnti); - return false; + return ul_srb_sched_outcome::next_ue; } if (used_crbs.all()) { logger.debug("ue={} rnti={} PUSCH allocation skipped. Cause: No more RBs available", u.ue_index, u.crnti); - return false; + return ul_srb_sched_outcome::next_slot; } // In fallback, we do not multiplex any HARQ-ACK or CSI within the PUSCH. @@ -1030,7 +1055,7 @@ bool ue_fallback_scheduler::schedule_ul_srb(ue& u.crnti, ue_grant_crbs.length(), final_nof_prbs); - return false; + return ul_srb_sched_outcome::next_slot; } } else { pusch_mcs_table fallback_mcs_table = pusch_mcs_table::qam64; @@ -1062,7 +1087,7 @@ bool ue_fallback_scheduler::schedule_ul_srb(ue& if (ue_grant_crbs.empty()) { logger.debug("ue={} rnti={} PUSCH allocation for SRB1 skipped. Cause: no PRBs available", u.ue_index, u.crnti); - return false; + return ul_srb_sched_outcome::next_slot; } if (ue_grant_crbs.length() <= min_allocable_prbs and mcs < min_mcs_for_1_prb) { @@ -1072,7 +1097,7 @@ bool ue_fallback_scheduler::schedule_ul_srb(ue& u.crnti, prbs_tbs.nof_prbs, mcs.to_uint()); - return false; + return ul_srb_sched_outcome::next_slot; } bool contains_dc = dc_offset_helper::is_contained( @@ -1086,7 +1111,7 @@ bool ue_fallback_scheduler::schedule_ul_srb(ue& logger.warning("ue={} rnti={}: Failed to allocate PUSCH for SRB1. Cause: no MCS such that code rate <= 0.95", u.ue_index, u.crnti); - return false; + return ul_srb_sched_outcome::next_slot; } final_mcs_tbs = mcs_tbs_info.value(); } @@ -1096,7 +1121,7 @@ bool ue_fallback_scheduler::schedule_ul_srb(ue& pdcch_sch.alloc_ul_pdcch_common(pdcch_alloc, u.crnti, ss_cfg.get_id(), aggregation_level::n4); if (pdcch == nullptr) { logger.info("ue={} rnti={}: Failed to allocate PUSCH. Cause: No space in PDCCH.", u.ue_index, u.crnti); - return false; + return ul_srb_sched_outcome::stop_ul_scheduling; } // Mark resources as occupied in the ResourceGrid. @@ -1116,7 +1141,7 @@ bool ue_fallback_scheduler::schedule_ul_srb(ue& final_mcs_tbs.tbs, is_retx); - return true; + return ul_srb_sched_outcome::next_ue; } void ue_fallback_scheduler::fill_ul_srb_grant(ue& u, @@ -1171,8 +1196,8 @@ void ue_fallback_scheduler::fill_ul_srb_grant(ue& u, // Save set PDCCH and PUSCH PDU parameters in HARQ process. h_ul.save_alloc_params(pdcch.dci.type, msg.pusch_cfg); - // NOTE: there is no need to reset SRs, as in fallback the UL grants are triggered by the BSRs. Any SR should instead - // be used to remove the UE from the fallback UE. + // In case there is a SR pending, reset it. + u.reset_sr_indication(); } unsigned ue_fallback_scheduler::has_pending_bytes_for_srb1(du_ue_index_t ue_idx) const @@ -1388,6 +1413,14 @@ void ue_fallback_scheduler::slot_indication(slot_point sl) it_ue_harq = ongoing_ues_ack_retxs.erase(it_ue_harq); continue; } + + // If the HARQ process has the "fallback" flag set to false, it means that the HARQ process got reset by its + // timeout, and in the meantime got reused by the non-fallback scheduler. In this case, it cannot be processed by + // the fallback scheduler. NOTE: this very unlikely to happen, but not impossible under certain extreme conditions. + if (not h_dl.last_alloc_params().is_fallback) { + it_ue_harq = ongoing_ues_ack_retxs.erase(it_ue_harq); + continue; + } ++it_ue_harq; } @@ -1428,7 +1461,6 @@ void ue_fallback_scheduler::slot_indication(slot_point sl) for (uint32_t h_id = 0; h_id != ue.get_pcell().harqs.nof_ul_harqs(); ++h_id) { ue.get_pcell().harqs.ul_harq(h_id).cancel_harq_retxs(); } - logger.debug("Removing ue_idx={} rnti={} from fallback sched UL UE list", ue.ue_index, ue.crnti); ue_it = pending_ul_ues.erase(ue_it); continue; } diff --git a/lib/scheduler/ue_scheduling/ue_fallback_scheduler.h b/lib/scheduler/ue_scheduling/ue_fallback_scheduler.h index a2b9436973..0eae246172 100644 --- a/lib/scheduler/ue_scheduling/ue_fallback_scheduler.h +++ b/lib/scheduler/ue_scheduling/ue_fallback_scheduler.h @@ -56,13 +56,17 @@ class ue_fallback_scheduler /// \param[in] bsr_ind Buffer State Report indication message. void handle_ul_bsr_indication(du_ue_index_t ue_index, const ul_bsr_indication_message& bsr_ind); + /// Handles SR indication reported by UE. + /// \param[in] ue_index UE's DU Index for which UL SRB1 message needs to be scheduled. + void handle_sr_indication(du_ue_index_t ue_index); + /// Schedule UE's SRB0 DL grants for a given slot and one or more cells. /// \param[in] res_alloc Resource Grid of the cell where the DL grant is going to be allocated. void run_slot(cell_resource_allocator& res_alloc); private: - /// Helper that schedules DL SRB0 and SRB1 retx. Returns false if the DL fallback schedule should exit, true - /// otherwise. + /// Helper that schedules DL SRB0 and SRB1 retx. Returns false if the DL fallback schedule should stop the DL + /// allocation, true otherwise. bool schedule_dl_retx(cell_resource_allocator& res_alloc); /// Helper that schedules new UL SRB1 tx. @@ -87,18 +91,21 @@ class ue_fallback_scheduler slot_point most_recent_ack_slot; }; - enum class sched_outcome { success, next_ue, exit_scheduler }; + enum class dl_sched_outcome { success, next_ue, stop_dl_scheduling }; /// \brief Tries to schedule DL SRB0/SRB1 message for a UE, iterating over several PDSCH slots ahead of the current /// reference slot. - sched_outcome schedule_dl_srb(cell_resource_allocator& res_alloc, - ue& u, - bool is_srb0, - dl_harq_process* h_dl_retx, - optional most_recent_tx_ack_slots); + dl_sched_outcome schedule_dl_srb(cell_resource_allocator& res_alloc, + ue& u, + bool is_srb0, + dl_harq_process* h_dl_retx, + optional most_recent_tx_ack_slots); + + enum class ul_srb_sched_outcome { next_ue, next_slot, stop_ul_scheduling }; - /// \brief Tries to schedule UL SRB1 message for a UE iterating over the possible k2 values. - void schedule_ul_ue(cell_resource_allocator& res_alloc, ue& u, ul_harq_process* h_ul_retx); + /// \brief Tries to schedule UL SRB1 message for a UE iterating over the possible k2 values. Returns true if the + /// scheduler should keep allocating the next UL UE, false if it should stop the UL allocation. + ul_srb_sched_outcome schedule_ul_ue(cell_resource_allocator& res_alloc, ue& u, ul_harq_process* h_ul_retx); struct sched_srb_results { dl_harq_process* h_dl = nullptr; @@ -125,11 +132,11 @@ class ue_fallback_scheduler dl_harq_process* h_dl_retx = nullptr); /// \brief Tries to schedule SRB1 message for a specific PUSCH time domain resource. - bool schedule_ul_srb(ue& u, - cell_resource_allocator& res_alloc, - unsigned pusch_time_res, - const pusch_time_domain_resource_allocation& pusch_td, - ul_harq_process* h_ul_retx); + ul_srb_sched_outcome schedule_ul_srb(ue& u, + cell_resource_allocator& res_alloc, + unsigned pusch_time_res, + const pusch_time_domain_resource_allocation& pusch_td, + ul_harq_process* h_ul_retx); unsigned fill_dl_srb_grant(ue& u, slot_point pdsch_slot, diff --git a/lib/support/byte_buffer.cpp b/lib/support/byte_buffer.cpp index 29c0c370e9..83f2ee07c4 100644 --- a/lib/support/byte_buffer.cpp +++ b/lib/support/byte_buffer.cpp @@ -99,6 +99,11 @@ byte_buffer::byte_buffer(fallback_allocation_tag tag, span other) (void)var; } +byte_buffer::byte_buffer(fallback_allocation_tag tag, const std::initializer_list& other) noexcept : + byte_buffer(tag, span(other.begin(), other.end())) +{ +} + byte_buffer::byte_buffer(fallback_allocation_tag tag, const byte_buffer& other) noexcept { // Append new head segment to linked list with fallback allocator mode. @@ -112,6 +117,26 @@ byte_buffer::byte_buffer(fallback_allocation_tag tag, const byte_buffer& other) } } +expected byte_buffer::deep_copy() const +{ + if (ctrl_blk_ptr == nullptr) { + return byte_buffer{}; + } + + byte_buffer buf; + for (node_t* seg = ctrl_blk_ptr->segments.head; seg != nullptr; seg = seg->next) { + if (not buf.append(span{seg->data(), seg->length()})) { + return default_error_t{}; + } + } + return buf; +} + +expected byte_buffer::deep_copy(fallback_allocation_tag tag) const +{ + return byte_buffer{tag, *this}; +} + bool byte_buffer::append(span bytes) { if (bytes.empty()) { @@ -137,6 +162,11 @@ bool byte_buffer::append(span bytes) return true; } +SRSRAN_NODISCARD bool byte_buffer::append(const std::initializer_list& bytes) +{ + return append(span(bytes.begin(), bytes.size())); +} + bool byte_buffer::append(const byte_buffer& other) { srsran_assert(&other != this, "Self-append not supported"); @@ -204,6 +234,18 @@ bool byte_buffer::append(byte_buffer&& other) return true; } +SRSRAN_NODISCARD bool byte_buffer::append(const byte_buffer_view& view) +{ + // Append segment by segment. + auto view_segs = view.segments(); + for (span seg : view_segs) { + if (not append(seg)) { + return false; + } + } + return true; +} + byte_buffer::node_t* byte_buffer::add_head_segment(size_t headroom, bool use_fallback) { auto& pool = detail::get_default_byte_buffer_segment_pool(); @@ -301,6 +343,23 @@ bool byte_buffer::prepend_segment(size_t headroom_suggestion) return true; } +void byte_buffer::pop_last_segment() +{ + node_t* tail = ctrl_blk_ptr->segments.tail; + if (tail == nullptr) { + return; + } + + // Decrement bytes stored in the tail. + ctrl_blk_ptr->pkt_len -= tail->length(); + + // Remove tail from linked list. + ctrl_blk_ptr->segments.pop_back(); + + // Deallocate tail segment. + ctrl_blk_ptr->destroy_node(tail); +} + bool byte_buffer::prepend(span bytes) { if (empty()) { @@ -520,3 +579,53 @@ void byte_buffer::warn_alloc_failure() static srslog::basic_logger& logger = srslog::fetch_basic_logger("ALL"); logger.warning("POOL: Failure to allocate byte buffer segment"); } + +byte_buffer srsran::make_byte_buffer(const std::string& hex_str) +{ + srsran_assert(hex_str.size() % 2 == 0, "The number of hex digits must be even"); + + byte_buffer ret{byte_buffer::fallback_allocation_tag{}}; + for (size_t i = 0, e = hex_str.size(); i != e; i += 2) { + uint8_t val; + std::sscanf(hex_str.data() + i, "%02hhX", &val); + bool success = ret.append(val); + srsran_sanity_check(success, "Failed to append byte to byte_buffer with fallback allocator"); + (void)success; + } + return ret; +} + +span srsran::to_span(const byte_buffer& src, span tmp_mem) +{ + // Empty buffer. + if (src.empty()) { + return {}; + } + + // Is contiguous: shortcut without copy. + if (src.is_contiguous()) { + return *src.segments().begin(); + } + + // Non-contiguous: copy required. + srsran_assert(src.length() <= tmp_mem.size(), + "Insufficient temporary memory to fit the byte_buffer. buffer_size={}, tmp_size={}", + src.length(), + tmp_mem.size()); + span result = {tmp_mem.data(), src.length()}; + copy_segments(src, result); + return result; +} + +// ---- byte_buffer_writer + +SRSRAN_NODISCARD bool byte_buffer_writer::append_zeros(size_t nof_zeros) +{ + // TODO: optimize. + for (size_t i = 0; i != nof_zeros; ++i) { + if (not buffer->append(0)) { + return false; + } + } + return true; +} diff --git a/tests/benchmarks/du_high/du_high_benchmark.cpp b/tests/benchmarks/du_high/du_high_benchmark.cpp index 4192fc5406..e09a59bd4e 100644 --- a/tests/benchmarks/du_high/du_high_benchmark.cpp +++ b/tests/benchmarks/du_high/du_high_benchmark.cpp @@ -57,6 +57,9 @@ using namespace srsran; using namespace srs_du; +/// Constant used to bound the number of bytes pushed to the DU DL F1-U interface per slot. +const unsigned MAX_F1U_DL_BITRATE_PER_PORT_BPS = 500e6; + /// \brief Parameters of the benchmark. struct bench_params { /// \brief Number of runs for the benchmark. Each repetition corresponds to a slot. @@ -169,6 +172,12 @@ static void parse_args(int argc, char** argv, bench_params& params) exit(0); } } + + // apply limits. + subcarrier_spacing scs = params.dplx_mode == duplex_mode::FDD ? subcarrier_spacing::kHz15 : subcarrier_spacing::kHz30; + double slot_dur = 0.001 / get_nof_slots_per_subframe(scs); + unsigned max_dl_bytes_per_slot = MAX_F1U_DL_BITRATE_PER_PORT_BPS * slot_dur / 8; + params.dl_bytes_per_slot = std::min(max_dl_bytes_per_slot, params.dl_bytes_per_slot); } static void print_args(const bench_params& params) @@ -187,7 +196,7 @@ static void print_args(const bench_params& params) } } const double slot_dur_sec = params.dplx_mode == srsran::duplex_mode::FDD ? 0.001 : 0.0005; - fmt::print("- F1-U DL Bitrate [Mbps]: {}\n", params.dl_bytes_per_slot * 1e6 * slot_dur_sec); + fmt::print("- F1-U DL Bitrate [Mbps]: {}\n", params.dl_bytes_per_slot * 8.0 * 1.0e-6 / slot_dur_sec); fmt::print("- F1-U DL PDU size [bytes]: {}\n", params.pdu_size); fmt::print("- BSR size [bytes]: {}\n", params.ul_bsr_bytes); fmt::print("- Max DL RB grant size [RBs]: {}\n", params.max_dl_rb_grant); @@ -562,7 +571,7 @@ class du_high_bench span du_cell_cores, const cell_config_builder_params& builder_params = {}) : params(builder_params), - dl_buffer_state_bytes(dl_buffer_state_bytes_), + f1u_dl_pdu_bytes_per_slot(dl_buffer_state_bytes_), f1u_pdu_size(f1u_pdu_size_), workers(du_cell_cores), ul_bsr_bytes(ul_bsr_bytes_) @@ -754,41 +763,45 @@ class du_high_bench // more PDUs. static const size_t SATURATION_DL_BS_BYTES = 1e5; - if (dl_buffer_state_bytes == 0) { + if (f1u_dl_pdu_bytes_per_slot == 0) { // Early return. return; } - if (metrics_handler.tot_dl_bs > SATURATION_DL_BS_BYTES) { + uint64_t bytes_to_sched = f1u_dl_total_bytes.load(std::memory_order_relaxed); + bytes_to_sched -= std::min(bytes_to_sched, sim_phy.metrics.nof_dl_bytes); + if (bytes_to_sched > SATURATION_DL_BS_BYTES) { // Saturation of the DU DL detected. We throttle the F1-U interface to avoid depleting the byte buffer pool. return; } + while (not workers.dl_exec.defer([this]() { static std::array pdcp_sn_list{0}; - const unsigned nof_dl_pdus_per_slot = divide_ceil(dl_buffer_state_bytes, this->f1u_pdu_size.value()); - const unsigned last_dl_pdu_size = dl_buffer_state_bytes % this->f1u_pdu_size.value(); - - // Forward DL buffer occupancy updates to all bearers. - for (unsigned bearer_idx = 0; bearer_idx != sim_cu_up.du_notif_list.size(); ++bearer_idx) { - const auto& du_notif = sim_cu_up.du_notif_list[bearer_idx]; - for (unsigned i = 0; i != nof_dl_pdus_per_slot; ++i) { - // Update PDCP SN. - pdcp_sn_list[bearer_idx] = (pdcp_sn_list[bearer_idx] + 1) % (1U << 18U); - // We perform a deep-copy of the byte buffer to better simulate a real deployment, where there is stress over - // the byte buffer pool. - auto pdu_copy = pdcp_pdu.deep_copy(); - if (pdu_copy.is_error()) { - test_logger.warning("Byte buffer segment pool depleted"); + const unsigned nof_dl_pdus_per_slot = divide_ceil(f1u_dl_pdu_bytes_per_slot, this->f1u_pdu_size.value()); + const unsigned last_dl_pdu_size = f1u_dl_pdu_bytes_per_slot % this->f1u_pdu_size.value(); + + // Forward DL buffer occupancy updates to all bearers in a Round-robin fashion. + for (unsigned i = 0; i != nof_dl_pdus_per_slot; ++i) { + unsigned bearer_idx = f1u_dl_rr_count++ % sim_cu_up.du_notif_list.size(); + const auto& du_notif = sim_cu_up.du_notif_list[bearer_idx]; + + // Update PDCP SN. + pdcp_sn_list[bearer_idx] = (pdcp_sn_list[bearer_idx] + 1) % (1U << 18U); + // We perform a deep-copy of the byte buffer to better simulate a real deployment, where there is stress over + // the byte buffer pool. + auto pdu_copy = pdcp_pdu.deep_copy(); + if (pdu_copy.is_error()) { + test_logger.warning("Byte buffer segment pool depleted"); + return; + } + if (i == nof_dl_pdus_per_slot - 1 and last_dl_pdu_size != 0) { + // If it is last DL PDU. + if (!pdu_copy.value().resize(last_dl_pdu_size)) { + test_logger.warning("Unable to resize PDU to {} bytes", last_dl_pdu_size); return; } - if (i == nof_dl_pdus_per_slot - 1 and last_dl_pdu_size != 0) { - // If it is last DL PDU. - if (!pdu_copy.value().resize(last_dl_pdu_size)) { - test_logger.warning("Unable to resize PDU to {} bytes", last_dl_pdu_size); - return; - } - } - du_notif->on_new_sdu(pdcp_tx_pdu{.buf = std::move(pdu_copy.value()), .pdcp_sn = pdcp_sn_list[bearer_idx]}); } + f1u_dl_total_bytes.fetch_add(pdu_copy.value().length(), std::memory_order_relaxed); + du_notif->on_new_sdu(pdcp_tx_pdu{.buf = std::move(pdu_copy.value()), .pdcp_sn = pdcp_sn_list[bearer_idx]}); } })) { // keep trying to push new PDUs. @@ -1002,7 +1015,7 @@ class du_high_bench cell_config_builder_params params; du_high_configuration cfg{}; /// Size of the DL buffer status to push for DL Tx. - unsigned dl_buffer_state_bytes; + unsigned f1u_dl_pdu_bytes_per_slot; units::bytes f1u_pdu_size{DEFAULT_DL_PDU_SIZE}; srslog::basic_logger& test_logger = srslog::fetch_basic_logger("TEST"); @@ -1042,6 +1055,11 @@ class du_high_bench /// Size of the UL Buffer status report to push for UL Tx. unsigned ul_bsr_bytes; + + // Round-robin indexer for pushing DL PDUs to attached UEs. + unsigned f1u_dl_rr_count = 0; + // Sum of total F1-U DL bytes pushed into DU. + std::atomic f1u_dl_total_bytes{0}; }; /// \brief Generate custom cell configuration builder params based on duplex mode. diff --git a/tests/integrationtests/du_high/du_high_many_cells_test.cpp b/tests/integrationtests/du_high/du_high_many_cells_test.cpp index a32a796672..39ef3e54a1 100644 --- a/tests/integrationtests/du_high/du_high_many_cells_test.cpp +++ b/tests/integrationtests/du_high/du_high_many_cells_test.cpp @@ -104,6 +104,7 @@ TEST_P(du_high_many_cells_tester, when_ue_created_in_multiple_cells_then_traffic rnti_t rnti = to_rnti(0x4601 + i); ASSERT_TRUE(add_ue(rnti, to_du_cell_index(i))); ASSERT_TRUE(run_rrc_setup(rnti)); + ASSERT_TRUE(force_ue_fallback(rnti)); ASSERT_TRUE(run_ue_context_setup(rnti)); // Ensure DU<->CU-UP tunnel was created. @@ -126,6 +127,7 @@ TEST_P(du_high_many_cells_tester, when_ue_created_in_multiple_cells_then_traffic for (unsigned i = 0; i != GetParam().nof_cells; ++i) { phy.cells[i].last_dl_data.reset(); } + while (bytes_sched < expected_bytes_sched and this->run_until([this]() { for (unsigned i = 0; i != du_high_cfg.cells.size(); ++i) { if (phy.cells[i].last_dl_data.has_value() and not phy.cells[i].last_dl_data.value().ue_pdus.empty()) { @@ -153,11 +155,15 @@ TEST_P(du_high_many_cells_tester, when_ue_created_in_multiple_cells_then_traffic phy.cells[c].last_dl_data.reset(); } } + ASSERT_GE(bytes_sched, expected_bytes_sched) - << "Not enough PDSCH grants were scheduled to meet the enqueued PDCP PDUs"; + << fmt::format("Not enough PDSCH grants (bytes={}) were scheduled to meet the enqueued PDCP PDUs (bytes={})", + bytes_sched, + expected_bytes_sched); for (unsigned c = 0; c != du_high_cfg.cells.size(); ++c) { - ASSERT_GE(bytes_sched_per_cell[c], nof_pdcp_pdus * pdcp_pdu_size); + ASSERT_GE(bytes_sched_per_cell[c], nof_pdcp_pdus * pdcp_pdu_size) << fmt::format( + "In cell={} scheduled bytes {} < expected bytes {}", c, bytes_sched_per_cell[c], nof_pdcp_pdus * pdcp_pdu_size); } } diff --git a/tests/integrationtests/du_high/du_high_test.cpp b/tests/integrationtests/du_high/du_high_test.cpp index bfef20f3f8..ab58cbf0b7 100644 --- a/tests/integrationtests/du_high/du_high_test.cpp +++ b/tests/integrationtests/du_high/du_high_test.cpp @@ -82,6 +82,7 @@ TEST_F(du_high_tester, when_ue_context_setup_completes_then_drb_is_active) rnti_t rnti = to_rnti(0x4601); ASSERT_TRUE(add_ue(rnti)); ASSERT_TRUE(run_rrc_setup(rnti)); + ASSERT_TRUE(force_ue_fallback(rnti)); ASSERT_TRUE(run_ue_context_setup(rnti)); // Ensure DU<->CU-UP tunnel was created. diff --git a/tests/integrationtests/du_high/test_utils/du_high_env_simulator.cpp b/tests/integrationtests/du_high/test_utils/du_high_env_simulator.cpp index 8f4d1e0d74..936ee41410 100644 --- a/tests/integrationtests/du_high/test_utils/du_high_env_simulator.cpp +++ b/tests/integrationtests/du_high/test_utils/du_high_env_simulator.cpp @@ -289,19 +289,6 @@ bool du_high_env_simulator::run_rrc_setup(rnti_t rnti) run_slot(); } - // Wait for CSI for UE to leave fallback mode. - // TODO. For now we just bypass by sending the RRC Setup Complete. - if (not run_until([&]() { - if (phy_cell.last_ul_res.has_value() and phy_cell.last_ul_res.value().ul_res != nullptr) { - if (find_ue_pucch_with_csi(rnti, phy_cell.last_ul_res.value().ul_res->pucchs) != nullptr) { - return true; - } - } - return false; - })) { - return false; - } - // UE sends RRC Setup Complete. Wait until F1AP forwards UL RRC Message to CU-CP. cu_notifier.last_f1ap_msgs.clear(); du_hi->get_pdu_handler().handle_rx_data_indication( @@ -380,6 +367,64 @@ bool du_high_env_simulator::run_ue_context_setup(rnti_t rnti) return true; } +bool du_high_env_simulator::force_ue_fallback(rnti_t rnti) +{ + auto it = ues.find(rnti); + if (it == ues.end()) { + return false; + } + const ue_sim_context& u = it->second; + const auto& phy_cell = phy.cells[u.pcell_index]; + + // For the UE to transition to non-fallback mode, the GNB needs to receive either an SR or CSI pkus then 2 CRC = OK. + // In the following, we force 2 SRs, which in turn will 2 PUSCH. We also need to force 2 CRC=OK corresponding to each + // of the PUSCH. + for (unsigned crc_cnt = 0; crc_cnt != 2; ++crc_cnt) { + const unsigned max_slot_count = 100; + // Run until the slot the SR PUCCH is scheduled for. + optional slot_sr = nullopt; + for (unsigned count = 0; count != max_slot_count; ++count) { + const bool found_sr = phy_cell.last_ul_res.has_value() and phy_cell.last_ul_res.value().ul_res != nullptr and + find_ue_pucch_with_sr(rnti, phy_cell.last_ul_res.value().ul_res->pucchs) != nullptr; + if (found_sr) { + slot_sr = next_slot; + break; + } + run_slot(); + } + + // Enforce an UCI indication for the SR; this will trigger the SRB1 fallback scheduler to allocate a PUSCH grant. + if (slot_sr.has_value()) { + static_vector pucchs{ + pucch_info{.crnti = rnti, + .format = pucch_format::FORMAT_1, + .format_1 = {.sr_bits = sr_nof_bits::one, .harq_ack_nof_bits = 0}}}; + mac_uci_indication_message uci_ind = test_helpers::create_uci_indication(*slot_sr, pucchs); + } else { + return false; + } + + // Search for the PUSCH grant and force a CRC indication with OK. + for (unsigned count = 0; count != max_slot_count; ++count) { + const ul_sched_info* pusch = nullptr; + if (phy_cell.last_ul_res.has_value() and phy_cell.last_ul_res.value().ul_res != nullptr) { + pusch = find_ue_pusch(rnti, phy_cell.last_ul_res.value().ul_res->puschs); + if (pusch != nullptr) { + du_hi->get_control_info_handler(it->second.pcell_index) + .handle_crc(test_helpers::create_crc_indication( + phy_cell.last_ul_res->slot, rnti, to_harq_id(pusch->pusch_cfg.harq_id))); + if (crc_cnt == 1) { + return true; + } + break; + } + } + run_slot(); + } + } + return false; +} + void du_high_env_simulator::run_slot() { for (unsigned i = 0; i != du_high_cfg.cells.size(); ++i) { diff --git a/tests/integrationtests/du_high/test_utils/du_high_env_simulator.h b/tests/integrationtests/du_high/test_utils/du_high_env_simulator.h index 6021516f21..8f5e7160a9 100644 --- a/tests/integrationtests/du_high/test_utils/du_high_env_simulator.h +++ b/tests/integrationtests/du_high/test_utils/du_high_env_simulator.h @@ -71,6 +71,8 @@ class du_high_env_simulator bool run_ue_context_setup(rnti_t rnti); + bool force_ue_fallback(rnti_t rnti); + void run_slot(); bool run_until(unique_function condition, unsigned max_slot_count = 1000); diff --git a/tests/test_doubles/f1ap/f1ap_test_message_validators.cpp b/tests/test_doubles/f1ap/f1ap_test_message_validators.cpp index cc43949e9d..e83783dcec 100644 --- a/tests/test_doubles/f1ap/f1ap_test_message_validators.cpp +++ b/tests/test_doubles/f1ap/f1ap_test_message_validators.cpp @@ -132,3 +132,12 @@ bool srsran::test_helpers::is_valid_ue_context_modification_request(const f1ap_m return true; } + +bool srsran::test_helpers::is_valid_ue_context_release_command(const f1ap_message& msg) +{ + TRUE_OR_RETURN(msg.pdu.type() == asn1::f1ap::f1ap_pdu_c::types_opts::init_msg); + TRUE_OR_RETURN(msg.pdu.init_msg().proc_code == ASN1_F1AP_ID_UE_CONTEXT_RELEASE); + TRUE_OR_RETURN(is_packable(msg)); + + return true; +} diff --git a/tests/test_doubles/f1ap/f1ap_test_message_validators.h b/tests/test_doubles/f1ap/f1ap_test_message_validators.h index 76214b51bb..a17c6b91d5 100644 --- a/tests/test_doubles/f1ap/f1ap_test_message_validators.h +++ b/tests/test_doubles/f1ap/f1ap_test_message_validators.h @@ -50,5 +50,7 @@ bool is_ue_context_setup_response_valid(const f1ap_message& msg); bool is_valid_ue_context_modification_request(const f1ap_message& msg); +bool is_valid_ue_context_release_command(const f1ap_message& msg); + } // namespace test_helpers } // namespace srsran \ No newline at end of file diff --git a/tests/test_doubles/mac/mac_test_messages.cpp b/tests/test_doubles/mac/mac_test_messages.cpp index 8703b27807..81b0f48a98 100644 --- a/tests/test_doubles/mac/mac_test_messages.cpp +++ b/tests/test_doubles/mac/mac_test_messages.cpp @@ -52,6 +52,12 @@ mac_rx_data_indication srsran::test_helpers::create_pdu_with_sdu(slot_point sl_r .value()}}}; } +mac_crc_indication_message srsran::test_helpers::create_crc_indication(slot_point sl_rx, rnti_t rnti, harq_id_t h_id) +{ + return mac_crc_indication_message{.sl_rx = sl_rx, + .crcs = {mac_crc_pdu{.rnti = rnti, .harq_id = h_id, .tb_crc_success = true}}}; +} + mac_uci_pdu srsran::test_helpers::create_uci_pdu(const pucch_info& pucch) { mac_uci_pdu pdu{}; @@ -70,7 +76,7 @@ mac_uci_pdu srsran::test_helpers::create_uci_pdu(const pucch_info& pucch) if (pucch_f1.sr_bits != sr_nof_bits::no_sr) { uci_f1.sr_info.emplace(); - uci_f1.sr_info->detected = false; + uci_f1.sr_info->detected = true; } } break; case pucch_format::FORMAT_2: { diff --git a/tests/test_doubles/mac/mac_test_messages.h b/tests/test_doubles/mac/mac_test_messages.h index e6f44f55b2..382b623243 100644 --- a/tests/test_doubles/mac/mac_test_messages.h +++ b/tests/test_doubles/mac/mac_test_messages.h @@ -24,6 +24,7 @@ #include "srsran/mac/mac_cell_control_information_handler.h" #include "srsran/mac/mac_pdu_handler.h" +#include "srsran/scheduler/harq_id.h" namespace srsran { @@ -43,5 +44,7 @@ mac_uci_pdu create_uci_pdu(const pucch_info& pucch); mac_uci_indication_message create_uci_indication(slot_point sl_rx, span pucchs); +mac_crc_indication_message create_crc_indication(slot_point sl_rx, rnti_t rnti, harq_id_t h_id); + } // namespace test_helpers -} // namespace srsran \ No newline at end of file +} // namespace srsran diff --git a/tests/unittests/cu_cp/cu_cp_reestablishment_test.cpp b/tests/unittests/cu_cp/cu_cp_reestablishment_test.cpp index cde453b25f..6b868917d9 100644 --- a/tests/unittests/cu_cp/cu_cp_reestablishment_test.cpp +++ b/tests/unittests/cu_cp/cu_cp_reestablishment_test.cpp @@ -53,7 +53,10 @@ class cu_cp_reestablishment_test : public cu_cp_test_environment, public ::testi /// Run RRC Reestablishment. /// \return Returns true if Reestablishment is successful, and false if the gNB fallbacked to RRC Setup. - bool run_rrc_reestablishment(gnb_du_ue_f1ap_id_t new_du_ue_id, rnti_t new_rnti, rnti_t old_rnti_, pci_t old_pci_) + bool send_rrc_reest_request_and_wait_response(gnb_du_ue_f1ap_id_t new_du_ue_id, + rnti_t new_rnti, + rnti_t old_rnti_, + pci_t old_pci_) { // Generate RRC Reestablishment Request. byte_buffer rrc_container = @@ -79,6 +82,34 @@ class cu_cp_reestablishment_test : public cu_cp_test_environment, public ::testi return dl_rrc_msg.srb_id == 1; } + optional ue_sends_rrc_setup_request_and_waits_rrc_setup(gnb_du_ue_f1ap_id_t du_ue_id, rnti_t crnti) + { + ngap_message ngap_pdu; + srsran_assert(not this->get_amf().try_pop_rx_pdu(ngap_pdu), "there are still NGAP messages to pop from AMF"); + f1ap_message f1ap_pdu; + srsran_assert(not this->get_du(du_idx).try_pop_dl_pdu(f1ap_pdu), "there are still F1AP DL messages to pop from DU"); + + // Inject Initial UL RRC message + f1ap_message init_ul_rrc_msg = generate_init_ul_rrc_message_transfer(du_ue_id, crnti); + test_logger.info("c-rnti={} du_ue_id={}: Injecting Initial UL RRC message", crnti, du_ue_id); + get_du(du_idx).push_ul_pdu(init_ul_rrc_msg); + + // Wait for DL RRC message transfer (containing RRC Setup) + bool result = this->wait_for_f1ap_tx_pdu(du_idx, f1ap_pdu, std::chrono::milliseconds{1000}); + if (not result) { + return nullopt; + } + + // Check if the DL RRC Message with Msg4 is valid. + report_error_if_not(test_helpers::is_valid_dl_rrc_message_transfer_with_msg4(f1ap_pdu), "invalid DL RRC message"); + dl_rrc_msg_transfer_s& dl_rrc_msg = f1ap_pdu.pdu.init_msg().value.dl_rrc_msg_transfer(); + report_error_if_not(int_to_gnb_du_ue_f1ap_id(dl_rrc_msg->gnb_du_ue_f1ap_id) == du_ue_id, + "invalid gNB-DU-UE-F1AP-ID"); + report_error_if_not(int_to_srb_id(dl_rrc_msg->srb_id) == srb_id_t::srb0, "invalid SRB-Id"); + + return f1ap_pdu; + } + void ue_sends_rrc_setup_complete(gnb_du_ue_f1ap_id_t du_ue_id, gnb_cu_ue_f1ap_id_t cu_ue_id) { // Generate RRC Setup Complete. @@ -123,7 +154,8 @@ TEST_F(cu_cp_reestablishment_test, when_old_ue_does_not_exist_then_reestablishme EXPECT_TRUE(connect_new_ue(du_idx, old_du_ue_id, old_crnti)); // Reestablishment Request to RNTI that does not exist. - ASSERT_FALSE(run_rrc_reestablishment(int_to_gnb_du_ue_f1ap_id(1), to_rnti(0x4602), to_rnti(0x4603), old_pci)) + ASSERT_FALSE( + send_rrc_reest_request_and_wait_response(int_to_gnb_du_ue_f1ap_id(1), to_rnti(0x4602), to_rnti(0x4603), old_pci)) << "RRC setup should have been sent"; // UE sends RRC Setup Complete @@ -139,7 +171,8 @@ TEST_F(cu_cp_reestablishment_test, when_old_ue_has_no_ngap_context_then_reestabl // Connect UE 0x4601. EXPECT_TRUE(connect_new_ue(du_idx, old_du_ue_id, old_crnti)); - ASSERT_FALSE(run_rrc_reestablishment(int_to_gnb_du_ue_f1ap_id(1), to_rnti(0x4602), old_crnti, old_pci)) + ASSERT_FALSE( + send_rrc_reest_request_and_wait_response(int_to_gnb_du_ue_f1ap_id(1), to_rnti(0x4602), old_crnti, old_pci)) << "RRC setup should have been sent"; // old UE should not be removed at this stage. @@ -202,3 +235,45 @@ TEST_F(cu_cp_reestablishment_test, auto report = this->get_cu_cp().get_metrics_handler().request_metrics_report(); ASSERT_EQ(report.ues.size(), 1) << "Old UE should not be removed yet"; } + +TEST_F(cu_cp_reestablishment_test, when_old_ue_is_busy_with_a_procedure_then_reestablishment_fallback_still_completes) +{ + // UE 0x4601 sends RRC Setup Request, the gNB responds with RRC Setup and waits for RRC Setup Complete. + auto msg = this->ue_sends_rrc_setup_request_and_waits_rrc_setup(old_du_ue_id, old_crnti); + ASSERT_TRUE(msg.has_value()); + + // Send RRC Reestablishment Request and DU receives RRC Setup (Fallback). + gnb_du_ue_f1ap_id_t new_du_ue_id = int_to_gnb_du_ue_f1ap_id(1); + rnti_t new_crnti = to_rnti(0x4602); + ASSERT_FALSE(send_rrc_reest_request_and_wait_response(new_du_ue_id, new_crnti, old_crnti, old_pci)); + + // EVENT: new UE sends RRC Setup Complete and completes fallback procedure. + gnb_cu_ue_f1ap_id_t cu_ue_id = int_to_gnb_cu_ue_f1ap_id(1); + this->ue_sends_rrc_setup_complete(new_du_ue_id, cu_ue_id); + + // STATUS: NGAP Initial UE Message should be sent for the new UE. + ngap_message ngap_pdu; + ASSERT_TRUE(this->wait_for_ngap_tx_pdu(ngap_pdu)); + ASSERT_TRUE(test_helpers::is_valid_init_ue_message(ngap_pdu)); + + // STATUS: UE Context Released Command for old UE not yet sent because the old UE is busy with an RRC Setup procedure. + f1ap_message f1ap_pdu; + ASSERT_FALSE(this->get_du(du_idx).try_pop_dl_pdu(f1ap_pdu)) << "UE Context Release Command sent to soon"; + + // RRC Setup timeout for old UE. + std::chrono::milliseconds timeout{this->get_cu_cp_cfg().rrc_config.rrc_procedure_timeout_ms}; + for (unsigned i = 0; i != timeout.count(); ++i) { + this->tick(); + } + + // STATUS: CU-CP sends F1AP UE Context Release Command for old UE after the previous procedure times out. + ASSERT_TRUE(this->wait_for_f1ap_tx_pdu(du_idx, f1ap_pdu)); + ASSERT_TRUE(test_helpers::is_valid_ue_context_release_command(f1ap_pdu)); + auto report = this->get_cu_cp().get_metrics_handler().request_metrics_report(); + ASSERT_EQ(report.ues.size(), 2) << "Old UE should not be removed yet as its RRC Setup procedure is not completed"; + + // STATUS: UE Context Release Complete for old UE and it should be finally removed. + this->get_du(du_idx).push_ul_pdu(test_helpers::generate_ue_context_release_complete(f1ap_pdu)); + report = this->get_cu_cp().get_metrics_handler().request_metrics_report(); + ASSERT_EQ(report.ues.size(), 1) << "Old UE was not removed"; +} diff --git a/tests/unittests/cu_cp/cu_cp_test_environment.cpp b/tests/unittests/cu_cp/cu_cp_test_environment.cpp index 86ce14c332..586a69fbba 100644 --- a/tests/unittests/cu_cp/cu_cp_test_environment.cpp +++ b/tests/unittests/cu_cp/cu_cp_test_environment.cpp @@ -75,28 +75,28 @@ cu_cp_test_environment::cu_cp_test_environment(cu_cp_test_env_params params_) : srslog::init(); // create CU-CP config - cu_cp_configuration cfg = config_helpers::make_default_cu_cp_config(); - cfg.cu_cp_executor = cu_cp_workers->exec.get(); - cfg.ngap_notifier = &*amf_stub; - cfg.timers = &timers; - cfg.ngap_config = config_helpers::make_default_ngap_config(); - cfg.max_nof_dus = params.max_nof_dus; - cfg.max_nof_cu_ups = params.max_nof_cu_ups; - cfg.ue_config.max_nof_supported_ues = params.max_nof_dus * MAX_NOF_UES_PER_DU; + cu_cp_cfg = config_helpers::make_default_cu_cp_config(); + cu_cp_cfg.cu_cp_executor = cu_cp_workers->exec.get(); + cu_cp_cfg.ngap_notifier = &*amf_stub; + cu_cp_cfg.timers = &timers; + cu_cp_cfg.ngap_config = config_helpers::make_default_ngap_config(); + cu_cp_cfg.max_nof_dus = params.max_nof_dus; + cu_cp_cfg.max_nof_cu_ups = params.max_nof_cu_ups; + cu_cp_cfg.ue_config.max_nof_supported_ues = params.max_nof_dus * MAX_NOF_UES_PER_DU; // > RRC config. - cfg.rrc_config.gnb_id = cfg.ngap_config.gnb_id; - cfg.rrc_config.drb_config = config_helpers::make_default_cu_cp_qos_config_list(); - cfg.rrc_config.int_algo_pref_list = {security::integrity_algorithm::nia2, - security::integrity_algorithm::nia1, - security::integrity_algorithm::nia3, - security::integrity_algorithm::nia0}; - cfg.rrc_config.enc_algo_pref_list = {security::ciphering_algorithm::nea0, - security::ciphering_algorithm::nea2, - security::ciphering_algorithm::nea1, - security::ciphering_algorithm::nea3}; + cu_cp_cfg.rrc_config.gnb_id = cu_cp_cfg.ngap_config.gnb_id; + cu_cp_cfg.rrc_config.drb_config = config_helpers::make_default_cu_cp_qos_config_list(); + cu_cp_cfg.rrc_config.int_algo_pref_list = {security::integrity_algorithm::nia2, + security::integrity_algorithm::nia1, + security::integrity_algorithm::nia3, + security::integrity_algorithm::nia0}; + cu_cp_cfg.rrc_config.enc_algo_pref_list = {security::ciphering_algorithm::nea0, + security::ciphering_algorithm::nea2, + security::ciphering_algorithm::nea1, + security::ciphering_algorithm::nea3}; // create CU-CP instance. - cu_cp_inst = create_cu_cp(cfg); + cu_cp_inst = create_cu_cp(cu_cp_cfg); // Pass CU-CP PDU handler to AMF. amf_stub->attach_cu_cp_pdu_handler(cu_cp_inst->get_ng_handler().get_ngap_message_handler()); @@ -108,6 +108,8 @@ cu_cp_test_environment::~cu_cp_test_environment() cu_ups.clear(); cu_cp_inst->stop(); cu_cp_workers->stop(); + + srslog::flush(); } void cu_cp_test_environment::tick() @@ -266,7 +268,7 @@ bool cu_cp_test_environment::connect_new_ue(unsigned du_idx, gnb_du_ue_f1ap_id_t // Inject Initial UL RRC message f1ap_message init_ul_rrc_msg = generate_init_ul_rrc_message_transfer(du_ue_id, crnti); - test_logger.info("c-rnti={} du_ue_id={}: Injecting Initial UL RRC message", crnti, du_ue_id); + test_logger.info("c-rnti={} du_ue={}: Injecting Initial UL RRC message", crnti, du_ue_id); get_du(du_idx).push_ul_pdu(init_ul_rrc_msg); // Wait for DL RRC message transfer (containing RRC Setup) @@ -527,6 +529,8 @@ bool cu_cp_test_environment::reestablish_ue(unsigned du_idx, rnti_t old_crnti, pci_t old_pci) { + f1ap_message f1ap_pdu; + // Send Initial UL RRC Message (containing RRC Reestablishment Request) to CU-CP. byte_buffer rrc_container = pack_ul_ccch_msg(create_rrc_reestablishment_request(old_crnti, old_pci, "0011000101110000")); @@ -535,8 +539,7 @@ bool cu_cp_test_environment::reestablish_ue(unsigned du_idx, get_du(du_idx).push_ul_pdu(f1ap_init_ul_rrc_msg); // Wait for DL RRC message transfer (with RRC Reestablishment / RRC Setup / RRC Reject). - f1ap_message f1ap_pdu; - bool result = this->wait_for_f1ap_tx_pdu(du_idx, f1ap_pdu); + bool result = this->wait_for_f1ap_tx_pdu(du_idx, f1ap_pdu); report_fatal_error_if_not(result, "F1AP DL RRC Message Transfer with Msg4 not sent to DU"); report_fatal_error_if_not(test_helpers::is_valid_dl_rrc_message_transfer_with_msg4(f1ap_pdu), "Invalid Msg4"); diff --git a/tests/unittests/cu_cp/cu_cp_test_environment.h b/tests/unittests/cu_cp/cu_cp_test_environment.h index 5d21286c72..1fb5886c14 100644 --- a/tests/unittests/cu_cp/cu_cp_test_environment.h +++ b/tests/unittests/cu_cp/cu_cp_test_environment.h @@ -26,6 +26,7 @@ #include "test_doubles/mock_cu_up.h" #include "test_doubles/mock_du.h" #include "srsran/cu_cp/cu_cp.h" +#include "srsran/cu_cp/cu_cp_configuration.h" #include "srsran/ngap/ngap_configuration.h" #include "srsran/ngap/ngap_configuration_helpers.h" #include @@ -113,10 +114,14 @@ class cu_cp_test_environment const ue_context* find_ue_context(unsigned du_idx, gnb_du_ue_f1ap_id_t du_ue_id) const; + /// Get CU-CP configuration used to instantiate CU-CP. + const cu_cp_configuration& get_cu_cp_cfg() const { return cu_cp_cfg; } + private: class worker_manager; cu_cp_test_env_params params; + cu_cp_configuration cu_cp_cfg{}; /// Workers for CU-CP. std::unique_ptr cu_cp_workers; diff --git a/tests/unittests/cu_cp/du_processor/du_processor_test.cpp b/tests/unittests/cu_cp/du_processor/du_processor_test.cpp index 0037c2c5bc..913da87932 100644 --- a/tests/unittests/cu_cp/du_processor/du_processor_test.cpp +++ b/tests/unittests/cu_cp/du_processor/du_processor_test.cpp @@ -219,7 +219,7 @@ TEST_F(du_processor_test, when_ue_context_release_command_received_then_ue_delet cu_cp_ue_context_release_command ue_context_release_command = generate_ue_context_release_command(ue_index); // Pass message to DU processor - t = du_processor_obj->get_du_processor_rrc_ue_interface().handle_ue_context_release_command( + t = du_processor_obj->get_du_processor_ue_context_notifier().handle_ue_context_release_command( ue_context_release_command); t_launcher.emplace(t); @@ -274,7 +274,7 @@ TEST_F(du_processor_test, when_valid_ue_creation_request_received_after_ue_was_r cu_cp_ue_context_release_command ue_context_release_command = generate_ue_context_release_command(ue_index_t::min); // Pass message to DU processor - t = du_processor_obj->get_du_processor_rrc_ue_interface().handle_ue_context_release_command( + t = du_processor_obj->get_du_processor_ue_context_notifier().handle_ue_context_release_command( ue_context_release_command); t_launcher.emplace(t); diff --git a/tests/unittests/cu_cp/test_helpers.h b/tests/unittests/cu_cp/test_helpers.h index 00f000f673..25b1130c67 100644 --- a/tests/unittests/cu_cp/test_helpers.h +++ b/tests/unittests/cu_cp/test_helpers.h @@ -132,9 +132,19 @@ struct dummy_du_processor_cu_cp_notifier : public du_processor_cu_cp_notifier { }); } + async_task on_ue_release_required(const cu_cp_ue_context_release_request& request) override + { + logger.info("ue={}: Received UE release request", request.ue_index); + + return launch_async([](coro_context>& ctx) mutable { + CORO_BEGIN(ctx); + CORO_RETURN(); + }); + } + async_task on_ue_transfer_required(ue_index_t ue_index, ue_index_t old_ue_index) override { - logger.info("Received UE transfer required"); + logger.info("ue={} old_ue={}: Received UE transfer required", ue_index, old_ue_index); return launch_async([this](coro_context>& ctx) mutable { CORO_BEGIN(ctx); @@ -144,7 +154,7 @@ struct dummy_du_processor_cu_cp_notifier : public du_processor_cu_cp_notifier { void on_handover_ue_context_push(ue_index_t source_ue_index, ue_index_t target_ue_index) override { - logger.info("Received handover ue context push"); + logger.info("source_ue={} target_ue={}: Received handover ue context push", source_ue_index, target_ue_index); } private: diff --git a/tests/unittests/cu_up/pdu_session_manager_test.cpp b/tests/unittests/cu_up/pdu_session_manager_test.cpp index 5908dde0bb..a78f8c493d 100644 --- a/tests/unittests/cu_up/pdu_session_manager_test.cpp +++ b/tests/unittests/cu_up/pdu_session_manager_test.cpp @@ -28,7 +28,7 @@ using namespace srsran; using namespace srs_cu_up; /// PDU session handling tests (creation/deletion) -TEST_F(pdu_session_manager_test, when_valid_pdu_session_setup_item_session_can_be_added) +TEST_P(pdu_session_manager_test_set_n3_ext_addr, when_valid_pdu_session_setup_item_session_can_be_added) { // no sessions added yet ASSERT_EQ(pdu_session_mng->get_nof_pdu_sessions(), 0); @@ -47,6 +47,10 @@ TEST_F(pdu_session_manager_test, when_valid_pdu_session_setup_item_session_can_b // check successful outcome ASSERT_TRUE(setup_result.success); ASSERT_EQ(setup_result.gtp_tunnel.gtp_teid.value(), 1); + const std::string tp_address_expect = net_config.n3_ext_addr.empty() || net_config.n3_ext_addr == "auto" + ? net_config.n3_bind_addr + : net_config.n3_ext_addr; + ASSERT_EQ(setup_result.gtp_tunnel.tp_address.to_string(), tp_address_expect); ASSERT_EQ(setup_result.drb_setup_results[0].gtp_tunnel.gtp_teid.value(), 0); ASSERT_EQ(pdu_session_mng->get_nof_pdu_sessions(), 1); @@ -399,6 +403,10 @@ TEST_F(pdu_session_manager_test, when_new_ul_info_is_requested_f1u_is_disconnect ASSERT_EQ(pdu_session_mng->get_nof_pdu_sessions(), 1); } +INSTANTIATE_TEST_SUITE_P(pdu_session_manager_test_n3_ext_addr, + pdu_session_manager_test_set_n3_ext_addr, + ::testing::Values("", "auto", "1.2.3.4")); + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); diff --git a/tests/unittests/cu_up/pdu_session_manager_test.h b/tests/unittests/cu_up/pdu_session_manager_test.h index 32ceff87b5..5f14c2e5ec 100644 --- a/tests/unittests/cu_up/pdu_session_manager_test.h +++ b/tests/unittests/cu_up/pdu_session_manager_test.h @@ -30,11 +30,16 @@ using namespace srsran; using namespace srs_cu_up; -/// Fixture class for UE manager tests -class pdu_session_manager_test : public ::testing::Test +const network_interface_config net_config_default = {}; + +/// Fixture base class for PDU session manager tests +class pdu_session_manager_test_base { protected: - void SetUp() override + virtual ~pdu_session_manager_test_base() = default; + virtual network_interface_config get_net_config() = 0; + + void init() { srslog::fetch_basic_logger("TEST").set_level(srslog::basic_levels::debug); srslog::init(); @@ -54,6 +59,7 @@ class pdu_session_manager_test : public ::testing::Test manual_task_worker teid_worker{128}; + net_config = get_net_config(); pdu_session_mng = std::make_unique(MIN_UE_INDEX, qos, security_info, @@ -75,7 +81,7 @@ class pdu_session_manager_test : public ::testing::Test gtpu_pcap); } - void TearDown() override + void finish() { // flush logger after each test srslog::flush(); @@ -98,6 +104,30 @@ class pdu_session_manager_test : public ::testing::Test cu_up_ue_logger logger{"CU-UP", {MIN_UE_INDEX}}; }; +/// Fixture class for PDU session manager tests with default network interface config +class pdu_session_manager_test : public pdu_session_manager_test_base, public ::testing::Test +{ +protected: + network_interface_config get_net_config() override { return net_config_default; } + void SetUp() override { init(); } + void TearDown() override { finish(); } +}; + +/// Fixture class for PDU session manager tests with configurable N3 ext addr +class pdu_session_manager_test_set_n3_ext_addr : public pdu_session_manager_test_base, + public ::testing::TestWithParam +{ +protected: + network_interface_config get_net_config() override + { + network_interface_config cfg = net_config_default; + cfg.n3_ext_addr = GetParam(); + return cfg; + } + void SetUp() override { init(); } + void TearDown() override { finish(); } +}; + inline e1ap_pdu_session_res_to_setup_item generate_pdu_session_res_to_setup_item(pdu_session_id_t psi, drb_id_t drb_id, qos_flow_id_t qfi, five_qi_t five_qi) { diff --git a/tests/unittests/f1ap/cu_cp/f1ap_cu_test_helpers.cpp b/tests/unittests/f1ap/cu_cp/f1ap_cu_test_helpers.cpp index 46c2c17e9a..39671a3707 100644 --- a/tests/unittests/f1ap/cu_cp/f1ap_cu_test_helpers.cpp +++ b/tests/unittests/f1ap/cu_cp/f1ap_cu_test_helpers.cpp @@ -43,7 +43,11 @@ f1ap_cu_test::f1ap_cu_test(const f1ap_configuration& f1ap_cfg) f1ap_logger.set_level(srslog::basic_levels::debug); srslog::init(); - f1ap = create_f1ap(f1ap_cfg, f1ap_pdu_notifier, du_processor_notifier, f1ap_du_mgmt_notifier, timers, ctrl_worker); + // We enable Json logging by default for the purpose of testing. + f1ap_configuration tmp = f1ap_cfg; + tmp.json_log_enabled = true; + + f1ap = create_f1ap(tmp, f1ap_pdu_notifier, du_processor_notifier, f1ap_du_mgmt_notifier, timers, ctrl_worker); } f1ap_cu_test::~f1ap_cu_test() diff --git a/tests/unittests/f1ap/du/CMakeLists.txt b/tests/unittests/f1ap/du/CMakeLists.txt index 0a3212c204..0998078bef 100644 --- a/tests/unittests/f1ap/du/CMakeLists.txt +++ b/tests/unittests/f1ap/du/CMakeLists.txt @@ -33,6 +33,7 @@ set(SOURCES f1ap_du_setup_procedure_test.cpp add_executable(f1ap_du_test ${SOURCES}) target_include_directories(f1ap_du_test PRIVATE ${CMAKE_SOURCE_DIR}) +set_target_properties(f1ap_du_test PROPERTIES UNITY_BUILD ON) target_link_libraries(f1ap_du_test srsran_f1ap_du srsran_support @@ -42,4 +43,4 @@ target_link_libraries(f1ap_du_test f1ap_du_test_helpers gtest gtest_main) -gtest_discover_tests(f1ap_du_test PROPERTIES "LABELS;f1ap_du") +gtest_discover_tests(f1ap_du_test PROPERTIES "LABELS;f1ap;f1ap_du") diff --git a/tests/unittests/f1ap/du/f1ap_du_ue_context_modification_test.cpp b/tests/unittests/f1ap/du/f1ap_du_ue_context_modification_test.cpp index 1c3e6a481b..b7379c0c3e 100644 --- a/tests/unittests/f1ap/du/f1ap_du_ue_context_modification_test.cpp +++ b/tests/unittests/f1ap/du/f1ap_du_ue_context_modification_test.cpp @@ -39,7 +39,7 @@ class f1ap_du_ue_context_modification_test : public f1ap_du_test run_ue_context_setup_procedure(test_ue_index, generate_ue_context_setup_request({})); } - void start_procedure(const std::initializer_list& drbs) + void start_procedure(const std::initializer_list& drbs, byte_buffer rrc_container = {}) { // Prepare DU manager response to F1AP. this->f1ap_du_cfg_handler.next_ue_context_update_response.result = true; @@ -56,6 +56,11 @@ class f1ap_du_ue_context_modification_test : public f1ap_du_test // Initiate procedure in F1AP. f1ap_message msg = generate_ue_context_modification_request(drbs); + if (not rrc_container.empty()) { + // If specified, add RRC container. + msg.pdu.init_msg().value.ue_context_mod_request()->rrc_container_present = true; + msg.pdu.init_msg().value.ue_context_mod_request()->rrc_container = std::move(rrc_container); + } f1ap->handle_message(msg); } @@ -127,7 +132,7 @@ TEST_F(f1ap_du_ue_context_modification_test, f1ap_message msg = generate_ue_context_modification_request({drb_id_t::drb1}); f1ap->handle_message(msg); - // F1AP sends UE CONTEXT SETUP RESPONSE to CU-CP with failed DRB. + // F1AP sends UE CONTEXT MODIFICATION RESPONSE to CU-CP with failed DRB. ASSERT_TRUE(was_ue_context_modification_response_sent()); ue_context_mod_resp_s& resp = this->f1c_gw.last_tx_f1ap_pdu.pdu.successful_outcome().value.ue_context_mod_resp(); ASSERT_FALSE(resp->srbs_failed_to_be_setup_mod_list_present); @@ -149,7 +154,7 @@ TEST_F(f1ap_du_ue_context_modification_test, { start_procedure({drb_id_t::drb1, drb_id_t::drb2}); - // F1AP sends UE CONTEXT SETUP RESPONSE to CU-CP. + // F1AP sends UE CONTEXT MODIFICATION RESPONSE to CU-CP. ASSERT_TRUE(was_ue_context_modification_response_sent()); ue_context_mod_resp_s& resp = this->f1c_gw.last_tx_f1ap_pdu.pdu.successful_outcome().value.ue_context_mod_resp(); ASSERT_FALSE(resp->srbs_setup_mod_list_present); @@ -165,3 +170,18 @@ TEST_F(f1ap_du_ue_context_modification_test, ASSERT_EQ(drb2_setup.drb_id, 2); ASSERT_EQ(drb2_setup.dl_up_tnl_info_to_be_setup_list.size(), 1); } + +TEST_F(f1ap_du_ue_context_modification_test, + when_ue_context_mod_req_contains_rrc_container_then_rrc_container_is_sent_to_lower_layers) +{ + byte_buffer rrc_container = byte_buffer::create(test_rgen::random_vector(100)).value(); + start_procedure({drb_id_t::drb1}, rrc_container.copy()); + + // F1AP sends UE CONTEXT MODIFICATION RESPONSE to CU-CP. + ASSERT_TRUE(was_ue_context_modification_response_sent()); + ue_context_mod_resp_s& resp = this->f1c_gw.last_tx_f1ap_pdu.pdu.successful_outcome().value.ue_context_mod_resp(); + ASSERT_TRUE(resp->drbs_setup_mod_list_present); + + // Check if RRC container is sent to lower layers. + ASSERT_EQ(this->test_ues[test_ue_index].f1c_bearers[1].rx_sdu_notifier.last_pdu, rrc_container); +} diff --git a/tests/unittests/ngap/CMakeLists.txt b/tests/unittests/ngap/CMakeLists.txt index 8b594a061e..8d5d1b8b90 100644 --- a/tests/unittests/ngap/CMakeLists.txt +++ b/tests/unittests/ngap/CMakeLists.txt @@ -38,5 +38,6 @@ set(SOURCES add_executable(ngap_test ${SOURCES}) target_include_directories(ngap_test PRIVATE ${CMAKE_SOURCE_DIR}) +set_target_properties(ngap_test PROPERTIES UNITY_BUILD ON) target_link_libraries(ngap_test ngap_test_helpers srsran_ngap srsran_support ngap_asn1 srsran_ran srslog gtest gtest_main) gtest_discover_tests(ngap_test PROPERTIES "LABELS;ngap") diff --git a/tests/unittests/ngap/ngap_test_messages.cpp b/tests/unittests/ngap/ngap_test_messages.cpp index 4e2a71f081..144cee4bdf 100644 --- a/tests/unittests/ngap/ngap_test_messages.cpp +++ b/tests/unittests/ngap/ngap_test_messages.cpp @@ -24,6 +24,7 @@ #include "lib/ngap/ngap_asn1_converters.h" #include "srsran/asn1/ngap/common.h" #include "srsran/asn1/ngap/ngap_ies.h" +#include "srsran/asn1/ngap/ngap_pdu_contents.h" #include "srsran/ngap/ngap_message.h" #include "srsran/ngap/ngap_types.h" #include "srsran/ran/cu_types.h" diff --git a/tests/unittests/ngap/test_helpers.h b/tests/unittests/ngap/test_helpers.h index f9b1fa2d6d..b7330c6662 100644 --- a/tests/unittests/ngap/test_helpers.h +++ b/tests/unittests/ngap/test_helpers.h @@ -72,11 +72,6 @@ class dummy_ngap_amf_notifier : public ngap_message_notifier asn1::bit_ref bref(pack_buffer); ASSERT_EQ(msg.pdu.pack(bref), asn1::SRSASN_SUCCESS); - if (logger.debug.enabled()) { - asn1::json_writer js; - msg.pdu.to_json(js); - logger.debug("Tx NGAP PDU: {}", js.to_string()); - } last_ngap_msgs.push_back(msg); if (handler != nullptr) { diff --git a/tests/unittests/phy/upper/channel_processors/pucch_detector_test.cpp b/tests/unittests/phy/upper/channel_processors/pucch_detector_test.cpp index 11729c6c89..da8aa1fc0b 100644 --- a/tests/unittests/phy/upper/channel_processors/pucch_detector_test.cpp +++ b/tests/unittests/phy/upper/channel_processors/pucch_detector_test.cpp @@ -39,13 +39,15 @@ namespace srsran { std::ostream& operator<<(std::ostream& os, const test_case_t& tc) { std::string hops = (tc.cfg.second_hop_prb.has_value() ? "intraslot frequency hopping" : "no frequency hopping"); - return os << fmt::format("Numerology {}, {}, symbol allocation [{}, {}], {} HARQ-ACK bit(s), {} SR bit(s).", - tc.cfg.slot.numerology(), - hops, - tc.cfg.start_symbol_index, - tc.cfg.nof_symbols, - tc.cfg.nof_harq_ack, - tc.sr_bit.size()); + return os << fmt::format( + "Numerology {}, {} port(s), {}, symbol allocation [{}, {}], {} HARQ-ACK bit(s), {} SR bit(s).", + tc.cfg.slot.numerology(), + tc.cfg.ports.size(), + hops, + tc.cfg.start_symbol_index, + tc.cfg.nof_symbols, + tc.cfg.nof_harq_ack, + tc.sr_bit.size()); } } // namespace srsran @@ -79,7 +81,7 @@ class PUCCHDetectFixture : public ::testing::TestWithParam channel_estimate::channel_estimate_dimensions ch_dims; ch_dims.nof_tx_layers = 1; - ch_dims.nof_rx_ports = 1; + ch_dims.nof_rx_ports = MAX_PORTS; ch_dims.nof_symbols = MAX_NSYMB_PER_SLOT; ch_dims.nof_prb = MAX_RB; csi.resize(ch_dims); @@ -106,20 +108,24 @@ TEST_P(PUCCHDetectFixture, Format1Test) pucch_detector::format1_configuration config = test_data.cfg; unsigned nof_res = config.nof_symbols / 2 * NRE; + unsigned nof_ports = config.ports.size(); std::vector grid_entries = test_data.received_symbols.read(); - ASSERT_EQ(grid_entries.size(), nof_res) << "The number of grid entries and the number of PUCCH REs do not match"; + ASSERT_EQ(grid_entries.size(), nof_res * nof_ports) + << "The number of grid entries and the number of PUCCH REs do not match"; resource_grid_reader_spy grid(0, 0, 0); grid.write(grid_entries); std::vector channel_entries = test_data.ch_estimates.read(); - ASSERT_EQ(channel_entries.size(), nof_res) + ASSERT_EQ(channel_entries.size(), nof_res * nof_ports) << "The number of channel estimates and the number of PUCCH REs do not match"; fill_ch_estimate(csi, channel_entries); - csi.set_noise_variance(test_data.noise_var, 0); + for (unsigned i_port = 0; i_port != nof_ports; ++i_port) { + csi.set_noise_variance(test_data.noise_var, i_port); + } pucch_detector::pucch_detection_result res = detector_test->detect(grid, csi, test_data.cfg); @@ -127,7 +133,10 @@ TEST_P(PUCCHDetectFixture, Format1Test) if (test_data.cfg.nof_harq_ack == 0) { if (test_data.sr_bit.empty()) { - ASSERT_EQ(msg.get_status(), uci_status::invalid) << "An empty PUCCH occasion should return an 'invalid' UCI."; + // The second part of the condition is to accept false detection if the detection metric is just above the + // threshold. False alarm probability has to be evaluated in a dedicated test. + ASSERT_TRUE((msg.get_status() == uci_status::invalid) || (res.detection_metric < 1.3)) + << "An empty PUCCH occasion should return an 'invalid' UCI."; return; } if (test_data.sr_bit[0] == 1) { @@ -155,20 +164,24 @@ TEST_P(PUCCHDetectFixture, Format1Variance0Test) pucch_detector::format1_configuration config = test_data.cfg; unsigned nof_res = config.nof_symbols / 2 * NRE; + unsigned nof_ports = config.ports.size(); std::vector grid_entries = test_data.received_symbols.read(); - ASSERT_EQ(grid_entries.size(), nof_res) << "The number of grid entries and the number of PUCCH REs do not match"; + ASSERT_EQ(grid_entries.size(), nof_res * nof_ports) + << "The number of grid entries and the number of PUCCH REs do not match"; resource_grid_reader_spy grid(0, 0, 0); grid.write(grid_entries); std::vector channel_entries = test_data.ch_estimates.read(); - ASSERT_EQ(channel_entries.size(), nof_res) + ASSERT_EQ(channel_entries.size(), nof_res * nof_ports) << "The number of channel estimates and the number of PUCCH REs do not match"; fill_ch_estimate(csi, channel_entries); - csi.set_noise_variance(0, 0); + for (unsigned i_port = 0; i_port != nof_ports; ++i_port) { + csi.set_noise_variance(0, i_port); + } pucch_detector::pucch_detection_result res = detector_test->detect(grid, csi, test_data.cfg); pucch_uci_message& msg = res.uci_message; diff --git a/tests/unittests/phy/upper/channel_processors/pucch_detector_test_data.h b/tests/unittests/phy/upper/channel_processors/pucch_detector_test_data.h index 16d7ade2c8..6291ae2e35 100644 --- a/tests/unittests/phy/upper/channel_processors/pucch_detector_test_data.h +++ b/tests/unittests/phy/upper/channel_processors/pucch_detector_test_data.h @@ -22,7 +22,7 @@ #pragma once -// This file was generated using the following MATLAB class on 14-09-2023 (seed 0): +// This file was generated using the following MATLAB class on 10-04-2024 (seed 0): // + "srsPUCCHDetectorFormat1Unittest.m" #include "../../support/resource_grid_test_doubles.h" @@ -44,102 +44,294 @@ struct test_case_t { static const std::vector pucch_detector_test_data = { // clang-format off - {{{0, 9}, cyclic_prefix::NORMAL, 6, {}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 6, 6, 821, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols0.dat"}, {"test_data/pucch_detector_test_ch_estimates0.dat"}}, - {{{0, 5}, cyclic_prefix::NORMAL, 48, {}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 2, 9, 426, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols1.dat"}, {"test_data/pucch_detector_test_ch_estimates1.dat"}}, - {{{0, 2}, cyclic_prefix::NORMAL, 16, {}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 3, 6, 303, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols2.dat"}, {"test_data/pucch_detector_test_ch_estimates2.dat"}}, - {{{0, 9}, cyclic_prefix::NORMAL, 1, {}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 3, 0, 810, 1}, 0.01, {1}, {1}, {"test_data/pucch_detector_test_received_symbols3.dat"}, {"test_data/pucch_detector_test_ch_estimates3.dat"}}, - {{{0, 2}, cyclic_prefix::NORMAL, 23, {}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 2, 9, 536, 2}, 0.01, {}, {1, 1}, {"test_data/pucch_detector_test_received_symbols4.dat"}, {"test_data/pucch_detector_test_ch_estimates4.dat"}}, - {{{0, 9}, cyclic_prefix::NORMAL, 9, {}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 3, 6, 595, 2}, 0.01, {1}, {0, 1}, {"test_data/pucch_detector_test_received_symbols5.dat"}, {"test_data/pucch_detector_test_ch_estimates5.dat"}}, - {{{0, 1}, cyclic_prefix::NORMAL, 1, {31}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 1, 9, 658, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols6.dat"}, {"test_data/pucch_detector_test_ch_estimates6.dat"}}, - {{{0, 4}, cyclic_prefix::NORMAL, 9, {43}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 1, 3, 708, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols7.dat"}, {"test_data/pucch_detector_test_ch_estimates7.dat"}}, - {{{0, 2}, cyclic_prefix::NORMAL, 34, {42}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 2, 0, 118, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols8.dat"}, {"test_data/pucch_detector_test_ch_estimates8.dat"}}, - {{{0, 8}, cyclic_prefix::NORMAL, 9, {13}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 0, 6, 858, 1}, 0.01, {1}, {1}, {"test_data/pucch_detector_test_received_symbols9.dat"}, {"test_data/pucch_detector_test_ch_estimates9.dat"}}, - {{{0, 5}, cyclic_prefix::NORMAL, 0, {41}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 1, 3, 933, 2}, 0.01, {}, {0, 0}, {"test_data/pucch_detector_test_received_symbols10.dat"}, {"test_data/pucch_detector_test_ch_estimates10.dat"}}, - {{{0, 6}, cyclic_prefix::NORMAL, 32, {37}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 2, 6, 637, 2}, 0.01, {0}, {0, 1}, {"test_data/pucch_detector_test_received_symbols11.dat"}, {"test_data/pucch_detector_test_ch_estimates11.dat"}}, - {{{0, 4}, cyclic_prefix::NORMAL, 46, {}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 5, 6, 286, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols12.dat"}, {"test_data/pucch_detector_test_ch_estimates12.dat"}}, - {{{0, 8}, cyclic_prefix::NORMAL, 33, {}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 5, 9, 581, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols13.dat"}, {"test_data/pucch_detector_test_ch_estimates13.dat"}}, - {{{0, 8}, cyclic_prefix::NORMAL, 6, {}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 1, 0, 902, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols14.dat"}, {"test_data/pucch_detector_test_ch_estimates14.dat"}}, - {{{0, 3}, cyclic_prefix::NORMAL, 41, {}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 1, 3, 570, 1}, 0.01, {1}, {1}, {"test_data/pucch_detector_test_received_symbols15.dat"}, {"test_data/pucch_detector_test_ch_estimates15.dat"}}, - {{{0, 5}, cyclic_prefix::NORMAL, 38, {}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 0, 3, 903, 2}, 0.01, {}, {0, 1}, {"test_data/pucch_detector_test_received_symbols16.dat"}, {"test_data/pucch_detector_test_ch_estimates16.dat"}}, - {{{0, 5}, cyclic_prefix::NORMAL, 19, {}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 4, 9, 993, 2}, 0.01, {1}, {0, 1}, {"test_data/pucch_detector_test_received_symbols17.dat"}, {"test_data/pucch_detector_test_ch_estimates17.dat"}}, - {{{0, 2}, cyclic_prefix::NORMAL, 27, {40}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 1, 9, 530, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols18.dat"}, {"test_data/pucch_detector_test_ch_estimates18.dat"}}, - {{{0, 8}, cyclic_prefix::NORMAL, 28, {10}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 0, 6, 76, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols19.dat"}, {"test_data/pucch_detector_test_ch_estimates19.dat"}}, - {{{0, 5}, cyclic_prefix::NORMAL, 42, {32}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 0, 9, 428, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols20.dat"}, {"test_data/pucch_detector_test_ch_estimates20.dat"}}, - {{{0, 0}, cyclic_prefix::NORMAL, 8, {37}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 0, 9, 294, 1}, 0.01, {0}, {0}, {"test_data/pucch_detector_test_received_symbols21.dat"}, {"test_data/pucch_detector_test_ch_estimates21.dat"}}, - {{{0, 9}, cyclic_prefix::NORMAL, 39, {29}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 1, 3, 614, 2}, 0.01, {}, {1, 0}, {"test_data/pucch_detector_test_received_symbols22.dat"}, {"test_data/pucch_detector_test_ch_estimates22.dat"}}, - {{{0, 8}, cyclic_prefix::NORMAL, 33, {43}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 0, 9, 177, 2}, 0.01, {0}, {0, 1}, {"test_data/pucch_detector_test_received_symbols23.dat"}, {"test_data/pucch_detector_test_ch_estimates23.dat"}}, - {{{0, 9}, cyclic_prefix::NORMAL, 21, {}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 0, 9, 836, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols24.dat"}, {"test_data/pucch_detector_test_ch_estimates24.dat"}}, - {{{0, 0}, cyclic_prefix::NORMAL, 32, {}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 1, 3, 631, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols25.dat"}, {"test_data/pucch_detector_test_ch_estimates25.dat"}}, - {{{0, 2}, cyclic_prefix::NORMAL, 40, {}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 1, 0, 812, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols26.dat"}, {"test_data/pucch_detector_test_ch_estimates26.dat"}}, - {{{0, 0}, cyclic_prefix::NORMAL, 38, {}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 0, 0, 744, 1}, 0.01, {0}, {1}, {"test_data/pucch_detector_test_received_symbols27.dat"}, {"test_data/pucch_detector_test_ch_estimates27.dat"}}, - {{{0, 8}, cyclic_prefix::NORMAL, 40, {}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 0, 9, 804, 2}, 0.01, {}, {0, 0}, {"test_data/pucch_detector_test_received_symbols28.dat"}, {"test_data/pucch_detector_test_ch_estimates28.dat"}}, - {{{0, 5}, cyclic_prefix::NORMAL, 29, {}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 1, 9, 883, 2}, 0.01, {1}, {1, 0}, {"test_data/pucch_detector_test_received_symbols29.dat"}, {"test_data/pucch_detector_test_ch_estimates29.dat"}}, - {{{0, 2}, cyclic_prefix::NORMAL, 1, {43}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 0, 6, 78, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols30.dat"}, {"test_data/pucch_detector_test_ch_estimates30.dat"}}, - {{{0, 4}, cyclic_prefix::NORMAL, 42, {8}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 0, 0, 784, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols31.dat"}, {"test_data/pucch_detector_test_ch_estimates31.dat"}}, - {{{0, 3}, cyclic_prefix::NORMAL, 40, {29}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 0, 0, 320, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols32.dat"}, {"test_data/pucch_detector_test_ch_estimates32.dat"}}, - {{{0, 0}, cyclic_prefix::NORMAL, 48, {13}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 0, 0, 192, 1}, 0.01, {1}, {0}, {"test_data/pucch_detector_test_received_symbols33.dat"}, {"test_data/pucch_detector_test_ch_estimates33.dat"}}, - {{{0, 1}, cyclic_prefix::NORMAL, 9, {48}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 0, 0, 293, 2}, 0.01, {}, {1, 0}, {"test_data/pucch_detector_test_received_symbols34.dat"}, {"test_data/pucch_detector_test_ch_estimates34.dat"}}, - {{{0, 1}, cyclic_prefix::NORMAL, 17, {28}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 0, 0, 429, 2}, 0.01, {0}, {0, 1}, {"test_data/pucch_detector_test_received_symbols35.dat"}, {"test_data/pucch_detector_test_ch_estimates35.dat"}}, - {{{0, 4}, cyclic_prefix::NORMAL, 31, {}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 0, 0, 190, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols36.dat"}, {"test_data/pucch_detector_test_ch_estimates36.dat"}}, - {{{0, 8}, cyclic_prefix::NORMAL, 12, {}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 0, 3, 178, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols37.dat"}, {"test_data/pucch_detector_test_ch_estimates37.dat"}}, - {{{0, 7}, cyclic_prefix::NORMAL, 19, {}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 0, 0, 670, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols38.dat"}, {"test_data/pucch_detector_test_ch_estimates38.dat"}}, - {{{0, 7}, cyclic_prefix::NORMAL, 40, {}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 0, 3, 241, 1}, 0.01, {0}, {0}, {"test_data/pucch_detector_test_received_symbols39.dat"}, {"test_data/pucch_detector_test_ch_estimates39.dat"}}, - {{{0, 5}, cyclic_prefix::NORMAL, 22, {}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 0, 6, 45, 2}, 0.01, {}, {1, 1}, {"test_data/pucch_detector_test_received_symbols40.dat"}, {"test_data/pucch_detector_test_ch_estimates40.dat"}}, - {{{0, 4}, cyclic_prefix::NORMAL, 16, {}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 1, 9, 168, 2}, 0.01, {1}, {0, 1}, {"test_data/pucch_detector_test_received_symbols41.dat"}, {"test_data/pucch_detector_test_ch_estimates41.dat"}}, - {{{0, 5}, cyclic_prefix::NORMAL, 32, {30}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 0, 3, 554, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols42.dat"}, {"test_data/pucch_detector_test_ch_estimates42.dat"}}, - {{{0, 5}, cyclic_prefix::NORMAL, 44, {41}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 0, 0, 243, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols43.dat"}, {"test_data/pucch_detector_test_ch_estimates43.dat"}}, - {{{0, 7}, cyclic_prefix::NORMAL, 17, {44}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 0, 3, 374, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols44.dat"}, {"test_data/pucch_detector_test_ch_estimates44.dat"}}, - {{{0, 6}, cyclic_prefix::NORMAL, 48, {39}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 0, 9, 286, 1}, 0.01, {1}, {1}, {"test_data/pucch_detector_test_received_symbols45.dat"}, {"test_data/pucch_detector_test_ch_estimates45.dat"}}, - {{{0, 4}, cyclic_prefix::NORMAL, 24, {9}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 0, 9, 495, 2}, 0.01, {}, {1, 0}, {"test_data/pucch_detector_test_received_symbols46.dat"}, {"test_data/pucch_detector_test_ch_estimates46.dat"}}, - {{{0, 2}, cyclic_prefix::NORMAL, 27, {28}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 0, 9, 251, 2}, 0.01, {0}, {0, 1}, {"test_data/pucch_detector_test_received_symbols47.dat"}, {"test_data/pucch_detector_test_ch_estimates47.dat"}}, - {{{1, 0}, cyclic_prefix::NORMAL, 13, {}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 6, 3, 767, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols48.dat"}, {"test_data/pucch_detector_test_ch_estimates48.dat"}}, - {{{1, 10}, cyclic_prefix::NORMAL, 30, {}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 1, 3, 798, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols49.dat"}, {"test_data/pucch_detector_test_ch_estimates49.dat"}}, - {{{1, 14}, cyclic_prefix::NORMAL, 15, {}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 0, 6, 657, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols50.dat"}, {"test_data/pucch_detector_test_ch_estimates50.dat"}}, - {{{1, 14}, cyclic_prefix::NORMAL, 22, {}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 1, 0, 720, 1}, 0.01, {1}, {0}, {"test_data/pucch_detector_test_received_symbols51.dat"}, {"test_data/pucch_detector_test_ch_estimates51.dat"}}, - {{{1, 2}, cyclic_prefix::NORMAL, 10, {}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 1, 9, 12, 2}, 0.01, {}, {0, 0}, {"test_data/pucch_detector_test_received_symbols52.dat"}, {"test_data/pucch_detector_test_ch_estimates52.dat"}}, - {{{1, 4}, cyclic_prefix::NORMAL, 3, {}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 4, 6, 872, 2}, 0.01, {1}, {1, 0}, {"test_data/pucch_detector_test_received_symbols53.dat"}, {"test_data/pucch_detector_test_ch_estimates53.dat"}}, - {{{1, 2}, cyclic_prefix::NORMAL, 43, {18}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 0, 3, 489, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols54.dat"}, {"test_data/pucch_detector_test_ch_estimates54.dat"}}, - {{{1, 18}, cyclic_prefix::NORMAL, 38, {17}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 2, 6, 773, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols55.dat"}, {"test_data/pucch_detector_test_ch_estimates55.dat"}}, - {{{1, 11}, cyclic_prefix::NORMAL, 0, {28}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 2, 3, 328, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols56.dat"}, {"test_data/pucch_detector_test_ch_estimates56.dat"}}, - {{{1, 3}, cyclic_prefix::NORMAL, 29, {10}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 1, 9, 438, 1}, 0.01, {1}, {0}, {"test_data/pucch_detector_test_received_symbols57.dat"}, {"test_data/pucch_detector_test_ch_estimates57.dat"}}, - {{{1, 14}, cyclic_prefix::NORMAL, 26, {25}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 2, 0, 386, 2}, 0.01, {}, {0, 0}, {"test_data/pucch_detector_test_received_symbols58.dat"}, {"test_data/pucch_detector_test_ch_estimates58.dat"}}, - {{{1, 11}, cyclic_prefix::NORMAL, 15, {41}, 0, 14, pucch_group_hopping::NEITHER, 0, 1, 2, 6, 752, 2}, 0.01, {1}, {1, 1}, {"test_data/pucch_detector_test_received_symbols59.dat"}, {"test_data/pucch_detector_test_ch_estimates59.dat"}}, - {{{1, 10}, cyclic_prefix::NORMAL, 42, {}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 3, 0, 43, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols60.dat"}, {"test_data/pucch_detector_test_ch_estimates60.dat"}}, - {{{1, 3}, cyclic_prefix::NORMAL, 47, {}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 3, 0, 38, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols61.dat"}, {"test_data/pucch_detector_test_ch_estimates61.dat"}}, - {{{1, 18}, cyclic_prefix::NORMAL, 25, {}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 4, 0, 190, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols62.dat"}, {"test_data/pucch_detector_test_ch_estimates62.dat"}}, - {{{1, 2}, cyclic_prefix::NORMAL, 2, {}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 1, 3, 752, 1}, 0.01, {1}, {0}, {"test_data/pucch_detector_test_received_symbols63.dat"}, {"test_data/pucch_detector_test_ch_estimates63.dat"}}, - {{{1, 7}, cyclic_prefix::NORMAL, 49, {}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 0, 9, 1003, 2}, 0.01, {}, {0, 1}, {"test_data/pucch_detector_test_received_symbols64.dat"}, {"test_data/pucch_detector_test_ch_estimates64.dat"}}, - {{{1, 1}, cyclic_prefix::NORMAL, 30, {}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 4, 6, 858, 2}, 0.01, {1}, {0, 1}, {"test_data/pucch_detector_test_received_symbols65.dat"}, {"test_data/pucch_detector_test_ch_estimates65.dat"}}, - {{{1, 7}, cyclic_prefix::NORMAL, 40, {40}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 0, 9, 874, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols66.dat"}, {"test_data/pucch_detector_test_ch_estimates66.dat"}}, - {{{1, 16}, cyclic_prefix::NORMAL, 10, {22}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 2, 0, 648, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols67.dat"}, {"test_data/pucch_detector_test_ch_estimates67.dat"}}, - {{{1, 17}, cyclic_prefix::NORMAL, 48, {1}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 1, 9, 403, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols68.dat"}, {"test_data/pucch_detector_test_ch_estimates68.dat"}}, - {{{1, 11}, cyclic_prefix::NORMAL, 39, {38}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 2, 3, 554, 1}, 0.01, {1}, {0}, {"test_data/pucch_detector_test_received_symbols69.dat"}, {"test_data/pucch_detector_test_ch_estimates69.dat"}}, - {{{1, 5}, cyclic_prefix::NORMAL, 45, {7}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 2, 9, 807, 2}, 0.01, {}, {0, 0}, {"test_data/pucch_detector_test_received_symbols70.dat"}, {"test_data/pucch_detector_test_ch_estimates70.dat"}}, - {{{1, 6}, cyclic_prefix::NORMAL, 12, {37}, 1, 13, pucch_group_hopping::NEITHER, 0, 1, 1, 9, 139, 2}, 0.01, {0}, {0, 0}, {"test_data/pucch_detector_test_received_symbols71.dat"}, {"test_data/pucch_detector_test_ch_estimates71.dat"}}, - {{{1, 17}, cyclic_prefix::NORMAL, 48, {}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 0, 9, 404, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols72.dat"}, {"test_data/pucch_detector_test_ch_estimates72.dat"}}, - {{{1, 4}, cyclic_prefix::NORMAL, 21, {}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 1, 9, 287, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols73.dat"}, {"test_data/pucch_detector_test_ch_estimates73.dat"}}, - {{{1, 9}, cyclic_prefix::NORMAL, 19, {}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 1, 9, 919, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols74.dat"}, {"test_data/pucch_detector_test_ch_estimates74.dat"}}, - {{{1, 7}, cyclic_prefix::NORMAL, 37, {}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 0, 0, 66, 1}, 0.01, {1}, {0}, {"test_data/pucch_detector_test_received_symbols75.dat"}, {"test_data/pucch_detector_test_ch_estimates75.dat"}}, - {{{1, 11}, cyclic_prefix::NORMAL, 13, {}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 1, 0, 77, 2}, 0.01, {}, {1, 0}, {"test_data/pucch_detector_test_received_symbols76.dat"}, {"test_data/pucch_detector_test_ch_estimates76.dat"}}, - {{{1, 5}, cyclic_prefix::NORMAL, 43, {}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 1, 0, 54, 2}, 0.01, {1}, {0, 0}, {"test_data/pucch_detector_test_received_symbols77.dat"}, {"test_data/pucch_detector_test_ch_estimates77.dat"}}, - {{{1, 19}, cyclic_prefix::NORMAL, 6, {46}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 0, 9, 119, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols78.dat"}, {"test_data/pucch_detector_test_ch_estimates78.dat"}}, - {{{1, 19}, cyclic_prefix::NORMAL, 26, {42}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 0, 0, 228, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols79.dat"}, {"test_data/pucch_detector_test_ch_estimates79.dat"}}, - {{{1, 17}, cyclic_prefix::NORMAL, 28, {40}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 0, 6, 756, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols80.dat"}, {"test_data/pucch_detector_test_ch_estimates80.dat"}}, - {{{1, 15}, cyclic_prefix::NORMAL, 39, {43}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 0, 6, 163, 1}, 0.01, {0}, {0}, {"test_data/pucch_detector_test_received_symbols81.dat"}, {"test_data/pucch_detector_test_ch_estimates81.dat"}}, - {{{1, 4}, cyclic_prefix::NORMAL, 29, {35}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 0, 3, 535, 2}, 0.01, {}, {1, 1}, {"test_data/pucch_detector_test_received_symbols82.dat"}, {"test_data/pucch_detector_test_ch_estimates82.dat"}}, - {{{1, 17}, cyclic_prefix::NORMAL, 7, {5}, 5, 5, pucch_group_hopping::NEITHER, 0, 1, 0, 0, 451, 2}, 0.01, {0}, {1, 1}, {"test_data/pucch_detector_test_received_symbols83.dat"}, {"test_data/pucch_detector_test_ch_estimates83.dat"}}, - {{{1, 11}, cyclic_prefix::NORMAL, 49, {}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 1, 9, 889, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols84.dat"}, {"test_data/pucch_detector_test_ch_estimates84.dat"}}, - {{{1, 2}, cyclic_prefix::NORMAL, 35, {}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 1, 3, 467, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols85.dat"}, {"test_data/pucch_detector_test_ch_estimates85.dat"}}, - {{{1, 7}, cyclic_prefix::NORMAL, 10, {}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 1, 9, 759, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols86.dat"}, {"test_data/pucch_detector_test_ch_estimates86.dat"}}, - {{{1, 6}, cyclic_prefix::NORMAL, 26, {}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 0, 9, 448, 1}, 0.01, {0}, {0}, {"test_data/pucch_detector_test_received_symbols87.dat"}, {"test_data/pucch_detector_test_ch_estimates87.dat"}}, - {{{1, 12}, cyclic_prefix::NORMAL, 42, {}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 1, 0, 862, 2}, 0.01, {}, {1, 0}, {"test_data/pucch_detector_test_received_symbols88.dat"}, {"test_data/pucch_detector_test_ch_estimates88.dat"}}, - {{{1, 18}, cyclic_prefix::NORMAL, 37, {}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 0, 6, 726, 2}, 0.01, {0}, {0, 0}, {"test_data/pucch_detector_test_received_symbols89.dat"}, {"test_data/pucch_detector_test_ch_estimates89.dat"}}, - {{{1, 6}, cyclic_prefix::NORMAL, 10, {32}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 0, 6, 267, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols90.dat"}, {"test_data/pucch_detector_test_ch_estimates90.dat"}}, - {{{1, 9}, cyclic_prefix::NORMAL, 42, {41}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 0, 6, 603, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols91.dat"}, {"test_data/pucch_detector_test_ch_estimates91.dat"}}, - {{{1, 1}, cyclic_prefix::NORMAL, 13, {5}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 0, 9, 341, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols92.dat"}, {"test_data/pucch_detector_test_ch_estimates92.dat"}}, - {{{1, 6}, cyclic_prefix::NORMAL, 37, {18}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 0, 3, 563, 1}, 0.01, {0}, {0}, {"test_data/pucch_detector_test_received_symbols93.dat"}, {"test_data/pucch_detector_test_ch_estimates93.dat"}}, - {{{1, 18}, cyclic_prefix::NORMAL, 17, {2}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 0, 9, 501, 2}, 0.01, {}, {1, 1}, {"test_data/pucch_detector_test_received_symbols94.dat"}, {"test_data/pucch_detector_test_ch_estimates94.dat"}}, - {{{1, 18}, cyclic_prefix::NORMAL, 43, {21}, 10, 4, pucch_group_hopping::NEITHER, 0, 1, 0, 3, 762, 2}, 0.01, {0}, {1, 0}, {"test_data/pucch_detector_test_received_symbols95.dat"}, {"test_data/pucch_detector_test_ch_estimates95.dat"}}, + {{{0, 9}, cyclic_prefix::NORMAL, 6, {}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 6, 6, 821, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols0.dat"}, {"test_data/pucch_detector_test_ch_estimates0.dat"}}, + {{{0, 5}, cyclic_prefix::NORMAL, 48, {}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 2, 9, 426, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols1.dat"}, {"test_data/pucch_detector_test_ch_estimates1.dat"}}, + {{{0, 2}, cyclic_prefix::NORMAL, 16, {}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 3, 6, 303, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols2.dat"}, {"test_data/pucch_detector_test_ch_estimates2.dat"}}, + {{{0, 9}, cyclic_prefix::NORMAL, 1, {}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 3, 0, 810, 1}, 0.01, {1}, {1}, {"test_data/pucch_detector_test_received_symbols3.dat"}, {"test_data/pucch_detector_test_ch_estimates3.dat"}}, + {{{0, 2}, cyclic_prefix::NORMAL, 23, {}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 2, 9, 536, 2}, 0.01, {}, {1, 1}, {"test_data/pucch_detector_test_received_symbols4.dat"}, {"test_data/pucch_detector_test_ch_estimates4.dat"}}, + {{{0, 9}, cyclic_prefix::NORMAL, 9, {}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 3, 6, 595, 2}, 0.01, {1}, {0, 1}, {"test_data/pucch_detector_test_received_symbols5.dat"}, {"test_data/pucch_detector_test_ch_estimates5.dat"}}, + {{{0, 1}, cyclic_prefix::NORMAL, 1, {31}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 1, 9, 658, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols6.dat"}, {"test_data/pucch_detector_test_ch_estimates6.dat"}}, + {{{0, 4}, cyclic_prefix::NORMAL, 9, {43}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 1, 3, 708, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols7.dat"}, {"test_data/pucch_detector_test_ch_estimates7.dat"}}, + {{{0, 2}, cyclic_prefix::NORMAL, 34, {42}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 2, 0, 118, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols8.dat"}, {"test_data/pucch_detector_test_ch_estimates8.dat"}}, + {{{0, 8}, cyclic_prefix::NORMAL, 9, {13}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 0, 6, 858, 1}, 0.01, {1}, {1}, {"test_data/pucch_detector_test_received_symbols9.dat"}, {"test_data/pucch_detector_test_ch_estimates9.dat"}}, + {{{0, 5}, cyclic_prefix::NORMAL, 0, {41}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 1, 3, 933, 2}, 0.01, {}, {0, 0}, {"test_data/pucch_detector_test_received_symbols10.dat"}, {"test_data/pucch_detector_test_ch_estimates10.dat"}}, + {{{0, 6}, cyclic_prefix::NORMAL, 32, {37}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 2, 6, 637, 2}, 0.01, {0}, {0, 1}, {"test_data/pucch_detector_test_received_symbols11.dat"}, {"test_data/pucch_detector_test_ch_estimates11.dat"}}, + {{{0, 4}, cyclic_prefix::NORMAL, 46, {}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 5, 6, 286, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols12.dat"}, {"test_data/pucch_detector_test_ch_estimates12.dat"}}, + {{{0, 8}, cyclic_prefix::NORMAL, 33, {}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 5, 9, 581, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols13.dat"}, {"test_data/pucch_detector_test_ch_estimates13.dat"}}, + {{{0, 8}, cyclic_prefix::NORMAL, 6, {}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 1, 0, 902, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols14.dat"}, {"test_data/pucch_detector_test_ch_estimates14.dat"}}, + {{{0, 3}, cyclic_prefix::NORMAL, 41, {}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 1, 3, 570, 1}, 0.01, {1}, {1}, {"test_data/pucch_detector_test_received_symbols15.dat"}, {"test_data/pucch_detector_test_ch_estimates15.dat"}}, + {{{0, 5}, cyclic_prefix::NORMAL, 38, {}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 0, 3, 903, 2}, 0.01, {}, {0, 1}, {"test_data/pucch_detector_test_received_symbols16.dat"}, {"test_data/pucch_detector_test_ch_estimates16.dat"}}, + {{{0, 5}, cyclic_prefix::NORMAL, 19, {}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 4, 9, 993, 2}, 0.01, {1}, {0, 1}, {"test_data/pucch_detector_test_received_symbols17.dat"}, {"test_data/pucch_detector_test_ch_estimates17.dat"}}, + {{{0, 2}, cyclic_prefix::NORMAL, 27, {40}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 1, 9, 530, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols18.dat"}, {"test_data/pucch_detector_test_ch_estimates18.dat"}}, + {{{0, 8}, cyclic_prefix::NORMAL, 28, {10}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 0, 6, 76, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols19.dat"}, {"test_data/pucch_detector_test_ch_estimates19.dat"}}, + {{{0, 5}, cyclic_prefix::NORMAL, 42, {32}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 0, 9, 428, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols20.dat"}, {"test_data/pucch_detector_test_ch_estimates20.dat"}}, + {{{0, 0}, cyclic_prefix::NORMAL, 8, {37}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 0, 9, 294, 1}, 0.01, {0}, {0}, {"test_data/pucch_detector_test_received_symbols21.dat"}, {"test_data/pucch_detector_test_ch_estimates21.dat"}}, + {{{0, 9}, cyclic_prefix::NORMAL, 39, {29}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 1, 3, 614, 2}, 0.01, {}, {1, 0}, {"test_data/pucch_detector_test_received_symbols22.dat"}, {"test_data/pucch_detector_test_ch_estimates22.dat"}}, + {{{0, 8}, cyclic_prefix::NORMAL, 33, {43}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 0, 9, 177, 2}, 0.01, {0}, {0, 1}, {"test_data/pucch_detector_test_received_symbols23.dat"}, {"test_data/pucch_detector_test_ch_estimates23.dat"}}, + {{{0, 9}, cyclic_prefix::NORMAL, 21, {}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 0, 9, 836, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols24.dat"}, {"test_data/pucch_detector_test_ch_estimates24.dat"}}, + {{{0, 0}, cyclic_prefix::NORMAL, 32, {}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 1, 3, 631, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols25.dat"}, {"test_data/pucch_detector_test_ch_estimates25.dat"}}, + {{{0, 2}, cyclic_prefix::NORMAL, 40, {}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 1, 0, 812, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols26.dat"}, {"test_data/pucch_detector_test_ch_estimates26.dat"}}, + {{{0, 0}, cyclic_prefix::NORMAL, 38, {}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 0, 0, 744, 1}, 0.01, {0}, {1}, {"test_data/pucch_detector_test_received_symbols27.dat"}, {"test_data/pucch_detector_test_ch_estimates27.dat"}}, + {{{0, 8}, cyclic_prefix::NORMAL, 40, {}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 0, 9, 804, 2}, 0.01, {}, {0, 0}, {"test_data/pucch_detector_test_received_symbols28.dat"}, {"test_data/pucch_detector_test_ch_estimates28.dat"}}, + {{{0, 5}, cyclic_prefix::NORMAL, 29, {}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 1, 9, 883, 2}, 0.01, {1}, {1, 0}, {"test_data/pucch_detector_test_received_symbols29.dat"}, {"test_data/pucch_detector_test_ch_estimates29.dat"}}, + {{{0, 2}, cyclic_prefix::NORMAL, 1, {43}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 0, 6, 78, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols30.dat"}, {"test_data/pucch_detector_test_ch_estimates30.dat"}}, + {{{0, 4}, cyclic_prefix::NORMAL, 42, {8}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 0, 0, 784, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols31.dat"}, {"test_data/pucch_detector_test_ch_estimates31.dat"}}, + {{{0, 3}, cyclic_prefix::NORMAL, 40, {29}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 0, 0, 320, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols32.dat"}, {"test_data/pucch_detector_test_ch_estimates32.dat"}}, + {{{0, 0}, cyclic_prefix::NORMAL, 48, {13}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 0, 0, 192, 1}, 0.01, {1}, {0}, {"test_data/pucch_detector_test_received_symbols33.dat"}, {"test_data/pucch_detector_test_ch_estimates33.dat"}}, + {{{0, 1}, cyclic_prefix::NORMAL, 9, {48}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 0, 0, 293, 2}, 0.01, {}, {1, 0}, {"test_data/pucch_detector_test_received_symbols34.dat"}, {"test_data/pucch_detector_test_ch_estimates34.dat"}}, + {{{0, 1}, cyclic_prefix::NORMAL, 17, {28}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 0, 0, 429, 2}, 0.01, {0}, {0, 1}, {"test_data/pucch_detector_test_received_symbols35.dat"}, {"test_data/pucch_detector_test_ch_estimates35.dat"}}, + {{{0, 4}, cyclic_prefix::NORMAL, 31, {}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 0, 0, 190, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols36.dat"}, {"test_data/pucch_detector_test_ch_estimates36.dat"}}, + {{{0, 8}, cyclic_prefix::NORMAL, 12, {}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 0, 3, 178, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols37.dat"}, {"test_data/pucch_detector_test_ch_estimates37.dat"}}, + {{{0, 7}, cyclic_prefix::NORMAL, 19, {}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 0, 0, 670, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols38.dat"}, {"test_data/pucch_detector_test_ch_estimates38.dat"}}, + {{{0, 7}, cyclic_prefix::NORMAL, 40, {}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 0, 3, 241, 1}, 0.01, {0}, {0}, {"test_data/pucch_detector_test_received_symbols39.dat"}, {"test_data/pucch_detector_test_ch_estimates39.dat"}}, + {{{0, 5}, cyclic_prefix::NORMAL, 22, {}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 0, 6, 45, 2}, 0.01, {}, {1, 1}, {"test_data/pucch_detector_test_received_symbols40.dat"}, {"test_data/pucch_detector_test_ch_estimates40.dat"}}, + {{{0, 4}, cyclic_prefix::NORMAL, 16, {}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 1, 9, 168, 2}, 0.01, {1}, {0, 1}, {"test_data/pucch_detector_test_received_symbols41.dat"}, {"test_data/pucch_detector_test_ch_estimates41.dat"}}, + {{{0, 5}, cyclic_prefix::NORMAL, 32, {30}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 0, 3, 554, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols42.dat"}, {"test_data/pucch_detector_test_ch_estimates42.dat"}}, + {{{0, 5}, cyclic_prefix::NORMAL, 44, {41}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 0, 0, 243, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols43.dat"}, {"test_data/pucch_detector_test_ch_estimates43.dat"}}, + {{{0, 7}, cyclic_prefix::NORMAL, 17, {44}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 0, 3, 374, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols44.dat"}, {"test_data/pucch_detector_test_ch_estimates44.dat"}}, + {{{0, 6}, cyclic_prefix::NORMAL, 48, {39}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 0, 9, 286, 1}, 0.01, {1}, {1}, {"test_data/pucch_detector_test_received_symbols45.dat"}, {"test_data/pucch_detector_test_ch_estimates45.dat"}}, + {{{0, 4}, cyclic_prefix::NORMAL, 24, {9}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 0, 9, 495, 2}, 0.01, {}, {1, 0}, {"test_data/pucch_detector_test_received_symbols46.dat"}, {"test_data/pucch_detector_test_ch_estimates46.dat"}}, + {{{0, 2}, cyclic_prefix::NORMAL, 27, {28}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 0, 9, 251, 2}, 0.01, {0}, {0, 1}, {"test_data/pucch_detector_test_received_symbols47.dat"}, {"test_data/pucch_detector_test_ch_estimates47.dat"}}, + {{{0, 0}, cyclic_prefix::NORMAL, 13, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 6, 3, 767, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols48.dat"}, {"test_data/pucch_detector_test_ch_estimates48.dat"}}, + {{{0, 0}, cyclic_prefix::NORMAL, 36, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 2, 6, 405, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols49.dat"}, {"test_data/pucch_detector_test_ch_estimates49.dat"}}, + {{{0, 7}, cyclic_prefix::NORMAL, 30, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 6, 574, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols50.dat"}, {"test_data/pucch_detector_test_ch_estimates50.dat"}}, + {{{0, 5}, cyclic_prefix::NORMAL, 0, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 9, 491, 1}, 0.01, {1}, {1}, {"test_data/pucch_detector_test_received_symbols51.dat"}, {"test_data/pucch_detector_test_ch_estimates51.dat"}}, + {{{0, 6}, cyclic_prefix::NORMAL, 11, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 3, 6, 202, 2}, 0.01, {}, {0, 1}, {"test_data/pucch_detector_test_received_symbols52.dat"}, {"test_data/pucch_detector_test_ch_estimates52.dat"}}, + {{{0, 4}, cyclic_prefix::NORMAL, 2, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 2, 3, 979, 2}, 0.01, {0}, {0, 0}, {"test_data/pucch_detector_test_received_symbols53.dat"}, {"test_data/pucch_detector_test_ch_estimates53.dat"}}, + {{{0, 9}, cyclic_prefix::NORMAL, 33, {39}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 0, 413, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols54.dat"}, {"test_data/pucch_detector_test_ch_estimates54.dat"}}, + {{{0, 0}, cyclic_prefix::NORMAL, 19, {21}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 2, 3, 616, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols55.dat"}, {"test_data/pucch_detector_test_ch_estimates55.dat"}}, + {{{0, 8}, cyclic_prefix::NORMAL, 34, {49}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 0, 251, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols56.dat"}, {"test_data/pucch_detector_test_ch_estimates56.dat"}}, + {{{0, 1}, cyclic_prefix::NORMAL, 19, {16}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 2, 3, 317, 1}, 0.01, {0}, {1}, {"test_data/pucch_detector_test_received_symbols57.dat"}, {"test_data/pucch_detector_test_ch_estimates57.dat"}}, + {{{0, 0}, cyclic_prefix::NORMAL, 16, {12}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 6, 189, 2}, 0.01, {}, {1, 0}, {"test_data/pucch_detector_test_received_symbols58.dat"}, {"test_data/pucch_detector_test_ch_estimates58.dat"}}, + {{{0, 5}, cyclic_prefix::NORMAL, 21, {16}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 3, 178, 2}, 0.01, {1}, {1, 0}, {"test_data/pucch_detector_test_received_symbols59.dat"}, {"test_data/pucch_detector_test_ch_estimates59.dat"}}, + {{{0, 7}, cyclic_prefix::NORMAL, 50, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 3, 3, 838, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols60.dat"}, {"test_data/pucch_detector_test_ch_estimates60.dat"}}, + {{{0, 3}, cyclic_prefix::NORMAL, 12, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 0, 547, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols61.dat"}, {"test_data/pucch_detector_test_ch_estimates61.dat"}}, + {{{0, 7}, cyclic_prefix::NORMAL, 38, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 2, 0, 709, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols62.dat"}, {"test_data/pucch_detector_test_ch_estimates62.dat"}}, + {{{0, 8}, cyclic_prefix::NORMAL, 18, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 9, 728, 1}, 0.01, {1}, {1}, {"test_data/pucch_detector_test_received_symbols63.dat"}, {"test_data/pucch_detector_test_ch_estimates63.dat"}}, + {{{0, 5}, cyclic_prefix::NORMAL, 12, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 5, 0, 110, 2}, 0.01, {}, {1, 1}, {"test_data/pucch_detector_test_received_symbols64.dat"}, {"test_data/pucch_detector_test_ch_estimates64.dat"}}, + {{{0, 9}, cyclic_prefix::NORMAL, 12, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 2, 9, 816, 2}, 0.01, {1}, {1, 0}, {"test_data/pucch_detector_test_received_symbols65.dat"}, {"test_data/pucch_detector_test_ch_estimates65.dat"}}, + {{{0, 6}, cyclic_prefix::NORMAL, 37, {45}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 2, 3, 649, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols66.dat"}, {"test_data/pucch_detector_test_ch_estimates66.dat"}}, + {{{0, 7}, cyclic_prefix::NORMAL, 13, {1}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 2, 9, 730, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols67.dat"}, {"test_data/pucch_detector_test_ch_estimates67.dat"}}, + {{{0, 1}, cyclic_prefix::NORMAL, 14, {31}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 2, 9, 627, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols68.dat"}, {"test_data/pucch_detector_test_ch_estimates68.dat"}}, + {{{0, 4}, cyclic_prefix::NORMAL, 4, {21}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 9, 411, 1}, 0.01, {0}, {1}, {"test_data/pucch_detector_test_received_symbols69.dat"}, {"test_data/pucch_detector_test_ch_estimates69.dat"}}, + {{{0, 2}, cyclic_prefix::NORMAL, 27, {43}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 6, 331, 2}, 0.01, {}, {0, 1}, {"test_data/pucch_detector_test_received_symbols70.dat"}, {"test_data/pucch_detector_test_ch_estimates70.dat"}}, + {{{0, 3}, cyclic_prefix::NORMAL, 15, {15}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 2, 6, 411, 2}, 0.01, {0}, {0, 1}, {"test_data/pucch_detector_test_received_symbols71.dat"}, {"test_data/pucch_detector_test_ch_estimates71.dat"}}, + {{{0, 0}, cyclic_prefix::NORMAL, 48, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 3, 877, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols72.dat"}, {"test_data/pucch_detector_test_ch_estimates72.dat"}}, + {{{0, 1}, cyclic_prefix::NORMAL, 31, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 9, 685, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols73.dat"}, {"test_data/pucch_detector_test_ch_estimates73.dat"}}, + {{{0, 2}, cyclic_prefix::NORMAL, 40, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 9, 482, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols74.dat"}, {"test_data/pucch_detector_test_ch_estimates74.dat"}}, + {{{0, 9}, cyclic_prefix::NORMAL, 20, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 0, 410, 1}, 0.01, {1}, {1}, {"test_data/pucch_detector_test_received_symbols75.dat"}, {"test_data/pucch_detector_test_ch_estimates75.dat"}}, + {{{0, 2}, cyclic_prefix::NORMAL, 47, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 3, 750, 2}, 0.01, {}, {0, 0}, {"test_data/pucch_detector_test_received_symbols76.dat"}, {"test_data/pucch_detector_test_ch_estimates76.dat"}}, + {{{0, 8}, cyclic_prefix::NORMAL, 12, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 9, 4, 2}, 0.01, {1}, {1, 0}, {"test_data/pucch_detector_test_received_symbols77.dat"}, {"test_data/pucch_detector_test_ch_estimates77.dat"}}, + {{{0, 7}, cyclic_prefix::NORMAL, 27, {11}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 6, 746, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols78.dat"}, {"test_data/pucch_detector_test_ch_estimates78.dat"}}, + {{{0, 8}, cyclic_prefix::NORMAL, 41, {19}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 6, 267, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols79.dat"}, {"test_data/pucch_detector_test_ch_estimates79.dat"}}, + {{{0, 4}, cyclic_prefix::NORMAL, 3, {38}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 6, 1007, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols80.dat"}, {"test_data/pucch_detector_test_ch_estimates80.dat"}}, + {{{0, 7}, cyclic_prefix::NORMAL, 1, {33}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 9, 61, 1}, 0.01, {1}, {0}, {"test_data/pucch_detector_test_received_symbols81.dat"}, {"test_data/pucch_detector_test_ch_estimates81.dat"}}, + {{{0, 1}, cyclic_prefix::NORMAL, 19, {32}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 0, 706, 2}, 0.01, {}, {1, 1}, {"test_data/pucch_detector_test_received_symbols82.dat"}, {"test_data/pucch_detector_test_ch_estimates82.dat"}}, + {{{0, 9}, cyclic_prefix::NORMAL, 21, {25}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 6, 569, 2}, 0.01, {0}, {0, 0}, {"test_data/pucch_detector_test_received_symbols83.dat"}, {"test_data/pucch_detector_test_ch_estimates83.dat"}}, + {{{0, 2}, cyclic_prefix::NORMAL, 13, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 6, 678, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols84.dat"}, {"test_data/pucch_detector_test_ch_estimates84.dat"}}, + {{{0, 0}, cyclic_prefix::NORMAL, 4, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 6, 172, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols85.dat"}, {"test_data/pucch_detector_test_ch_estimates85.dat"}}, + {{{0, 1}, cyclic_prefix::NORMAL, 12, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 0, 601, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols86.dat"}, {"test_data/pucch_detector_test_ch_estimates86.dat"}}, + {{{0, 2}, cyclic_prefix::NORMAL, 25, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 9, 312, 1}, 0.01, {0}, {1}, {"test_data/pucch_detector_test_received_symbols87.dat"}, {"test_data/pucch_detector_test_ch_estimates87.dat"}}, + {{{0, 7}, cyclic_prefix::NORMAL, 16, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 9, 649, 2}, 0.01, {}, {0, 1}, {"test_data/pucch_detector_test_received_symbols88.dat"}, {"test_data/pucch_detector_test_ch_estimates88.dat"}}, + {{{0, 7}, cyclic_prefix::NORMAL, 17, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 9, 325, 2}, 0.01, {1}, {1, 0}, {"test_data/pucch_detector_test_received_symbols89.dat"}, {"test_data/pucch_detector_test_ch_estimates89.dat"}}, + {{{0, 6}, cyclic_prefix::NORMAL, 7, {43}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 9, 298, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols90.dat"}, {"test_data/pucch_detector_test_ch_estimates90.dat"}}, + {{{0, 8}, cyclic_prefix::NORMAL, 50, {31}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 9, 513, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols91.dat"}, {"test_data/pucch_detector_test_ch_estimates91.dat"}}, + {{{0, 8}, cyclic_prefix::NORMAL, 17, {6}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 9, 447, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols92.dat"}, {"test_data/pucch_detector_test_ch_estimates92.dat"}}, + {{{0, 9}, cyclic_prefix::NORMAL, 43, {28}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 6, 851, 1}, 0.01, {0}, {1}, {"test_data/pucch_detector_test_received_symbols93.dat"}, {"test_data/pucch_detector_test_ch_estimates93.dat"}}, + {{{0, 5}, cyclic_prefix::NORMAL, 26, {49}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 0, 254, 2}, 0.01, {}, {0, 1}, {"test_data/pucch_detector_test_received_symbols94.dat"}, {"test_data/pucch_detector_test_ch_estimates94.dat"}}, + {{{0, 2}, cyclic_prefix::NORMAL, 12, {48}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 6, 991, 2}, 0.01, {1}, {0, 0}, {"test_data/pucch_detector_test_received_symbols95.dat"}, {"test_data/pucch_detector_test_ch_estimates95.dat"}}, + {{{0, 5}, cyclic_prefix::NORMAL, 35, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 6, 12, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols96.dat"}, {"test_data/pucch_detector_test_ch_estimates96.dat"}}, + {{{0, 6}, cyclic_prefix::NORMAL, 0, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 2, 3, 312, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols97.dat"}, {"test_data/pucch_detector_test_ch_estimates97.dat"}}, + {{{0, 5}, cyclic_prefix::NORMAL, 43, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 6, 740, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols98.dat"}, {"test_data/pucch_detector_test_ch_estimates98.dat"}}, + {{{0, 4}, cyclic_prefix::NORMAL, 44, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 4, 3, 429, 1}, 0.01, {0}, {1}, {"test_data/pucch_detector_test_received_symbols99.dat"}, {"test_data/pucch_detector_test_ch_estimates99.dat"}}, + {{{0, 5}, cyclic_prefix::NORMAL, 18, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 6, 3, 836, 2}, 0.01, {}, {1, 1}, {"test_data/pucch_detector_test_received_symbols100.dat"}, {"test_data/pucch_detector_test_ch_estimates100.dat"}}, + {{{0, 9}, cyclic_prefix::NORMAL, 25, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 6, 3, 290, 2}, 0.01, {1}, {1, 1}, {"test_data/pucch_detector_test_received_symbols101.dat"}, {"test_data/pucch_detector_test_ch_estimates101.dat"}}, + {{{0, 9}, cyclic_prefix::NORMAL, 33, {8}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 9, 602, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols102.dat"}, {"test_data/pucch_detector_test_ch_estimates102.dat"}}, + {{{0, 7}, cyclic_prefix::NORMAL, 27, {11}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 0, 926, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols103.dat"}, {"test_data/pucch_detector_test_ch_estimates103.dat"}}, + {{{0, 3}, cyclic_prefix::NORMAL, 3, {30}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 0, 245, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols104.dat"}, {"test_data/pucch_detector_test_ch_estimates104.dat"}}, + {{{0, 9}, cyclic_prefix::NORMAL, 34, {29}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 3, 353, 1}, 0.01, {1}, {1}, {"test_data/pucch_detector_test_received_symbols105.dat"}, {"test_data/pucch_detector_test_ch_estimates105.dat"}}, + {{{0, 3}, cyclic_prefix::NORMAL, 38, {4}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 6, 528, 2}, 0.01, {}, {1, 0}, {"test_data/pucch_detector_test_received_symbols106.dat"}, {"test_data/pucch_detector_test_ch_estimates106.dat"}}, + {{{0, 8}, cyclic_prefix::NORMAL, 24, {32}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 6, 116, 2}, 0.01, {1}, {1, 0}, {"test_data/pucch_detector_test_received_symbols107.dat"}, {"test_data/pucch_detector_test_ch_estimates107.dat"}}, + {{{0, 1}, cyclic_prefix::NORMAL, 43, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 5, 0, 406, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols108.dat"}, {"test_data/pucch_detector_test_ch_estimates108.dat"}}, + {{{0, 3}, cyclic_prefix::NORMAL, 48, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 4, 3, 67, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols109.dat"}, {"test_data/pucch_detector_test_ch_estimates109.dat"}}, + {{{0, 3}, cyclic_prefix::NORMAL, 14, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 3, 0, 714, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols110.dat"}, {"test_data/pucch_detector_test_ch_estimates110.dat"}}, + {{{0, 6}, cyclic_prefix::NORMAL, 44, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 4, 9, 358, 1}, 0.01, {1}, {0}, {"test_data/pucch_detector_test_received_symbols111.dat"}, {"test_data/pucch_detector_test_ch_estimates111.dat"}}, + {{{0, 4}, cyclic_prefix::NORMAL, 32, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 2, 6, 946, 2}, 0.01, {}, {0, 1}, {"test_data/pucch_detector_test_received_symbols112.dat"}, {"test_data/pucch_detector_test_ch_estimates112.dat"}}, + {{{0, 6}, cyclic_prefix::NORMAL, 43, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 3, 988, 2}, 0.01, {1}, {0, 1}, {"test_data/pucch_detector_test_received_symbols113.dat"}, {"test_data/pucch_detector_test_ch_estimates113.dat"}}, + {{{0, 1}, cyclic_prefix::NORMAL, 21, {45}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 9, 324, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols114.dat"}, {"test_data/pucch_detector_test_ch_estimates114.dat"}}, + {{{0, 0}, cyclic_prefix::NORMAL, 22, {19}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 2, 3, 575, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols115.dat"}, {"test_data/pucch_detector_test_ch_estimates115.dat"}}, + {{{0, 0}, cyclic_prefix::NORMAL, 42, {35}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 0, 34, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols116.dat"}, {"test_data/pucch_detector_test_ch_estimates116.dat"}}, + {{{0, 5}, cyclic_prefix::NORMAL, 47, {49}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 9, 416, 1}, 0.01, {1}, {0}, {"test_data/pucch_detector_test_received_symbols117.dat"}, {"test_data/pucch_detector_test_ch_estimates117.dat"}}, + {{{0, 4}, cyclic_prefix::NORMAL, 17, {38}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 3, 841, 2}, 0.01, {}, {0, 0}, {"test_data/pucch_detector_test_received_symbols118.dat"}, {"test_data/pucch_detector_test_ch_estimates118.dat"}}, + {{{0, 4}, cyclic_prefix::NORMAL, 19, {35}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 2, 3, 89, 2}, 0.01, {0}, {1, 0}, {"test_data/pucch_detector_test_received_symbols119.dat"}, {"test_data/pucch_detector_test_ch_estimates119.dat"}}, + {{{0, 6}, cyclic_prefix::NORMAL, 49, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 9, 927, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols120.dat"}, {"test_data/pucch_detector_test_ch_estimates120.dat"}}, + {{{0, 0}, cyclic_prefix::NORMAL, 11, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 3, 507, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols121.dat"}, {"test_data/pucch_detector_test_ch_estimates121.dat"}}, + {{{0, 8}, cyclic_prefix::NORMAL, 3, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 0, 811, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols122.dat"}, {"test_data/pucch_detector_test_ch_estimates122.dat"}}, + {{{0, 0}, cyclic_prefix::NORMAL, 38, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 0, 770, 1}, 0.01, {0}, {1}, {"test_data/pucch_detector_test_received_symbols123.dat"}, {"test_data/pucch_detector_test_ch_estimates123.dat"}}, + {{{0, 3}, cyclic_prefix::NORMAL, 36, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 0, 733, 2}, 0.01, {}, {0, 0}, {"test_data/pucch_detector_test_received_symbols124.dat"}, {"test_data/pucch_detector_test_ch_estimates124.dat"}}, + {{{0, 6}, cyclic_prefix::NORMAL, 24, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 0, 880, 2}, 0.01, {0}, {1, 0}, {"test_data/pucch_detector_test_received_symbols125.dat"}, {"test_data/pucch_detector_test_ch_estimates125.dat"}}, + {{{0, 3}, cyclic_prefix::NORMAL, 6, {31}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 9, 623, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols126.dat"}, {"test_data/pucch_detector_test_ch_estimates126.dat"}}, + {{{0, 1}, cyclic_prefix::NORMAL, 48, {38}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 3, 551, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols127.dat"}, {"test_data/pucch_detector_test_ch_estimates127.dat"}}, + {{{0, 6}, cyclic_prefix::NORMAL, 48, {3}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 6, 653, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols128.dat"}, {"test_data/pucch_detector_test_ch_estimates128.dat"}}, + {{{0, 8}, cyclic_prefix::NORMAL, 42, {29}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 3, 400, 1}, 0.01, {0}, {1}, {"test_data/pucch_detector_test_received_symbols129.dat"}, {"test_data/pucch_detector_test_ch_estimates129.dat"}}, + {{{0, 2}, cyclic_prefix::NORMAL, 17, {49}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 6, 142, 2}, 0.01, {}, {0, 1}, {"test_data/pucch_detector_test_received_symbols130.dat"}, {"test_data/pucch_detector_test_ch_estimates130.dat"}}, + {{{0, 5}, cyclic_prefix::NORMAL, 10, {3}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 9, 542, 2}, 0.01, {1}, {1, 1}, {"test_data/pucch_detector_test_received_symbols131.dat"}, {"test_data/pucch_detector_test_ch_estimates131.dat"}}, + {{{0, 9}, cyclic_prefix::NORMAL, 23, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 3, 508, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols132.dat"}, {"test_data/pucch_detector_test_ch_estimates132.dat"}}, + {{{0, 7}, cyclic_prefix::NORMAL, 4, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 9, 176, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols133.dat"}, {"test_data/pucch_detector_test_ch_estimates133.dat"}}, + {{{0, 4}, cyclic_prefix::NORMAL, 22, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 3, 642, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols134.dat"}, {"test_data/pucch_detector_test_ch_estimates134.dat"}}, + {{{0, 7}, cyclic_prefix::NORMAL, 43, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 0, 317, 1}, 0.01, {1}, {1}, {"test_data/pucch_detector_test_received_symbols135.dat"}, {"test_data/pucch_detector_test_ch_estimates135.dat"}}, + {{{0, 8}, cyclic_prefix::NORMAL, 27, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 6, 731, 2}, 0.01, {}, {0, 1}, {"test_data/pucch_detector_test_received_symbols136.dat"}, {"test_data/pucch_detector_test_ch_estimates136.dat"}}, + {{{0, 4}, cyclic_prefix::NORMAL, 29, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 9, 173, 2}, 0.01, {1}, {1, 1}, {"test_data/pucch_detector_test_received_symbols137.dat"}, {"test_data/pucch_detector_test_ch_estimates137.dat"}}, + {{{0, 6}, cyclic_prefix::NORMAL, 6, {13}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 0, 243, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols138.dat"}, {"test_data/pucch_detector_test_ch_estimates138.dat"}}, + {{{0, 2}, cyclic_prefix::NORMAL, 24, {3}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 6, 625, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols139.dat"}, {"test_data/pucch_detector_test_ch_estimates139.dat"}}, + {{{0, 4}, cyclic_prefix::NORMAL, 50, {36}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 9, 649, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols140.dat"}, {"test_data/pucch_detector_test_ch_estimates140.dat"}}, + {{{0, 9}, cyclic_prefix::NORMAL, 48, {49}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 9, 240, 1}, 0.01, {0}, {0}, {"test_data/pucch_detector_test_received_symbols141.dat"}, {"test_data/pucch_detector_test_ch_estimates141.dat"}}, + {{{0, 8}, cyclic_prefix::NORMAL, 38, {21}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 0, 350, 2}, 0.01, {}, {1, 1}, {"test_data/pucch_detector_test_received_symbols142.dat"}, {"test_data/pucch_detector_test_ch_estimates142.dat"}}, + {{{0, 4}, cyclic_prefix::NORMAL, 45, {19}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 0, 765, 2}, 0.01, {1}, {1, 0}, {"test_data/pucch_detector_test_received_symbols143.dat"}, {"test_data/pucch_detector_test_ch_estimates143.dat"}}, + {{{1, 3}, cyclic_prefix::NORMAL, 14, {}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 2, 9, 453, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols144.dat"}, {"test_data/pucch_detector_test_ch_estimates144.dat"}}, + {{{1, 13}, cyclic_prefix::NORMAL, 16, {}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 2, 0, 109, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols145.dat"}, {"test_data/pucch_detector_test_ch_estimates145.dat"}}, + {{{1, 13}, cyclic_prefix::NORMAL, 2, {}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 0, 0, 359, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols146.dat"}, {"test_data/pucch_detector_test_ch_estimates146.dat"}}, + {{{1, 0}, cyclic_prefix::NORMAL, 45, {}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 6, 6, 498, 1}, 0.01, {1}, {0}, {"test_data/pucch_detector_test_received_symbols147.dat"}, {"test_data/pucch_detector_test_ch_estimates147.dat"}}, + {{{1, 15}, cyclic_prefix::NORMAL, 33, {}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 1, 6, 609, 2}, 0.01, {}, {1, 0}, {"test_data/pucch_detector_test_received_symbols148.dat"}, {"test_data/pucch_detector_test_ch_estimates148.dat"}}, + {{{1, 12}, cyclic_prefix::NORMAL, 47, {}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 6, 6, 992, 2}, 0.01, {1}, {0, 0}, {"test_data/pucch_detector_test_received_symbols149.dat"}, {"test_data/pucch_detector_test_ch_estimates149.dat"}}, + {{{1, 7}, cyclic_prefix::NORMAL, 10, {21}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 2, 0, 814, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols150.dat"}, {"test_data/pucch_detector_test_ch_estimates150.dat"}}, + {{{1, 17}, cyclic_prefix::NORMAL, 24, {19}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 2, 9, 400, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols151.dat"}, {"test_data/pucch_detector_test_ch_estimates151.dat"}}, + {{{1, 0}, cyclic_prefix::NORMAL, 25, {47}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 0, 6, 524, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols152.dat"}, {"test_data/pucch_detector_test_ch_estimates152.dat"}}, + {{{1, 7}, cyclic_prefix::NORMAL, 32, {20}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 0, 3, 808, 1}, 0.01, {1}, {1}, {"test_data/pucch_detector_test_received_symbols153.dat"}, {"test_data/pucch_detector_test_ch_estimates153.dat"}}, + {{{1, 2}, cyclic_prefix::NORMAL, 49, {47}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 1, 0, 473, 2}, 0.01, {}, {1, 1}, {"test_data/pucch_detector_test_received_symbols154.dat"}, {"test_data/pucch_detector_test_ch_estimates154.dat"}}, + {{{1, 18}, cyclic_prefix::NORMAL, 19, {4}, 0, 14, pucch_group_hopping::NEITHER, {0}, 1, 0, 9, 382, 2}, 0.01, {0}, {0, 1}, {"test_data/pucch_detector_test_received_symbols155.dat"}, {"test_data/pucch_detector_test_ch_estimates155.dat"}}, + {{{1, 19}, cyclic_prefix::NORMAL, 1, {}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 2, 6, 561, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols156.dat"}, {"test_data/pucch_detector_test_ch_estimates156.dat"}}, + {{{1, 0}, cyclic_prefix::NORMAL, 14, {}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 2, 9, 372, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols157.dat"}, {"test_data/pucch_detector_test_ch_estimates157.dat"}}, + {{{1, 11}, cyclic_prefix::NORMAL, 2, {}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 5, 6, 288, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols158.dat"}, {"test_data/pucch_detector_test_ch_estimates158.dat"}}, + {{{1, 19}, cyclic_prefix::NORMAL, 18, {}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 0, 3, 565, 1}, 0.01, {1}, {0}, {"test_data/pucch_detector_test_received_symbols159.dat"}, {"test_data/pucch_detector_test_ch_estimates159.dat"}}, + {{{1, 17}, cyclic_prefix::NORMAL, 33, {}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 3, 9, 302, 2}, 0.01, {}, {1, 1}, {"test_data/pucch_detector_test_received_symbols160.dat"}, {"test_data/pucch_detector_test_ch_estimates160.dat"}}, + {{{1, 11}, cyclic_prefix::NORMAL, 48, {}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 3, 0, 905, 2}, 0.01, {1}, {0, 1}, {"test_data/pucch_detector_test_received_symbols161.dat"}, {"test_data/pucch_detector_test_ch_estimates161.dat"}}, + {{{1, 19}, cyclic_prefix::NORMAL, 39, {41}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 0, 3, 651, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols162.dat"}, {"test_data/pucch_detector_test_ch_estimates162.dat"}}, + {{{1, 15}, cyclic_prefix::NORMAL, 21, {2}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 0, 0, 290, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols163.dat"}, {"test_data/pucch_detector_test_ch_estimates163.dat"}}, + {{{1, 1}, cyclic_prefix::NORMAL, 32, {37}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 0, 9, 153, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols164.dat"}, {"test_data/pucch_detector_test_ch_estimates164.dat"}}, + {{{1, 10}, cyclic_prefix::NORMAL, 26, {38}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 2, 9, 609, 1}, 0.01, {1}, {0}, {"test_data/pucch_detector_test_received_symbols165.dat"}, {"test_data/pucch_detector_test_ch_estimates165.dat"}}, + {{{1, 1}, cyclic_prefix::NORMAL, 8, {45}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 1, 0, 169, 2}, 0.01, {}, {1, 0}, {"test_data/pucch_detector_test_received_symbols166.dat"}, {"test_data/pucch_detector_test_ch_estimates166.dat"}}, + {{{1, 15}, cyclic_prefix::NORMAL, 13, {41}, 1, 13, pucch_group_hopping::NEITHER, {0}, 1, 1, 0, 758, 2}, 0.01, {1}, {0, 0}, {"test_data/pucch_detector_test_received_symbols167.dat"}, {"test_data/pucch_detector_test_ch_estimates167.dat"}}, + {{{1, 19}, cyclic_prefix::NORMAL, 34, {}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 1, 3, 547, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols168.dat"}, {"test_data/pucch_detector_test_ch_estimates168.dat"}}, + {{{1, 13}, cyclic_prefix::NORMAL, 49, {}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 0, 6, 655, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols169.dat"}, {"test_data/pucch_detector_test_ch_estimates169.dat"}}, + {{{1, 17}, cyclic_prefix::NORMAL, 22, {}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 1, 9, 805, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols170.dat"}, {"test_data/pucch_detector_test_ch_estimates170.dat"}}, + {{{1, 3}, cyclic_prefix::NORMAL, 38, {}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 1, 9, 718, 1}, 0.01, {0}, {1}, {"test_data/pucch_detector_test_received_symbols171.dat"}, {"test_data/pucch_detector_test_ch_estimates171.dat"}}, + {{{1, 4}, cyclic_prefix::NORMAL, 9, {}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 0, 9, 183, 2}, 0.01, {}, {1, 0}, {"test_data/pucch_detector_test_received_symbols172.dat"}, {"test_data/pucch_detector_test_ch_estimates172.dat"}}, + {{{1, 5}, cyclic_prefix::NORMAL, 31, {}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 1, 0, 744, 2}, 0.01, {1}, {1, 0}, {"test_data/pucch_detector_test_received_symbols173.dat"}, {"test_data/pucch_detector_test_ch_estimates173.dat"}}, + {{{1, 13}, cyclic_prefix::NORMAL, 1, {11}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 0, 6, 773, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols174.dat"}, {"test_data/pucch_detector_test_ch_estimates174.dat"}}, + {{{1, 4}, cyclic_prefix::NORMAL, 27, {40}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 0, 9, 196, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols175.dat"}, {"test_data/pucch_detector_test_ch_estimates175.dat"}}, + {{{1, 11}, cyclic_prefix::NORMAL, 30, {25}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 0, 3, 157, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols176.dat"}, {"test_data/pucch_detector_test_ch_estimates176.dat"}}, + {{{1, 14}, cyclic_prefix::NORMAL, 20, {44}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 0, 9, 968, 1}, 0.01, {1}, {1}, {"test_data/pucch_detector_test_received_symbols177.dat"}, {"test_data/pucch_detector_test_ch_estimates177.dat"}}, + {{{1, 9}, cyclic_prefix::NORMAL, 18, {16}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 0, 3, 74, 2}, 0.01, {}, {1, 0}, {"test_data/pucch_detector_test_received_symbols178.dat"}, {"test_data/pucch_detector_test_ch_estimates178.dat"}}, + {{{1, 6}, cyclic_prefix::NORMAL, 36, {3}, 5, 5, pucch_group_hopping::NEITHER, {0}, 1, 0, 0, 166, 2}, 0.01, {0}, {0, 1}, {"test_data/pucch_detector_test_received_symbols179.dat"}, {"test_data/pucch_detector_test_ch_estimates179.dat"}}, + {{{1, 2}, cyclic_prefix::NORMAL, 39, {}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 0, 9, 259, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols180.dat"}, {"test_data/pucch_detector_test_ch_estimates180.dat"}}, + {{{1, 16}, cyclic_prefix::NORMAL, 47, {}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 1, 0, 259, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols181.dat"}, {"test_data/pucch_detector_test_ch_estimates181.dat"}}, + {{{1, 7}, cyclic_prefix::NORMAL, 23, {}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 1, 0, 771, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols182.dat"}, {"test_data/pucch_detector_test_ch_estimates182.dat"}}, + {{{1, 17}, cyclic_prefix::NORMAL, 16, {}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 1, 0, 753, 1}, 0.01, {0}, {0}, {"test_data/pucch_detector_test_received_symbols183.dat"}, {"test_data/pucch_detector_test_ch_estimates183.dat"}}, + {{{1, 7}, cyclic_prefix::NORMAL, 40, {}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 1, 9, 154, 2}, 0.01, {}, {1, 1}, {"test_data/pucch_detector_test_received_symbols184.dat"}, {"test_data/pucch_detector_test_ch_estimates184.dat"}}, + {{{1, 7}, cyclic_prefix::NORMAL, 25, {}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 1, 6, 18, 2}, 0.01, {1}, {1, 1}, {"test_data/pucch_detector_test_received_symbols185.dat"}, {"test_data/pucch_detector_test_ch_estimates185.dat"}}, + {{{1, 7}, cyclic_prefix::NORMAL, 14, {34}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 0, 0, 234, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols186.dat"}, {"test_data/pucch_detector_test_ch_estimates186.dat"}}, + {{{1, 1}, cyclic_prefix::NORMAL, 46, {5}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 0, 0, 774, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols187.dat"}, {"test_data/pucch_detector_test_ch_estimates187.dat"}}, + {{{1, 4}, cyclic_prefix::NORMAL, 7, {23}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 0, 0, 57, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols188.dat"}, {"test_data/pucch_detector_test_ch_estimates188.dat"}}, + {{{1, 1}, cyclic_prefix::NORMAL, 21, {21}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 0, 6, 844, 1}, 0.01, {1}, {0}, {"test_data/pucch_detector_test_received_symbols189.dat"}, {"test_data/pucch_detector_test_ch_estimates189.dat"}}, + {{{1, 12}, cyclic_prefix::NORMAL, 0, {19}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 0, 0, 852, 2}, 0.01, {}, {1, 1}, {"test_data/pucch_detector_test_received_symbols190.dat"}, {"test_data/pucch_detector_test_ch_estimates190.dat"}}, + {{{1, 2}, cyclic_prefix::NORMAL, 12, {33}, 10, 4, pucch_group_hopping::NEITHER, {0}, 1, 0, 9, 628, 2}, 0.01, {1}, {1, 0}, {"test_data/pucch_detector_test_received_symbols191.dat"}, {"test_data/pucch_detector_test_ch_estimates191.dat"}}, + {{{1, 16}, cyclic_prefix::NORMAL, 46, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 5, 9, 404, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols192.dat"}, {"test_data/pucch_detector_test_ch_estimates192.dat"}}, + {{{1, 17}, cyclic_prefix::NORMAL, 13, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 9, 18, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols193.dat"}, {"test_data/pucch_detector_test_ch_estimates193.dat"}}, + {{{1, 11}, cyclic_prefix::NORMAL, 43, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 6, 782, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols194.dat"}, {"test_data/pucch_detector_test_ch_estimates194.dat"}}, + {{{1, 3}, cyclic_prefix::NORMAL, 34, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 6, 0, 546, 1}, 0.01, {0}, {0}, {"test_data/pucch_detector_test_received_symbols195.dat"}, {"test_data/pucch_detector_test_ch_estimates195.dat"}}, + {{{1, 12}, cyclic_prefix::NORMAL, 15, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 6, 9, 891, 2}, 0.01, {}, {0, 0}, {"test_data/pucch_detector_test_received_symbols196.dat"}, {"test_data/pucch_detector_test_ch_estimates196.dat"}}, + {{{1, 7}, cyclic_prefix::NORMAL, 20, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 5, 9, 562, 2}, 0.01, {0}, {1, 0}, {"test_data/pucch_detector_test_received_symbols197.dat"}, {"test_data/pucch_detector_test_ch_estimates197.dat"}}, + {{{1, 10}, cyclic_prefix::NORMAL, 32, {25}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 3, 485, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols198.dat"}, {"test_data/pucch_detector_test_ch_estimates198.dat"}}, + {{{1, 19}, cyclic_prefix::NORMAL, 40, {26}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 2, 3, 190, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols199.dat"}, {"test_data/pucch_detector_test_ch_estimates199.dat"}}, + {{{1, 13}, cyclic_prefix::NORMAL, 13, {13}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 3, 644, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols200.dat"}, {"test_data/pucch_detector_test_ch_estimates200.dat"}}, + {{{1, 18}, cyclic_prefix::NORMAL, 33, {16}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 3, 781, 1}, 0.01, {1}, {0}, {"test_data/pucch_detector_test_received_symbols201.dat"}, {"test_data/pucch_detector_test_ch_estimates201.dat"}}, + {{{1, 2}, cyclic_prefix::NORMAL, 50, {43}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 2, 6, 161, 2}, 0.01, {}, {0, 0}, {"test_data/pucch_detector_test_received_symbols202.dat"}, {"test_data/pucch_detector_test_ch_estimates202.dat"}}, + {{{1, 2}, cyclic_prefix::NORMAL, 42, {39}, 0, 14, pucch_group_hopping::NEITHER, {0, 1}, 1, 2, 0, 734, 2}, 0.01, {1}, {0, 1}, {"test_data/pucch_detector_test_received_symbols203.dat"}, {"test_data/pucch_detector_test_ch_estimates203.dat"}}, + {{{1, 7}, cyclic_prefix::NORMAL, 17, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 3, 751, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols204.dat"}, {"test_data/pucch_detector_test_ch_estimates204.dat"}}, + {{{1, 0}, cyclic_prefix::NORMAL, 38, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 4, 0, 534, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols205.dat"}, {"test_data/pucch_detector_test_ch_estimates205.dat"}}, + {{{1, 11}, cyclic_prefix::NORMAL, 17, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 3, 6, 340, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols206.dat"}, {"test_data/pucch_detector_test_ch_estimates206.dat"}}, + {{{1, 13}, cyclic_prefix::NORMAL, 38, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 3, 6, 838, 1}, 0.01, {0}, {1}, {"test_data/pucch_detector_test_received_symbols207.dat"}, {"test_data/pucch_detector_test_ch_estimates207.dat"}}, + {{{1, 10}, cyclic_prefix::NORMAL, 10, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 9, 361, 2}, 0.01, {}, {1, 0}, {"test_data/pucch_detector_test_received_symbols208.dat"}, {"test_data/pucch_detector_test_ch_estimates208.dat"}}, + {{{1, 10}, cyclic_prefix::NORMAL, 35, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 5, 3, 237, 2}, 0.01, {0}, {0, 0}, {"test_data/pucch_detector_test_received_symbols209.dat"}, {"test_data/pucch_detector_test_ch_estimates209.dat"}}, + {{{1, 0}, cyclic_prefix::NORMAL, 20, {19}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 0, 979, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols210.dat"}, {"test_data/pucch_detector_test_ch_estimates210.dat"}}, + {{{1, 9}, cyclic_prefix::NORMAL, 9, {5}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 2, 6, 644, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols211.dat"}, {"test_data/pucch_detector_test_ch_estimates211.dat"}}, + {{{1, 0}, cyclic_prefix::NORMAL, 31, {9}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 2, 0, 247, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols212.dat"}, {"test_data/pucch_detector_test_ch_estimates212.dat"}}, + {{{1, 8}, cyclic_prefix::NORMAL, 14, {23}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 2, 6, 560, 1}, 0.01, {1}, {1}, {"test_data/pucch_detector_test_received_symbols213.dat"}, {"test_data/pucch_detector_test_ch_estimates213.dat"}}, + {{{1, 13}, cyclic_prefix::NORMAL, 37, {14}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 3, 927, 2}, 0.01, {}, {0, 1}, {"test_data/pucch_detector_test_received_symbols214.dat"}, {"test_data/pucch_detector_test_ch_estimates214.dat"}}, + {{{1, 3}, cyclic_prefix::NORMAL, 14, {45}, 1, 13, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 3, 277, 2}, 0.01, {1}, {0, 0}, {"test_data/pucch_detector_test_received_symbols215.dat"}, {"test_data/pucch_detector_test_ch_estimates215.dat"}}, + {{{1, 19}, cyclic_prefix::NORMAL, 15, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 0, 906, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols216.dat"}, {"test_data/pucch_detector_test_ch_estimates216.dat"}}, + {{{1, 13}, cyclic_prefix::NORMAL, 8, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 3, 532, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols217.dat"}, {"test_data/pucch_detector_test_ch_estimates217.dat"}}, + {{{1, 2}, cyclic_prefix::NORMAL, 39, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 0, 99, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols218.dat"}, {"test_data/pucch_detector_test_ch_estimates218.dat"}}, + {{{1, 12}, cyclic_prefix::NORMAL, 48, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 0, 103, 1}, 0.01, {1}, {0}, {"test_data/pucch_detector_test_received_symbols219.dat"}, {"test_data/pucch_detector_test_ch_estimates219.dat"}}, + {{{1, 13}, cyclic_prefix::NORMAL, 10, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 6, 943, 2}, 0.01, {}, {1, 0}, {"test_data/pucch_detector_test_received_symbols220.dat"}, {"test_data/pucch_detector_test_ch_estimates220.dat"}}, + {{{1, 18}, cyclic_prefix::NORMAL, 45, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 3, 276, 2}, 0.01, {0}, {1, 0}, {"test_data/pucch_detector_test_received_symbols221.dat"}, {"test_data/pucch_detector_test_ch_estimates221.dat"}}, + {{{1, 1}, cyclic_prefix::NORMAL, 5, {50}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 0, 619, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols222.dat"}, {"test_data/pucch_detector_test_ch_estimates222.dat"}}, + {{{1, 10}, cyclic_prefix::NORMAL, 0, {1}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 3, 907, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols223.dat"}, {"test_data/pucch_detector_test_ch_estimates223.dat"}}, + {{{1, 3}, cyclic_prefix::NORMAL, 6, {45}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 3, 999, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols224.dat"}, {"test_data/pucch_detector_test_ch_estimates224.dat"}}, + {{{1, 6}, cyclic_prefix::NORMAL, 43, {37}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 9, 495, 1}, 0.01, {0}, {1}, {"test_data/pucch_detector_test_received_symbols225.dat"}, {"test_data/pucch_detector_test_ch_estimates225.dat"}}, + {{{1, 17}, cyclic_prefix::NORMAL, 20, {41}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 3, 508, 2}, 0.01, {}, {1, 0}, {"test_data/pucch_detector_test_received_symbols226.dat"}, {"test_data/pucch_detector_test_ch_estimates226.dat"}}, + {{{1, 10}, cyclic_prefix::NORMAL, 36, {2}, 5, 5, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 9, 834, 2}, 0.01, {0}, {0, 0}, {"test_data/pucch_detector_test_received_symbols227.dat"}, {"test_data/pucch_detector_test_ch_estimates227.dat"}}, + {{{1, 19}, cyclic_prefix::NORMAL, 36, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 6, 100, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols228.dat"}, {"test_data/pucch_detector_test_ch_estimates228.dat"}}, + {{{1, 17}, cyclic_prefix::NORMAL, 38, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 9, 729, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols229.dat"}, {"test_data/pucch_detector_test_ch_estimates229.dat"}}, + {{{1, 1}, cyclic_prefix::NORMAL, 48, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 9, 622, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols230.dat"}, {"test_data/pucch_detector_test_ch_estimates230.dat"}}, + {{{1, 10}, cyclic_prefix::NORMAL, 14, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 6, 376, 1}, 0.01, {1}, {0}, {"test_data/pucch_detector_test_received_symbols231.dat"}, {"test_data/pucch_detector_test_ch_estimates231.dat"}}, + {{{1, 6}, cyclic_prefix::NORMAL, 0, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 1, 3, 186, 2}, 0.01, {}, {1, 1}, {"test_data/pucch_detector_test_received_symbols232.dat"}, {"test_data/pucch_detector_test_ch_estimates232.dat"}}, + {{{1, 10}, cyclic_prefix::NORMAL, 22, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 6, 860, 2}, 0.01, {0}, {0, 1}, {"test_data/pucch_detector_test_received_symbols233.dat"}, {"test_data/pucch_detector_test_ch_estimates233.dat"}}, + {{{1, 3}, cyclic_prefix::NORMAL, 25, {43}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 0, 887, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols234.dat"}, {"test_data/pucch_detector_test_ch_estimates234.dat"}}, + {{{1, 18}, cyclic_prefix::NORMAL, 45, {34}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 0, 10, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols235.dat"}, {"test_data/pucch_detector_test_ch_estimates235.dat"}}, + {{{1, 12}, cyclic_prefix::NORMAL, 45, {39}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 0, 603, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols236.dat"}, {"test_data/pucch_detector_test_ch_estimates236.dat"}}, + {{{1, 9}, cyclic_prefix::NORMAL, 4, {18}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 0, 1006, 1}, 0.01, {0}, {0}, {"test_data/pucch_detector_test_received_symbols237.dat"}, {"test_data/pucch_detector_test_ch_estimates237.dat"}}, + {{{1, 0}, cyclic_prefix::NORMAL, 28, {5}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 3, 98, 2}, 0.01, {}, {1, 1}, {"test_data/pucch_detector_test_received_symbols238.dat"}, {"test_data/pucch_detector_test_ch_estimates238.dat"}}, + {{{1, 14}, cyclic_prefix::NORMAL, 39, {9}, 10, 4, pucch_group_hopping::NEITHER, {0, 1}, 1, 0, 3, 876, 2}, 0.01, {1}, {1, 0}, {"test_data/pucch_detector_test_received_symbols239.dat"}, {"test_data/pucch_detector_test_ch_estimates239.dat"}}, + {{{1, 5}, cyclic_prefix::NORMAL, 28, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 4, 6, 675, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols240.dat"}, {"test_data/pucch_detector_test_ch_estimates240.dat"}}, + {{{1, 12}, cyclic_prefix::NORMAL, 45, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 2, 0, 858, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols241.dat"}, {"test_data/pucch_detector_test_ch_estimates241.dat"}}, + {{{1, 5}, cyclic_prefix::NORMAL, 7, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 4, 3, 226, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols242.dat"}, {"test_data/pucch_detector_test_ch_estimates242.dat"}}, + {{{1, 3}, cyclic_prefix::NORMAL, 44, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 0, 349, 1}, 0.01, {0}, {0}, {"test_data/pucch_detector_test_received_symbols243.dat"}, {"test_data/pucch_detector_test_ch_estimates243.dat"}}, + {{{1, 10}, cyclic_prefix::NORMAL, 10, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 5, 0, 426, 2}, 0.01, {}, {0, 1}, {"test_data/pucch_detector_test_received_symbols244.dat"}, {"test_data/pucch_detector_test_ch_estimates244.dat"}}, + {{{1, 6}, cyclic_prefix::NORMAL, 39, {}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 5, 9, 661, 2}, 0.01, {0}, {1, 1}, {"test_data/pucch_detector_test_received_symbols245.dat"}, {"test_data/pucch_detector_test_ch_estimates245.dat"}}, + {{{1, 13}, cyclic_prefix::NORMAL, 2, {42}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 2, 9, 202, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols246.dat"}, {"test_data/pucch_detector_test_ch_estimates246.dat"}}, + {{{1, 14}, cyclic_prefix::NORMAL, 35, {40}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 3, 215, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols247.dat"}, {"test_data/pucch_detector_test_ch_estimates247.dat"}}, + {{{1, 12}, cyclic_prefix::NORMAL, 4, {38}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 9, 563, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols248.dat"}, {"test_data/pucch_detector_test_ch_estimates248.dat"}}, + {{{1, 1}, cyclic_prefix::NORMAL, 2, {37}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 2, 6, 733, 1}, 0.01, {1}, {0}, {"test_data/pucch_detector_test_received_symbols249.dat"}, {"test_data/pucch_detector_test_ch_estimates249.dat"}}, + {{{1, 19}, cyclic_prefix::NORMAL, 17, {29}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 2, 3, 877, 2}, 0.01, {}, {0, 0}, {"test_data/pucch_detector_test_received_symbols250.dat"}, {"test_data/pucch_detector_test_ch_estimates250.dat"}}, + {{{1, 10}, cyclic_prefix::NORMAL, 24, {22}, 0, 14, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 2, 3, 530, 2}, 0.01, {1}, {1, 0}, {"test_data/pucch_detector_test_received_symbols251.dat"}, {"test_data/pucch_detector_test_ch_estimates251.dat"}}, + {{{1, 13}, cyclic_prefix::NORMAL, 31, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 6, 561, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols252.dat"}, {"test_data/pucch_detector_test_ch_estimates252.dat"}}, + {{{1, 15}, cyclic_prefix::NORMAL, 2, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 5, 0, 211, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols253.dat"}, {"test_data/pucch_detector_test_ch_estimates253.dat"}}, + {{{1, 18}, cyclic_prefix::NORMAL, 37, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 4, 6, 710, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols254.dat"}, {"test_data/pucch_detector_test_ch_estimates254.dat"}}, + {{{1, 13}, cyclic_prefix::NORMAL, 31, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 4, 6, 531, 1}, 0.01, {0}, {1}, {"test_data/pucch_detector_test_received_symbols255.dat"}, {"test_data/pucch_detector_test_ch_estimates255.dat"}}, + {{{1, 11}, cyclic_prefix::NORMAL, 18, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 3, 3, 842, 2}, 0.01, {}, {0, 1}, {"test_data/pucch_detector_test_received_symbols256.dat"}, {"test_data/pucch_detector_test_ch_estimates256.dat"}}, + {{{1, 19}, cyclic_prefix::NORMAL, 7, {}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 3, 239, 2}, 0.01, {0}, {0, 0}, {"test_data/pucch_detector_test_received_symbols257.dat"}, {"test_data/pucch_detector_test_ch_estimates257.dat"}}, + {{{1, 11}, cyclic_prefix::NORMAL, 13, {50}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 0, 433, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols258.dat"}, {"test_data/pucch_detector_test_ch_estimates258.dat"}}, + {{{1, 18}, cyclic_prefix::NORMAL, 30, {39}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 9, 597, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols259.dat"}, {"test_data/pucch_detector_test_ch_estimates259.dat"}}, + {{{1, 17}, cyclic_prefix::NORMAL, 10, {30}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 2, 3, 846, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols260.dat"}, {"test_data/pucch_detector_test_ch_estimates260.dat"}}, + {{{1, 4}, cyclic_prefix::NORMAL, 49, {36}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 0, 195, 1}, 0.01, {0}, {1}, {"test_data/pucch_detector_test_received_symbols261.dat"}, {"test_data/pucch_detector_test_ch_estimates261.dat"}}, + {{{1, 13}, cyclic_prefix::NORMAL, 35, {40}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 2, 3, 843, 2}, 0.01, {}, {1, 0}, {"test_data/pucch_detector_test_received_symbols262.dat"}, {"test_data/pucch_detector_test_ch_estimates262.dat"}}, + {{{1, 12}, cyclic_prefix::NORMAL, 7, {38}, 1, 13, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 2, 9, 849, 2}, 0.01, {0}, {1, 0}, {"test_data/pucch_detector_test_received_symbols263.dat"}, {"test_data/pucch_detector_test_ch_estimates263.dat"}}, + {{{1, 6}, cyclic_prefix::NORMAL, 29, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 0, 664, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols264.dat"}, {"test_data/pucch_detector_test_ch_estimates264.dat"}}, + {{{1, 1}, cyclic_prefix::NORMAL, 26, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 3, 497, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols265.dat"}, {"test_data/pucch_detector_test_ch_estimates265.dat"}}, + {{{1, 14}, cyclic_prefix::NORMAL, 18, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 3, 227, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols266.dat"}, {"test_data/pucch_detector_test_ch_estimates266.dat"}}, + {{{1, 15}, cyclic_prefix::NORMAL, 6, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 3, 515, 1}, 0.01, {0}, {0}, {"test_data/pucch_detector_test_received_symbols267.dat"}, {"test_data/pucch_detector_test_ch_estimates267.dat"}}, + {{{1, 6}, cyclic_prefix::NORMAL, 30, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 3, 142, 2}, 0.01, {}, {0, 0}, {"test_data/pucch_detector_test_received_symbols268.dat"}, {"test_data/pucch_detector_test_ch_estimates268.dat"}}, + {{{1, 0}, cyclic_prefix::NORMAL, 43, {}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 9, 155, 2}, 0.01, {1}, {0, 0}, {"test_data/pucch_detector_test_received_symbols269.dat"}, {"test_data/pucch_detector_test_ch_estimates269.dat"}}, + {{{1, 4}, cyclic_prefix::NORMAL, 7, {40}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 0, 255, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols270.dat"}, {"test_data/pucch_detector_test_ch_estimates270.dat"}}, + {{{1, 16}, cyclic_prefix::NORMAL, 7, {38}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 0, 341, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols271.dat"}, {"test_data/pucch_detector_test_ch_estimates271.dat"}}, + {{{1, 4}, cyclic_prefix::NORMAL, 43, {49}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 3, 620, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols272.dat"}, {"test_data/pucch_detector_test_ch_estimates272.dat"}}, + {{{1, 16}, cyclic_prefix::NORMAL, 11, {9}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 9, 942, 1}, 0.01, {1}, {0}, {"test_data/pucch_detector_test_received_symbols273.dat"}, {"test_data/pucch_detector_test_ch_estimates273.dat"}}, + {{{1, 17}, cyclic_prefix::NORMAL, 29, {41}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 0, 245, 2}, 0.01, {}, {0, 1}, {"test_data/pucch_detector_test_received_symbols274.dat"}, {"test_data/pucch_detector_test_ch_estimates274.dat"}}, + {{{1, 18}, cyclic_prefix::NORMAL, 17, {46}, 5, 5, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 3, 867, 2}, 0.01, {1}, {0, 0}, {"test_data/pucch_detector_test_received_symbols275.dat"}, {"test_data/pucch_detector_test_ch_estimates275.dat"}}, + {{{1, 8}, cyclic_prefix::NORMAL, 24, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 9, 110, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols276.dat"}, {"test_data/pucch_detector_test_ch_estimates276.dat"}}, + {{{1, 9}, cyclic_prefix::NORMAL, 47, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 9, 510, 0}, 0.01, {1}, {}, {"test_data/pucch_detector_test_received_symbols277.dat"}, {"test_data/pucch_detector_test_ch_estimates277.dat"}}, + {{{1, 4}, cyclic_prefix::NORMAL, 34, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 9, 715, 1}, 0.01, {}, {0}, {"test_data/pucch_detector_test_received_symbols278.dat"}, {"test_data/pucch_detector_test_ch_estimates278.dat"}}, + {{{1, 18}, cyclic_prefix::NORMAL, 48, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 3, 500, 1}, 0.01, {0}, {0}, {"test_data/pucch_detector_test_received_symbols279.dat"}, {"test_data/pucch_detector_test_ch_estimates279.dat"}}, + {{{1, 18}, cyclic_prefix::NORMAL, 28, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 1, 6, 811, 2}, 0.01, {}, {1, 1}, {"test_data/pucch_detector_test_received_symbols280.dat"}, {"test_data/pucch_detector_test_ch_estimates280.dat"}}, + {{{1, 1}, cyclic_prefix::NORMAL, 36, {}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 9, 109, 2}, 0.01, {0}, {1, 0}, {"test_data/pucch_detector_test_received_symbols281.dat"}, {"test_data/pucch_detector_test_ch_estimates281.dat"}}, + {{{1, 11}, cyclic_prefix::NORMAL, 15, {11}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 3, 479, 0}, 0.01, {}, {}, {"test_data/pucch_detector_test_received_symbols282.dat"}, {"test_data/pucch_detector_test_ch_estimates282.dat"}}, + {{{1, 13}, cyclic_prefix::NORMAL, 39, {33}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 3, 805, 0}, 0.01, {0}, {}, {"test_data/pucch_detector_test_received_symbols283.dat"}, {"test_data/pucch_detector_test_ch_estimates283.dat"}}, + {{{1, 14}, cyclic_prefix::NORMAL, 9, {40}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 9, 777, 1}, 0.01, {}, {1}, {"test_data/pucch_detector_test_received_symbols284.dat"}, {"test_data/pucch_detector_test_ch_estimates284.dat"}}, + {{{1, 12}, cyclic_prefix::NORMAL, 0, {8}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 3, 901, 1}, 0.01, {0}, {0}, {"test_data/pucch_detector_test_received_symbols285.dat"}, {"test_data/pucch_detector_test_ch_estimates285.dat"}}, + {{{1, 3}, cyclic_prefix::NORMAL, 46, {21}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 9, 598, 2}, 0.01, {}, {0, 0}, {"test_data/pucch_detector_test_received_symbols286.dat"}, {"test_data/pucch_detector_test_ch_estimates286.dat"}}, + {{{1, 10}, cyclic_prefix::NORMAL, 30, {13}, 10, 4, pucch_group_hopping::NEITHER, {0, 1, 2, 3}, 1, 0, 0, 217, 2}, 0.01, {1}, {1, 1}, {"test_data/pucch_detector_test_received_symbols287.dat"}, {"test_data/pucch_detector_test_ch_estimates287.dat"}}, // clang-format on }; diff --git a/tests/unittests/phy/upper/channel_processors/pucch_processor_format1_unittest.cpp b/tests/unittests/phy/upper/channel_processors/pucch_processor_format1_unittest.cpp index 1ff87e6d5b..659754ff55 100644 --- a/tests/unittests/phy/upper/channel_processors/pucch_processor_format1_unittest.cpp +++ b/tests/unittests/phy/upper/channel_processors/pucch_processor_format1_unittest.cpp @@ -209,7 +209,7 @@ TEST_P(PucchProcessorFormat1Fixture, UnitTest) ASSERT_EQ(detector_entry.config.start_symbol_index, config.start_symbol_index); ASSERT_EQ(detector_entry.config.nof_symbols, config.nof_symbols); ASSERT_EQ(detector_entry.config.group_hopping, pucch_group_hopping::NEITHER); - ASSERT_EQ(detector_entry.config.port, config.ports.front()); + ASSERT_EQ(detector_entry.config.ports, config.ports); ASSERT_EQ(detector_entry.config.beta_pucch, 1.0F); ASSERT_EQ(detector_entry.config.time_domain_occ, config.time_domain_occ); ASSERT_EQ(detector_entry.config.initial_cyclic_shift, config.initial_cyclic_shift); diff --git a/tests/unittests/phy/upper/signal_processors/srs/srs_estimator_test_data.h b/tests/unittests/phy/upper/signal_processors/srs/srs_estimator_test_data.h index 083b30d8c9..7f209c04b7 100644 --- a/tests/unittests/phy/upper/signal_processors/srs/srs_estimator_test_data.h +++ b/tests/unittests/phy/upper/signal_processors/srs/srs_estimator_test_data.h @@ -22,7 +22,7 @@ #pragma once -// This file was generated using the following MATLAB class on 08-03-2024 (seed 0): +// This file was generated using the following MATLAB class on 12-04-2024 (seed 0): // + "srsSRSEstimatorUnittest.m" #include "srsran/phy/upper/signal_processors/srs/srs_estimator_configuration.h" @@ -43,78 +43,78 @@ struct test_case_t { static const std::vector srs_estimator_test_data = { // clang-format off - {{{{0, 130, 8, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 12, 17, 647, 2, srs_resource_configuration::comb_size_enum(2), 1, 1, 66, 1, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.512938, -0.044587), cf_t(0.505400, -0.144933)}}, 1, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input0.dat"}}, - {{{{0, 811, 4, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 13, 54, 671, 3, srs_resource_configuration::comb_size_enum(4), 2, 8, 26, 0, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.207799, -0.556693), cf_t(0.628191, -0.811101)}}, 1, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input1.dat"}}, - {{{{0, 99, 2, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 11, 15, 711, 3, srs_resource_configuration::comb_size_enum(2), 0, 3, 41, 8, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.125423, -0.138631), cf_t(0.139874, 1.122673), cf_t(1.003604, 0.206181), cf_t(-0.568843, 0.492588)}}, 2, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input2.dat"}}, - {{{{0, 581, 7, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 1, 49, 55, 3, srs_resource_configuration::comb_size_enum(4), 0, 5, 0, 5, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.310396, -1.269030), cf_t(0.070776, -0.385040), cf_t(0.594235, -0.627934), cf_t(0.214622, -0.424495)}}, 2, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input3.dat"}}, - {{{{0, 766, 6, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 6, 9, 85, 0, srs_resource_configuration::comb_size_enum(2), 1, 6, 9, 2, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.797257, 0.247614), cf_t(-0.185259, -1.237587), cf_t(-0.692403, -0.817699), cf_t(0.681813, 0.367738), cf_t(-0.211472, 0.016186), cf_t(-0.201986, -0.587865), cf_t(-0.377282, -1.416077), cf_t(-0.014162, -0.024587)}}, 4, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input4.dat"}}, - {{{{0, 378, 3, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 1, 15, 798, 1, srs_resource_configuration::comb_size_enum(4), 0, 11, 65, 4, 1, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.217460, -0.888917), cf_t(0.559616, -0.941869), cf_t(0.235828, 0.276729), cf_t(0.129888, -0.336691), cf_t(-0.611978, -0.124828), cf_t(-1.647465, -1.024667), cf_t(0.319386, -0.092125), cf_t(0.609541, -0.962863)}}, 4, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input5.dat"}}, - {{{{0, 377, 6, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 8, 59, 798, 3, srs_resource_configuration::comb_size_enum(2), 0, 5, 20, 0, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.016296, 0.036268), cf_t(0.584115, 1.079736)}}, 1, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input6.dat"}}, - {{{{0, 831, 6, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 6, 56, 359, 2, srs_resource_configuration::comb_size_enum(4), 2, 2, 20, 10, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.103276, -0.376189), cf_t(1.189427, -0.619234)}}, 1, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input7.dat"}}, - {{{{0, 233, 2, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 5, 27, 318, 0, srs_resource_configuration::comb_size_enum(2), 1, 5, 7, 10, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.314399, -0.110267), cf_t(0.313547, 0.277111), cf_t(0.195210, -0.184671), cf_t(-0.884364, -0.670310)}}, 2, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input8.dat"}}, - {{{{0, 434, 2, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 6, 51, 87, 0, srs_resource_configuration::comb_size_enum(4), 3, 5, 39, 2, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-1.106662, -0.059778), cf_t(0.029256, -0.519136), cf_t(1.134161, 0.069542), cf_t(-0.021789, 0.164294)}}, 2, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input9.dat"}}, - {{{{0, 376, 6, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 12, 58, 38, 3, srs_resource_configuration::comb_size_enum(2), 0, 4, 46, 9, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-1.181010, 0.333496), cf_t(0.461285, 0.231266), cf_t(-0.460261, 0.181766), cf_t(0.653951, 0.000035), cf_t(-0.857612, 0.046803), cf_t(0.765537, 0.711404), cf_t(-0.667776, -0.934646), cf_t(-0.038834, 0.644264)}}, 4, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input10.dat"}}, - {{{{0, 880, 6, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 10, 15, 590, 3, srs_resource_configuration::comb_size_enum(4), 0, 2, 66, 2, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.609325, 0.000822), cf_t(0.410951, -1.550286), cf_t(-0.670677, 0.290968), cf_t(-0.488723, 0.317758), cf_t(-0.050089, -1.758068), cf_t(-1.639979, 0.056522), cf_t(0.478696, 0.606508), cf_t(0.071159, 0.584120)}}, 4, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input11.dat"}}, - {{{{0, 464, 6, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 4, 33, 845, 1, srs_resource_configuration::comb_size_enum(2), 1, 3, 29, 4, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-1.507619, 0.809893), cf_t(-0.444834, -0.851250)}}, 1, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input12.dat"}}, - {{{{0, 501, 3, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 3, 3, 974, 2, srs_resource_configuration::comb_size_enum(4), 1, 6, 64, 10, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.193073, 1.114613), cf_t(-0.340074, 0.231586)}}, 1, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input13.dat"}}, - {{{{0, 714, 6, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 7, 12, 182, 1, srs_resource_configuration::comb_size_enum(2), 0, 7, 15, 4, 1, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.066638, 0.237739), cf_t(0.247532, -1.298148), cf_t(-0.639687, -0.203828), cf_t(0.732546, 1.714353)}}, 2, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input14.dat"}}, - {{{{0, 598, 7, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 1, 52, 928, 1, srs_resource_configuration::comb_size_enum(4), 2, 5, 21, 9, 1, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.725303, -0.642580), cf_t(0.429638, -0.083296), cf_t(-0.148420, -1.201278), cf_t(0.494381, 0.190670)}}, 2, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input15.dat"}}, - {{{{0, 70, 6, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 3, 26, 543, 3, srs_resource_configuration::comb_size_enum(2), 1, 6, 22, 7, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-1.047507, 0.109947), cf_t(-0.382394, -0.218243), cf_t(-0.127802, 0.032415), cf_t(0.077299, 1.282703), cf_t(0.578803, -0.206891), cf_t(-0.775409, -0.348611), cf_t(-0.045101, 0.432279), cf_t(0.220634, 1.275970)}}, 4, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input16.dat"}}, - {{{{0, 296, 2, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 7, 16, 711, 0, srs_resource_configuration::comb_size_enum(4), 2, 4, 53, 0, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.385755, -0.743616), cf_t(1.072163, -0.023028), cf_t(0.416792, -0.044400), cf_t(0.433111, -0.038810), cf_t(0.281052, -0.531670), cf_t(1.156826, -0.300562), cf_t(-1.429741, -0.694472), cf_t(-0.791063, -0.442917)}}, 4, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input17.dat"}}, - {{{{0, 756, 6, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 3, 48, 939, 0, srs_resource_configuration::comb_size_enum(2), 0, 6, 46, 2, 0, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.212476, -0.263801), cf_t(0.576637, 0.564898), cf_t(0.084998, 0.403933), cf_t(0.291891, -0.697887)}}, 1, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input18.dat"}}, - {{{{0, 122, 7, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 8, 42, 460, 3, srs_resource_configuration::comb_size_enum(4), 1, 4, 57, 5, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.311126, -0.436190), cf_t(0.194339, 0.425043), cf_t(0.065272, 1.223183), cf_t(-0.430315, -0.521180)}}, 1, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input19.dat"}}, - {{{{0, 661, 1, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 6, 7, 654, 0, srs_resource_configuration::comb_size_enum(2), 0, 4, 51, 1, 1, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.275253, 0.531199), cf_t(0.047204, 0.025088), cf_t(1.257417, 0.864836), cf_t(1.574846, -0.048942), cf_t(-0.907399, -1.646820), cf_t(-0.358732, 0.166743), cf_t(0.637762, -1.297993), cf_t(0.173810, 0.049529)}}, 2, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input20.dat"}}, - {{{{0, 600, 3, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 3, 15, 45, 1, srs_resource_configuration::comb_size_enum(4), 2, 8, 26, 8, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.716159, 0.445007), cf_t(0.695472, -0.210504), cf_t(-0.150624, -0.612140), cf_t(0.808703, -0.375912), cf_t(-0.737589, -0.190967), cf_t(0.687708, -0.369287), cf_t(-0.309813, -0.288976), cf_t(0.124859, 0.686415)}}, 2, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input21.dat"}}, - {{{{0, 809, 3, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 13, 56, 335, 2, srs_resource_configuration::comb_size_enum(2), 0, 4, 50, 5, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.224441, 0.292467), cf_t(-0.010013, -0.817083), cf_t(0.625061, 0.127461), cf_t(-0.033849, 1.203025), cf_t(-0.408061, 0.101825), cf_t(-0.006735, -0.487770), cf_t(0.389513, 0.482929), cf_t(-0.360421, -0.002019), cf_t(-1.158712, -0.537465), cf_t(-0.471427, 0.611046), cf_t(0.827745, 0.336484), cf_t(0.650444, 0.105931), cf_t(-0.578974, 0.367504), cf_t(0.080200, 0.281685), cf_t(0.998599, 0.015987), cf_t(0.993438, 0.731234)}}, 4, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input22.dat"}}, - {{{{0, 682, 5, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 1, 46, 640, 3, srs_resource_configuration::comb_size_enum(4), 3, 6, 63, 7, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.300864, -0.929650), cf_t(0.424471, -0.962736), cf_t(-0.832210, -0.700412), cf_t(-0.001575, 0.065838), cf_t(-0.294447, 0.865985), cf_t(0.245785, -0.128583), cf_t(-0.829459, -1.220062), cf_t(-0.267397, -1.048410), cf_t(-0.030819, 0.411835), cf_t(-0.664351, -0.026540), cf_t(0.203808, -1.127258), cf_t(-0.030984, 0.679406), cf_t(-0.711703, 0.045620), cf_t(-1.340890, -1.504707), cf_t(0.077936, 0.556540), cf_t(1.229125, -0.304202)}}, 4, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input23.dat"}}, - {{{{0, 655, 2, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 5, 5, 210, 0, srs_resource_configuration::comb_size_enum(2), 0, 7, 39, 10, 0, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.502566, 0.549424), cf_t(0.440099, 0.457767), cf_t(-0.300967, 0.741459), cf_t(0.467190, 1.773970)}}, 1, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input24.dat"}}, - {{{{0, 525, 8, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 2, 1, 408, 3, srs_resource_configuration::comb_size_enum(4), 1, 3, 31, 1, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.289443, -0.673615), cf_t(0.224377, 0.055169), cf_t(0.936482, -0.150734), cf_t(-0.095091, -0.828274)}}, 1, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input25.dat"}}, - {{{{0, 338, 1, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 11, 34, 120, 2, srs_resource_configuration::comb_size_enum(2), 1, 4, 31, 10, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.487940, 0.392979), cf_t(-0.260223, -0.962346), cf_t(-0.792140, -1.083778), cf_t(0.551237, 0.310711), cf_t(-0.776310, -1.001103), cf_t(-0.063373, 0.722083), cf_t(0.042123, -0.290798), cf_t(-0.617997, 0.293237)}}, 2, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input26.dat"}}, - {{{{0, 188, 7, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 7, 13, 307, 3, srs_resource_configuration::comb_size_enum(4), 0, 0, 30, 1, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-2.285396, -0.768596), cf_t(-0.176800, -1.109674), cf_t(-1.008643, -0.717325), cf_t(-0.337561, -0.946092), cf_t(-0.150803, -0.230056), cf_t(0.021425, 0.603223), cf_t(1.374897, -0.404305), cf_t(0.285850, -0.495413)}}, 2, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input27.dat"}}, - {{{{0, 926, 0, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 6, 21, 111, 1, srs_resource_configuration::comb_size_enum(2), 1, 0, 45, 9, 1, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.283365, 0.067276), cf_t(-0.357473, -0.843795), cf_t(-0.043939, 0.847841), cf_t(1.972178, 0.514471), cf_t(0.351209, 0.765260), cf_t(0.457478, -0.250049), cf_t(0.566890, 0.744799), cf_t(-0.546639, 0.591589), cf_t(0.686210, -0.402039), cf_t(0.032834, -0.560699), cf_t(-0.529536, -0.662083), cf_t(-0.797850, -1.007252), cf_t(0.572737, 0.122504), cf_t(-1.096379, 0.121330), cf_t(-0.897380, 0.352125), cf_t(0.507308, -0.550062)}}, 4, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input28.dat"}}, - {{{{0, 597, 6, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 9, 62, 240, 3, srs_resource_configuration::comb_size_enum(4), 0, 4, 46, 8, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.291353, 0.286727), cf_t(0.191186, -0.461579), cf_t(-0.502981, 0.043449), cf_t(0.382285, 0.690024), cf_t(-0.257232, -0.423749), cf_t(0.337451, -0.050431), cf_t(-1.305411, -0.281664), cf_t(-0.110924, 0.196434), cf_t(-0.416902, 0.603545), cf_t(-0.663479, 0.114101), cf_t(-0.384347, -0.644810), cf_t(0.452207, -0.057260), cf_t(-1.310275, -0.146585), cf_t(-0.189634, -0.289824), cf_t(0.461528, -0.519208), cf_t(0.382453, -0.892768)}}, 4, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input29.dat"}}, - {{{{0, 272, 9, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 8, 25, 499, 1, srs_resource_configuration::comb_size_enum(2), 0, 5, 30, 8, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.286806, -1.003631), cf_t(-0.515796, 0.811283), cf_t(0.422754, -0.906003), cf_t(-1.557943, -0.403932)}}, 1, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input30.dat"}}, - {{{{0, 671, 0, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 7, 40, 543, 0, srs_resource_configuration::comb_size_enum(4), 0, 1, 11, 1, 0, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.636361, -0.202011), cf_t(-0.326981, -0.289762), cf_t(-0.356056, 0.872073), cf_t(0.431551, 0.041770)}}, 1, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input31.dat"}}, - {{{{0, 79, 1, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 10, 20, 723, 0, srs_resource_configuration::comb_size_enum(2), 1, 2, 17, 6, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.059603, -1.408555), cf_t(-0.441845, -0.826412), cf_t(0.594851, -0.293208), cf_t(0.277593, 0.920540), cf_t(1.352116, -0.276407), cf_t(-0.419768, 0.308564), cf_t(0.289335, -0.807819), cf_t(-0.356638, 0.072201)}}, 2, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input32.dat"}}, - {{{{0, 343, 8, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 3, 23, 463, 2, srs_resource_configuration::comb_size_enum(4), 2, 5, 8, 4, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.547663, -0.985192), cf_t(0.578071, 0.346595), cf_t(-0.273109, 0.371646), cf_t(0.541114, 0.550326), cf_t(1.077114, 1.271728), cf_t(-1.046734, 0.382095), cf_t(-0.082650, -0.226413), cf_t(-0.064728, -0.537580)}}, 2, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input33.dat"}}, - {{{{0, 179, 0, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 1, 33, 681, 2, srs_resource_configuration::comb_size_enum(2), 0, 6, 46, 9, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-0.673214, 0.603899), cf_t(-0.134281, -0.730380), cf_t(0.119153, -0.212985), cf_t(0.634096, 0.356899), cf_t(0.275168, -0.817416), cf_t(-0.228602, 0.542016), cf_t(-0.494023, 0.588858), cf_t(-0.283477, -0.363345), cf_t(0.028101, -0.318621), cf_t(1.233670, -0.820612), cf_t(-0.491160, -0.326601), cf_t(0.563117, -0.474603), cf_t(0.077250, -0.177168), cf_t(1.681084, 1.079100), cf_t(0.624812, 0.308259), cf_t(0.839095, 0.559111)}}, 4, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input34.dat"}}, - {{{{0, 650, 8, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 10, 55, 454, 2, srs_resource_configuration::comb_size_enum(4), 1, 2, 44, 0, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.208033, -0.550019), cf_t(1.250866, 1.068143), cf_t(-0.283071, -0.475036), cf_t(-0.248823, 0.190594), cf_t(-0.753019, -1.250458), cf_t(0.115973, -0.199944), cf_t(0.407031, -0.550195), cf_t(-1.813340, 0.329416), cf_t(-0.299049, -0.744656), cf_t(0.814704, -0.810703), cf_t(-0.752051, 0.391015), cf_t(1.310666, 0.734889), cf_t(0.458032, -0.224597), cf_t(0.476377, -0.473134), cf_t(-0.299409, 0.255681), cf_t(0.644101, -0.169516)}}, 4, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input35.dat"}}, - {{{{1, 542, 6, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 3, 14, 254, 3, srs_resource_configuration::comb_size_enum(2), 1, 6, 5, 4, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.921012, 0.996958), cf_t(-1.175595, 1.374392)}}, 1, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input36.dat"}}, - {{{{1, 802, 0, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 7, 40, 906, 0, srs_resource_configuration::comb_size_enum(4), 0, 0, 7, 9, 0, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.055288, 1.489612), cf_t(-0.506181, -0.198355)}}, 1, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input37.dat"}}, - {{{{1, 692, 9, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 13, 42, 785, 0, srs_resource_configuration::comb_size_enum(2), 0, 6, 27, 3, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.206282, 0.698406), cf_t(0.197838, 0.036218), cf_t(0.277847, 0.137569), cf_t(-0.547630, 0.556339)}}, 2, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input38.dat"}}, - {{{{1, 994, 9, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 4, 26, 907, 0, srs_resource_configuration::comb_size_enum(4), 0, 8, 53, 5, 1, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.372978, 0.604012), cf_t(-0.118482, 0.249620), cf_t(0.948829, -1.767437), cf_t(0.507175, -0.922669)}}, 2, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input39.dat"}}, - {{{{1, 488, 2, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 8, 53, 242, 3, srs_resource_configuration::comb_size_enum(2), 1, 2, 6, 1, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-0.029460, -0.435229), cf_t(-1.232027, 0.145172), cf_t(-0.894940, -0.105593), cf_t(0.585758, 0.153964), cf_t(0.929248, -1.028887), cf_t(0.843529, -0.567682), cf_t(-1.157143, 0.012264), cf_t(-1.350040, -0.379590)}}, 4, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input40.dat"}}, - {{{{1, 442, 0, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 13, 57, 831, 0, srs_resource_configuration::comb_size_enum(4), 1, 11, 48, 5, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.347014, -0.414454), cf_t(0.406248, 0.199292), cf_t(0.449819, 0.560862), cf_t(1.129429, 0.079507), cf_t(0.526724, -0.585594), cf_t(0.805611, -0.301134), cf_t(-0.635249, 0.110482), cf_t(-0.218231, 0.322907)}}, 4, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input41.dat"}}, - {{{{1, 423, 3, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 6, 20, 711, 3, srs_resource_configuration::comb_size_enum(2), 1, 0, 24, 10, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.166872, -0.590556), cf_t(-0.902237, 0.436310)}}, 1, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input42.dat"}}, - {{{{1, 676, 6, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 0, 15, 357, 2, srs_resource_configuration::comb_size_enum(4), 3, 8, 9, 4, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.947208, -0.685286), cf_t(0.147584, -0.437412)}}, 1, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input43.dat"}}, - {{{{1, 501, 8, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 11, 23, 361, 2, srs_resource_configuration::comb_size_enum(2), 1, 1, 66, 7, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.282669, -0.462991), cf_t(-0.639815, -0.285800), cf_t(-0.209550, -1.058481), cf_t(-0.513217, -0.612697)}}, 2, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input44.dat"}}, - {{{{1, 905, 3, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 1, 3, 952, 1, srs_resource_configuration::comb_size_enum(4), 2, 6, 46, 4, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.447532, -1.031698), cf_t(-0.317564, 0.671239), cf_t(-0.411331, -1.294111), cf_t(0.507308, 1.617739)}}, 2, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input45.dat"}}, - {{{{1, 944, 5, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 10, 63, 292, 2, srs_resource_configuration::comb_size_enum(2), 1, 5, 38, 5, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-0.558371, 1.006185), cf_t(-0.604529, -0.760306), cf_t(0.844868, 0.428552), cf_t(-0.684263, 0.142872), cf_t(0.004477, 0.485415), cf_t(-0.064324, -0.178737), cf_t(0.382201, -1.021726), cf_t(-0.245987, 0.912230)}}, 4, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input46.dat"}}, - {{{{1, 820, 9, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 11, 60, 611, 2, srs_resource_configuration::comb_size_enum(4), 2, 0, 30, 9, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.253469, 0.025613), cf_t(0.156464, 1.930669), cf_t(1.119082, 1.929857), cf_t(0.568076, -0.933312), cf_t(-0.257833, 1.252300), cf_t(-0.209421, 0.399017), cf_t(0.214652, -0.558797), cf_t(-0.193638, 0.192239)}}, 4, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input47.dat"}}, - {{{{1, 496, 9, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 8, 63, 427, 3, srs_resource_configuration::comb_size_enum(2), 0, 2, 53, 10, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.945606, 1.503083), cf_t(0.038216, 0.115284)}}, 1, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input48.dat"}}, - {{{{1, 461, 1, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 2, 56, 921, 1, srs_resource_configuration::comb_size_enum(4), 2, 1, 27, 8, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.286323, 0.373253), cf_t(-0.712030, 0.770058)}}, 1, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input49.dat"}}, - {{{{1, 509, 8, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 7, 21, 854, 1, srs_resource_configuration::comb_size_enum(2), 1, 4, 27, 2, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.023821, 0.324054), cf_t(-0.202709, 0.422861), cf_t(0.906250, 0.438470), cf_t(-0.173618, -1.259171)}}, 2, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input50.dat"}}, - {{{{1, 332, 0, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 3, 6, 11, 0, srs_resource_configuration::comb_size_enum(4), 2, 11, 38, 5, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.041238, -0.405974), cf_t(-1.241520, -0.181979), cf_t(-0.138036, -0.035731), cf_t(0.530006, -0.403591)}}, 2, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input51.dat"}}, - {{{{1, 907, 6, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 10, 44, 195, 3, srs_resource_configuration::comb_size_enum(2), 1, 1, 26, 4, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.171510, -0.071169), cf_t(0.725625, -0.536077), cf_t(0.317324, 0.000443), cf_t(-0.561339, 0.607959), cf_t(-1.149083, -1.070859), cf_t(1.469612, -1.571182), cf_t(-0.534742, 0.285901), cf_t(0.047284, -1.159222)}}, 4, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input52.dat"}}, - {{{{1, 20, 0, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 4, 33, 852, 3, srs_resource_configuration::comb_size_enum(4), 0, 1, 35, 6, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-0.257082, 0.105752), cf_t(0.385946, 1.421233), cf_t(-0.664008, -1.229594), cf_t(0.739507, -0.672446), cf_t(-1.374982, 1.077556), cf_t(1.001737, 0.008118), cf_t(0.012000, 0.154969), cf_t(0.562007, 0.050509)}}, 4, 2}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input53.dat"}}, - {{{{1, 604, 1, 1}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 6, 37, 964, 3, srs_resource_configuration::comb_size_enum(2), 0, 5, 20, 2, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.231468, 0.336242), cf_t(-0.091927, -0.420271), cf_t(-0.313791, -0.935170), cf_t(-0.607773, -0.999143)}}, 1, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input54.dat"}}, - {{{{1, 304, 4, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 5, 45, 122, 0, srs_resource_configuration::comb_size_enum(4), 3, 4, 0, 5, 0, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.243688, -0.936150), cf_t(-0.406470, -0.523759), cf_t(-0.837923, -1.460077), cf_t(0.399558, 1.414314)}}, 1, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input55.dat"}}, - {{{{1, 494, 9, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 5, 48, 536, 0, srs_resource_configuration::comb_size_enum(2), 1, 7, 37, 10, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.373312, 0.511281), cf_t(0.288552, 0.758270), cf_t(-0.601000, -0.563129), cf_t(0.684467, 0.191022), cf_t(0.512891, 1.192567), cf_t(-0.448593, 0.445310), cf_t(-0.273222, -0.357144), cf_t(-0.056027, 0.973650)}}, 2, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input56.dat"}}, - {{{{1, 733, 0, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 2, 20, 344, 1, srs_resource_configuration::comb_size_enum(4), 2, 6, 18, 2, 1, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.260730, -0.592676), cf_t(-0.413370, -0.437986), cf_t(-0.199740, 2.524278), cf_t(0.378515, 0.341523), cf_t(2.409489, 0.810993), cf_t(0.076703, -0.240618), cf_t(0.556675, -0.903780), cf_t(-0.654674, 0.003763)}}, 2, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input57.dat"}}, - {{{{1, 589, 7, 1}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 10, 32, 661, 1, srs_resource_configuration::comb_size_enum(2), 0, 2, 45, 1, 1, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-0.162135, 0.185439), cf_t(-0.262829, 0.843482), cf_t(0.476870, 0.479701), cf_t(0.231619, 1.066407), cf_t(-0.634967, -1.525443), cf_t(0.674331, -1.000588), cf_t(-0.221027, 0.268166), cf_t(-0.628554, -0.678317), cf_t(0.066409, -0.672587), cf_t(-0.023586, 0.184638), cf_t(-1.593040, -0.826149), cf_t(-0.348405, 0.311874), cf_t(0.829146, 1.226898), cf_t(0.369813, 0.478561), cf_t(0.849918, -0.467213), cf_t(-0.144790, -0.691414)}}, 4, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input58.dat"}}, - {{{{1, 383, 1, 1}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 1, 39, 445, 0, srs_resource_configuration::comb_size_enum(4), 2, 2, 30, 1, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.206324, -0.434601), cf_t(0.010686, -0.823421), cf_t(-1.538884, -0.845455), cf_t(0.680434, -0.179587), cf_t(-0.009647, 0.291408), cf_t(0.349905, -0.859735), cf_t(0.258326, 1.705855), cf_t(1.337673, -0.862850), cf_t(-0.662665, 0.590545), cf_t(1.124857, 0.372274), cf_t(0.523434, 0.219024), cf_t(-0.266040, 0.037073), cf_t(0.773956, 0.227109), cf_t(0.187255, -0.278316), cf_t(-0.383243, 0.086467), cf_t(-1.381640, 0.399813)}}, 4, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input59.dat"}}, - {{{{1, 849, 5, 1}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 11, 28, 807, 3, srs_resource_configuration::comb_size_enum(2), 0, 3, 35, 3, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(1.266379, 0.563750), cf_t(-0.312895, -0.444835), cf_t(1.084558, 1.932905), cf_t(0.118397, -0.338609)}}, 1, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input60.dat"}}, - {{{{1, 598, 0, 1}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 12, 18, 62, 3, srs_resource_configuration::comb_size_enum(4), 0, 4, 56, 6, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.302950, -0.710066), cf_t(-0.447378, -0.739429), cf_t(0.357589, 0.234727), cf_t(-1.156411, -1.348340)}}, 1, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input61.dat"}}, - {{{{1, 565, 4, 1}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 0, 28, 55, 1, srs_resource_configuration::comb_size_enum(2), 1, 6, 48, 8, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.579742, -0.604119), cf_t(-0.588301, 0.352068), cf_t(0.252662, 1.943459), cf_t(1.637395, -0.561320), cf_t(-1.069836, 0.306870), cf_t(0.382516, -0.395316), cf_t(-0.162470, -0.584838), cf_t(1.397643, 0.385133)}}, 2, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input62.dat"}}, - {{{{1, 510, 4, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 10, 37, 365, 3, srs_resource_configuration::comb_size_enum(4), 0, 8, 2, 0, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.442257, 0.532423), cf_t(-0.247652, 1.145426), cf_t(0.150956, -0.544638), cf_t(-0.035944, -0.574664), cf_t(-0.005045, 0.065893), cf_t(-0.310010, 0.607129), cf_t(0.661322, 0.469178), cf_t(0.138039, 0.628520)}}, 2, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input63.dat"}}, - {{{{1, 547, 9, 1}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 12, 19, 118, 2, srs_resource_configuration::comb_size_enum(2), 1, 6, 29, 0, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.048509, 0.531387), cf_t(0.199778, -0.795551), cf_t(-0.165430, -0.740153), cf_t(0.694686, -0.078803), cf_t(-0.487499, 0.318773), cf_t(-0.699310, -1.071874), cf_t(1.119576, -0.176362), cf_t(0.916376, 1.259566), cf_t(-1.106640, -0.055717), cf_t(-1.575795, -0.107142), cf_t(0.915010, 0.255959), cf_t(-0.046397, -0.180794), cf_t(-0.665947, -0.461643), cf_t(0.822184, -0.128648), cf_t(-0.186006, 0.765192), cf_t(0.286122, 0.248730)}}, 4, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input64.dat"}}, - {{{{1, 752, 7, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 12, 19, 555, 0, srs_resource_configuration::comb_size_enum(4), 0, 5, 0, 5, 0, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(1.588208, 0.522751), cf_t(-0.115881, -0.047546), cf_t(0.874858, 0.761694), cf_t(-0.122555, 0.435790), cf_t(-1.462046, -0.816391), cf_t(-0.708233, -0.392912), cf_t(-0.024636, -0.356373), cf_t(0.610883, -1.001910), cf_t(-0.075796, -1.211188), cf_t(-0.955695, 0.257536), cf_t(-0.865512, 0.083799), cf_t(0.141387, 0.138470), cf_t(-0.325195, 0.771454), cf_t(-0.319759, 0.553923), cf_t(0.678450, -0.241074), cf_t(-0.106867, 0.631294)}}, 4, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input65.dat"}}, - {{{{1, 648, 3, 1}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 6, 63, 335, 3, srs_resource_configuration::comb_size_enum(2), 0, 0, 41, 8, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.700760, -0.178987), cf_t(0.713658, 0.036071), cf_t(-0.311433, -0.599958), cf_t(-0.169968, 0.426335)}}, 1, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input66.dat"}}, - {{{{1, 877, 1, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 0, 18, 708, 0, srs_resource_configuration::comb_size_enum(4), 2, 7, 61, 10, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.835890, 0.412843), cf_t(-0.410129, -0.396947), cf_t(-1.100185, 0.777965), cf_t(0.123785, 0.709660)}}, 1, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input67.dat"}}, - {{{{1, 973, 8, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 5, 47, 773, 0, srs_resource_configuration::comb_size_enum(2), 0, 3, 22, 0, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.114515, -0.345706), cf_t(0.469718, -0.496576), cf_t(-0.157531, 0.192380), cf_t(0.354492, 0.382060), cf_t(-0.826276, -0.865664), cf_t(0.700567, 0.699596), cf_t(-1.484556, -0.275943), cf_t(-0.487084, -0.605876)}}, 2, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input68.dat"}}, - {{{{1, 851, 5, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 8, 43, 570, 0, srs_resource_configuration::comb_size_enum(4), 1, 6, 67, 2, 1, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.525011, 0.808650), cf_t(0.777273, 0.461898), cf_t(-0.646801, 0.127139), cf_t(-0.357141, -0.336578), cf_t(-0.695332, 0.272115), cf_t(-1.450673, -0.316999), cf_t(0.230303, 0.916630), cf_t(-1.096862, 0.657486)}}, 2, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input69.dat"}}, - {{{{1, 346, 7, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 9, 57, 565, 1, srs_resource_configuration::comb_size_enum(2), 1, 7, 54, 10, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.636428, -1.082735), cf_t(0.429025, 1.155765), cf_t(-1.474862, 0.166852), cf_t(-0.183946, -0.161783), cf_t(0.356821, -0.611092), cf_t(-0.440877, -0.954665), cf_t(-0.550427, 0.777508), cf_t(-0.371099, 0.797842), cf_t(-0.266265, 0.557208), cf_t(-0.821828, -0.667706), cf_t(-0.604975, 0.005344), cf_t(0.389007, 1.311787), cf_t(0.210863, -0.115783), cf_t(-0.474618, 0.407775), cf_t(-0.662973, -0.481935), cf_t(-0.196080, 0.754222)}}, 4, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input70.dat"}}, - {{{{1, 820, 0, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 2, 52, 954, 2, srs_resource_configuration::comb_size_enum(4), 3, 2, 21, 8, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.131911, 0.672419), cf_t(-0.394426, -0.075346), cf_t(-0.388132, -0.089103), cf_t(0.518831, 0.085087), cf_t(-0.558981, -0.346113), cf_t(-0.152142, 0.334808), cf_t(0.211835, 0.209425), cf_t(0.803508, -0.485622), cf_t(2.103271, -0.440234), cf_t(0.965654, -1.158102), cf_t(0.849082, 0.770869), cf_t(0.333530, 0.203889), cf_t(1.357859, 0.679635), cf_t(1.430992, 0.549980), cf_t(-0.253641, -0.091873), cf_t(0.984190, -0.951414)}}, 4, 4}, 0, phy_time_unit::from_seconds(0.000000)}}, {"test_data/srs_estimator_test_input71.dat"}}, + {{{{0, 130, 8, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 12, 17, 647, 2, srs_resource_configuration::comb_size_enum(2), 1, 1, 66, 1, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.964003, -0.265891), cf_t(-0.995781, 0.091758)}}, 1, 2}, 0, {0.000000313}}}, {"test_data/srs_estimator_test_input0.dat"}}, + {{{{0, 937, 1, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 11, 2, 982, 3, srs_resource_configuration::comb_size_enum(4), 3, 9, 50, 7, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.779357, 0.626580), cf_t(-0.559597, -0.828765)}}, 1, 2}, 0, {-0.000000343}}}, {"test_data/srs_estimator_test_input1.dat"}}, + {{{{0, 283, 7, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 0, 44, 99, 1, srs_resource_configuration::comb_size_enum(2), 1, 5, 25, 9, 1, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.097340, -0.995251), cf_t(0.386322, 0.922364), cf_t(0.280197, -0.959943), cf_t(-0.997933, 0.064268)}}, 2, 2}, 0, {-0.000000057}}}, {"test_data/srs_estimator_test_input2.dat"}}, + {{{{0, 772, 6, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 3, 10, 696, 0, srs_resource_configuration::comb_size_enum(4), 1, 4, 39, 7, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.163803, 0.986493), cf_t(-0.032008, 0.999488), cf_t(0.007961, -0.999968), cf_t(-0.999300, -0.037421)}}, 2, 2}, 0, {0.000000207}}}, {"test_data/srs_estimator_test_input3.dat"}}, + {{{{0, 560, 8, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 1, 58, 152, 1, srs_resource_configuration::comb_size_enum(2), 1, 4, 38, 6, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.888557, 0.458767), cf_t(-0.981336, -0.192301), cf_t(0.915268, -0.402845), cf_t(-0.907949, -0.419080), cf_t(0.943095, 0.332524), cf_t(0.182239, -0.983254), cf_t(0.684977, 0.728565), cf_t(-0.981563, 0.191141)}}, 4, 2}, 0, {-0.000000508}}}, {"test_data/srs_estimator_test_input4.dat"}}, + {{{{0, 813, 3, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 4, 38, 541, 1, srs_resource_configuration::comb_size_enum(4), 2, 8, 30, 1, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.864488, 0.502653), cf_t(0.855377, -0.518005), cf_t(0.458558, -0.888664), cf_t(0.999705, -0.024284), cf_t(0.131708, 0.991289), cf_t(0.575632, 0.817709), cf_t(-0.971121, -0.238589), cf_t(0.881772, 0.471677)}}, 4, 2}, 0, {-0.000000060}}}, {"test_data/srs_estimator_test_input5.dat"}}, + {{{{0, 4, 1, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 10, 5, 836, 1, srs_resource_configuration::comb_size_enum(2), 0, 5, 61, 9, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.415250, 0.909707), cf_t(-0.086618, 0.996242)}}, 1, 2}, 0, {-0.000000369}}}, {"test_data/srs_estimator_test_input6.dat"}}, + {{{{0, 593, 1, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 7, 39, 148, 1, srs_resource_configuration::comb_size_enum(4), 2, 0, 16, 9, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.714536, 0.699599), cf_t(0.403437, 0.915008)}}, 1, 2}, 0, {-0.000000271}}}, {"test_data/srs_estimator_test_input7.dat"}}, + {{{{0, 924, 4, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 12, 15, 502, 1, srs_resource_configuration::comb_size_enum(2), 1, 0, 11, 0, 1, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.592272, -0.805738), cf_t(-0.599184, -0.800612), cf_t(-0.114589, -0.993413), cf_t(-0.952834, 0.303492)}}, 2, 2}, 0, {0.000000049}}}, {"test_data/srs_estimator_test_input8.dat"}}, + {{{{0, 193, 2, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 8, 40, 187, 3, srs_resource_configuration::comb_size_enum(4), 0, 9, 33, 4, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.919884, 0.392191), cf_t(-0.346703, 0.937975), cf_t(-0.944618, 0.328172), cf_t(-0.998571, -0.053436)}}, 2, 2}, 0, {0.000000011}}}, {"test_data/srs_estimator_test_input9.dat"}}, + {{{{0, 659, 8, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 4, 22, 831, 3, srs_resource_configuration::comb_size_enum(2), 1, 7, 39, 5, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.262404, 0.964958), cf_t(-0.983358, 0.181679), cf_t(0.558486, -0.829514), cf_t(0.150711, 0.988578), cf_t(-0.316455, 0.948607), cf_t(0.122290, 0.992494), cf_t(0.340131, 0.940378), cf_t(0.477850, 0.878441)}}, 4, 2}, 0, {-0.000000284}}}, {"test_data/srs_estimator_test_input10.dat"}}, + {{{{0, 945, 4, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 5, 62, 189, 1, srs_resource_configuration::comb_size_enum(4), 0, 4, 40, 9, 1, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-0.076653, 0.997058), cf_t(-0.241284, -0.970455), cf_t(0.739979, 0.672630), cf_t(-0.418821, 0.908069), cf_t(-0.798388, -0.602143), cf_t(0.176590, 0.984285), cf_t(-0.289087, 0.957303), cf_t(-0.888618, 0.458649)}}, 4, 2}, 0, {0.000000008}}}, {"test_data/srs_estimator_test_input11.dat"}}, + {{{{0, 820, 0, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 0, 14, 951, 1, srs_resource_configuration::comb_size_enum(2), 1, 4, 24, 5, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.997150, -0.075440), cf_t(0.972018, 0.234904)}}, 1, 2}, 0, {0.000000401}}}, {"test_data/srs_estimator_test_input12.dat"}}, + {{{{0, 101, 9, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 2, 8, 343, 2, srs_resource_configuration::comb_size_enum(4), 0, 5, 52, 7, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.217916, -0.975968), cf_t(0.822535, -0.568714)}}, 1, 2}, 0, {0.000000407}}}, {"test_data/srs_estimator_test_input13.dat"}}, + {{{{0, 202, 3, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 0, 11, 761, 0, srs_resource_configuration::comb_size_enum(2), 1, 5, 11, 6, 0, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.991042, -0.133553), cf_t(-0.999996, -0.002963), cf_t(-0.232257, -0.972654), cf_t(-0.983546, 0.180660)}}, 2, 2}, 0, {-0.000000459}}}, {"test_data/srs_estimator_test_input14.dat"}}, + {{{{0, 73, 6, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 5, 52, 99, 2, srs_resource_configuration::comb_size_enum(4), 0, 6, 66, 8, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.592900, -0.805276), cf_t(-0.958159, 0.286237), cf_t(0.310992, -0.950413), cf_t(-0.911123, 0.412136)}}, 2, 2}, 0, {0.000000339}}}, {"test_data/srs_estimator_test_input15.dat"}}, + {{{{0, 177, 0, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 4, 3, 851, 1, srs_resource_configuration::comb_size_enum(2), 1, 7, 42, 8, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-0.260745, 0.965408), cf_t(0.995269, 0.097155), cf_t(0.497267, 0.867597), cf_t(-0.695505, 0.718521), cf_t(-0.909196, 0.416369), cf_t(0.994991, -0.099963), cf_t(0.785448, 0.618928), cf_t(0.320239, 0.947337)}}, 4, 2}, 0, {-0.000000011}}}, {"test_data/srs_estimator_test_input16.dat"}}, + {{{{0, 942, 3, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 0, 27, 755, 2, srs_resource_configuration::comb_size_enum(4), 3, 11, 20, 2, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-0.302444, -0.953167), cf_t(-0.969934, -0.243370), cf_t(-0.500755, -0.865589), cf_t(0.693588, 0.720372), cf_t(-0.501783, -0.864994), cf_t(-0.320316, -0.947311), cf_t(0.436367, 0.899769), cf_t(0.999983, -0.005778)}}, 4, 2}, 0, {-0.000000343}}}, {"test_data/srs_estimator_test_input17.dat"}}, + {{{{0, 903, 0, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 9, 29, 195, 3, srs_resource_configuration::comb_size_enum(2), 0, 7, 25, 4, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.362722, 0.931897), cf_t(-0.900099, 0.435686), cf_t(-0.993627, 0.112719), cf_t(0.726333, 0.687343)}}, 1, 4}, 0, {0.000000093}}}, {"test_data/srs_estimator_test_input18.dat"}}, + {{{{0, 596, 2, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 3, 16, 297, 3, srs_resource_configuration::comb_size_enum(4), 3, 4, 39, 6, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.779372, 0.626561), cf_t(0.831673, -0.555266), cf_t(0.727478, -0.686132), cf_t(0.413006, -0.910728)}}, 1, 4}, 0, {-0.000000249}}}, {"test_data/srs_estimator_test_input19.dat"}}, + {{{{0, 435, 5, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 4, 2, 165, 0, srs_resource_configuration::comb_size_enum(2), 0, 7, 27, 7, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.425673, -0.904877), cf_t(-0.454806, 0.890591), cf_t(-0.197500, -0.980303), cf_t(0.787726, 0.616026), cf_t(0.980662, -0.195711), cf_t(-0.766660, -0.642054), cf_t(-0.980682, -0.195607), cf_t(0.179984, -0.983670)}}, 2, 4}, 0, {-0.000000080}}}, {"test_data/srs_estimator_test_input20.dat"}}, + {{{{0, 157, 0, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 3, 29, 450, 3, srs_resource_configuration::comb_size_enum(4), 2, 7, 65, 5, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.058356, 0.998296), cf_t(-0.337907, -0.941179), cf_t(-0.447696, -0.894186), cf_t(0.910125, 0.414334), cf_t(-0.242993, 0.970028), cf_t(-0.030093, 0.999547), cf_t(-0.471767, -0.881723), cf_t(0.162389, 0.986727)}}, 2, 4}, 0, {0.000000175}}}, {"test_data/srs_estimator_test_input21.dat"}}, + {{{{0, 799, 8, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 9, 24, 6, 3, srs_resource_configuration::comb_size_enum(2), 0, 5, 31, 6, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.126329, -0.991988), cf_t(0.974860, 0.222818), cf_t(0.573868, 0.818948), cf_t(-0.072652, -0.997357), cf_t(-0.439780, 0.898105), cf_t(0.449088, 0.893487), cf_t(-0.541779, 0.840521), cf_t(0.044912, 0.998991), cf_t(0.216544, -0.976273), cf_t(-0.176520, -0.984297), cf_t(-0.780865, -0.624699), cf_t(0.868396, -0.495872), cf_t(-0.983849, 0.178998), cf_t(-0.986156, 0.165823), cf_t(0.357907, 0.933757), cf_t(-0.119481, 0.992836)}}, 4, 4}, 0, {0.000000277}}}, {"test_data/srs_estimator_test_input22.dat"}}, + {{{{0, 93, 1, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 8, 27, 699, 2, srs_resource_configuration::comb_size_enum(4), 2, 7, 64, 6, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.255166, 0.966897), cf_t(-0.781200, -0.624281), cf_t(0.127113, -0.991888), cf_t(0.546021, -0.837771), cf_t(-0.253059, -0.967451), cf_t(-0.951324, 0.308194), cf_t(-0.588893, 0.808211), cf_t(0.497732, -0.867331), cf_t(0.086408, 0.996260), cf_t(-0.966561, 0.256438), cf_t(-0.525123, -0.851026), cf_t(-0.040459, 0.999181), cf_t(0.731560, 0.681777), cf_t(-0.525470, -0.850812), cf_t(-0.864425, 0.502762), cf_t(-0.756472, -0.654026)}}, 4, 4}, 0, {0.000000086}}}, {"test_data/srs_estimator_test_input23.dat"}}, + {{{{0, 271, 5, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 4, 41, 122, 1, srs_resource_configuration::comb_size_enum(2), 1, 7, 36, 10, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.180918, -0.983498), cf_t(-0.990028, -0.140872), cf_t(0.999218, -0.039545), cf_t(0.195542, 0.980695)}}, 1, 4}, 0, {-0.000000411}}}, {"test_data/srs_estimator_test_input24.dat"}}, + {{{{0, 414, 1, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 5, 40, 374, 3, srs_resource_configuration::comb_size_enum(4), 3, 2, 9, 8, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.331241, -0.943546), cf_t(0.831225, 0.555936), cf_t(-0.987288, -0.158944), cf_t(-0.981880, -0.189505)}}, 1, 4}, 0, {0.000000376}}}, {"test_data/srs_estimator_test_input25.dat"}}, + {{{{0, 687, 4, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 9, 1, 532, 1, srs_resource_configuration::comb_size_enum(2), 0, 2, 55, 4, 1, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.904617, 0.426226), cf_t(-0.797004, 0.603974), cf_t(0.761511, -0.648153), cf_t(0.359428, -0.933173), cf_t(-0.775230, 0.631679), cf_t(0.031895, -0.999491), cf_t(0.119811, -0.992797), cf_t(-0.717669, 0.696384)}}, 2, 4}, 0, {-0.000000296}}}, {"test_data/srs_estimator_test_input26.dat"}}, + {{{{0, 335, 7, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 8, 49, 449, 0, srs_resource_configuration::comb_size_enum(4), 3, 6, 60, 9, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.850908, -0.525314), cf_t(-0.008132, -0.999967), cf_t(0.563370, 0.826205), cf_t(0.457256, -0.889335), cf_t(0.309837, 0.950790), cf_t(0.248465, -0.968641), cf_t(-0.833922, 0.551883), cf_t(-0.417371, 0.908736)}}, 2, 4}, 0, {0.000000035}}}, {"test_data/srs_estimator_test_input27.dat"}}, + {{{{0, 139, 0, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 8, 31, 507, 0, srs_resource_configuration::comb_size_enum(2), 0, 6, 63, 2, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-0.328863, -0.944377), cf_t(0.997574, -0.069614), cf_t(0.998007, -0.063103), cf_t(0.138768, 0.990325), cf_t(-0.867724, -0.497046), cf_t(0.999995, 0.003282), cf_t(-0.984914, -0.173044), cf_t(-0.999928, 0.011974), cf_t(0.399437, -0.916761), cf_t(0.663376, -0.748286), cf_t(-0.991735, 0.128304), cf_t(0.812154, -0.583443), cf_t(0.724713, -0.689050), cf_t(-0.760135, -0.649766), cf_t(0.317059, -0.948406), cf_t(-0.891971, -0.452093)}}, 4, 4}, 0, {0.000000360}}}, {"test_data/srs_estimator_test_input28.dat"}}, + {{{{0, 252, 7, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 8, 42, 85, 2, srs_resource_configuration::comb_size_enum(4), 3, 9, 39, 6, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.900263, -0.435346), cf_t(0.650454, -0.759545), cf_t(-0.946509, -0.322677), cf_t(-0.649025, 0.760767), cf_t(-0.876033, -0.482251), cf_t(-0.995136, 0.098508), cf_t(-0.685081, -0.728467), cf_t(0.951960, 0.306223), cf_t(0.994312, 0.106505), cf_t(0.561333, -0.827590), cf_t(0.979866, 0.199655), cf_t(-0.997853, 0.065487), cf_t(0.725261, 0.688474), cf_t(0.252309, 0.967647), cf_t(-0.751301, -0.659960), cf_t(0.353414, 0.935467)}}, 4, 4}, 0, {-0.000000393}}}, {"test_data/srs_estimator_test_input29.dat"}}, + {{{{0, 193, 2, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 0, 34, 650, 2, srs_resource_configuration::comb_size_enum(2), 0, 5, 8, 3, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.998165, 0.060550), cf_t(0.602920, -0.797801), cf_t(0.702325, -0.711856), cf_t(-0.127168, 0.991881)}}, 1, 4}, 0, {-0.000000304}}}, {"test_data/srs_estimator_test_input30.dat"}}, + {{{{0, 427, 5, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 2, 6, 970, 0, srs_resource_configuration::comb_size_enum(4), 0, 6, 3, 0, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.908015, -0.418937), cf_t(-0.133671, -0.991026), cf_t(-0.076319, -0.997083), cf_t(0.921690, 0.387928)}}, 1, 4}, 0, {0.000000375}}}, {"test_data/srs_estimator_test_input31.dat"}}, + {{{{0, 879, 9, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 8, 25, 525, 0, srs_resource_configuration::comb_size_enum(2), 0, 3, 20, 1, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.497838, 0.867270), cf_t(0.547481, -0.836818), cf_t(-0.978669, 0.205443), cf_t(-0.931997, -0.362466), cf_t(-0.596905, -0.802312), cf_t(0.608429, -0.793608), cf_t(0.987463, 0.157850), cf_t(-0.576953, 0.816777)}}, 2, 4}, 0, {-0.000000056}}}, {"test_data/srs_estimator_test_input32.dat"}}, + {{{{0, 678, 0, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 3, 63, 920, 2, srs_resource_configuration::comb_size_enum(4), 2, 3, 28, 1, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.975697, 0.219125), cf_t(0.436455, 0.899726), cf_t(0.087583, -0.996157), cf_t(-0.635655, 0.771973), cf_t(0.415542, -0.909574), cf_t(0.937199, 0.348796), cf_t(0.808198, 0.588911), cf_t(-0.990560, -0.137079)}}, 2, 4}, 0, {-0.000000171}}}, {"test_data/srs_estimator_test_input33.dat"}}, + {{{{0, 926, 1, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 7, 6, 479, 2, srs_resource_configuration::comb_size_enum(2), 1, 2, 40, 10, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-0.308640, 0.951179), cf_t(0.900903, 0.434021), cf_t(0.996519, 0.083364), cf_t(-0.352692, 0.935739), cf_t(0.665435, 0.746455), cf_t(0.047191, 0.998886), cf_t(0.798519, -0.601970), cf_t(-0.962127, 0.272603), cf_t(0.232825, 0.972519), cf_t(0.943503, 0.331364), cf_t(0.328917, 0.944359), cf_t(0.802807, 0.596239), cf_t(0.789930, -0.613197), cf_t(-0.933705, 0.358043), cf_t(0.832792, 0.553586), cf_t(0.999580, -0.028963)}}, 4, 4}, 0, {-0.000000175}}}, {"test_data/srs_estimator_test_input34.dat"}}, + {{{{0, 305, 2, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 0, 40, 517, 0, srs_resource_configuration::comb_size_enum(4), 0, 10, 36, 8, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.773890, 0.633320), cf_t(-0.023161, -0.999732), cf_t(-0.796018, -0.605273), cf_t(0.195873, -0.980629), cf_t(0.458513, -0.888688), cf_t(0.997892, 0.064901), cf_t(-0.986581, -0.163272), cf_t(-0.236358, 0.971666), cf_t(-0.525697, 0.850672), cf_t(0.954026, 0.299724), cf_t(-0.127144, -0.991884), cf_t(-0.353287, -0.935515), cf_t(-0.272789, 0.962074), cf_t(-0.493186, -0.869924), cf_t(-0.265367, -0.964148), cf_t(-0.937275, -0.348591)}}, 4, 4}, 0, {-0.000000108}}}, {"test_data/srs_estimator_test_input35.dat"}}, + {{{{1, 345, 0, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 8, 7, 759, 0, srs_resource_configuration::comb_size_enum(2), 1, 7, 50, 7, 1, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.866489, -0.499197), cf_t(-0.062588, -0.998039)}}, 1, 2}, 0, {-0.000000138}}}, {"test_data/srs_estimator_test_input36.dat"}}, + {{{{1, 887, 7, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 1, 43, 375, 2, srs_resource_configuration::comb_size_enum(4), 3, 2, 5, 4, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.137379, -0.990519), cf_t(0.274919, 0.961467)}}, 1, 2}, 0, {-0.000000058}}}, {"test_data/srs_estimator_test_input37.dat"}}, + {{{{1, 657, 5, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 6, 6, 155, 1, srs_resource_configuration::comb_size_enum(2), 0, 1, 27, 8, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.790747, 0.612143), cf_t(0.214634, -0.976695), cf_t(0.761287, 0.648415), cf_t(-0.258234, 0.966082)}}, 2, 2}, 0, {0.000000054}}}, {"test_data/srs_estimator_test_input38.dat"}}, + {{{{1, 711, 9, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 10, 7, 443, 3, srs_resource_configuration::comb_size_enum(4), 0, 9, 33, 7, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.118837, -0.992914), cf_t(-0.143630, 0.989631), cf_t(-0.794016, 0.607897), cf_t(0.972758, 0.231824)}}, 2, 2}, 0, {0.000000090}}}, {"test_data/srs_estimator_test_input39.dat"}}, + {{{{1, 624, 4, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 0, 44, 323, 0, srs_resource_configuration::comb_size_enum(2), 0, 0, 28, 8, 0, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-0.559101, -0.829099), cf_t(-0.980835, -0.194839), cf_t(-0.676415, -0.736521), cf_t(0.664589, 0.747209), cf_t(-0.169313, -0.985562), cf_t(0.775226, 0.631684), cf_t(0.700412, 0.713739), cf_t(0.814178, 0.580616)}}, 4, 2}, 0, {-0.000000186}}}, {"test_data/srs_estimator_test_input40.dat"}}, + {{{{1, 325, 1, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(1), 4, 57, 222, 2, srs_resource_configuration::comb_size_enum(4), 2, 2, 5, 2, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.856881, -0.515515), cf_t(-0.934801, -0.355173), cf_t(0.502518, 0.864567), cf_t(0.997128, -0.075736), cf_t(-0.268626, -0.963245), cf_t(-0.388070, 0.921630), cf_t(-0.718138, -0.695900), cf_t(0.479373, 0.877611)}}, 4, 2}, 0, {-0.000000126}}}, {"test_data/srs_estimator_test_input41.dat"}}, + {{{{1, 700, 3, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 5, 43, 1006, 1, srs_resource_configuration::comb_size_enum(2), 1, 7, 22, 3, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.304382, 0.952550), cf_t(-0.955964, 0.293485)}}, 1, 2}, 0, {-0.000000040}}}, {"test_data/srs_estimator_test_input42.dat"}}, + {{{{1, 760, 3, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 5, 1, 439, 1, srs_resource_configuration::comb_size_enum(4), 1, 11, 63, 1, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.965195, 0.261531), cf_t(0.059790, 0.998211)}}, 1, 2}, 0, {0.000000137}}}, {"test_data/srs_estimator_test_input43.dat"}}, + {{{{1, 761, 7, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 1, 13, 697, 0, srs_resource_configuration::comb_size_enum(2), 1, 1, 45, 5, 0, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.787798, -0.615934), cf_t(-0.292825, -0.956166), cf_t(-0.994593, -0.103851), cf_t(0.569387, 0.822070)}}, 2, 2}, 0, {0.000000236}}}, {"test_data/srs_estimator_test_input44.dat"}}, + {{{{1, 37, 5, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 10, 33, 766, 1, srs_resource_configuration::comb_size_enum(4), 2, 4, 12, 1, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.033839, 0.999427), cf_t(0.887198, -0.461389), cf_t(0.991687, 0.128672), cf_t(-0.568821, -0.822461)}}, 2, 2}, 0, {0.000000225}}}, {"test_data/srs_estimator_test_input45.dat"}}, + {{{{1, 813, 1, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 7, 32, 450, 2, srs_resource_configuration::comb_size_enum(2), 1, 0, 58, 9, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-0.679083, -0.734061), cf_t(0.999823, -0.018828), cf_t(-0.575257, -0.817973), cf_t(-0.759366, 0.650663), cf_t(-0.613273, 0.789871), cf_t(0.161574, 0.986861), cf_t(-0.790191, -0.612861), cf_t(0.626775, 0.779200)}}, 4, 2}, 0, {-0.000000247}}}, {"test_data/srs_estimator_test_input46.dat"}}, + {{{{1, 743, 4, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(2), 4, 36, 861, 0, srs_resource_configuration::comb_size_enum(4), 3, 11, 15, 8, 1, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-0.700697, 0.713459), cf_t(-0.636860, -0.770980), cf_t(0.960204, 0.279299), cf_t(-0.574685, 0.818375), cf_t(0.852639, 0.522500), cf_t(0.422269, 0.906471), cf_t(-0.167759, -0.985828), cf_t(-0.532550, -0.846398)}}, 4, 2}, 0, {-0.000000060}}}, {"test_data/srs_estimator_test_input47.dat"}}, + {{{{1, 932, 6, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 8, 24, 763, 2, srs_resource_configuration::comb_size_enum(2), 1, 3, 16, 8, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.954188, 0.299208), cf_t(0.139577, 0.990211)}}, 1, 2}, 0, {0.000000159}}}, {"test_data/srs_estimator_test_input48.dat"}}, + {{{{1, 548, 9, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 0, 4, 821, 3, srs_resource_configuration::comb_size_enum(4), 0, 9, 36, 10, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.751603, -0.659616), cf_t(0.805326, -0.592832)}}, 1, 2}, 0, {0.000000066}}}, {"test_data/srs_estimator_test_input49.dat"}}, + {{{{1, 186, 1, 0}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 0, 49, 109, 1, srs_resource_configuration::comb_size_enum(2), 1, 3, 46, 10, 1, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.984732, -0.174075), cf_t(-0.799163, -0.601114), cf_t(-0.849650, 0.527346), cf_t(0.003268, -0.999995)}}, 2, 2}, 0, {0.000000044}}}, {"test_data/srs_estimator_test_input50.dat"}}, + {{{{1, 524, 5, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 0, 22, 736, 3, srs_resource_configuration::comb_size_enum(4), 1, 5, 28, 10, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.201359, 0.979517), cf_t(-0.361775, 0.932265), cf_t(0.704193, 0.710009), cf_t(-0.149577, -0.988750)}}, 2, 2}, 0, {0.000000147}}}, {"test_data/srs_estimator_test_input51.dat"}}, + {{{{1, 863, 6, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 10, 24, 789, 2, srs_resource_configuration::comb_size_enum(2), 1, 3, 45, 0, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-0.990017, 0.140950), cf_t(0.085066, 0.996375), cf_t(0.479789, -0.877384), cf_t(0.916448, -0.400154), cf_t(-0.712787, -0.701381), cf_t(0.442061, 0.896985), cf_t(0.106122, -0.994353), cf_t(0.778900, 0.627148)}}, 4, 2}, 0, {-0.000000166}}}, {"test_data/srs_estimator_test_input52.dat"}}, + {{{{1, 197, 0, 1}, {srs_resource_configuration::one_two_four_enum(2), srs_resource_configuration::one_two_four_enum(4), 9, 35, 101, 3, srs_resource_configuration::comb_size_enum(4), 1, 4, 14, 0, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-0.997966, -0.063747), cf_t(-0.689460, -0.724324), cf_t(-0.773925, 0.633277), cf_t(-0.999968, -0.008061), cf_t(0.831868, -0.554973), cf_t(0.803315, 0.595555), cf_t(0.941694, 0.336470), cf_t(-0.909379, 0.415969)}}, 4, 2}, 0, {0.000000259}}}, {"test_data/srs_estimator_test_input53.dat"}}, + {{{{1, 915, 8, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 1, 62, 399, 2, srs_resource_configuration::comb_size_enum(2), 0, 4, 51, 6, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.857206, 0.514974), cf_t(-0.998844, 0.048079), cf_t(-0.340255, -0.940333), cf_t(0.985361, -0.170481)}}, 1, 4}, 0, {-0.000000090}}}, {"test_data/srs_estimator_test_input54.dat"}}, + {{{{1, 977, 8, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 0, 18, 365, 0, srs_resource_configuration::comb_size_enum(4), 2, 7, 44, 7, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.955692, 0.294369), cf_t(-0.581591, 0.813481), cf_t(-0.953626, 0.300995), cf_t(0.057114, 0.998368)}}, 1, 4}, 0, {0.000000112}}}, {"test_data/srs_estimator_test_input55.dat"}}, + {{{{1, 748, 8, 1}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 1, 37, 856, 1, srs_resource_configuration::comb_size_enum(2), 1, 5, 59, 1, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.603640, 0.797257), cf_t(0.985600, -0.169091), cf_t(-0.949970, 0.312340), cf_t(0.372754, 0.927930), cf_t(0.973861, -0.227146), cf_t(-0.497530, -0.867447), cf_t(0.964892, 0.262647), cf_t(-0.856102, -0.516806)}}, 2, 4}, 0, {0.000000091}}}, {"test_data/srs_estimator_test_input56.dat"}}, + {{{{1, 830, 3, 1}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 0, 41, 85, 0, srs_resource_configuration::comb_size_enum(4), 1, 3, 17, 10, 0, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.490896, 0.871218), cf_t(0.746581, -0.665294), cf_t(0.576372, 0.817188), cf_t(0.829621, 0.558328), cf_t(-0.577612, 0.816311), cf_t(0.904936, -0.425548), cf_t(0.721796, 0.692106), cf_t(-0.805382, 0.592756)}}, 2, 4}, 0, {-0.000000236}}}, {"test_data/srs_estimator_test_input57.dat"}}, + {{{{1, 813, 3, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 7, 37, 702, 0, srs_resource_configuration::comb_size_enum(2), 1, 3, 36, 9, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.995429, -0.095508), cf_t(-0.983015, 0.183524), cf_t(-0.999702, -0.024425), cf_t(-0.988245, 0.152878), cf_t(-0.213983, -0.976837), cf_t(-0.928117, -0.372289), cf_t(-0.603883, -0.797073), cf_t(-0.649253, 0.760572), cf_t(0.530349, -0.847779), cf_t(-0.119668, 0.992814), cf_t(-0.354918, 0.934897), cf_t(0.237191, -0.971463), cf_t(-0.913359, 0.407154), cf_t(-0.006167, -0.999981), cf_t(0.643578, 0.765381), cf_t(0.189207, -0.981937)}}, 4, 4}, 0, {0.000000088}}}, {"test_data/srs_estimator_test_input58.dat"}}, + {{{{1, 573, 1, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(1), 4, 18, 961, 3, srs_resource_configuration::comb_size_enum(4), 3, 10, 64, 10, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-0.952678, -0.303982), cf_t(-0.944096, 0.329672), cf_t(0.922999, -0.384803), cf_t(-0.833438, -0.552613), cf_t(-0.135383, -0.990793), cf_t(-0.606423, -0.795142), cf_t(0.479179, -0.877717), cf_t(0.696150, -0.717896), cf_t(-0.885937, -0.463806), cf_t(-0.991139, -0.132828), cf_t(0.583127, -0.812381), cf_t(0.913975, -0.405771), cf_t(0.986831, 0.161753), cf_t(-0.695067, 0.718945), cf_t(-0.696067, 0.717977), cf_t(-0.490187, -0.871617)}}, 4, 4}, 0, {-0.000000153}}}, {"test_data/srs_estimator_test_input59.dat"}}, + {{{{1, 416, 6, 1}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 8, 18, 956, 2, srs_resource_configuration::comb_size_enum(2), 1, 4, 18, 9, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.207893, -0.978152), cf_t(-0.208225, 0.978081), cf_t(0.794749, -0.606938), cf_t(0.462807, -0.886459)}}, 1, 4}, 0, {-0.000000057}}}, {"test_data/srs_estimator_test_input60.dat"}}, + {{{{1, 854, 4, 1}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 7, 29, 588, 2, srs_resource_configuration::comb_size_enum(4), 3, 0, 45, 3, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.926287, 0.376820), cf_t(-0.924648, 0.380824), cf_t(0.741587, 0.670857), cf_t(0.395312, -0.918547)}}, 1, 4}, 0, {-0.000000091}}}, {"test_data/srs_estimator_test_input61.dat"}}, + {{{{1, 384, 2, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 7, 8, 575, 0, srs_resource_configuration::comb_size_enum(2), 0, 3, 20, 9, 0, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.997308, 0.073328), cf_t(-0.679303, -0.733858), cf_t(-0.968731, -0.248112), cf_t(0.634128, -0.773228), cf_t(0.825762, 0.564019), cf_t(0.986911, -0.161263), cf_t(0.605359, 0.795953), cf_t(-0.902572, -0.430540)}}, 2, 4}, 0, {0.000000259}}}, {"test_data/srs_estimator_test_input62.dat"}}, + {{{{1, 338, 5, 1}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 5, 56, 503, 0, srs_resource_configuration::comb_size_enum(4), 1, 4, 41, 0, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(0.418036, -0.908430), cf_t(-0.053898, 0.998546), cf_t(0.755220, -0.655471), cf_t(0.801062, -0.598581), cf_t(0.907779, -0.419449), cf_t(-0.832822, -0.553541), cf_t(0.363536, 0.931580), cf_t(-0.999709, -0.024126)}}, 2, 4}, 0, {0.000000059}}}, {"test_data/srs_estimator_test_input63.dat"}}, + {{{{1, 206, 8, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 5, 39, 438, 2, srs_resource_configuration::comb_size_enum(2), 1, 6, 37, 10, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.554288, 0.832325), cf_t(0.515896, -0.856651), cf_t(-0.755858, 0.654736), cf_t(-0.346102, -0.938197), cf_t(-0.924943, -0.380105), cf_t(-0.116682, -0.993169), cf_t(0.159876, -0.987137), cf_t(0.941334, -0.337476), cf_t(-0.339901, -0.940461), cf_t(-0.637574, 0.770389), cf_t(-0.098667, -0.995121), cf_t(0.213435, -0.976957), cf_t(-0.895121, 0.445823), cf_t(-0.958901, 0.283740), cf_t(-0.905569, 0.424199), cf_t(-0.275539, -0.961290)}}, 4, 4}, 0, {-0.000000203}}}, {"test_data/srs_estimator_test_input64.dat"}}, + {{{{1, 470, 3, 1}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(2), 0, 1, 234, 3, srs_resource_configuration::comb_size_enum(4), 0, 6, 14, 9, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-0.900458, -0.434943), cf_t(0.938785, 0.344502), cf_t(-0.918192, 0.396135), cf_t(0.655961, -0.754795), cf_t(0.719484, 0.694509), cf_t(0.937989, 0.346665), cf_t(0.493938, -0.869497), cf_t(0.817434, 0.576022), cf_t(-0.475320, -0.879813), cf_t(0.575002, 0.818152), cf_t(-0.740095, -0.672502), cf_t(0.837707, -0.546120), cf_t(-0.810545, -0.585677), cf_t(0.992410, 0.122971), cf_t(-0.992012, -0.126140), cf_t(0.778396, 0.627773)}}, 4, 4}, 0, {0.000000009}}}, {"test_data/srs_estimator_test_input65.dat"}}, + {{{{1, 4, 1, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 8, 63, 869, 2, srs_resource_configuration::comb_size_enum(2), 0, 6, 39, 10, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(0.080884, -0.996724), cf_t(0.867188, 0.497982), cf_t(-0.527332, -0.849659), cf_t(-0.994315, -0.106480)}}, 1, 4}, 0, {-0.000000171}}}, {"test_data/srs_estimator_test_input66.dat"}}, + {{{{1, 451, 9, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 10, 53, 671, 2, srs_resource_configuration::comb_size_enum(4), 2, 4, 16, 4, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0}}, {{{{cf_t(-0.879545, -0.475815), cf_t(0.670159, -0.742218), cf_t(-0.833304, 0.552815), cf_t(0.759936, 0.649998)}}, 1, 4}, 0, {-0.000000029}}}, {"test_data/srs_estimator_test_input67.dat"}}, + {{{{1, 853, 3, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 4, 8, 399, 1, srs_resource_configuration::comb_size_enum(2), 0, 3, 20, 3, 2, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.890602, 0.454783), cf_t(0.040369, 0.999185), cf_t(0.732369, 0.680908), cf_t(0.218573, -0.975821), cf_t(-0.999520, 0.030990), cf_t(0.893589, 0.448886), cf_t(-0.270490, -0.962723), cf_t(-0.785836, 0.618435)}}, 2, 4}, 0, {-0.000000259}}}, {"test_data/srs_estimator_test_input68.dat"}}, + {{{{1, 193, 2, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 1, 8, 274, 2, srs_resource_configuration::comb_size_enum(4), 3, 2, 32, 1, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1}}, {{{{cf_t(-0.711585, 0.702600), cf_t(-0.921034, 0.389482), cf_t(-0.988858, -0.148859), cf_t(0.460400, 0.887712), cf_t(-0.093311, 0.995637), cf_t(0.986576, 0.163301), cf_t(0.909174, 0.416417), cf_t(0.959728, -0.280932)}}, 2, 4}, 0, {-0.000000036}}}, {"test_data/srs_estimator_test_input69.dat"}}, + {{{{1, 7, 9, 1}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 7, 35, 722, 0, srs_resource_configuration::comb_size_enum(2), 1, 4, 60, 7, 0, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(0.619720, -0.784823), cf_t(0.844985, -0.534791), cf_t(0.603248, -0.797553), cf_t(-0.979316, -0.202335), cf_t(-0.817910, 0.575347), cf_t(0.841278, -0.540602), cf_t(-0.935217, 0.354075), cf_t(-0.208952, -0.977926), cf_t(-0.414485, 0.910056), cf_t(-0.838918, -0.544258), cf_t(0.824798, -0.565428), cf_t(0.429744, 0.902951), cf_t(-0.775950, -0.630794), cf_t(-0.495848, 0.868409), cf_t(0.978348, 0.206966), cf_t(-0.517308, 0.855799)}}, 4, 4}, 0, {-0.000000163}}}, {"test_data/srs_estimator_test_input70.dat"}}, + {{{{1, 561, 3, 0}, {srs_resource_configuration::one_two_four_enum(4), srs_resource_configuration::one_two_four_enum(4), 0, 15, 565, 0, srs_resource_configuration::comb_size_enum(4), 0, 11, 55, 3, 3, srs_resource_configuration::group_or_sequence_hopping_enum::neither, {}}, {0, 1, 2, 3}}, {{{{cf_t(-0.136161, -0.990687), cf_t(0.999972, 0.007530), cf_t(-0.963619, -0.267278), cf_t(0.072483, -0.997370), cf_t(0.449440, 0.893310), cf_t(-0.405318, 0.914176), cf_t(-0.927533, 0.373742), cf_t(0.077883, -0.996962), cf_t(-0.639218, 0.769025), cf_t(-0.311305, -0.950310), cf_t(-0.233001, 0.972477), cf_t(-0.887975, -0.459892), cf_t(0.375183, 0.926951), cf_t(-0.705972, -0.708240), cf_t(-0.999946, -0.010424), cf_t(-0.014684, -0.999892)}}, 4, 4}, 0, {0.000000076}}}, {"test_data/srs_estimator_test_input71.dat"}}, // clang-format on }; diff --git a/tests/unittests/phy/upper/signal_processors/srs/srs_estimator_validator_test.cpp b/tests/unittests/phy/upper/signal_processors/srs/srs_estimator_validator_test.cpp index 7730f7e8a5..fcdfcba150 100644 --- a/tests/unittests/phy/upper/signal_processors/srs/srs_estimator_validator_test.cpp +++ b/tests/unittests/phy/upper/signal_processors/srs/srs_estimator_validator_test.cpp @@ -98,8 +98,18 @@ class srsEstimatorValidatorFixture : public ::testing::TestWithParam dft_factory = create_dft_processor_factory_fftw_fast(); + if (!dft_factory) { + dft_factory = create_dft_processor_factory_generic(); + report_fatal_error_if_not(dft_factory, "Invalid DFT factory."); + } + + std::shared_ptr ta_est_factory = + create_time_alignment_estimator_dft_factory(dft_factory); + report_fatal_error_if_not(ta_est_factory, "Invalid TA estimator factory."); + std::shared_ptr srs_est_factory = - create_srs_estimator_generic_factory(sequence_generator_factory); + create_srs_estimator_generic_factory(sequence_generator_factory, ta_est_factory); ASSERT_NE(srs_est_factory, nullptr); estimator = srs_est_factory->create(); diff --git a/tests/unittests/phy/upper/signal_processors/srs/srs_estimator_vectortest.cpp b/tests/unittests/phy/upper/signal_processors/srs/srs_estimator_vectortest.cpp index 01d06476d9..d83c1d14a6 100644 --- a/tests/unittests/phy/upper/signal_processors/srs/srs_estimator_vectortest.cpp +++ b/tests/unittests/phy/upper/signal_processors/srs/srs_estimator_vectortest.cpp @@ -22,6 +22,7 @@ #include "../../../support/resource_grid_test_doubles.h" #include "srs_estimator_test_data.h" +#include "srsran/phy/generic_functions/generic_functions_factories.h" #include "srsran/phy/upper/signal_processors/srs/formatters.h" #include "srsran/phy/upper/signal_processors/srs/srs_estimator.h" #include "srsran/phy/upper/signal_processors/srs/srs_estimator_configuration.h" @@ -48,7 +49,7 @@ std::ostream& operator<<(std::ostream& os, const srs_channel_matrix& channel) bool operator==(const srs_channel_matrix& left, const srs_channel_matrix& right) { - return left.is_near(right, 1e-2); + return left.is_near(right, 0.05F); } } // namespace srsran @@ -60,12 +61,19 @@ class srsEstimatorFixture : public ::testing::TestWithParam protected: void SetUp() override { + std::shared_ptr dft_proc_factory = create_dft_processor_factory_fftw_slow(); + ASSERT_NE(dft_proc_factory, nullptr); + + std::shared_ptr ta_estimator_factory = + create_time_alignment_estimator_dft_factory(dft_proc_factory); + ASSERT_NE(ta_estimator_factory, nullptr); + std::shared_ptr sequence_generator_factory = create_low_papr_sequence_generator_sw_factory(); ASSERT_NE(sequence_generator_factory, nullptr); std::shared_ptr srs_est_factory = - create_srs_estimator_generic_factory(sequence_generator_factory); + create_srs_estimator_generic_factory(sequence_generator_factory, ta_estimator_factory); ASSERT_NE(srs_est_factory, nullptr); estimator = srs_est_factory->create(); @@ -91,7 +99,8 @@ TEST_P(srsEstimatorFixture, FromVector) srs_estimator_result result = estimator->estimate(grid, config); - ASSERT_EQ(test_case.context.result.channel_matrix, result.channel_matrix); + ASSERT_EQ(test_case.context.result.channel_matrix.normalize(), result.channel_matrix.normalize()); + ASSERT_NEAR(test_case.context.result.time_alignment.time_alignment, result.time_alignment.time_alignment, 1e-7); } INSTANTIATE_TEST_SUITE_P(srsEstimatorFixture, srsEstimatorFixture, ::testing::ValuesIn(srs_estimator_test_data)); diff --git a/tests/unittests/rlc/rlc_sdu_queue_lockfree_test.cpp b/tests/unittests/rlc/rlc_sdu_queue_lockfree_test.cpp index bb0b8bad67..6de1a4073a 100644 --- a/tests/unittests/rlc/rlc_sdu_queue_lockfree_test.cpp +++ b/tests/unittests/rlc/rlc_sdu_queue_lockfree_test.cpp @@ -40,16 +40,18 @@ void queue_unqueue_test() TESTASSERT(tx_queue.write(std::move(write_sdu))); // Check basic stats - TESTASSERT_EQ(1, tx_queue.size_sdus()); - TESTASSERT_EQ(2, tx_queue.size_bytes()); + rlc_sdu_queue_lockfree::state_t state = tx_queue.get_state(); + TESTASSERT_EQ(1, state.n_sdus); + TESTASSERT_EQ(2, state.n_bytes); // Read one SDU rlc_sdu read_sdu; TESTASSERT(tx_queue.read(read_sdu)); // Check basic stats - TESTASSERT_EQ(0, tx_queue.size_sdus()); - TESTASSERT_EQ(0, tx_queue.size_bytes()); + state = tx_queue.get_state(); + TESTASSERT_EQ(0, state.n_sdus); + TESTASSERT_EQ(0, state.n_bytes); // Check SDU byte_buffer expected_msg = byte_buffer::create({0x00, 0x01}).value(); @@ -77,8 +79,9 @@ void full_capacity_test() TESTASSERT(tx_queue.write(std::move(write_sdu)) == false); } } - TESTASSERT_EQ(capacity, tx_queue.size_sdus()); - TESTASSERT_EQ(2 * capacity, tx_queue.size_bytes()); + rlc_sdu_queue_lockfree::state_t state = tx_queue.get_state(); + TESTASSERT_EQ(capacity, state.n_sdus); + TESTASSERT_EQ(2 * capacity, state.n_bytes); // Read all SDUs and try to read on SDU over capacity for (uint32_t pdcp_sn = 0; pdcp_sn < capacity + 1; pdcp_sn++) { @@ -94,8 +97,9 @@ void full_capacity_test() } } - TESTASSERT_EQ(0, tx_queue.size_sdus()); - TESTASSERT_EQ(0, tx_queue.size_bytes()); + state = tx_queue.get_state(); + TESTASSERT_EQ(0, state.n_sdus); + TESTASSERT_EQ(0, state.n_bytes); } void discard_test() @@ -114,8 +118,9 @@ void discard_test() rlc_sdu write_sdu = {std::move(buf), pdcp_sn}; TESTASSERT(tx_queue.write(std::move(write_sdu)) == true); } - TESTASSERT_EQ(n_sdus, tx_queue.size_sdus()); - TESTASSERT_EQ(2 * n_sdus, tx_queue.size_bytes()); + rlc_sdu_queue_lockfree::state_t state = tx_queue.get_state(); + TESTASSERT_EQ(n_sdus, state.n_sdus); + TESTASSERT_EQ(2 * n_sdus, state.n_bytes); // Discard pdcp_sn 2 and 4 TESTASSERT(tx_queue.try_discard(2)); @@ -126,16 +131,18 @@ void discard_test() // Double check correct number of SDUs and SDU bytes unsigned leftover_sdus = n_sdus - 2; - TESTASSERT_EQ(leftover_sdus, tx_queue.size_sdus()); - TESTASSERT_EQ(leftover_sdus * 2, tx_queue.size_bytes()); + state = tx_queue.get_state(); + TESTASSERT_EQ(leftover_sdus, state.n_sdus); + TESTASSERT_EQ(leftover_sdus * 2, state.n_bytes); // Read SDUs for (uint32_t n = 0; n < leftover_sdus; n++) { rlc_sdu read_sdu = {}; TESTASSERT(tx_queue.read(read_sdu)); } - TESTASSERT_EQ(0, tx_queue.size_sdus()); - TESTASSERT_EQ(0, tx_queue.size_bytes()); + state = tx_queue.get_state(); + TESTASSERT_EQ(0, state.n_sdus); + TESTASSERT_EQ(0, state.n_bytes); } void discard_all_test() @@ -154,24 +161,27 @@ void discard_all_test() rlc_sdu write_sdu = {std::move(buf), pdcp_sn}; TESTASSERT(tx_queue.write(std::move(write_sdu)) == true); } - TESTASSERT_EQ(n_sdus, tx_queue.size_sdus()); - TESTASSERT_EQ(2 * n_sdus, tx_queue.size_bytes()); + rlc_sdu_queue_lockfree::state_t state = tx_queue.get_state(); + TESTASSERT_EQ(n_sdus, state.n_sdus); + TESTASSERT_EQ(2 * n_sdus, state.n_bytes); // Discard all SDUs for (uint32_t pdcp_sn = 0; pdcp_sn < n_sdus; pdcp_sn++) { TESTASSERT(tx_queue.try_discard(pdcp_sn)); } - TESTASSERT_EQ(0, tx_queue.size_sdus()); - TESTASSERT_EQ(0, tx_queue.size_bytes()); + state = tx_queue.get_state(); + TESTASSERT_EQ(0, state.n_sdus); + TESTASSERT_EQ(0, state.n_bytes); // Read SDU { rlc_sdu read_sdu = {}; TESTASSERT(tx_queue.read(read_sdu) == false); } - TESTASSERT_EQ(0, tx_queue.size_sdus()); - TESTASSERT_EQ(0, tx_queue.size_bytes()); + state = tx_queue.get_state(); + TESTASSERT_EQ(0, state.n_sdus); + TESTASSERT_EQ(0, state.n_bytes); } } // namespace srsran diff --git a/tests/unittests/rrc/rrc_ue_setup_proc_test.cpp b/tests/unittests/rrc/rrc_ue_setup_proc_test.cpp index 65fda62bfa..ad2e93c16a 100644 --- a/tests/unittests/rrc/rrc_ue_setup_proc_test.cpp +++ b/tests/unittests/rrc/rrc_ue_setup_proc_test.cpp @@ -21,10 +21,6 @@ */ #include "rrc_ue_test_helpers.h" -#include "srsran/adt/byte_buffer.h" -#include "srsran/rrc/rrc_du_factory.h" -#include "srsran/support/async/fifo_async_task_scheduler.h" -#include "srsran/support/test_utils.h" #include using namespace srsran; diff --git a/tests/unittests/rrc/rrc_ue_test_helpers.h b/tests/unittests/rrc/rrc_ue_test_helpers.h index 5c448ccd87..d1963402d6 100644 --- a/tests/unittests/rrc/rrc_ue_test_helpers.h +++ b/tests/unittests/rrc/rrc_ue_test_helpers.h @@ -294,12 +294,12 @@ class rrc_ue_test_helper void check_ue_release_not_requested() { - ASSERT_NE(rrc_ue_ev_notifier.last_cu_cp_ue_context_release_command.ue_index, ALLOCATED_UE_INDEX); + ASSERT_NE(rrc_ue_cu_cp_notifier.last_cu_cp_ue_context_release_request.ue_index, ALLOCATED_UE_INDEX); } void check_ue_release_requested() { - ASSERT_EQ(rrc_ue_ev_notifier.last_cu_cp_ue_context_release_command.ue_index, ALLOCATED_UE_INDEX); + ASSERT_EQ(rrc_ue_cu_cp_notifier.last_cu_cp_ue_context_release_request.ue_index, ALLOCATED_UE_INDEX); } void receive_smc_complete() diff --git a/tests/unittests/rrc/test_helpers.h b/tests/unittests/rrc/test_helpers.h index b782c5ff52..ff25101d82 100644 --- a/tests/unittests/rrc/test_helpers.h +++ b/tests/unittests/rrc/test_helpers.h @@ -48,19 +48,6 @@ class dummy_rrc_f1ap_pdu_notifier : public rrc_pdu_f1ap_notifier class dummy_rrc_ue_du_processor_adapter : public rrc_ue_du_processor_notifier { public: - async_task - on_ue_context_release_command(const cu_cp_ue_context_release_command& msg) override - { - logger.info("Received UE Context Release Command"); - last_cu_cp_ue_context_release_command.ue_index = msg.ue_index; - last_cu_cp_ue_context_release_command.cause = msg.cause; - - return launch_async([](coro_context>& ctx) mutable { - CORO_BEGIN(ctx); - CORO_RETURN(cu_cp_ue_context_release_complete{}); - }); - } - async_task on_rrc_reestablishment_context_modification_required(ue_index_t ue_index) override { logger.info("Received Reestablishment Context Modification Required for ue={}", ue_index); @@ -94,15 +81,6 @@ class dummy_rrc_ue_ngap_adapter : public rrc_ue_nas_notifier, public rrc_ue_cont logger.info("Received UL NAS Transport message"); } - virtual async_task on_ue_context_release_request(const cu_cp_ue_context_release_request& msg) override - { - logger.info("Received a UE Context Release Request"); - return launch_async([this](coro_context>& ctx) { - CORO_BEGIN(ctx); - CORO_RETURN(ue_context_release_outcome); - }); - } - void on_inter_cu_ho_rrc_recfg_complete_received(const ue_index_t ue_index, const nr_cell_global_id_t& cgi, const unsigned tac) override @@ -126,41 +104,62 @@ class dummy_rrc_ue_cu_cp_adapter : public rrc_ue_context_update_notifier, public bool on_ue_setup_request() override { return next_ue_setup_response; } - rrc_ue_reestablishment_context_response - on_rrc_reestablishment_request(pci_t old_pci, rnti_t old_c_rnti, ue_index_t ue_index) override + rrc_ue_reestablishment_context_response on_rrc_reestablishment_request(pci_t old_pci, rnti_t old_c_rnti) override { - logger.info("ue={} old_pci={} old_c-rnti={}: Received RRC Reestablishment Request", ue_index, old_pci, old_c_rnti); + logger.info("old_pci={} old_c-rnti={}: Received RRC Reestablishment Request", old_pci, old_c_rnti); return reest_context; } - async_task on_ue_transfer_required(ue_index_t ue_index, ue_index_t old_ue_index) override + void on_rrc_reestablishment_failure(const cu_cp_ue_context_release_request& request) override + { + logger.info("ue={}: Received RRC Reestablishment failure notification", request.ue_index); + } + + void on_rrc_reestablishment_complete(ue_index_t old_ue_index) override + { + logger.info("ue={}: Received RRC Reestablishment complete notification", old_ue_index); + } + + async_task on_ue_transfer_required(ue_index_t old_ue_index) override { - logger.info("Requested a UE context transfer from ue={} with old_ue={}.", ue_index, old_ue_index); + logger.info("Requested a UE context transfer from old_ue={}.", old_ue_index); return launch_async([](coro_context>& ctx) mutable { CORO_BEGIN(ctx); CORO_RETURN(true); }); } - async_task on_ue_removal_required(ue_index_t ue_index) override + async_task on_ue_removal_required() override { - logger.info("ue={}: Requested a UE removal", ue_index); + logger.info("UE removal requested"); return launch_async([](coro_context>& ctx) mutable { CORO_BEGIN(ctx); CORO_RETURN(); }); } - optional on_measurement_config_request(ue_index_t ue_index, - nr_cell_id_t nci, + async_task on_ue_release_required(const cu_cp_ue_context_release_request& request) override + { + logger.info("ue={}: Requested a UE release", request.ue_index); + last_cu_cp_ue_context_release_request = request; + + return launch_async([](coro_context>& ctx) mutable { + CORO_BEGIN(ctx); + CORO_RETURN(); + }); + } + + optional on_measurement_config_request(nr_cell_id_t nci, optional current_meas_config = {}) override { optional meas_cfg; return meas_cfg; } - void on_measurement_report(const ue_index_t ue_index, const rrc_meas_results& meas_results) override {} + void on_measurement_report(const rrc_meas_results& meas_results) override {} + + cu_cp_ue_context_release_request last_cu_cp_ue_context_release_request; private: rrc_ue_reestablishment_context_response reest_context = {}; diff --git a/tests/unittests/scheduler/policy/scheduler_policy_test.cpp b/tests/unittests/scheduler/policy/scheduler_policy_test.cpp index 695fa9e86f..90f56cfeaf 100644 --- a/tests/unittests/scheduler/policy/scheduler_policy_test.cpp +++ b/tests/unittests/scheduler/policy/scheduler_policy_test.cpp @@ -75,10 +75,12 @@ class dummy_pusch_allocator : public ue_pusch_allocator class base_scheduler_policy_test { protected: - base_scheduler_policy_test(policy_type policy, - const sched_cell_configuration_request_message& msg = - test_helpers::make_default_sched_cell_configuration_request()) : - logger(srslog::fetch_basic_logger("SCHED", true)), cell_cfg(*[this, &msg]() { + base_scheduler_policy_test( + policy_type policy, + scheduler_expert_config sched_cfg_ = config_helpers::make_default_scheduler_expert_config(), + const sched_cell_configuration_request_message& msg = + test_helpers::make_default_sched_cell_configuration_request()) : + logger(srslog::fetch_basic_logger("SCHED", true)), sched_cfg(sched_cfg_), cell_cfg(*[this, &msg]() { return cell_cfg_list.emplace(to_du_cell_index(0), std::make_unique(sched_cfg, msg)).get(); }()) { @@ -89,7 +91,7 @@ class base_scheduler_policy_test switch (policy) { case policy_type::time_rr: - sched = std::make_unique(expert_cfg); + sched = std::make_unique(sched_cfg.ue); break; default: report_fatal_error("Invalid policy"); @@ -173,8 +175,7 @@ class base_scheduler_policy_test } srslog::basic_logger& logger; - scheduler_expert_config sched_cfg = config_helpers::make_default_scheduler_expert_config(); - const scheduler_ue_expert_config& expert_cfg{sched_cfg.ue}; + scheduler_expert_config sched_cfg; cell_common_configuration_list cell_cfg_list; std::vector> ue_ded_cell_cfg_list; @@ -317,7 +318,7 @@ class scheduler_policy_partial_slot_tdd_test : public base_scheduler_policy_test { protected: scheduler_policy_partial_slot_tdd_test() : - base_scheduler_policy_test(GetParam(), []() { + base_scheduler_policy_test(GetParam(), config_helpers::make_default_scheduler_expert_config(), []() { cell_config_builder_params builder_params{}; // Band 40. builder_params.dl_arfcn = 465000; @@ -440,7 +441,62 @@ TEST_F(scheduler_round_robin_test, round_robin_must_not_attempt_to_allocate_twic } } +class scheduler_policy_alloc_bounds_test : public base_scheduler_policy_test, + public ::testing::TestWithParam +{ +protected: + scheduler_policy_alloc_bounds_test() : + base_scheduler_policy_test(GetParam(), []() { + scheduler_expert_config sched_cfg_ = config_helpers::make_default_scheduler_expert_config(); + + // Modify boundaries within which PDSCH and PUSCH needs to be allocated to UEs. + sched_cfg_.ue.pdsch_crb_limits = {10, 15}; + sched_cfg_.ue.pusch_crb_limits = {10, 15}; + + return sched_cfg_; + }()) + { + } +}; + +TEST_P(scheduler_policy_alloc_bounds_test, scheduler_allocates_pdsch_within_configured_boundaries) +{ + lcg_id_t lcg_id = uint_to_lcg_id(2); + const ue& u1 = add_ue(make_ue_create_req(to_du_ue_index(0), to_rnti(0x4601), {uint_to_lcid(5)}, lcg_id)); + + // Buffer has to be large enough so that the allocation does not stop. + push_dl_bs(u1.ue_index, uint_to_lcid(5), 100000); + + // Run for partial slot. + run_slot(); + + // Expected CRBs is based on the expert configuration passed to scheduler during class initialization. + crb_interval expected_crb_allocation{10, 15}; + + ASSERT_EQ(pdsch_alloc.last_grants.size(), 1); + ASSERT_TRUE(pdsch_alloc.last_grants.back().crbs == expected_crb_allocation); +} + +TEST_P(scheduler_policy_alloc_bounds_test, scheduler_allocates_pusch_within_configured_boundaries) +{ + lcg_id_t lcg_id = uint_to_lcg_id(2); + const ue& u1 = add_ue(make_ue_create_req(to_du_ue_index(0), to_rnti(0x4601), {uint_to_lcid(5)}, lcg_id)); + + // Buffer has to be large enough so that the allocation does not stop. + notify_ul_bsr(u1.ue_index, lcg_id, 2000000); + + // Run for partial slot. + run_slot(); + + // Expected CRBs is based on the expert configuration passed to scheduler during class initialization. + crb_interval expected_crb_allocation{10, 15}; + + ASSERT_EQ(pusch_alloc.last_grants.size(), 1); + ASSERT_TRUE(pusch_alloc.last_grants.back().crbs == expected_crb_allocation); +} + INSTANTIATE_TEST_SUITE_P(scheduler_policy, scheduler_policy_test, testing::Values(policy_type::time_rr)); INSTANTIATE_TEST_SUITE_P(scheduler_policy, scheduler_policy_partial_slot_tdd_test, testing::Values(policy_type::time_rr)); +INSTANTIATE_TEST_SUITE_P(scheduler_policy, scheduler_policy_alloc_bounds_test, testing::Values(policy_type::time_rr)); diff --git a/tests/unittests/scheduler/scheduler_ue_fallback_mode_test.cpp b/tests/unittests/scheduler/scheduler_ue_fallback_mode_test.cpp index efbb2e9326..ca31270d9e 100644 --- a/tests/unittests/scheduler/scheduler_ue_fallback_mode_test.cpp +++ b/tests/unittests/scheduler/scheduler_ue_fallback_mode_test.cpp @@ -191,7 +191,8 @@ TEST_P(scheduler_con_res_msg4_test, while_ue_is_in_fallback_then_common_pucch_is return false; })); - // Enqueue SRB1 data. + // Enqueue SRB1 data; with the UE in fallback mode, and after the MSG4 has been delivered, both common and dedicated + // resources should be used. this->push_dl_buffer_state(dl_buffer_state_indication_message{this->ue_index, LCID_SRB1, crnti_msg_size}); // Ensure common resources for PDCCH and PDSCH are used rather than UE-dedicated. @@ -203,15 +204,58 @@ TEST_P(scheduler_con_res_msg4_test, while_ue_is_in_fallback_then_common_pucch_is const dl_msg_alloc& pdsch = *find_ue_pdsch(rnti, *this->last_sched_res_list[to_du_cell_index(0)]); ASSERT_EQ(pdsch.pdsch_cfg.dci_fmt, dci_dl_format::f1_0); - // Ensure common PUCCH resources are used. - const pucch_info* pucch_ptr = nullptr; - ASSERT_TRUE(this->run_slot_until([this, &pucch_ptr]() { - for (const auto& pucch : this->last_sched_res_list[to_du_cell_index(0)]->ul.pucchs) { - if (pucch.crnti == rnti and pucch.format == pucch_format::FORMAT_1 and pucch.format_1.harq_ack_nof_bits > 0) { - pucch_ptr = &pucch; - return true; + // Ensure both common and PUCCH resources are used. + struct pucch_ptrs { + const pucch_info* f1_common_ptr = nullptr; + const pucch_info* f1_ded_ptr = nullptr; + const pucch_info* f1_ded_sr_ptr = nullptr; + const pucch_info* f2_ptr = nullptr; + } pucch_res_ptrs; + + ASSERT_TRUE(this->run_slot_until([this, &pucch_res_ptrs]() { + // Depending on the SR and CSI slots, we can have different combinations of PUCCH grants. There must be at least one + // PUCCH F1 grant using common resources, plus: + // - 1 PUCCH F1 ded. with 1 HARQ-ACK bit and NO SR. + // - 1 PUCCH F1 ded. with 1 HARQ-ACK bit and NO SR and 1 PUCCH F1 ded. with 1 HARQ-ACK bit and SR. + // - 1 PUCCH F2 ded. with 1 HARQ-ACK bit and CSI, with optional SR. + + // Case of 2 PUCCH grants. + if (this->last_sched_res_list[to_du_cell_index(0)]->ul.pucchs.size() == 2) { + for (const auto& pucch : this->last_sched_res_list[to_du_cell_index(0)]->ul.pucchs) { + if (pucch.crnti == rnti and pucch.format == pucch_format::FORMAT_1 and + pucch.format_1.sr_bits == sr_nof_bits::no_sr and pucch.format_1.harq_ack_nof_bits > 0) { + pucch.resources.second_hop_prbs.empty() ? pucch_res_ptrs.f1_ded_ptr = &pucch + : pucch_res_ptrs.f1_common_ptr = &pucch; + } else if (pucch.crnti == rnti and pucch.format == pucch_format::FORMAT_2 and + pucch.format_2.harq_ack_nof_bits > 0) { + pucch_res_ptrs.f2_ptr = &pucch; + } + if (pucch_res_ptrs.f1_common_ptr != nullptr and + (pucch_res_ptrs.f1_ded_ptr != nullptr or pucch_res_ptrs.f2_ptr != nullptr)) { + return true; + } } } + // Case of 3 PUCCH grants. + else if (this->last_sched_res_list[to_du_cell_index(0)]->ul.pucchs.size() == 3) { + for (const auto& pucch : this->last_sched_res_list[to_du_cell_index(0)]->ul.pucchs) { + if (pucch.crnti == rnti and pucch.format == pucch_format::FORMAT_1) { + if (pucch.format_1.sr_bits == sr_nof_bits::no_sr) { + if (pucch.format_1.harq_ack_nof_bits > 0) { + pucch.resources.second_hop_prbs.empty() ? pucch_res_ptrs.f1_ded_ptr = &pucch + : pucch_res_ptrs.f1_common_ptr = &pucch; + } + } else { + pucch_res_ptrs.f1_ded_sr_ptr = &pucch; + } + } + if (pucch_res_ptrs.f1_common_ptr != nullptr and pucch_res_ptrs.f1_ded_ptr != nullptr and + pucch_res_ptrs.f1_ded_sr_ptr != nullptr) { + return true; + } + } + } + return false; })); // TODO: Once PUCCH scheduler avoids multiplexing SR and HARQ-ACK for common PUCCH resources, uncomment the following. @@ -220,10 +264,12 @@ TEST_P(scheduler_con_res_msg4_test, while_ue_is_in_fallback_then_common_pucch_is // [this](const pucch_info& pucch) { return pucch.crnti == rnti; }), // 1) // << "In case of common PUCCH scheduling, multiplexing with SR or CSI should be avoided"; - ASSERT_NE(pucch_ptr, nullptr); - ASSERT_EQ(pucch_ptr->format, pucch_format::FORMAT_1); - ASSERT_EQ(pucch_ptr->format_1.sr_bits, sr_nof_bits::no_sr); - ASSERT_FALSE(pucch_ptr->resources.second_hop_prbs.empty()) << "For common PUCCH resources, second hop is used"; + + const bool two_pucch_grants = pucch_res_ptrs.f1_common_ptr != nullptr and + (pucch_res_ptrs.f1_ded_ptr != nullptr or pucch_res_ptrs.f2_ptr != nullptr); + const bool three_pucch_grants = pucch_res_ptrs.f1_common_ptr != nullptr and pucch_res_ptrs.f1_ded_ptr != nullptr and + pucch_res_ptrs.f1_ded_sr_ptr != nullptr; + ASSERT_TRUE(two_pucch_grants or three_pucch_grants) << "Invalid PUCCH grants combination"; } TEST_P(scheduler_con_res_msg4_test, while_ue_is_in_fallback_then_common_ss_is_used) diff --git a/tests/unittests/scheduler/uci_and_pucch/pucch_alloc_harq_sr_csi_test.cpp b/tests/unittests/scheduler/uci_and_pucch/pucch_alloc_harq_sr_csi_test.cpp index 6f3e576fb1..a3b98057c6 100644 --- a/tests/unittests/scheduler/uci_and_pucch/pucch_alloc_harq_sr_csi_test.cpp +++ b/tests/unittests/scheduler/uci_and_pucch/pucch_alloc_harq_sr_csi_test.cpp @@ -34,7 +34,7 @@ class test_pucch_allocator_ded_resources : public ::testing::Test { public: test_pucch_allocator_ded_resources(unsigned max_pucchs_per_slot_ = 32U, unsigned max_ul_grants_per_slot_ = 32U) : - t_bench(test_bench_params{}, max_pucchs_per_slot_, max_ul_grants_per_slot_) + t_bench(test_bench_params{.pucch_res_common = 11, .n_cces = 1}, max_pucchs_per_slot_, max_ul_grants_per_slot_) { // Set expected grant for PUCCH Format 1 SR. pucch_expected_f1_sr.format = pucch_format::FORMAT_1; @@ -652,6 +652,162 @@ TEST_F(test_pucch_allocator_ded_resources, test_harq_alloc_4bits_over_sr_and_csi ASSERT_FALSE(test_pucch_res_indicator.has_value()); } +/////// Test allocation of common + dedicated resources. /////// + +TEST_F(test_pucch_allocator_ded_resources, test_common_plus_ded_resource_without_existing_grants) +{ + optional test_pucch_res_indicator = + t_bench.pucch_alloc.alloc_common_and_ded_harq_res(t_bench.res_grid, + t_bench.get_main_ue().crnti, + t_bench.get_main_ue().get_pcell().cfg(), + t_bench.k0, + t_bench.k1, + t_bench.dci_info); + + auto& slot_grid = t_bench.res_grid[t_bench.k0 + t_bench.k1]; + + ASSERT_TRUE(test_pucch_res_indicator.has_value()); + ASSERT_EQ(2, slot_grid.result.ul.pucchs.size()); + // PUCCH dedicated resource. + ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[0].format); + ASSERT_EQ(1, slot_grid.result.ul.pucchs[0].format_1.harq_ack_nof_bits); + ASSERT_EQ(sr_nof_bits::no_sr, slot_grid.result.ul.pucchs[0].format_1.sr_bits); + ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_f1_harq, slot_grid.result.ul.pucchs[0])); + // PUCCH common resource. + ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[1].format); + ASSERT_EQ(1, slot_grid.result.ul.pucchs[1].format_1.harq_ack_nof_bits); + ASSERT_EQ(sr_nof_bits::no_sr, slot_grid.result.ul.pucchs[1].format_1.sr_bits); + ASSERT_FALSE(slot_grid.result.ul.pucchs[1].resources.second_hop_prbs.empty()); +} + +TEST_F(test_pucch_allocator_ded_resources, test_common_plus_ded_resource_with_existing_f1_sr) +{ + add_sr_grant(); + + optional test_pucch_res_indicator = + t_bench.pucch_alloc.alloc_common_and_ded_harq_res(t_bench.res_grid, + t_bench.get_main_ue().crnti, + t_bench.get_main_ue().get_pcell().cfg(), + t_bench.k0, + t_bench.k1, + t_bench.dci_info); + + auto& slot_grid = t_bench.res_grid[t_bench.k0 + t_bench.k1]; + + ASSERT_TRUE(test_pucch_res_indicator.has_value()); + ASSERT_EQ(3, slot_grid.result.ul.pucchs.size()); + // PUCCH dedicated resource for SR. + ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[0].format); + ASSERT_EQ(sr_nof_bits::one, slot_grid.result.ul.pucchs[0].format_1.sr_bits); + ASSERT_EQ(1, slot_grid.result.ul.pucchs[0].format_1.harq_ack_nof_bits); + // PUCCH dedicated resource for HARQ-ACK. + ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[1].format); + ASSERT_EQ(1, slot_grid.result.ul.pucchs[1].format_1.harq_ack_nof_bits); + ASSERT_EQ(sr_nof_bits::no_sr, slot_grid.result.ul.pucchs[1].format_1.sr_bits); + ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_f1_harq, slot_grid.result.ul.pucchs[1])); + // PUCCH common resource. + ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[2].format); + ASSERT_EQ(1, slot_grid.result.ul.pucchs[2].format_1.harq_ack_nof_bits); + ASSERT_EQ(sr_nof_bits::no_sr, slot_grid.result.ul.pucchs[2].format_1.sr_bits); + ASSERT_FALSE(slot_grid.result.ul.pucchs[2].resources.second_hop_prbs.empty()); +} + +TEST_F(test_pucch_allocator_ded_resources, test_common_plus_ded_resource_with_existing_f2_csi) +{ + add_csi_grant(); + + optional test_pucch_res_indicator = + t_bench.pucch_alloc.alloc_common_and_ded_harq_res(t_bench.res_grid, + t_bench.get_main_ue().crnti, + t_bench.get_main_ue().get_pcell().cfg(), + t_bench.k0, + t_bench.k1, + t_bench.dci_info); + + auto& slot_grid = t_bench.res_grid[t_bench.k0 + t_bench.k1]; + + ASSERT_TRUE(test_pucch_res_indicator.has_value()); + ASSERT_EQ(2, slot_grid.result.ul.pucchs.size()); + // PUCCH dedicated resource for HARQ-ACK. + ASSERT_EQ(pucch_format::FORMAT_2, slot_grid.result.ul.pucchs[0].format); + ASSERT_EQ(1, slot_grid.result.ul.pucchs[0].format_2.harq_ack_nof_bits); + ASSERT_EQ(sr_nof_bits::no_sr, slot_grid.result.ul.pucchs[0].format_2.sr_bits); + ASSERT_EQ(4, slot_grid.result.ul.pucchs[0].format_2.csi_part1_bits); + // PUCCH common resource. + ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[1].format); + ASSERT_EQ(1, slot_grid.result.ul.pucchs[1].format_1.harq_ack_nof_bits); + ASSERT_EQ(sr_nof_bits::no_sr, slot_grid.result.ul.pucchs[1].format_1.sr_bits); + ASSERT_FALSE(slot_grid.result.ul.pucchs[1].resources.second_hop_prbs.empty()); +} + +TEST_F(test_pucch_allocator_ded_resources, test_common_plus_ded_res_fails_due_to_no_free_resources) +{ + add_ue_with_harq_grant(); + add_ue_with_harq_grant(); + add_ue_with_harq_grant(); + auto& slot_grid = t_bench.res_grid[t_bench.k0 + t_bench.k1]; + ASSERT_EQ(3, slot_grid.result.ul.pucchs.size()); + + optional test_pucch_res_indicator = + t_bench.pucch_alloc.alloc_common_and_ded_harq_res(t_bench.res_grid, + t_bench.get_main_ue().crnti, + t_bench.get_main_ue().get_pcell().cfg(), + t_bench.k0, + t_bench.k1, + t_bench.dci_info); + + ASSERT_FALSE(test_pucch_res_indicator.has_value()); + // The number of grants hasn't changed. + ASSERT_EQ(3, slot_grid.result.ul.pucchs.size()); +} + +TEST_F(test_pucch_allocator_ded_resources, test_common_plus_ded_with_sr_res_fails_due_to_no_free_f1_resources) +{ + add_sr_grant(); + add_ue_with_harq_grant(); + add_ue_with_harq_grant(); + add_ue_with_harq_grant(); + auto& slot_grid = t_bench.res_grid[t_bench.k0 + t_bench.k1]; + ASSERT_EQ(4, slot_grid.result.ul.pucchs.size()); + + optional test_pucch_res_indicator = + t_bench.pucch_alloc.alloc_common_and_ded_harq_res(t_bench.res_grid, + t_bench.get_main_ue().crnti, + t_bench.get_main_ue().get_pcell().cfg(), + t_bench.k0, + t_bench.k1, + t_bench.dci_info); + + ASSERT_FALSE(test_pucch_res_indicator.has_value()); + // The number of grants hasn't changed. + ASSERT_EQ(4, slot_grid.result.ul.pucchs.size()); +} + +TEST_F(test_pucch_allocator_ded_resources, test_common_plus_ded_with_csi_res_fails_due_to_no_free_f2_resources) +{ + add_csi_grant(); + add_ue_with_format2_harq_grant(); + add_ue_with_format2_harq_grant(); + add_ue_with_format2_harq_grant(); + add_ue_with_format2_harq_grant(); + add_ue_with_format2_harq_grant(); + add_ue_with_format2_harq_grant(); + auto& slot_grid = t_bench.res_grid[t_bench.k0 + t_bench.k1]; + ASSERT_EQ(7, slot_grid.result.ul.pucchs.size()); + + optional test_pucch_res_indicator = + t_bench.pucch_alloc.alloc_common_and_ded_harq_res(t_bench.res_grid, + t_bench.get_main_ue().crnti, + t_bench.get_main_ue().get_pcell().cfg(), + t_bench.k0, + t_bench.k1, + t_bench.dci_info); + + ASSERT_FALSE(test_pucch_res_indicator.has_value()); + // The number of grants hasn't changed. + ASSERT_EQ(7, slot_grid.result.ul.pucchs.size()); +} + /////// Test PUCCH Format 2 PRB configuration. /////// TEST_F(test_pucch_f2_alloc_several_prbs, test_prb_allocation_csi_only) diff --git a/tests/unittests/scheduler/uci_and_pucch/pucch_res_manager_test.cpp b/tests/unittests/scheduler/uci_and_pucch/pucch_res_manager_test.cpp index 569f057a34..fbb8c2bd3a 100644 --- a/tests/unittests/scheduler/uci_and_pucch/pucch_res_manager_test.cpp +++ b/tests/unittests/scheduler/uci_and_pucch/pucch_res_manager_test.cpp @@ -439,23 +439,38 @@ TEST_F(test_pucch_resource_manager, test_allocation_2_sr_resource) ASSERT_NE(nullptr, res_manager.reserve_sr_res_available(sl_tx, to_rnti(0x4604), pucch_cfg_ue_2)); } -// Tests release of SR resource. +TEST_F(test_pucch_resource_manager, test_allocation_specific_f1) +{ + const unsigned res_indicator = 2; + + // Attempt to allocate PUCCH resource Format 2 with given resource indicator. + ASSERT_TRUE(nullptr != res_manager.reserve_f1_res_by_res_indicator(sl_tx, to_rnti(0x4601), res_indicator, pucch_cfg)); + + // Verify the resource can be retrieved. + ASSERT_EQ(static_cast(res_indicator), res_manager.fetch_f1_pucch_res_indic(sl_tx, to_rnti(0x4601), pucch_cfg)); + + // Attempt to allocate another UE to the same resource and verify it gets returned nullptr. + ASSERT_TRUE(nullptr == res_manager.reserve_f1_res_by_res_indicator(sl_tx, to_rnti(0x4602), res_indicator, pucch_cfg)); + + // Attempt to allocate a third UE with wrong resource indicator and verify it gets returned nullptr. + ASSERT_TRUE(nullptr == res_manager.reserve_f1_res_by_res_indicator(sl_tx, to_rnti(0x4603), res_indicator, pucch_cfg)); +} + TEST_F(test_pucch_resource_manager, test_allocation_specific_f2) { const unsigned res_indicator = 3; // Attempt to allocate PUCCH resource Format 2 with given resource indicator. - ASSERT_TRUE(nullptr != res_manager.reserve_specific_format2_res(sl_tx, to_rnti(0x4601), res_indicator, pucch_cfg)); + ASSERT_TRUE(nullptr != res_manager.reserve_f2_res_by_res_indicator(sl_tx, to_rnti(0x4601), res_indicator, pucch_cfg)); // Verify the resource can be retrieved. ASSERT_EQ(static_cast(res_indicator), res_manager.fetch_f2_pucch_res_indic(sl_tx, to_rnti(0x4601), pucch_cfg)); // Attempt to allocate another UE to the same resource and verify it gets returned nullptr. - ASSERT_TRUE(nullptr == res_manager.reserve_specific_format2_res(sl_tx, to_rnti(0x4602), res_indicator, pucch_cfg)); + ASSERT_TRUE(nullptr == res_manager.reserve_f2_res_by_res_indicator(sl_tx, to_rnti(0x4602), res_indicator, pucch_cfg)); // Attempt to allocate a third UE with wrong resource indicator and verify it gets returned nullptr. - ASSERT_TRUE(nullptr == - res_manager.reserve_specific_format2_res(sl_tx, to_rnti(0x4602), /* res_indicator=*/8, pucch_cfg)); + ASSERT_TRUE(nullptr == res_manager.reserve_f2_res_by_res_indicator(sl_tx, to_rnti(0x4603), res_indicator, pucch_cfg)); } //////////// Test the PUCCH resource manager: UEs with different configs //////////// @@ -749,14 +764,14 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_2_ues_2_cfgs_alloc_specific_f2) // UE 0 and 1 will get assigned the same pucch_res_indicator, as they use different PUCCH configs. const unsigned ue_0_idx = 0; const pucch_resource* res_ue_0 = - res_manager.reserve_specific_format2_res(sl_tx, ues[ue_0_idx]->cnrti, 5, ues[ue_0_idx]->get_pucch_cfg()); + res_manager.reserve_f2_res_by_res_indicator(sl_tx, ues[ue_0_idx]->cnrti, 5, ues[ue_0_idx]->get_pucch_cfg()); ASSERT_EQ(&ues[ue_0_idx]->get_pucch_cfg().pucch_res_list[14], res_ue_0); ASSERT_EQ(23, res_ue_0->res_id.cell_res_id); const unsigned ue_1_idx = 1; const pucch_resource* res_ue_1 = - res_manager.reserve_specific_format2_res(sl_tx, ues[ue_1_idx]->cnrti, 5, ues[ue_1_idx]->get_pucch_cfg()); + res_manager.reserve_f2_res_by_res_indicator(sl_tx, ues[ue_1_idx]->cnrti, 5, ues[ue_1_idx]->get_pucch_cfg()); ASSERT_EQ(&ues[ue_1_idx]->get_pucch_cfg().pucch_res_list[14], res_ue_1); ASSERT_EQ(32, res_ue_1->res_id.cell_res_id); @@ -764,12 +779,12 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_2_ues_2_cfgs_alloc_specific_f2) // Try to allocate the same PUCCH resource (already reserved to UE 0 and 1) and check that the allocation fails. const unsigned ue_2_idx = 2; const pucch_resource* res_ue_2 = - res_manager.reserve_specific_format2_res(sl_tx, ues[ue_2_idx]->cnrti, 5, ues[ue_2_idx]->get_pucch_cfg()); + res_manager.reserve_f2_res_by_res_indicator(sl_tx, ues[ue_2_idx]->cnrti, 5, ues[ue_2_idx]->get_pucch_cfg()); ASSERT_EQ(nullptr, res_ue_2); const unsigned ue_3_idx = 3; const pucch_resource* res_ue_3 = - res_manager.reserve_specific_format2_res(sl_tx, ues[ue_3_idx]->cnrti, 5, ues[ue_3_idx]->get_pucch_cfg()); + res_manager.reserve_f2_res_by_res_indicator(sl_tx, ues[ue_3_idx]->cnrti, 5, ues[ue_3_idx]->get_pucch_cfg()); ASSERT_EQ(nullptr, res_ue_3); } diff --git a/tests/unittests/scheduler/ue_scheduling/fallback_scheduler_test.cpp b/tests/unittests/scheduler/ue_scheduling/fallback_scheduler_test.cpp index 871e034baf..5780678219 100644 --- a/tests/unittests/scheduler/ue_scheduling/fallback_scheduler_test.cpp +++ b/tests/unittests/scheduler/ue_scheduling/fallback_scheduler_test.cpp @@ -135,7 +135,7 @@ struct test_bench { } ue_db.add_ue(std::move(u)); auto& ue = ue_db[create_req.ue_index]; - ue.get_pcell().set_fallback_state(true); + ue.get_pcell().set_fallback_state(srsran::ue_cell::fallback_state::fallback); return true; } }; @@ -306,8 +306,7 @@ class base_fallback_tester void push_buffer_state_to_ul_ue(du_ue_index_t ue_idx, slot_point sl, unsigned buffer_size) { - // Notification from upper layers of DL buffer state. - + // Notification from upper layers of UL buffer status report. ul_bsr_indication_message msg{.cell_index = to_du_cell_index(0U), .ue_index = ue_idx, .crnti = to_rnti(0x4601 + static_cast(ue_idx)), @@ -317,10 +316,19 @@ class base_fallback_tester bench->ue_db[ue_idx].handle_bsr_indication(msg); - // Notify scheduler of DL buffer state. + // Notify scheduler of UL buffer status report. bench->fallback_sched.handle_ul_bsr_indication(ue_idx, msg); } + void generate_sr_for_ue(du_ue_index_t ue_idx, slot_point sl) + { + // Notification from upper layers of UL . + bench->ue_db[ue_idx].handle_sr_indication(); + + // Notify scheduler of SR. + bench->fallback_sched.handle_sr_indication(ue_idx); + } + unsigned get_pending_bytes(du_ue_index_t ue_idx) { return bench->ue_db[ue_idx].pending_dl_srb0_or_srb1_newtx_bytes(true); @@ -1132,8 +1140,9 @@ class ul_fallback_scheduler_tester : public base_fallback_tester, if (sl == slot_generate_srb_traffic) { const unsigned srb_buffer = test_rgen::uniform_int(128U, parent->MAX_MAC_UL_SRB1_SDU_SIZE); parent->push_buffer_state_to_ul_ue(test_ue.ue_index, sl, srb_buffer); - buffer_bytes = srb_buffer; - test_logger.info("rnti={}, slot={}: pushing traffic", test_ue.crnti, sl); + buffer_bytes = srb_buffer; + initied_with_ul_traffic = true; + test_logger.info("rnti={}, slot={}: generating initial BSR indication", test_ue.crnti, sl); } for (uint8_t h_id_idx = 0; h_id_idx != std::underlying_type_t(MAX_HARQ_ID); ++h_id_idx) { @@ -1173,6 +1182,7 @@ class ul_fallback_scheduler_tester : public base_fallback_tester, unsigned buffer_bytes = 0; srslog::basic_logger& test_logger = srslog::fetch_basic_logger("TEST"); slot_point slot_generate_srb_traffic; + bool initied_with_ul_traffic = false; }; ul_fallback_sched_test_params params; @@ -1183,8 +1193,11 @@ class ul_fallback_scheduler_tester : public base_fallback_tester, std::vector ues_testers; }; -TEST_P(ul_fallback_scheduler_tester, test_scheduling_srb1_ul_1_ue) +TEST_P(ul_fallback_scheduler_tester, all_ul_ue_are_served_and_buffer_gets_emptied) { + // This test verifies that all UEs get a BSR for UL SRB traffic, and by the end of the test, all UEs will get served + // and their buffer will be left with 0 bytes. + for (unsigned du_idx = 0; du_idx < MAX_UES; du_idx++) { add_ue(to_rnti(0x4601 + du_idx), to_du_ue_index(du_idx)); ues_testers.emplace_back(bench->cell_cfg, get_ue(to_du_ue_index(du_idx)), this); @@ -1199,15 +1212,67 @@ TEST_P(ul_fallback_scheduler_tester, test_scheduling_srb1_ul_1_ue) } for (auto& tester : ues_testers) { + ASSERT_TRUE(tester.initied_with_ul_traffic) + << fmt::format("No UL traffic generated for UE {}", tester.test_ue.ue_index); ASSERT_FALSE(tester.buffer_bytes > 0) << fmt::format("UE {} has still pending UL bytes", tester.test_ue.ue_index); } } -INSTANTIATE_TEST_SUITE_P(test1, +INSTANTIATE_TEST_SUITE_P(test_fdd_and_tdd, ul_fallback_scheduler_tester, testing::Values(ul_fallback_sched_test_params{.duplx_mode = duplex_mode::FDD}, ul_fallback_sched_test_params{.duplx_mode = duplex_mode::TDD})); +class ul_fallback_sched_tester_sr_indication : public base_fallback_tester, + public ::testing::TestWithParam +{ +protected: + ul_fallback_sched_tester_sr_indication() : base_fallback_tester(GetParam().duplx_mode) + { + setup_sched(config_helpers::make_default_scheduler_expert_config(), + test_helpers::make_default_sched_cell_configuration_request(builder_params)); + slot_generate_srb_traffic = + slot_point{to_numerology_value(bench->cell_cfg.dl_cfg_common.init_dl_bwp.generic_params.scs), + test_rgen::uniform_int(20U, 40U)}; + } + + ul_fallback_sched_test_params params; + const unsigned MAX_TEST_RUN_SLOTS = 100; + slot_point slot_generate_srb_traffic; +}; + +TEST_P(ul_fallback_sched_tester_sr_indication, when_gnb_receives_sr_ind_ue_gets_scheduled) +{ + unsigned du_idx = 0; + add_ue(to_rnti(0x4601 + du_idx), to_du_ue_index(du_idx)); + + auto& ue = bench->ue_db[to_du_ue_index(du_idx)]; + + // Generate SR for this UE. + generate_sr_for_ue(to_du_ue_index(du_idx), slot_generate_srb_traffic); + + bool pusch_allocated = false; + for (unsigned sl = 1; sl < MAX_TEST_RUN_SLOTS; ++sl) { + run_slot(); + + auto& puschs = bench->res_grid[0].result.ul.puschs; + auto pusch_it = std::find_if(puschs.begin(), puschs.end(), [rnti = ue.crnti](const ul_sched_info& pusch) { + return pusch.pusch_cfg.rnti == rnti; + }); + if (pusch_it != puschs.end()) { + pusch_allocated = true; + break; + } + } + + ASSERT_TRUE(pusch_allocated); +} + +INSTANTIATE_TEST_SUITE_P(test_fdd_and_tdd, + ul_fallback_sched_tester_sr_indication, + testing::Values(ul_fallback_sched_test_params{.duplx_mode = duplex_mode::FDD}, + ul_fallback_sched_test_params{.duplx_mode = duplex_mode::TDD})); + int main(int argc, char** argv) { srslog::fetch_basic_logger("SCHED", true).set_level(srslog::basic_levels::debug); diff --git a/tests/unittests/support/network/io_broker_epoll_test.cpp b/tests/unittests/support/network/io_broker_epoll_test.cpp index a712a3799f..d71c7f38d9 100644 --- a/tests/unittests/support/network/io_broker_epoll_test.cpp +++ b/tests/unittests/support/network/io_broker_epoll_test.cpp @@ -22,10 +22,9 @@ #include "srsran/adt/optional.h" #include "srsran/support/io/io_broker_factory.h" -#include // for std::function/std::bind +#include #include #include -#include #include #include #include @@ -51,6 +50,7 @@ class io_broker_epoll : public ::testing::Test void data_receive_callback(int fd) { + std::lock_guard lock(rx_mutex); // receive data on provided fd char rx_buf[1024]; int bytes = read(fd, rx_buf, sizeof(rx_buf)); @@ -60,6 +60,7 @@ class io_broker_epoll : public ::testing::Test if (socket_type == SOCK_DGRAM) { ASSERT_EQ(bytes, tx_buf.length()); } + rx_cvar.notify_one(); } void create_unix_sockets() @@ -175,22 +176,26 @@ class io_broker_epoll : public ::testing::Test ASSERT_TRUE(epoll_broker->register_fd(socket_fd, [this](int fd) { data_receive_callback(fd); })); } - void send_on_socket() + void send_on_socket() const { // send text int ret = send(socket_fd, tx_buf.c_str(), tx_buf.length(), 0); ASSERT_EQ(ret, tx_buf.length()); } - void run_tx_rx_test() + void run_tx_rx_test(std::chrono::milliseconds timeout_ms = std::chrono::milliseconds(1000)) { const int count = 5; int run = count; while (run-- > 0) { send_on_socket(); - std::this_thread::sleep_for(std::chrono::milliseconds(20)); } - std::this_thread::sleep_for(std::chrono::milliseconds(150)); + + // wait until all bytes are received + std::unique_lock lock(rx_mutex); + if (!rx_cvar.wait_for(lock, timeout_ms, [this]() { return total_rx_bytes >= tx_buf.length() * count; })) { + FAIL() << "Timeout: received only " << total_rx_bytes << " of " << tx_buf.length() * count << " Bytes."; + } ASSERT_EQ(total_rx_bytes, tx_buf.length() * count); } @@ -207,7 +212,10 @@ class io_broker_epoll : public ::testing::Test struct sockaddr_in server_addr_in = {}; struct sockaddr_in client_addr_in = {}; - int total_rx_bytes = 0; + std::mutex rx_mutex; + std::condition_variable rx_cvar; + + size_t total_rx_bytes = 0; }; TEST_F(io_broker_epoll, unix_socket_trx_test)