From 2befbd18250c1960b0ec56ae2f782fcf29c16f4d Mon Sep 17 00:00:00 2001 From: Francisco Date: Fri, 29 Jan 2021 17:21:07 +0000 Subject: [PATCH] setup of forwarding tunnels during enb s1 handover --- lib/include/srslte/asn1/s1ap_utils.h | 2 + .../srslte/interfaces/enb_interfaces.h | 13 +- lib/include/srslte/interfaces/ue_interfaces.h | 2 +- srsenb/hdr/stack/rrc/rrc.h | 5 +- srsenb/hdr/stack/rrc/rrc_bearer_cfg.h | 17 +- srsenb/hdr/stack/rrc/rrc_mobility.h | 33 +-- srsenb/hdr/stack/rrc/rrc_ue.h | 3 - srsenb/hdr/stack/upper/s1ap.h | 9 +- srsenb/src/stack/rrc/rrc.cc | 7 +- srsenb/src/stack/rrc/rrc_bearer_cfg.cc | 36 +++- srsenb/src/stack/rrc/rrc_mobility.cc | 202 ++++++++++++------ srsenb/src/stack/rrc/rrc_ue.cc | 13 +- srsenb/src/stack/upper/s1ap.cc | 61 +++--- srsenb/test/common/dummy_classes.h | 8 +- srsenb/test/upper/rrc_mobility_test.cc | 11 +- srsenb/test/upper/test_helpers.h | 14 +- 16 files changed, 282 insertions(+), 154 deletions(-) diff --git a/lib/include/srslte/asn1/s1ap_utils.h b/lib/include/srslte/asn1/s1ap_utils.h index 8200d937b..e283ac0d0 100644 --- a/lib/include/srslte/asn1/s1ap_utils.h +++ b/lib/include/srslte/asn1/s1ap_utils.h @@ -36,6 +36,8 @@ struct rrc_establishment_cause_opts; struct cause_radio_network_opts; struct bearers_subject_to_status_transfer_item_ies_o; struct erab_level_qos_params_s; +struct ho_cmd_s; +struct erab_admitted_item_s; template struct protocol_ie_single_container_s; diff --git a/lib/include/srslte/interfaces/enb_interfaces.h b/lib/include/srslte/interfaces/enb_interfaces.h index ebf7cedce..1d732cfe2 100644 --- a/lib/include/srslte/interfaces/enb_interfaces.h +++ b/lib/include/srslte/interfaces/enb_interfaces.h @@ -448,7 +448,10 @@ public: * @param is_success true if ho cmd was received * @param container TargeteNB RRCConnectionReconfiguration message with MobilityControlInfo */ - virtual void ho_preparation_complete(uint16_t rnti, bool is_success, srslte::unique_byte_buffer_t container) = 0; + virtual void ho_preparation_complete(uint16_t rnti, + bool is_success, + const asn1::s1ap::ho_cmd_s& msg, + srslte::unique_byte_buffer_t container) = 0; virtual uint16_t start_ho_ue_resource_alloc(const asn1::s1ap::ho_request_s& msg, const asn1::s1ap::sourceenb_to_targetenb_transparent_container_s& container) = 0; @@ -533,10 +536,10 @@ public: /* Acknowledge Handover Request message back to MME. * This message signals the completion of the HandoverPreparation from the TeNB point of view. */ - virtual bool send_ho_req_ack(const asn1::s1ap::ho_request_s& msg, - uint16_t rnti, - srslte::unique_byte_buffer_t ho_cmd, - srslte::span > admitted_bearers) = 0; + virtual bool send_ho_req_ack(const asn1::s1ap::ho_request_s& msg, + uint16_t rnti, + srslte::unique_byte_buffer_t ho_cmd, + srslte::span admitted_bearers) = 0; /** * Notify MME that Handover is complete diff --git a/lib/include/srslte/interfaces/ue_interfaces.h b/lib/include/srslte/interfaces/ue_interfaces.h index ad0e79736..2a447397a 100644 --- a/lib/include/srslte/interfaces/ue_interfaces.h +++ b/lib/include/srslte/interfaces/ue_interfaces.h @@ -203,7 +203,7 @@ public: virtual void paging_completed(bool outcome) = 0; virtual std::string get_rb_name(uint32_t lcid) = 0; virtual uint32_t get_lcid_for_eps_bearer(const uint32_t& eps_bearer_id) = 0; - virtual bool has_nr_dc() = 0; + virtual bool has_nr_dc() = 0; }; // RRC interface for PDCP diff --git a/srsenb/hdr/stack/rrc/rrc.h b/srsenb/hdr/stack/rrc/rrc.h index 9af1a70b8..cbe188891 100644 --- a/srsenb/hdr/stack/rrc/rrc.h +++ b/srsenb/hdr/stack/rrc/rrc.h @@ -92,7 +92,10 @@ public: std::vector* erabs_released, std::vector* erabs_failed_to_release) override; void add_paging_id(uint32_t ueid, const asn1::s1ap::ue_paging_id_c& UEPagingID) override; - void ho_preparation_complete(uint16_t rnti, bool is_success, srslte::unique_byte_buffer_t rrc_container) override; + void ho_preparation_complete(uint16_t rnti, + bool is_success, + const asn1::s1ap::ho_cmd_s& msg, + srslte::unique_byte_buffer_t rrc_container) override; uint16_t start_ho_ue_resource_alloc(const asn1::s1ap::ho_request_s& msg, const asn1::s1ap::sourceenb_to_targetenb_transparent_container_s& container) override; diff --git a/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h b/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h index 7f186ea03..e94c422d3 100644 --- a/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h +++ b/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h @@ -64,14 +64,20 @@ class bearer_cfg_handler { public: struct erab_t { + struct gtpu_tunnel { + uint32_t teid_out = 0; + uint32_t teid_in = 0; + uint32_t addr = 0; + }; uint8_t id = 0; asn1::s1ap::erab_level_qos_params_s qos_params; asn1::bounded_bitstring<1, 160, true, true> address; uint32_t teid_out = 0; uint32_t teid_in = 0; + std::vector tunnels; }; - bearer_cfg_handler(uint16_t rnti_, const rrc_cfg_t& cfg_); + bearer_cfg_handler(uint16_t rnti_, const rrc_cfg_t& cfg_, gtpu_interface_rrc* gtpu_); int add_erab(uint8_t erab_id, const asn1::s1ap::erab_level_qos_params_s& qos, @@ -85,8 +91,12 @@ public: const asn1::unbounded_octstring* nas_pdu); // Methods to apply bearer updates - void add_gtpu_bearer(gtpu_interface_rrc* gtpu, uint32_t erab_id); - void fill_pending_nas_info(asn1::rrc::rrc_conn_recfg_r8_ies_s* msg); + void add_gtpu_bearer(uint32_t erab_id); + uint32_t add_gtpu_bearer(uint32_t erab_id, + uint32_t teid_out, + uint32_t addr, + const gtpu_interface_rrc::bearer_props* props = nullptr); + void fill_pending_nas_info(asn1::rrc::rrc_conn_recfg_r8_ies_s* msg); const std::map& get_erabs() const { return erabs; } const asn1::rrc::drb_to_add_mod_list_l& get_established_drbs() const { return current_drbs; } @@ -98,6 +108,7 @@ private: srslog::basic_logger& logger; uint16_t rnti = 0; const rrc_cfg_t* cfg = nullptr; + gtpu_interface_rrc* gtpu = nullptr; // last cfg asn1::rrc::drb_to_add_mod_list_l current_drbs; diff --git a/srsenb/hdr/stack/rrc/rrc_mobility.h b/srsenb/hdr/stack/rrc/rrc_mobility.h index cf2f7460f..18d642731 100644 --- a/srsenb/hdr/stack/rrc/rrc_mobility.h +++ b/srsenb/hdr/stack/rrc/rrc_mobility.h @@ -37,7 +37,9 @@ public: bool fill_conn_recfg_no_ho_cmd(asn1::rrc::rrc_conn_recfg_r8_ies_s* conn_recfg); void handle_ue_meas_report(const asn1::rrc::meas_report_s& msg); - void handle_ho_preparation_complete(bool is_success, srslte::unique_byte_buffer_t container); + void handle_ho_preparation_complete(bool is_success, + const asn1::s1ap::ho_cmd_s& msg, + srslte::unique_byte_buffer_t container); bool is_ho_running() const { return not is_in_state(); } // S1-Handover @@ -56,7 +58,6 @@ private: // Handover from source cell bool start_ho_preparation(uint32_t target_eci, uint8_t measobj_id, bool fwd_direct_path_available); - bool start_enb_status_transfer(); // Handover to target cell void fill_mobility_reconf_common(asn1::rrc::dl_dcch_msg_s& msg, @@ -99,32 +100,38 @@ private: struct s1_target_ho_st {}; struct wait_recfg_comp {}; struct s1_source_ho_st : public subfsm_t { + struct ho_cmd_msg { + const asn1::s1ap::ho_cmd_s* s1ap_ho_cmd; + const asn1::rrc::ho_cmd_r8_ies_s* ho_cmd; + }; ho_meas_report_ev report; - using ho_cmd_msg = asn1::rrc::ho_cmd_r8_ies_s; - struct wait_ho_req_ack_st { + struct wait_ho_cmd { void enter(s1_source_ho_st* f, const ho_meas_report_ev& ev); }; - struct status_transfer_st { - void enter(s1_source_ho_st* f); - }; + struct status_transfer_st {}; - explicit s1_source_ho_st(rrc_mobility* parent_) : base_t(parent_) {} + explicit s1_source_ho_st(rrc_mobility* parent_); private: - void send_ho_cmd(wait_ho_req_ack_st& s, const ho_cmd_msg& ho_cmd); + void handle_ho_cmd(wait_ho_cmd& s, const ho_cmd_msg& ho_cmd); void handle_ho_cancel(const ho_cancel_ev& ev); + bool start_enb_status_transfer(const asn1::s1ap::ho_cmd_s& s1ap_ho_cmd); + + rrc* rrc_enb; + rrc::ue* rrc_ue; + srslog::basic_logger& logger; protected: using fsm = s1_source_ho_st; - state_list states{this}; + state_list states{this}; // clang-format off using transitions = transition_table< // Start Target Event Action Guard // +-------------------+------------------+---------------------+-----------------------+---------------------+ to_state< idle_st, srslte::failure_ev >, to_state< idle_st, ho_cancel_ev, &fsm::handle_ho_cancel >, - row< wait_ho_req_ack_st, status_transfer_st, ho_cmd_msg, &fsm::send_ho_cmd > + row< wait_ho_cmd, status_transfer_st, ho_cmd_msg, &fsm::handle_ho_cmd > // +-------------------+------------------+---------------------+-----------------------+---------------------+ >; // clang-format on @@ -137,7 +144,7 @@ private: // FSM transition handlers void handle_crnti_ce(intraenb_ho_st& s, const user_crnti_upd_ev& ev); void handle_recfg_complete(intraenb_ho_st& s, const recfg_complete_ev& ev); - void handle_ho_req(idle_st& s, const ho_req_rx_ev& ho_req); + void handle_ho_requested(idle_st& s, const ho_req_rx_ev& ho_req); void handle_status_transfer(s1_target_ho_st& s, const status_transfer_ev& ev); void defer_recfg_complete(s1_target_ho_st& s, const recfg_complete_ev& ev); void handle_recfg_complete(wait_recfg_comp& s, const recfg_complete_ev& ev); @@ -159,7 +166,7 @@ protected: // +----------------+-------------------+---------------------+----------------------------+-------------------------+ row< idle_st, s1_source_ho_st, ho_meas_report_ev, nullptr, &fsm::needs_s1_ho >, row< idle_st, intraenb_ho_st, ho_meas_report_ev, nullptr, &fsm::needs_intraenb_ho >, - row< idle_st, s1_target_ho_st, ho_req_rx_ev, &fsm::handle_ho_req >, + row< idle_st, s1_target_ho_st, ho_req_rx_ev, &fsm::handle_ho_requested >, // +----------------+-------------------+---------------------+----------------------------+-------------------------+ upd< intraenb_ho_st, user_crnti_upd_ev, &fsm::handle_crnti_ce >, row< intraenb_ho_st, idle_st, recfg_complete_ev, &fsm::handle_recfg_complete >, diff --git a/srsenb/hdr/stack/rrc/rrc_ue.h b/srsenb/hdr/stack/rrc/rrc_ue.h index 34591fd8b..6ac025062 100644 --- a/srsenb/hdr/stack/rrc/rrc_ue.h +++ b/srsenb/hdr/stack/rrc/rrc_ue.h @@ -76,9 +76,6 @@ public: const asn1::s1ap::erab_level_qos_params_s& qos_params, const asn1::unbounded_octstring* nas_pdu); - // handover - void handle_ho_preparation_complete(bool is_success, srslte::unique_byte_buffer_t container); - void notify_s1ap_ue_ctxt_setup_complete(); void notify_s1ap_ue_erab_setup_response(const asn1::s1ap::erab_to_be_setup_list_bearer_su_req_l& e); diff --git a/srsenb/hdr/stack/upper/s1ap.h b/srsenb/hdr/stack/upper/s1ap.h index 8b25d8513..78819f696 100644 --- a/srsenb/hdr/stack/upper/s1ap.h +++ b/srsenb/hdr/stack/upper/s1ap.h @@ -75,10 +75,10 @@ public: srslte::unique_byte_buffer_t rrc_container) override; bool send_enb_status_transfer_proc(uint16_t rnti, std::vector& bearer_status_list) override; bool send_ho_failure(uint32_t mme_ue_s1ap_id); - bool send_ho_req_ack(const asn1::s1ap::ho_request_s& msg, - uint16_t rnti, - srslte::unique_byte_buffer_t ho_cmd, - srslte::span > admitted_bearers) override; + bool send_ho_req_ack(const asn1::s1ap::ho_request_s& msg, + uint16_t rnti, + srslte::unique_byte_buffer_t ho_cmd, + srslte::span admitted_bearers) override; void send_ho_notify(uint16_t rnti, uint64_t target_eci) override; void send_ho_cancel(uint16_t rnti) override; // void ue_capabilities(uint16_t rnti, LIBLTE_RRC_UE_EUTRA_CAPABILITY_STRUCT *caps); @@ -174,6 +174,7 @@ private: uint32_t target_eci = 0; srslte::plmn_id_t target_plmn; srslte::unique_byte_buffer_t rrc_container; + const asn1::s1ap::ho_cmd_s* ho_cmd_msg = nullptr; }; explicit ue(s1ap* s1ap_ptr_); diff --git a/srsenb/src/stack/rrc/rrc.cc b/srsenb/src/stack/rrc/rrc.cc index 7c27f5c71..6453e91fc 100644 --- a/srsenb/src/stack/rrc/rrc.cc +++ b/srsenb/src/stack/rrc/rrc.cc @@ -527,9 +527,12 @@ void rrc::read_pdu_pcch(uint8_t* payload, uint32_t buffer_size) Handover functions *******************************************************************************/ -void rrc::ho_preparation_complete(uint16_t rnti, bool is_success, srslte::unique_byte_buffer_t rrc_container) +void rrc::ho_preparation_complete(uint16_t rnti, + bool is_success, + const asn1::s1ap::ho_cmd_s& msg, + srslte::unique_byte_buffer_t rrc_container) { - users.at(rnti)->mobility_handler->handle_ho_preparation_complete(is_success, std::move(rrc_container)); + users.at(rnti)->mobility_handler->handle_ho_preparation_complete(is_success, msg, std::move(rrc_container)); } void rrc::set_erab_status(uint16_t rnti, const asn1::s1ap::bearers_subject_to_status_transfer_list_l& erabs) diff --git a/srsenb/src/stack/rrc/rrc_bearer_cfg.cc b/srsenb/src/stack/rrc/rrc_bearer_cfg.cc index bb74a9b4b..aed8b8e99 100644 --- a/srsenb/src/stack/rrc/rrc_bearer_cfg.cc +++ b/srsenb/src/stack/rrc/rrc_bearer_cfg.cc @@ -200,8 +200,8 @@ void security_cfg_handler::regenerate_keys_handover(uint32_t new_pci, uint32_t n * Bearer Handler ****************************/ -bearer_cfg_handler::bearer_cfg_handler(uint16_t rnti_, const rrc_cfg_t& cfg_) : - rnti(rnti_), cfg(&cfg_), logger(srslog::fetch_basic_logger("RRC")) +bearer_cfg_handler::bearer_cfg_handler(uint16_t rnti_, const rrc_cfg_t& cfg_, gtpu_interface_rrc* gtpu_) : + rnti(rnti_), cfg(&cfg_), gtpu(gtpu_), logger(srslog::fetch_basic_logger("RRC")) {} int bearer_cfg_handler::add_erab(uint8_t erab_id, @@ -308,17 +308,35 @@ bool bearer_cfg_handler::modify_erab(uint8_t return true; } -void bearer_cfg_handler::add_gtpu_bearer(srsenb::gtpu_interface_rrc* gtpu, uint32_t erab_id) +void bearer_cfg_handler::add_gtpu_bearer(uint32_t erab_id) { auto it = erabs.find(erab_id); - if (it != erabs.end()) { - erab_t& erab = it->second; - // Initialize ERAB in GTPU right-away. DRBs are only created during RRC setup/reconf - uint32_t addr_ = erab.address.to_number(); - erab.teid_in = gtpu->add_bearer(rnti, erab.id - 2, addr_, erab.teid_out); - } else { + if (it == erabs.end()) { logger.error("Adding erab_id=%d to GTPU", erab_id); + return; } + it->second.teid_in = add_gtpu_bearer(erab_id, it->second.teid_out, it->second.address.to_number(), nullptr); +} + +uint32_t bearer_cfg_handler::add_gtpu_bearer(uint32_t erab_id, + uint32_t teid_out, + uint32_t addr, + const gtpu_interface_rrc::bearer_props* props) +{ + auto it = erabs.find(erab_id); + if (it == erabs.end()) { + logger.error("Adding erab_id=%d to GTPU", erab_id); + return 0; + } + + // Initialize ERAB tunnel in GTPU right-away. DRBs are only created during RRC setup/reconf + erab_t& erab = it->second; + erab_t::gtpu_tunnel bearer; + bearer.teid_out = teid_out; + bearer.addr = addr; + bearer.teid_in = gtpu->add_bearer(rnti, erab.id - 2, addr, teid_out, props); + erab.tunnels.push_back(bearer); + return bearer.teid_in; } void bearer_cfg_handler::fill_pending_nas_info(asn1::rrc::rrc_conn_recfg_r8_ies_s* msg) diff --git a/srsenb/src/stack/rrc/rrc_mobility.cc b/srsenb/src/stack/rrc/rrc_mobility.cc index 47c7a2609..17f610919 100644 --- a/srsenb/src/stack/rrc/rrc_mobility.cc +++ b/srsenb/src/stack/rrc/rrc_mobility.cc @@ -380,7 +380,9 @@ bool rrc::ue::rrc_mobility::start_ho_preparation(uint32_t target_eci, * @param is_success flag to whether an HandoverCommand or HandoverReject was received * @param container RRC container with HandoverCommand to send to UE */ -void rrc::ue::rrc_mobility::handle_ho_preparation_complete(bool is_success, srslte::unique_byte_buffer_t container) +void rrc::ue::rrc_mobility::handle_ho_preparation_complete(bool is_success, + const asn1::s1ap::ho_cmd_s& msg, + srslte::unique_byte_buffer_t container) { if (not is_success) { log_h->info("Received S1AP HandoverFailure. Aborting Handover..."); @@ -405,7 +407,7 @@ void rrc::ue::rrc_mobility::handle_ho_preparation_complete(bool is_success, srsl return; } - trigger(rrchocmd.crit_exts.c1().ho_cmd_r8()); + trigger(s1_source_ho_st::ho_cmd_msg{&msg, &rrchocmd.crit_exts.c1().ho_cmd_r8()}); } bool rrc::ue::rrc_mobility::start_s1_tenb_ho( @@ -476,12 +478,46 @@ void rrc::ue::rrc_mobility::fill_mobility_reconf_common(asn1::rrc::dl_dcch_msg_s true); } +/************************************* + * rrc_mobility FSM methods + *************************************/ + +bool rrc::ue::rrc_mobility::needs_s1_ho(idle_st& s, const ho_meas_report_ev& meas_result) +{ + if (rrc_ue->get_state() != RRC_STATE_REGISTERED) { + return false; + } + return rrc_details::eci_to_enbid(meas_result.target_eci) != rrc_enb->cfg.enb_id; +} + +bool rrc::ue::rrc_mobility::needs_intraenb_ho(idle_st& s, const ho_meas_report_ev& meas_result) +{ + if (rrc_ue->get_state() != RRC_STATE_REGISTERED) { + return false; + } + if (rrc_details::eci_to_enbid(meas_result.target_eci) != rrc_enb->cfg.enb_id) { + return false; + } + uint32_t cell_id = rrc_details::eci_to_cellid(meas_result.target_eci); + return rrc_ue->get_ue_cc_cfg(UE_PCELL_CC_IDX)->cell_cfg.cell_id != cell_id; +} + +/************************************* + * s1_source_ho subFSM methods + *************************************/ + +rrc::ue::rrc_mobility::s1_source_ho_st::s1_source_ho_st(rrc_mobility* parent_) : + base_t(parent_), rrc_enb(parent_->rrc_enb), rrc_ue(parent_->rrc_ue), logger(parent_->logger) +{} + /** * TS 36.413, Section 8.4.6 - eNB Status Transfer - * Description: Send "eNBStatusTransfer" message from source eNB to MME - * - Pass bearers' DL/UL HFN and PDCP SN to be put inside a transparent container + * @brief: Send "eNBStatusTransfer" message from source eNB to MME, and setup Forwarding GTPU tunnel + * - PDCP provides the bearers' DL/UL HFN and COUNT to be put inside a transparent container + * - The eNB sends eNBStatusTransfer to MME + * - A GTPU forwarding tunnel is opened to forward buffered PDCP PDUs and incoming GTPU PDUs */ -bool rrc::ue::rrc_mobility::start_enb_status_transfer() +bool rrc::ue::rrc_mobility::s1_source_ho_st::start_enb_status_transfer(const asn1::s1ap::ho_cmd_s& s1ap_ho_cmd) { std::vector s1ap_bearers; s1ap_bearers.reserve(rrc_ue->bearer_list.get_erabs().size()); @@ -503,42 +539,41 @@ bool rrc::ue::rrc_mobility::start_enb_status_transfer() } Info("PDCP Bearer list sent to S1AP to initiate the eNB Status Transfer"); - return rrc_enb->s1ap->send_enb_status_transfer_proc(rrc_ue->rnti, s1ap_bearers); -} - -/************************************* - * rrc_mobility FSM methods - *************************************/ - -bool rrc::ue::rrc_mobility::needs_s1_ho(idle_st& s, const ho_meas_report_ev& meas_result) -{ - if (rrc_ue->get_state() != RRC_STATE_REGISTERED) { + if (not rrc_enb->s1ap->send_enb_status_transfer_proc(rrc_ue->rnti, s1ap_bearers)) { return false; } - return rrc_details::eci_to_enbid(meas_result.target_eci) != rrc_enb->cfg.enb_id; -} -bool rrc::ue::rrc_mobility::needs_intraenb_ho(idle_st& s, const ho_meas_report_ev& meas_result) -{ - if (rrc_ue->get_state() != RRC_STATE_REGISTERED) { - return false; - } - if (rrc_details::eci_to_enbid(meas_result.target_eci) != rrc_enb->cfg.enb_id) { - return false; + // Setup GTPU forwarding tunnel + if (s1ap_ho_cmd.protocol_ies.erab_subjectto_data_forwarding_list_present) { + const auto& fwd_erab_list = s1ap_ho_cmd.protocol_ies.erab_subjectto_data_forwarding_list.value; + const auto& erab_list = rrc_ue->bearer_list.get_erabs(); + for (const auto& e : fwd_erab_list) { + const auto& fwd_erab = e.value.erab_data_forwarding_item(); + auto it = erab_list.find(fwd_erab.erab_id); + if (it == erab_list.end()) { + Warning("E-RAB id=%d subject to forwarding not found\n", fwd_erab.erab_id); + continue; + } + const bearer_cfg_handler::erab_t& erab = it->second; + if (fwd_erab.dl_g_tp_teid_present and fwd_erab.dl_transport_layer_address_present) { + gtpu_interface_rrc::bearer_props props; + props.forward_from_teidin_present = true; + props.forward_from_teidin = erab.teid_in; + rrc_ue->bearer_list.add_gtpu_bearer(fwd_erab.erab_id, + fwd_erab.dl_g_tp_teid.to_number(), + fwd_erab.dl_transport_layer_address.to_number(), + &props); + } + } } - uint32_t cell_id = rrc_details::eci_to_cellid(meas_result.target_eci); - return rrc_ue->get_ue_cc_cfg(UE_PCELL_CC_IDX)->cell_cfg.cell_id != cell_id; -} -/************************************* - * s1_source_ho subFSM methods - *************************************/ + return true; +} -void rrc::ue::rrc_mobility::s1_source_ho_st::wait_ho_req_ack_st::enter(s1_source_ho_st* f, const ho_meas_report_ev& ev) +void rrc::ue::rrc_mobility::s1_source_ho_st::wait_ho_cmd::enter(s1_source_ho_st* f, const ho_meas_report_ev& ev) { - srslte::console("Starting S1 Handover of rnti=0x%x to cellid=0x%x.\n", f->parent_fsm()->rrc_ue->rnti, ev.target_eci); - f->get_log()->info( - "Starting S1 Handover of rnti=0x%x to cellid=0x%x.", f->parent_fsm()->rrc_ue->rnti, ev.target_eci); + srslte::console("Starting S1 Handover of rnti=0x%x to cellid=0x%x.\n", f->rrc_ue->rnti, ev.target_eci); + f->get_log()->info("Starting S1 Handover of rnti=0x%x to cellid=0x%x.", f->rrc_ue->rnti, ev.target_eci); f->report = ev; bool success = f->parent_fsm()->start_ho_preparation(f->report.target_eci, f->report.meas_obj->meas_obj_id, false); @@ -547,65 +582,74 @@ void rrc::ue::rrc_mobility::s1_source_ho_st::wait_ho_req_ack_st::enter(s1_source } } -void rrc::ue::rrc_mobility::s1_source_ho_st::send_ho_cmd(wait_ho_req_ack_st& s, const ho_cmd_r8_ies_s& ho_cmd) +/** + * TS 36.413, Section 8.4.2 - Handover Resource Allocation + * @brief: Send "eNBStatusTransfer" message from source eNB to MME, and setup Forwarding GTPU tunnel + * - PDCP provides the bearers' DL/UL HFN and COUNT to be put inside a transparent container + * - The eNB sends eNBStatusTransfer to MME + * - A GTPU forwarding tunnel is opened to forward buffered PDCP PDUs and incoming GTPU PDUs + */ +void rrc::ue::rrc_mobility::s1_source_ho_st::handle_ho_cmd(wait_ho_cmd& s, const ho_cmd_msg& ho_cmd) { /* unpack DL-DCCH message containing the RRCRonnectionReconf (with MobilityInfo) to be sent to the UE */ asn1::rrc::dl_dcch_msg_s dl_dcch_msg; { - asn1::cbit_ref bref(&ho_cmd.ho_cmd_msg[0], ho_cmd.ho_cmd_msg.size()); + asn1::cbit_ref bref(&ho_cmd.ho_cmd->ho_cmd_msg[0], ho_cmd.ho_cmd->ho_cmd_msg.size()); if (dl_dcch_msg.unpack(bref) != asn1::SRSASN_SUCCESS) { - get_log()->warning("Unpacking of RRC DL-DCCH message with HO Command was unsuccessful."); + Warning("Unpacking of RRC DL-DCCH message with HO Command was unsuccessful."); trigger(ho_cancel_ev{}); return; } } if (dl_dcch_msg.msg.type().value != dl_dcch_msg_type_c::types_opts::c1 or dl_dcch_msg.msg.c1().type().value != dl_dcch_msg_type_c::c1_c_::types_opts::rrc_conn_recfg) { - get_log()->warning("HandoverCommand is expected to contain an RRC Connection Reconf message inside"); + Warning("HandoverCommand is expected to contain an RRC Connection Reconf message inside"); trigger(ho_cancel_ev{}); return; } asn1::rrc::rrc_conn_recfg_s& reconf = dl_dcch_msg.msg.c1().rrc_conn_recfg(); if (not reconf.crit_exts.c1().rrc_conn_recfg_r8().mob_ctrl_info_present) { - get_log()->warning("HandoverCommand is expected to have mobility control subfield"); + Warning("HandoverCommand is expected to have mobility control subfield"); trigger(ho_cancel_ev{}); return; } - // Disable DRBs - parent_fsm()->rrc_ue->mac_ctrl.set_drb_activation(false); - parent_fsm()->rrc_ue->mac_ctrl.update_mac(mac_controller::proc_stage_t::other); + /* Enter Handover Execution */ + // TODO: Do anything with MeasCfg info within the Msg (e.g. update ue_var_meas)? + + // Disable DRBs in the MAC, while Reconfiguration is taking place. + rrc_ue->mac_ctrl.set_drb_activation(false); + rrc_ue->mac_ctrl.update_mac(mac_controller::proc_stage_t::other); - /* Send HO Command to UE */ - if (not parent_fsm()->rrc_ue->send_dl_dcch(&dl_dcch_msg)) { + // Send HO Command to UE + if (not rrc_ue->send_dl_dcch(&dl_dcch_msg)) { trigger(ho_cancel_ev{}); return; } + + /* Start S1AP eNBStatusTransfer Procedure */ + if (not start_enb_status_transfer(*ho_cmd.s1ap_ho_cmd)) { + trigger(ho_cancel_ev{}); + } } //! Called in Source ENB during S1-Handover when there was a Reestablishment Request void rrc::ue::rrc_mobility::s1_source_ho_st::handle_ho_cancel(const ho_cancel_ev& ev) { - parent_fsm()->rrc_enb->s1ap->send_ho_cancel(parent_fsm()->rrc_ue->rnti); -} - -void rrc::ue::rrc_mobility::s1_source_ho_st::status_transfer_st::enter(s1_source_ho_st* f) -{ - f->get_log()->info("HandoverCommand of rnti=0x%x handled successfully.", f->parent_fsm()->rrc_ue->rnti); - - // TODO: Do anything with MeasCfg info within the Msg (e.g. update ue_var_meas)? - - /* Start S1AP eNBStatusTransfer Procedure */ - if (not f->parent_fsm()->start_enb_status_transfer()) { - f->trigger(srslte::failure_ev{}); - } + rrc_enb->s1ap->send_ho_cancel(rrc_ue->rnti); } /************************************* * s1_target_ho state methods *************************************/ -void rrc::ue::rrc_mobility::handle_ho_req(idle_st& s, const ho_req_rx_ev& ho_req) +/** + * @brief handle S1AP "HO Requested" message from the MME + * - MME --> TeNB + * @param s initial state + * @param ho_req event with received message + */ +void rrc::ue::rrc_mobility::handle_ho_requested(idle_st& s, const ho_req_rx_ev& ho_req) { const auto& rrc_container = ho_req.transparent_container->rrc_container; @@ -685,12 +729,42 @@ void rrc::ue::rrc_mobility::handle_ho_req(idle_st& s, const ho_req_rx_ev& ho_req // Apply PHY updates rrc_ue->apply_reconf_phy_config(recfg_r8, true); - /* send S1AP HandoverRequestAcknowledge */ - std::vector > admitted_erabs; + // Set admitted E-RABs + std::vector admitted_erabs; for (auto& erab : rrc_ue->bearer_list.get_erabs()) { admitted_erabs.emplace_back(); - srslte::uint32_to_uint8(erab.second.teid_in, admitted_erabs.back().data()); + asn1::s1ap::erab_admitted_item_s& admitted_erab = admitted_erabs.back(); + admitted_erab.erab_id = erab.second.id; + srslte::uint32_to_uint8(erab.second.teid_in, admitted_erab.gtp_teid.data()); + + // Establish GTPU Forwarding Paths + if (ho_req.transparent_container->erab_info_list_present) { + auto& lst = ho_req.transparent_container->erab_info_list; + auto it = std::find_if( + lst.begin(), + lst.end(), + [&erab](const asn1::s1ap::protocol_ie_single_container_s& fwd_erab) { + return fwd_erab.value.erab_info_list_item().erab_id == erab.second.id; + }); + if (it == lst.end()) { + continue; + } + auto& fwd_erab = it->value.erab_info_list_item(); + + if (fwd_erab.dl_forwarding_present and + fwd_erab.dl_forwarding.value == asn1::s1ap::dl_forwarding_opts::dl_forwarding_proposed) { + admitted_erab.dl_g_tp_teid_present = true; + gtpu_interface_rrc::bearer_props props; + props.flush_before_teidin_present = true; + props.flush_before_teidin = erab.second.teid_in; + uint32_t dl_teid_in = rrc_ue->bearer_list.add_gtpu_bearer( + erab.second.id, erab.second.teid_out, erab.second.address.to_number(), &props); + srslte::uint32_to_uint8(dl_teid_in, admitted_erabs.back().dl_g_tp_teid.data()); + } + } } + + // send S1AP HandoverRequestAcknowledge if (not rrc_enb->s1ap->send_ho_req_ack(*ho_req.ho_req_msg, rrc_ue->rnti, std::move(ho_cmd_pdu), admitted_erabs)) { trigger(srslte::failure_ev{}); return; @@ -721,11 +795,12 @@ bool rrc::ue::rrc_mobility::apply_ho_prep_cfg(const ho_prep_info_r8_ies_s& ho get_log()->warning("Data Forwarding of E-RABs not supported"); } + // Create E-RAB and associated main GTPU tunnel uint32_t teid_out; srslte::uint8_to_uint32(erab.gtp_teid.data(), &teid_out); rrc_ue->bearer_list.add_erab( erab.erab_id, erab.erab_level_qos_params, erab.transport_layer_address, teid_out, nullptr); - rrc_ue->bearer_list.add_gtpu_bearer(rrc_enb->gtpu, erab.erab_id); + rrc_ue->bearer_list.add_gtpu_bearer(erab.erab_id); } // Regenerate AS Keys @@ -900,8 +975,7 @@ void rrc::ue::rrc_mobility::handle_crnti_ce(intraenb_ho_st& s, const user_crnti_ void rrc::ue::rrc_mobility::handle_recfg_complete(intraenb_ho_st& s, const recfg_complete_ev& ev) { - logger.info( - "User rnti=0x%x successfully handovered to cell_id=0x%x", rrc_ue->rnti, s.target_cell->cell_cfg.cell_id); + logger.info("User rnti=0x%x successfully handovered to cell_id=0x%x", rrc_ue->rnti, s.target_cell->cell_cfg.cell_id); } } // namespace srsenb diff --git a/srsenb/src/stack/rrc/rrc_ue.cc b/srsenb/src/stack/rrc/rrc_ue.cc index 455c00eb7..e31ac80fb 100644 --- a/srsenb/src/stack/rrc/rrc_ue.cc +++ b/srsenb/src/stack/rrc/rrc_ue.cc @@ -36,7 +36,7 @@ rrc::ue::ue(rrc* outer_rrc, uint16_t rnti_, const sched_interface::ue_cfg_t& sch pool(srslte::byte_buffer_pool::get_instance()), phy_rrc_dedicated_list(sched_ue_cfg.supported_cc_list.size()), ue_cell_list(parent->cfg, *outer_rrc->cell_res_list, *outer_rrc->cell_common_list), - bearer_list(rnti_, parent->cfg), + bearer_list(rnti_, parent->cfg, outer_rrc->gtpu), ue_security_cfg(parent->cfg), mac_ctrl(rnti, ue_cell_list, bearer_list, parent->cfg, parent->mac, *parent->cell_common_list, sched_ue_cfg) {} @@ -858,7 +858,7 @@ bool rrc::ue::setup_erabs(const asn1::s1ap::erab_to_be_setup_list_ctxt_su_req_l& srslte::uint8_to_uint32(erab.gtp_teid.data(), &teid_out); const asn1::unbounded_octstring* nas_pdu = erab.nas_pdu_present ? &erab.nas_pdu : nullptr; bearer_list.add_erab(erab.erab_id, erab.erab_level_qos_params, erab.transport_layer_address, teid_out, nas_pdu); - bearer_list.add_gtpu_bearer(parent->gtpu, erab.erab_id); + bearer_list.add_gtpu_bearer(erab.erab_id); } return true; } @@ -882,7 +882,7 @@ bool rrc::ue::setup_erabs(const asn1::s1ap::erab_to_be_setup_list_bearer_su_req_ srslte::uint8_to_uint32(erab.gtp_teid.data(), &teid_out); bearer_list.add_erab( erab.erab_id, erab.erab_level_qos_params, erab.transport_layer_address, teid_out, &erab.nas_pdu); - bearer_list.add_gtpu_bearer(parent->gtpu, erab.erab_id); + bearer_list.add_gtpu_bearer(erab.erab_id); } // Work in progress @@ -978,13 +978,6 @@ void rrc::ue::update_scells() parent->logger.info("SCells activated for rnti=0x%x", rnti); } -/********************** Handover **************************/ - -void rrc::ue::handle_ho_preparation_complete(bool is_success, srslte::unique_byte_buffer_t container) -{ - mobility_handler->handle_ho_preparation_complete(is_success, std::move(container)); -} - /********************** HELPERS ***************************/ void rrc::ue::send_dl_ccch(dl_ccch_msg_s* dl_ccch_msg) diff --git a/srsenb/src/stack/upper/s1ap.cc b/srsenb/src/stack/upper/s1ap.cc index 64a91b497..25bacd477 100644 --- a/srsenb/src/stack/upper/s1ap.cc +++ b/srsenb/src/stack/upper/s1ap.cc @@ -59,6 +59,7 @@ srslte::proc_outcome_t s1ap::ue::ho_prep_proc_t::init(uint32_t srslte::plmn_id_t target_plmn_, srslte::unique_byte_buffer_t rrc_container_) { + ho_cmd_msg = nullptr; target_eci = target_eci_; target_plmn = target_plmn_; @@ -113,12 +114,6 @@ srslte::proc_outcome_t s1ap::ue::ho_prep_proc_t::react(const asn1::s1ap::ho_cmd_ // TODO } - // Check for E-RABs subject to being forwarded - if (msg.protocol_ies.erab_subjectto_data_forwarding_list_present) { - procWarning("Not handling E-RABSubjecttoDataForwardingList"); - // TODO - } - // In case of intra-system Handover, Target to Source Transparent Container IE shall be encoded as // Target eNB to Source eNB Transparent Container IE asn1::cbit_ref bref(msg.protocol_ies.target_to_source_transparent_container.value.data(), @@ -140,6 +135,7 @@ srslte::proc_outcome_t s1ap::ue::ho_prep_proc_t::react(const asn1::s1ap::ho_cmd_ } memcpy(rrc_container->msg, container.rrc_container.data(), container.rrc_container.size()); rrc_container->N_bytes = container.rrc_container.size(); + ho_cmd_msg = &msg; return srslte::proc_outcome_t::success; } @@ -147,9 +143,9 @@ srslte::proc_outcome_t s1ap::ue::ho_prep_proc_t::react(const asn1::s1ap::ho_cmd_ void s1ap::ue::ho_prep_proc_t::then(const srslte::proc_state_t& result) { if (result.is_error()) { - s1ap_ptr->rrc->ho_preparation_complete(ue_ptr->ctxt.rnti, false, {}); + s1ap_ptr->rrc->ho_preparation_complete(ue_ptr->ctxt.rnti, false, *ho_cmd_msg, {}); } else { - s1ap_ptr->rrc->ho_preparation_complete(ue_ptr->ctxt.rnti, true, std::move(rrc_container)); + s1ap_ptr->rrc->ho_preparation_complete(ue_ptr->ctxt.rnti, true, *ho_cmd_msg, std::move(rrc_container)); procInfo("Completed with success"); } } @@ -936,10 +932,10 @@ bool s1ap::send_ho_failure(uint32_t mme_ue_s1ap_id) return sctp_send_s1ap_pdu(tx_pdu, SRSLTE_INVALID_RNTI, "HandoverFailure"); } -bool s1ap::send_ho_req_ack(const asn1::s1ap::ho_request_s& msg, - uint16_t rnti, - srslte::unique_byte_buffer_t ho_cmd, - srslte::span > admitted_bearers) +bool s1ap::send_ho_req_ack(const asn1::s1ap::ho_request_s& msg, + uint16_t rnti, + srslte::unique_byte_buffer_t ho_cmd, + srslte::span admitted_bearers) { s1ap_pdu_c tx_pdu; tx_pdu.set_successful_outcome().load_info_obj(ASN1_S1AP_ID_HO_RES_ALLOC); @@ -951,21 +947,26 @@ bool s1ap::send_ho_req_ack(const asn1::s1ap::ho_request_s& msg, container.mme_ue_s1ap_id.value = msg.protocol_ies.mme_ue_s1ap_id.value.value; container.enb_ue_s1ap_id.value = ue_ptr->ctxt.enb_ue_s1ap_id; - // TODO: Add admitted E-RABs - for (uint32_t i = 0; i < msg.protocol_ies.erab_to_be_setup_list_ho_req.value.size(); ++i) { - const auto& erab = msg.protocol_ies.erab_to_be_setup_list_ho_req.value[i]; - const erab_to_be_setup_item_ho_req_s& erabsetup = erab.value.erab_to_be_setup_item_ho_req(); - container.erab_admitted_list.value.push_back({}); - container.erab_admitted_list.value.back().load_info_obj(ASN1_S1AP_ID_ERAB_ADMITTED_ITEM); - auto& c = container.erab_admitted_list.value.back().value.erab_admitted_item(); - c.erab_id = erabsetup.erab_id; - c.gtp_teid = admitted_bearers[i]; + // Add admitted E-RABs + container.erab_admitted_list.value.resize(admitted_bearers.size()); + for (size_t i = 0; i < admitted_bearers.size(); ++i) { + container.erab_admitted_list.value[i].load_info_obj(ASN1_S1AP_ID_ERAB_ADMITTED_ITEM); + auto& c = container.erab_admitted_list.value[i].value.erab_admitted_item(); + c = admitted_bearers[i]; c.transport_layer_address = addr_to_asn1(args.gtp_bind_addr.c_str()); - // c.dl_transport_layer_address_present = true; - // c.dl_transport_layer_address = c.transport_layer_address; - // c.ul_transport_layer_address_present = true; - // c.ul_transport_layer_address = c.transport_layer_address; + + // If E-RAB is proposed for forward tunneling + if (c.dl_g_tp_teid_present) { + c.dl_transport_layer_address_present = true; + c.dl_transport_layer_address = c.transport_layer_address; + } + if (c.ul_gtp_teid_present) { + c.ul_transport_layer_address_present = true; + c.ul_transport_layer_address = c.transport_layer_address; + } } + + // Pack transparent container asn1::s1ap::targetenb_to_sourceenb_transparent_container_s transparent_container; transparent_container.rrc_container.resize(ho_cmd->N_bytes); memcpy(transparent_container.rrc_container.data(), ho_cmd->msg, ho_cmd->N_bytes); @@ -1636,8 +1637,16 @@ bool s1ap::ue::send_ho_required(uint32_t target_eci, /*** fill the transparent container ***/ container.source_to_target_transparent_container_secondary_present = false; sourceenb_to_targetenb_transparent_container_s transparent_cntr; - transparent_cntr.erab_info_list_present = false; // TODO: CHECK + transparent_cntr.erab_info_list_present = true; // TODO: CHECK transparent_cntr.subscriber_profile_idfor_rfp_present = false; // TODO: CHECK + + // FIXME: Hardcoded ERABs + transparent_cntr.erab_info_list.resize(1); + transparent_cntr.erab_info_list[0].load_info_obj(ASN1_S1AP_ID_ERAB_INFO_LIST_ITEM); + transparent_cntr.erab_info_list[0].value.erab_info_list_item().erab_id = 5; + transparent_cntr.erab_info_list[0].value.erab_info_list_item().dl_forwarding_present = true; + transparent_cntr.erab_info_list[0].value.erab_info_list_item().dl_forwarding.value = + dl_forwarding_opts::dl_forwarding_proposed; // - set target cell ID target_plmn.to_s1ap_plmn_bytes(transparent_cntr.target_cell_id.plm_nid.data()); transparent_cntr.target_cell_id.cell_id.from_number(target_eci); // [ENBID|CELLID|0] diff --git a/srsenb/test/common/dummy_classes.h b/srsenb/test/common/dummy_classes.h index 876bdbc66..648ee6ce2 100644 --- a/srsenb/test/common/dummy_classes.h +++ b/srsenb/test/common/dummy_classes.h @@ -105,10 +105,10 @@ public: { return true; } - bool send_ho_req_ack(const asn1::s1ap::ho_request_s& msg, - uint16_t rnti, - srslte::unique_byte_buffer_t ho_cmd, - srslte::span > admitted_bearers) override + bool send_ho_req_ack(const asn1::s1ap::ho_request_s& msg, + uint16_t rnti, + srslte::unique_byte_buffer_t ho_cmd, + srslte::span admitted_bearers) override { return true; } diff --git a/srsenb/test/upper/rrc_mobility_test.cc b/srsenb/test/upper/rrc_mobility_test.cc index 2df42ccf0..6aebff24e 100644 --- a/srsenb/test/upper/rrc_mobility_test.cc +++ b/srsenb/test/upper/rrc_mobility_test.cc @@ -226,7 +226,7 @@ int test_s1ap_mobility(srslte::log_sink_spy& spy, mobility_test_params test_para /* Test Case: HandoverPreparation has failed */ if (test_params.fail_at == mobility_test_params::test_event::ho_prep_failure) { - tester.rrc.ho_preparation_complete(tester.rnti, false, nullptr); + tester.rrc.ho_preparation_complete(tester.rnti, false, {}, nullptr); // TESTASSERT(spy.get_error_counter() == 1); TESTASSERT(not s1ap.last_enb_status.status_present); return SRSLTE_SUCCESS; @@ -239,7 +239,7 @@ int test_s1ap_mobility(srslte::log_sink_spy& spy, mobility_test_params test_para 0x86, 0x0d, 0x30, 0x00, 0x0b, 0x5a, 0x02, 0x17, 0x86, 0x00, 0x05, 0xa0, 0x20}; test_helpers::copy_msg_to_buffer(pdu, ho_cmd_rrc_container); TESTASSERT(s1ap.last_enb_status.rnti != tester.rnti); - tester.rrc.ho_preparation_complete(tester.rnti, true, std::move(pdu)); + tester.rrc.ho_preparation_complete(tester.rnti, true, asn1::s1ap::ho_cmd_s{}, std::move(pdu)); TESTASSERT(s1ap.last_enb_status.status_present); TESTASSERT(spy.get_error_counter() == 0); asn1::rrc::dl_dcch_msg_s ho_cmd; @@ -282,6 +282,13 @@ int test_s1ap_tenb_mobility(mobility_test_params test_params) 0x5c, 0xe1, 0x86, 0x35, 0x39, 0x80, 0x0e, 0x06, 0xa4, 0x40, 0x0f, 0x22, 0x78}; // 0a100b818000018000f3020800001580001406a402f00404f00014804a000000021231b6f83ea06f05e465141d39d0544c00025400200460000000100100c000000000020500041400670dfbc46606500f00080020800c14ca2d5ce1863539800e06a4400f2278 container.rrc_container.resize(sizeof(ho_prep_container)); + container.erab_info_list_present = true; + container.erab_info_list.resize(1); + container.erab_info_list[0].load_info_obj(ASN1_S1AP_ID_ERAB_INFO_LIST_ITEM); + container.erab_info_list[0].value.erab_info_list_item().erab_id = 5; + container.erab_info_list[0].value.erab_info_list_item().dl_forwarding_present = true; + container.erab_info_list[0].value.erab_info_list_item().dl_forwarding.value = + asn1::s1ap::dl_forwarding_opts::dl_forwarding_proposed; memcpy(container.rrc_container.data(), ho_prep_container, sizeof(ho_prep_container)); tester.rrc.start_ho_ue_resource_alloc(ho_req, container); tester.tic(); diff --git a/srsenb/test/upper/test_helpers.h b/srsenb/test/upper/test_helpers.h index 26965559b..25ba827cb 100644 --- a/srsenb/test/upper/test_helpers.h +++ b/srsenb/test/upper/test_helpers.h @@ -75,9 +75,9 @@ public: } last_enb_status = {}; std::vector added_erab_ids; struct ho_req_ack { - uint16_t rnti; - srslte::unique_byte_buffer_t ho_cmd_pdu; - std::vector > admitted_bearers; + uint16_t rnti; + srslte::unique_byte_buffer_t ho_cmd_pdu; + std::vector admitted_bearers; } last_ho_req_ack; bool send_ho_required(uint16_t rnti, @@ -93,10 +93,10 @@ public: last_enb_status = {true, rnti, bearer_status_list}; return true; } - bool send_ho_req_ack(const asn1::s1ap::ho_request_s& msg, - uint16_t rnti, - srslte::unique_byte_buffer_t ho_cmd, - srslte::span > admitted_bearers) override + bool send_ho_req_ack(const asn1::s1ap::ho_request_s& msg, + uint16_t rnti, + srslte::unique_byte_buffer_t ho_cmd, + srslte::span admitted_bearers) override { last_ho_req_ack.rnti = rnti; last_ho_req_ack.ho_cmd_pdu = std::move(ho_cmd);