diff --git a/lib/include/srsran/interfaces/enb_s1ap_interfaces.h b/lib/include/srsran/interfaces/enb_s1ap_interfaces.h index 08e4caa2f..92f74c263 100644 --- a/lib/include/srsran/interfaces/enb_s1ap_interfaces.h +++ b/lib/include/srsran/interfaces/enb_s1ap_interfaces.h @@ -90,7 +90,7 @@ public: * Cancel on-going S1 Handover. MME should release UE context in target eNB * SeNB --> MME */ - virtual void send_ho_cancel(uint16_t rnti) = 0; + virtual void send_ho_cancel(uint16_t rnti, const asn1::s1ap::cause_c& cause) = 0; /************************* * Target eNB Handover diff --git a/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h b/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h index 1cffb9a6a..3649b47b2 100644 --- a/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h +++ b/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h @@ -83,7 +83,8 @@ public: const asn1::s1ap::erab_level_qos_params_s& qos, const asn1::bounded_bitstring<1, 160, true, true>& addr, uint32_t teid_out, - const asn1::unbounded_octstring* nas_pdu); + const asn1::unbounded_octstring* nas_pdu, + asn1::s1ap::cause_c& cause); bool release_erab(uint8_t erab_id); void release_erabs(); bool modify_erab(uint8_t erab_id, diff --git a/srsenb/hdr/stack/rrc/rrc_mobility.h b/srsenb/hdr/stack/rrc/rrc_mobility.h index 50e4b66d8..0f8b2f51b 100644 --- a/srsenb/hdr/stack/rrc/rrc_mobility.h +++ b/srsenb/hdr/stack/rrc/rrc_mobility.h @@ -30,7 +30,11 @@ public: uint16_t crnti; uint16_t temp_crnti; }; - struct ho_cancel_ev {}; + struct ho_cancel_ev { + asn1::s1ap::cause_c cause; + + ho_cancel_ev(const asn1::s1ap::cause_c& cause_) : cause(cause_) {} + }; rrc_mobility(srsenb::rrc::ue* outer_ue); @@ -119,9 +123,9 @@ private: explicit s1_source_ho_st(rrc_mobility* parent_); private: - 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); + void handle_ho_cmd(wait_ho_cmd& s, const ho_cmd_msg& ho_cmd); + void handle_ho_cancel(const ho_cancel_ev& ev); + asn1::s1ap::cause_c start_enb_status_transfer(const asn1::s1ap::ho_cmd_s& s1ap_ho_cmd); rrc* rrc_enb; rrc::ue* rrc_ue; diff --git a/srsenb/hdr/stack/upper/s1ap.h b/srsenb/hdr/stack/upper/s1ap.h index 3bf750491..149f25ff9 100644 --- a/srsenb/hdr/stack/upper/s1ap.h +++ b/srsenb/hdr/stack/upper/s1ap.h @@ -90,7 +90,7 @@ public: srsran::unique_byte_buffer_t ho_cmd, srsran::span admitted_bearers, srsran::const_span not_admitted_bearers) override; - void send_ho_cancel(uint16_t rnti) override; + void send_ho_cancel(uint16_t rnti, const asn1::s1ap::cause_c& cause) override; bool release_erabs(uint16_t rnti, const std::vector& erabs_successfully_released) override; bool send_error_indication(const asn1::s1ap::cause_c& cause, srsran::optional enb_ue_s1ap_id = {}, diff --git a/srsenb/src/stack/rrc/rrc_bearer_cfg.cc b/srsenb/src/stack/rrc/rrc_bearer_cfg.cc index 124f1beee..c1f6eec02 100644 --- a/srsenb/src/stack/rrc/rrc_bearer_cfg.cc +++ b/srsenb/src/stack/rrc/rrc_bearer_cfg.cc @@ -206,10 +206,12 @@ int bearer_cfg_handler::add_erab(uint8_t const asn1::s1ap::erab_level_qos_params_s& qos, const asn1::bounded_bitstring<1, 160, true, true>& addr, uint32_t teid_out, - const asn1::unbounded_octstring* nas_pdu) + const asn1::unbounded_octstring* nas_pdu, + asn1::s1ap::cause_c& cause) { if (erab_id < 5) { logger->error("ERAB id=%d is invalid", erab_id); + cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::unknown_erab_id; return SRSRAN_ERROR; } uint8_t lcid = erab_id - 2; // Map e.g. E-RAB 5 to LCID 3 (==DRB1) @@ -218,10 +220,12 @@ int bearer_cfg_handler::add_erab(uint8_t auto qci_it = cfg->qci_cfg.find(qos.qci); if (qci_it == cfg->qci_cfg.end() or not qci_it->second.configured) { logger->error("QCI=%d not configured", qos.qci); + cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::not_supported_qci_value; return SRSRAN_ERROR; } if (not srsran::is_lte_drb(lcid)) { logger->error("E-RAB=%d logical channel id=%d is invalid", erab_id, lcid); + cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::unknown_erab_id; return SRSRAN_ERROR; } const rrc_cfg_qci_t& qci_cfg = qci_it->second; @@ -233,6 +237,32 @@ int bearer_cfg_handler::add_erab(uint8_t if (addr.length() > 32) { logger->error("Only addresses with length <= 32 are supported"); + cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::invalid_qos_combination; + return SRSRAN_ERROR; + } + if (qos.gbr_qos_info_present and not qci_cfg.configured) { + logger->warning("Provided E-RAB id=%d QoS not supported", erab_id); + cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::invalid_qos_combination; + return SRSRAN_ERROR; + } + if (qos.gbr_qos_info_present) { + uint64_t req_bitrate = + std::max(qos.gbr_qos_info.erab_guaranteed_bitrate_dl, qos.gbr_qos_info.erab_guaranteed_bitrate_ul); + int16_t pbr_kbps = qci_cfg.lc_cfg.prioritised_bit_rate.to_number(); + uint64_t pbr = pbr_kbps < 0 ? std::numeric_limits::max() : pbr_kbps * 1000u; + if (req_bitrate > pbr) { + logger->warning("Provided E-RAB id=%d QoS not supported (guaranteed bitrates)", erab_id); + cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::invalid_qos_combination; + return SRSRAN_ERROR; + } + } + if (qos.alloc_retention_prio.pre_emption_cap.value == asn1::s1ap::pre_emption_cap_opts::may_trigger_pre_emption and + qos.alloc_retention_prio.prio_level < qci_cfg.lc_cfg.prio) { + logger->warning("Provided E-RAB id=%d QoS not supported (priority %d < %d)", + erab_id, + qos.alloc_retention_prio.prio_level, + qci_cfg.lc_cfg.prio); + cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::invalid_qos_combination; return SRSRAN_ERROR; } @@ -300,8 +330,8 @@ bool bearer_cfg_handler::modify_erab(uint8_t auto address = erab_it->second.address; uint32_t teid_out = erab_it->second.teid_out; release_erab(erab_id); - add_erab(erab_id, qos, address, teid_out, nas_pdu); - return true; + asn1::s1ap::cause_c cause; + return add_erab(erab_id, qos, address, teid_out, nas_pdu, cause) == SRSRAN_SUCCESS; } int bearer_cfg_handler::add_gtpu_bearer(uint32_t erab_id) diff --git a/srsenb/src/stack/rrc/rrc_mobility.cc b/srsenb/src/stack/rrc/rrc_mobility.cc index b42890433..72d7e8610 100644 --- a/srsenb/src/stack/rrc/rrc_mobility.cc +++ b/srsenb/src/stack/rrc/rrc_mobility.cc @@ -412,6 +412,16 @@ void rrc::ue::rrc_mobility::handle_ho_preparation_complete(bool trigger(srsran::failure_ev{}); return; } + + // Check if any E-RAB that was not admitted. Cancel Handover, in such case. + if (msg.protocol_ies.erab_to_release_list_ho_cmd_present) { + get_logger().warning("E-RAB id=%d was not admitted in target eNB. Cancelling handover...", + msg.protocol_ies.erab_to_release_list_ho_cmd.value[0].value.erab_item().erab_id); + asn1::s1ap::cause_c cause; + cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::no_radio_res_available_in_target_cell; + trigger(ho_cancel_ev{cause}); + } + /* unpack RRC HOCmd struct and perform sanity checks */ asn1::rrc::ho_cmd_s rrchocmd; { @@ -419,14 +429,18 @@ void rrc::ue::rrc_mobility::handle_ho_preparation_complete(bool if (rrchocmd.unpack(bref) != asn1::SRSASN_SUCCESS) { get_logger().warning("Unpacking of RRC HOCommand was unsuccessful"); get_logger().warning(container->msg, container->N_bytes, "Received container:"); - trigger(ho_cancel_ev{}); + asn1::s1ap::cause_c cause; + cause.set_protocol().value = asn1::s1ap::cause_protocol_opts::transfer_syntax_error; + trigger(ho_cancel_ev{cause}); return; } } if (rrchocmd.crit_exts.type().value != c1_or_crit_ext_opts::c1 or rrchocmd.crit_exts.c1().type().value != ho_cmd_s::crit_exts_c_::c1_c_::types_opts::ho_cmd_r8) { get_logger().warning("Only handling r8 Handover Commands"); - trigger(ho_cancel_ev{}); + asn1::s1ap::cause_c cause; + cause.set_protocol().value = asn1::s1ap::cause_protocol_opts::msg_not_compatible_with_receiver_state; + trigger(ho_cancel_ev{cause}); return; } @@ -545,8 +559,10 @@ rrc::ue::rrc_mobility::s1_source_ho_st::s1_source_ho_st(rrc_mobility* parent_) : * - 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::s1_source_ho_st::start_enb_status_transfer(const asn1::s1ap::ho_cmd_s& s1ap_ho_cmd) +asn1::s1ap::cause_c +rrc::ue::rrc_mobility::s1_source_ho_st::start_enb_status_transfer(const asn1::s1ap::ho_cmd_s& s1ap_ho_cmd) { + asn1::s1ap::cause_c cause; std::vector s1ap_bearers; s1ap_bearers.reserve(rrc_ue->bearer_list.get_erabs().size()); @@ -557,7 +573,8 @@ bool rrc::ue::rrc_mobility::s1_source_ho_st::start_enb_status_transfer(const asn srsran::pdcp_lte_state_t pdcp_state = {}; if (not rrc_enb->pdcp->get_bearer_state(rrc_ue->rnti, lcid, &pdcp_state)) { Error("PDCP bearer lcid=%d for rnti=0x%x was not found", lcid, rrc_ue->rnti); - return false; + cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::unknown_erab_id; + return cause; } b.dl_hfn = pdcp_state.tx_hfn; b.pdcp_dl_sn = pdcp_state.next_pdcp_tx_sn; @@ -568,7 +585,8 @@ bool rrc::ue::rrc_mobility::s1_source_ho_st::start_enb_status_transfer(const asn Info("PDCP Bearer list sent to S1AP to initiate the eNB Status Transfer"); if (not rrc_enb->s1ap->send_enb_status_transfer_proc(rrc_ue->rnti, s1ap_bearers)) { - return false; + cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::invalid_qos_combination; + return cause; } // Setup GTPU forwarding tunnel @@ -595,7 +613,7 @@ bool rrc::ue::rrc_mobility::s1_source_ho_st::start_enb_status_transfer(const asn } } - return true; + return cause; } void rrc::ue::rrc_mobility::s1_source_ho_st::enter(rrc_mobility* f, const ho_meas_report_ev& ev) @@ -624,20 +642,26 @@ void rrc::ue::rrc_mobility::s1_source_ho_st::handle_ho_cmd(wait_ho_cmd& s, const 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) { Warning("Unpacking of RRC DL-DCCH message with HO Command was unsuccessful."); - trigger(ho_cancel_ev{}); + asn1::s1ap::cause_c cause; + cause.set_protocol().value = asn1::s1ap::cause_protocol_opts::transfer_syntax_error; + trigger(ho_cancel_ev{cause}); 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) { Warning("HandoverCommand is expected to contain an RRC Connection Reconf message inside"); - trigger(ho_cancel_ev{}); + asn1::s1ap::cause_c cause; + cause.set_protocol().value = asn1::s1ap::cause_protocol_opts::semantic_error; + trigger(ho_cancel_ev{cause}); 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) { Warning("HandoverCommand is expected to have mobility control subfield"); - trigger(ho_cancel_ev{}); + asn1::s1ap::cause_c cause; + cause.set_protocol().value = asn1::s1ap::cause_protocol_opts::semantic_error; + trigger(ho_cancel_ev{cause}); return; } @@ -650,20 +674,23 @@ void rrc::ue::rrc_mobility::s1_source_ho_st::handle_ho_cmd(wait_ho_cmd& s, const // Send HO Command to UE if (not rrc_ue->send_dl_dcch(&dl_dcch_msg)) { - trigger(ho_cancel_ev{}); + asn1::s1ap::cause_c cause; + cause.set_protocol().value = asn1::s1ap::cause_protocol_opts::transfer_syntax_error; + trigger(ho_cancel_ev{cause}); return; } /* Start S1AP eNBStatusTransfer Procedure */ - if (not start_enb_status_transfer(*ho_cmd.s1ap_ho_cmd)) { - trigger(ho_cancel_ev{}); + asn1::s1ap::cause_c cause = start_enb_status_transfer(*ho_cmd.s1ap_ho_cmd); + if (cause.type().value != asn1::s1ap::cause_c::types_opts::nulltype) { + trigger(ho_cancel_ev{cause}); } } //! 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) { - rrc_enb->s1ap->send_ho_cancel(rrc_ue->rnti); + rrc_enb->s1ap->send_ho_cancel(rrc_ue->rnti, ev.cause); } /************************************* @@ -870,13 +897,13 @@ bool rrc::ue::rrc_mobility::apply_ho_prep_cfg(const ho_prep_info_r8_ies_s& // Create E-RAB and associated main GTPU tunnel uint32_t teid_out = 0; srsran::uint8_to_uint32(erab.gtp_teid.data(), &teid_out); + asn1::s1ap::cause_c erab_cause; if (rrc_ue->bearer_list.add_erab( - erab.erab_id, erab.erab_level_qos_params, erab.transport_layer_address, teid_out, nullptr) != + erab.erab_id, erab.erab_level_qos_params, erab.transport_layer_address, teid_out, nullptr, erab_cause) != SRSRAN_SUCCESS) { erabs_failed_to_setup.emplace_back(); erabs_failed_to_setup.back().erab_id = erab.erab_id; - erabs_failed_to_setup.back().cause.set_radio_network().value = - asn1::s1ap::cause_radio_network_opts::invalid_qos_combination; + erabs_failed_to_setup.back().cause = erab_cause; continue; } if (rrc_ue->bearer_list.add_gtpu_bearer(erab.erab_id) != SRSRAN_SUCCESS) { diff --git a/srsenb/src/stack/rrc/rrc_ue.cc b/srsenb/src/stack/rrc/rrc_ue.cc index b828f61d6..c51623bad 100644 --- a/srsenb/src/stack/rrc/rrc_ue.cc +++ b/srsenb/src/stack/rrc/rrc_ue.cc @@ -500,7 +500,9 @@ void rrc::ue::handle_rrc_con_reest_req(rrc_conn_reest_request_s* msg) old_rnti); // Cancel Handover in Target eNB if on-going - parent->users.at(old_rnti)->mobility_handler->trigger(rrc_mobility::ho_cancel_ev{}); + asn1::s1ap::cause_c cause; + cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::interaction_with_other_proc; + parent->users.at(old_rnti)->mobility_handler->trigger(rrc_mobility::ho_cancel_ev{cause}); // Recover security setup const enb_cell_common* pcell_cfg = get_ue_cc_cfg(UE_PCELL_CC_IDX); @@ -1055,7 +1057,9 @@ bool rrc::ue::setup_erabs(const asn1::s1ap::erab_to_be_setup_list_ctxt_su_req_l& uint32_t teid_out; srsran::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); + asn1::s1ap::cause_c cause; + bearer_list.add_erab( + erab.erab_id, erab.erab_level_qos_params, erab.transport_layer_address, teid_out, nas_pdu, cause); bearer_list.add_gtpu_bearer(erab.erab_id); } return true; @@ -1078,8 +1082,9 @@ bool rrc::ue::setup_erabs(const asn1::s1ap::erab_to_be_setup_list_bearer_su_req_ uint32_t teid_out; srsran::uint8_to_uint32(erab.gtp_teid.data(), &teid_out); + asn1::s1ap::cause_c cause; bearer_list.add_erab( - erab.erab_id, erab.erab_level_qos_params, erab.transport_layer_address, teid_out, &erab.nas_pdu); + erab.erab_id, erab.erab_level_qos_params, erab.transport_layer_address, teid_out, &erab.nas_pdu, cause); bearer_list.add_gtpu_bearer(erab.erab_id); } diff --git a/srsenb/src/stack/upper/s1ap.cc b/srsenb/src/stack/upper/s1ap.cc index aac6f0d19..2d0ec5863 100644 --- a/srsenb/src/stack/upper/s1ap.cc +++ b/srsenb/src/stack/upper/s1ap.cc @@ -1149,10 +1149,11 @@ void s1ap::send_ho_notify(uint16_t rnti, uint64_t target_eci) sctp_send_s1ap_pdu(tx_pdu, rnti, "HandoverNotify"); } -void s1ap::send_ho_cancel(uint16_t rnti) +void s1ap::send_ho_cancel(uint16_t rnti, const asn1::s1ap::cause_c& cause) { ue* user_ptr = users.find_ue_rnti(rnti); if (user_ptr == nullptr) { + logger.warning("Canceling handover for non-existent rnti=0x%x", rnti); return; } @@ -1161,9 +1162,9 @@ void s1ap::send_ho_cancel(uint16_t rnti) tx_pdu.set_init_msg().load_info_obj(ASN1_S1AP_ID_HO_CANCEL); ho_cancel_ies_container& container = tx_pdu.init_msg().value.ho_cancel().protocol_ies; - container.mme_ue_s1ap_id.value = user_ptr->ctxt.mme_ue_s1ap_id.value(); - container.enb_ue_s1ap_id.value = user_ptr->ctxt.enb_ue_s1ap_id; - container.cause.value.set_radio_network().value = cause_radio_network_opts::ho_cancelled; + container.mme_ue_s1ap_id.value = user_ptr->ctxt.mme_ue_s1ap_id.value(); + container.enb_ue_s1ap_id.value = user_ptr->ctxt.enb_ue_s1ap_id; + container.cause.value = cause; sctp_send_s1ap_pdu(tx_pdu, rnti, "HandoverCancel"); } diff --git a/srsenb/test/common/dummy_classes.h b/srsenb/test/common/dummy_classes.h index 440b03934..f49117527 100644 --- a/srsenb/test/common/dummy_classes.h +++ b/srsenb/test/common/dummy_classes.h @@ -130,7 +130,7 @@ public: } void send_ho_notify(uint16_t rnti, uint64_t target_eci) override {} - void send_ho_cancel(uint16_t rnti) override {} + void send_ho_cancel(uint16_t rnti, const asn1::s1ap::cause_c& cause) override {} bool release_erabs(uint16_t rnti, const std::vector& erabs_successfully_released) override { return true; } bool send_ue_cap_info_indication(uint16_t rnti, const srsran::unique_byte_buffer_t ue_radio_cap) override diff --git a/srsenb/test/upper/rrc_mobility_test.cc b/srsenb/test/upper/rrc_mobility_test.cc index c9167141d..49a5ba312 100644 --- a/srsenb/test/upper/rrc_mobility_test.cc +++ b/srsenb/test/upper/rrc_mobility_test.cc @@ -29,7 +29,7 @@ struct mobility_test_params { duplicate_crnti_ce, recover, wrong_target_cell, - wrong_qos, + unknown_qci, } fail_at; const char* to_string() { @@ -48,7 +48,7 @@ struct mobility_test_params { return "duplicate CRNTI CE"; case test_event::wrong_target_cell: return "wrong target cell"; - case test_event::wrong_qos: + case test_event::unknown_qci: return "invalid QoS"; default: return "none"; @@ -276,7 +276,7 @@ int test_s1ap_tenb_mobility(mobility_test_params test_params) auto& erab = ho_req.protocol_ies.erab_to_be_setup_list_ho_req.value[0].value.erab_to_be_setup_item_ho_req(); erab.erab_id = 5; erab.erab_level_qos_params.qci = 9; - if (test_params.fail_at == mobility_test_params::test_event::wrong_qos) { + if (test_params.fail_at == mobility_test_params::test_event::unknown_qci) { erab.erab_level_qos_params.qci = 10; } ho_req.protocol_ies.ue_security_cap.value.integrity_protection_algorithms.set(14, true); @@ -311,10 +311,10 @@ int test_s1ap_tenb_mobility(mobility_test_params test_params) TESTASSERT(tester.rrc.get_nof_users() == 0); return SRSRAN_SUCCESS; } - if (test_params.fail_at == mobility_test_params::test_event::wrong_qos) { + if (test_params.fail_at == mobility_test_params::test_event::unknown_qci) { TESTASSERT(rnti == SRSRAN_INVALID_RNTI); TESTASSERT(cause.type().value == asn1::s1ap::cause_c::types_opts::radio_network); - TESTASSERT(cause.radio_network().value == asn1::s1ap::cause_radio_network_opts::invalid_qos_combination); + TESTASSERT(cause.radio_network().value == asn1::s1ap::cause_radio_network_opts::not_supported_qci_value); TESTASSERT(tester.rrc.get_nof_users() == 0); return SRSRAN_SUCCESS; } @@ -560,7 +560,7 @@ int main(int argc, char** argv) TESTASSERT(test_s1ap_mobility(*spy, mobility_test_params{event::success}) == 0); TESTASSERT(test_s1ap_tenb_mobility(mobility_test_params{event::wrong_target_cell}) == 0); - TESTASSERT(test_s1ap_tenb_mobility(mobility_test_params{event::wrong_qos}) == 0); + TESTASSERT(test_s1ap_tenb_mobility(mobility_test_params{event::unknown_qci}) == 0); TESTASSERT(test_s1ap_tenb_mobility(mobility_test_params{event::success}) == 0); // intraeNB Handover