added intraenb handover fsm and respective test

This commit is contained in:
Francisco Paisana 2020-05-20 18:46:53 +01:00 committed by Francisco Paisana
parent 5d42d16b79
commit b37923312e
4 changed files with 389 additions and 111 deletions

View File

@ -24,6 +24,7 @@
#include "rrc.h"
#include "rrc_ue.h"
#include "srslte/common/fsm.h"
#include "srslte/common/logmap.h"
#include <map>
@ -71,6 +72,8 @@ private:
srslte::log_ref rrc_log;
};
enum class ho_interface_t { S1, X2, interSector };
class rrc::enb_mobility_handler
{
public:
@ -89,26 +92,34 @@ private:
const rrc_cfg_t* cfg = nullptr;
};
class rrc::ue::rrc_mobility
class rrc::ue::rrc_mobility : public srslte::fsm_t<rrc::ue::rrc_mobility>
{
public:
// public events
struct user_crnti_upd_ev {
uint16_t crnti;
uint16_t temp_crnti;
};
explicit rrc_mobility(srsenb::rrc::ue* outer_ue);
bool fill_conn_recfg_msg(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);
bool is_ho_running() const { return not is_in_state<idle_st>(); }
private:
enum class ho_interface_t { S1, X2, interSector };
// 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
bool update_ue_var_meas_cfg(const asn1::rrc::meas_cfg_s& source_meas_cfg,
uint32_t target_enb_cc_idx,
asn1::rrc::meas_cfg_s* diff_meas_cfg);
bool update_ue_var_meas_cfg(const var_meas_cfg_t& source_var_meas_cfg,
uint32_t target_enb_cc_idx,
asn1::rrc::meas_cfg_s* diff_meas_cfg);
void fill_mobility_reconf_common(asn1::rrc::dl_dcch_msg_s& msg, const cell_info_common& target_cell);
rrc::ue* rrc_ue = nullptr;
rrc* rrc_enb = nullptr;
@ -119,41 +130,87 @@ private:
// vars
std::shared_ptr<const var_meas_cfg_t> ue_var_meas;
class sourceenb_ho_proc_t
{
public:
struct ho_prep_result {
bool is_success;
srslte::unique_byte_buffer_t rrc_container;
// events
struct ho_meas_report_ev {
uint32_t target_eci = 0;
const asn1::rrc::cells_to_add_mod_s* meas_cell = nullptr;
const asn1::rrc::meas_obj_to_add_mod_s* meas_obj = nullptr;
};
struct ho_req_rx_ev {
uint32_t target_cell_id;
};
using unsuccessful_outcome_ev = std::false_type;
// states
struct idle_st {};
struct intraenb_ho_st {
const cell_info_common* target_cell = nullptr;
const cell_ctxt_dedicated* source_cell_ctxt = nullptr;
void enter(rrc_mobility* f);
};
struct s1_target_ho_st {
uint32_t target_cell_id;
};
struct s1_source_ho_st : public subfsm_t<s1_source_ho_st> {
ho_meas_report_ev report;
struct wait_ho_req_ack_st {
void enter(s1_source_ho_st* f);
};
struct status_transfer_st {
void enter(s1_source_ho_st* f);
bool is_ho_cmd_sent = false;
};
explicit sourceenb_ho_proc_t(rrc_mobility* ue_mobility_);
srslte::proc_outcome_t init(const asn1::rrc::meas_id_to_add_mod_s& measid_,
const asn1::rrc::meas_obj_to_add_mod_s& measobj_,
const asn1::rrc::report_cfg_to_add_mod_s& repcfg_,
const asn1::rrc::cells_to_add_mod_s& cell_,
const asn1::rrc::meas_result_eutra_s& meas_res_,
uint32_t target_eci_);
srslte::proc_outcome_t step() { return srslte::proc_outcome_t::yield; }
srslte::proc_outcome_t react(ho_prep_result);
static const char* name() { return "Handover"; }
explicit s1_source_ho_st(rrc_mobility* parent_) : base_t(parent_) {}
private:
// args
rrc_mobility* parent = nullptr;
// run args
const asn1::rrc::meas_id_to_add_mod_s* measid = nullptr;
const asn1::rrc::meas_obj_to_add_mod_s* measobj = nullptr;
const asn1::rrc::report_cfg_to_add_mod_s* repcfg = nullptr;
const asn1::rrc::cells_to_add_mod_s* cell = nullptr;
asn1::rrc::meas_result_eutra_s meas_res;
uint32_t target_eci = 0;
void handle_ho_cmd(wait_ho_req_ack_st& s, status_transfer_st& d, const srslte::unique_byte_buffer_t& container);
enum class state_t { ho_preparation, ho_execution } state{};
ho_interface_t ho_interface{};
bool fwd_direct_path_available = false;
protected:
using fsm = s1_source_ho_st;
state_list<wait_ho_req_ack_st, status_transfer_st> states{this};
// clang-format off
using transitions = transition_table<
// Start Target Event Action
// +-------------------+------------------+------------------------------+---------------------------+
from_any_state< idle_st, srslte::failure_ev >,
row< wait_ho_req_ack_st, status_transfer_st, srslte::unique_byte_buffer_t, &fsm::handle_ho_cmd >
// +-------------------+------------------+------------------------------+--------- -----------------+
>;
// clang-format on
};
srslte::proc_t<sourceenb_ho_proc_t> source_ho_proc;
// FSM guards
bool needs_s1_ho(idle_st& s, const ho_meas_report_ev& meas_report) const;
bool needs_intraenb_ho(idle_st& s, const ho_meas_report_ev& meas_report) const;
// FSM transition handlers
void handle_s1_meas_report(idle_st& s, s1_source_ho_st& d, const ho_meas_report_ev& meas_report);
void handle_intraenb_meas_report(idle_st& s, intraenb_ho_st& d, const ho_meas_report_ev& meas_report);
void handle_crnti_ce(intraenb_ho_st& s, idle_st& d, const user_crnti_upd_ev& ev);
protected:
// states
state_list<idle_st, intraenb_ho_st, s1_target_ho_st, s1_source_ho_st> states{this,
idle_st{},
intraenb_ho_st{},
s1_target_ho_st{},
s1_source_ho_st{this}};
// transitions
using fsm = rrc_mobility;
// clang-format off
using transitions = transition_table<
// Start Target Event Action Guard (optional)
// +------------+---------------+--------------------+---------------------------------+---------------------------+
row< idle_st, intraenb_ho_st, ho_meas_report_ev, &fsm::handle_intraenb_meas_report, &fsm::needs_intraenb_ho >,
row< idle_st, s1_source_ho_st, ho_meas_report_ev, &fsm::handle_s1_meas_report, &fsm::needs_s1_ho >,
row< intraenb_ho_st, idle_st, user_crnti_upd_ev, &fsm::handle_crnti_ce >
>;
// clang-format on
};
} // namespace srsenb

View File

@ -195,7 +195,10 @@ void rrc::upd_user(uint16_t new_rnti, uint16_t old_rnti)
// Send Reconfiguration to old_rnti if is RRC_CONNECT or RRC Release if already released here
auto old_it = users.find(old_rnti);
if (old_it != users.end()) {
if (old_it->second->is_connected()) {
auto ue_ptr = old_it->second.get();
if (ue_ptr->mobility_handler->is_ho_running()) {
ue_ptr->mobility_handler->trigger(ue::rrc_mobility::user_crnti_upd_ev{old_rnti, new_rnti});
} else if (ue_ptr->is_connected()) {
old_it->second->send_connection_reconf_upd(srslte::allocate_unique_buffer(*pool));
} else {
old_it->second->send_connection_reject();

View File

@ -460,12 +460,12 @@ rrc::enb_mobility_handler::enb_mobility_handler(rrc* rrc_) : rrc_ptr(rrc_), cfg(
************************************************************************************************/
rrc::ue::rrc_mobility::rrc_mobility(rrc::ue* outer_ue) :
base_t(outer_ue->parent->rrc_log),
rrc_ue(outer_ue),
rrc_enb(outer_ue->parent),
cfg(outer_ue->parent->enb_mobility_cfg.get()),
pool(outer_ue->pool),
rrc_log(outer_ue->parent->rrc_log),
source_ho_proc(this),
ue_var_meas(std::make_shared<var_meas_cfg_t>())
{}
@ -474,7 +474,7 @@ bool rrc::ue::rrc_mobility::fill_conn_recfg_msg(asn1::rrc::rrc_conn_recfg_r8_ies
{
// only reconfigure meas_cfg if no handover is occurring.
// NOTE: We basically freeze ue_var_meas for the whole duration of the handover procedure
if (source_ho_proc.is_busy()) {
if (is_ho_running()) {
return false;
}
@ -488,6 +488,10 @@ bool rrc::ue::rrc_mobility::fill_conn_recfg_msg(asn1::rrc::rrc_conn_recfg_r8_ies
//! Method called whenever the eNB receives a MeasReport from the UE. In normal situations, an HO procedure is started
void rrc::ue::rrc_mobility::handle_ue_meas_report(const meas_report_s& msg)
{
if (not is_in_state<idle_st>()) {
Info("Received a MeasReport while UE is performing Handover. Ignoring...\n");
return;
}
// Check if meas_id is valid
const meas_results_s& meas_res = msg.crit_exts.c1().meas_report_r8().meas_results;
if (not meas_res.meas_result_neigh_cells_present) {
@ -507,34 +511,33 @@ void rrc::ue::rrc_mobility::handle_ue_meas_report(const meas_report_s& msg)
const meas_result_list_eutra_l& eutra_list = meas_res.meas_result_neigh_cells.meas_result_list_eutra();
// Find respective ReportCfg and MeasObj
auto obj_it = srslte::find_rrc_obj_id(ue_var_meas->meas_objs(), measid_it->meas_obj_id);
auto rep_it = srslte::find_rrc_obj_id(ue_var_meas->rep_cfgs(), measid_it->report_cfg_id);
ho_meas_report_ev meas_ev{};
auto obj_it = srslte::find_rrc_obj_id(ue_var_meas->meas_objs(), measid_it->meas_obj_id);
meas_ev.meas_obj = &(*obj_it);
// iterate from strongest to weakest cell
// NOTE: From now we just look at the strongest.
if (eutra_list.size() > 0) {
uint32_t i = 0;
uint16_t pci = eutra_list[i].pci;
const cells_to_add_mod_list_l& cells = obj_it->meas_obj.meas_obj_eutra().cells_to_add_mod_list;
const cells_to_add_mod_s* cell_it =
std::find_if(cells.begin(), cells.end(), [pci](const cells_to_add_mod_s& c) { return c.pci == pci; });
const cells_to_add_mod_list_l& cells = obj_it->meas_obj.meas_obj_eutra().cells_to_add_mod_list;
const cell_ctxt_dedicated* pcell = rrc_ue->cell_ded_list.get_ue_cc_idx(UE_PCELL_CC_IDX);
const auto& meas_list_cfg = pcell->cell_common->cell_cfg.meas_cfg.meas_cells;
const cells_to_add_mod_s* cell_it = nullptr;
// const meas_result_eutra_s* chosen_meas = nullptr;
for (const meas_result_eutra_s& e : eutra_list) {
uint16_t pci = e.pci;
cell_it = std::find_if(cells.begin(), cells.end(), [pci](const cells_to_add_mod_s& c) { return c.pci == pci; });
if (cell_it == cells.end()) {
rrc_log->warning("The PCI=%d inside the MeasReport is not recognized.\n", pci);
return;
continue;
}
meas_ev.meas_cell = cell_it;
meas_ev.target_eci = std::find_if(meas_list_cfg.begin(),
meas_list_cfg.end(),
[pci](const meas_cell_cfg_t& c) { return c.pci == pci; })
->eci;
// eNB found the respective cell. eNB takes "HO Decision"
// TODO: check what to do here to take the decision.
// NOTE: for now just accept anything.
// Target cell to handover to was selected.
auto& L = rrc_enb->cfg.cell_list[rrc_ue->get_ue_cc_cfg(UE_PCELL_CC_IDX)->enb_cc_idx].meas_cfg.meas_cells;
uint32_t target_eci = std::find_if(L.begin(), L.end(), [pci](meas_cell_cfg_t& c) { return c.pci == pci; })->eci;
if (not source_ho_proc.launch(*measid_it, *obj_it, *rep_it, *cell_it, eutra_list[i], target_eci)) {
Error("Failed to start HO procedure, as it is already on-going\n");
return;
// NOTE: From now we just choose the strongest.
if (trigger(meas_ev)) {
break;
}
}
}
@ -693,9 +696,22 @@ bool rrc::ue::rrc_mobility::start_ho_preparation(uint32_t target_eci,
return success;
}
/**
* Description: Handover Preparation Complete (with success or failure)
* - MME --> SeNB
* - Response from MME on whether the HandoverRequired command is valid and the TeNB was able to allocate
* space for the UE
* @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)
{
source_ho_proc.trigger(sourceenb_ho_proc_t::ho_prep_result{is_success, std::move(container)});
if (not is_success) {
log_h->info("Received S1AP HandoverFailure. Aborting Handover...\n");
trigger(srslte::failure_ev{});
return;
}
trigger(container);
}
bool rrc::ue::rrc_mobility::update_ue_var_meas_cfg(const asn1::rrc::meas_cfg_s& source_meas_cfg,
@ -725,6 +741,52 @@ bool rrc::ue::rrc_mobility::update_ue_var_meas_cfg(const var_meas_cfg_t& source
return meas_cfg_present;
}
void rrc::ue::rrc_mobility::fill_mobility_reconf_common(asn1::rrc::dl_dcch_msg_s& msg,
const cell_info_common& target_cell)
{
auto& recfg = msg.msg.set_c1().set_rrc_conn_recfg();
recfg.rrc_transaction_id = rrc_ue->transaction_id;
rrc_ue->transaction_id = (rrc_ue->transaction_id + 1) % 4;
auto& recfg_r8 = recfg.crit_exts.set_c1().set_rrc_conn_recfg_r8();
// Pack MobilityControlInfo message with params of target Cell
recfg_r8.mob_ctrl_info_present = true;
auto& mob_info = recfg_r8.mob_ctrl_info;
mob_info.target_pci = target_cell.cell_cfg.pci;
mob_info.t304.value = mob_ctrl_info_s::t304_opts::ms2000; // TODO: make it reconfigurable
mob_info.new_ue_id.from_number(rrc_ue->rnti);
mob_info.rr_cfg_common.pusch_cfg_common = target_cell.sib2.rr_cfg_common.pusch_cfg_common;
mob_info.rr_cfg_common.prach_cfg.root_seq_idx = target_cell.sib2.rr_cfg_common.prach_cfg.root_seq_idx;
mob_info.rr_cfg_common.ul_cp_len = target_cell.sib2.rr_cfg_common.ul_cp_len;
mob_info.rr_cfg_common.p_max_present = true;
mob_info.rr_cfg_common.p_max = rrc_enb->cfg.sib1.p_max;
mob_info.carrier_freq_present = false; // same frequency handover for now
// Set security cfg
recfg_r8.security_cfg_ho_present = true;
auto& intralte = recfg_r8.security_cfg_ho.handov_type.set_intra_lte();
intralte.security_algorithm_cfg_present = false;
intralte.key_change_ind = false;
intralte.next_hop_chaining_count = 0;
recfg_r8.rr_cfg_ded_present = true;
recfg_r8.rr_cfg_ded.phys_cfg_ded_present = true;
// Allocate SR in new CC
recfg_r8.rr_cfg_ded.phys_cfg_ded.sched_request_cfg_present = true;
auto& sr_setup = recfg_r8.rr_cfg_ded.phys_cfg_ded.sched_request_cfg.set_setup();
sr_setup.dsr_trans_max = rrc_enb->cfg.sr_cfg.dsr_max;
// TODO: For intra-freq handover, SR resources do not get updated. Update for inter-freq case
sr_setup.sr_cfg_idx = rrc_ue->cell_ded_list.get_sr_res()->sr_I;
sr_setup.sr_pucch_res_idx = rrc_ue->cell_ded_list.get_sr_res()->sr_N_pucch;
// Antenna info - start at TM1
recfg_r8.rr_cfg_ded.phys_cfg_ded.ant_info_present = true;
auto& ant_info = recfg_r8.rr_cfg_ded.phys_cfg_ded.ant_info.set_explicit_value();
ant_info.tx_mode.value = ant_info_ded_s::tx_mode_e_::tm1;
ant_info.ue_tx_ant_sel.set(setup_e::release);
}
/**
* TS 36.413, Section 8.4.6 - eNB Status Transfer
* Description: Send "eNBStatusTransfer" message from source eNB to MME
@ -750,60 +812,67 @@ bool rrc::ue::rrc_mobility::start_enb_status_transfer()
return rrc_enb->s1ap->send_enb_status_transfer_proc(rrc_ue->rnti, s1ap_bearers);
}
/*************************************************************************************************
* sourceenb_ho_proc_t class
************************************************************************************************/
/*************************************
* rrc_mobility FSM methods
*************************************/
rrc::ue::rrc_mobility::sourceenb_ho_proc_t::sourceenb_ho_proc_t(rrc_mobility* ue_mobility_) : parent(ue_mobility_) {}
srslte::proc_outcome_t rrc::ue::rrc_mobility::sourceenb_ho_proc_t::init(const meas_id_to_add_mod_s& measid_,
const meas_obj_to_add_mod_s& measobj_,
const report_cfg_to_add_mod_s& repcfg_,
const cells_to_add_mod_s& cell_,
const meas_result_eutra_s& meas_res_,
uint32_t target_eci_)
bool rrc::ue::rrc_mobility::needs_s1_ho(idle_st& s, const ho_meas_report_ev& meas_result) const
{
measid = &measid_;
measobj = &measobj_;
repcfg = &repcfg_;
cell = &cell_;
meas_res = meas_res_;
target_eci = target_eci_;
// TODO: Check X2 is available first. If fail, go for S1.
// NOTE: For now only S1-HO is supported. X2 also not available for fwd direct path
ho_interface = ho_interface_t::S1;
fwd_direct_path_available = false;
state = state_t::ho_preparation;
procInfo("Started Handover of rnti=0x%x to %s.\n", parent->rrc_ue->rnti, rrc_details::to_string(*cell).c_str());
if (not parent->start_ho_preparation(target_eci, measobj->meas_obj_id, fwd_direct_path_available)) {
procError("Failed to send HO Required to MME.\n");
return srslte::proc_outcome_t::error;
if (rrc_ue->get_state() != RRC_STATE_REGISTERED) {
return false;
}
return srslte::proc_outcome_t::yield;
return rrc_details::eci_to_enbid(meas_result.target_eci) != rrc_enb->cfg.enb_id;
}
srslte::proc_outcome_t rrc::ue::rrc_mobility::sourceenb_ho_proc_t::react(ho_prep_result e)
bool rrc::ue::rrc_mobility::needs_intraenb_ho(idle_st& s, const ho_meas_report_ev& meas_result) const
{
if (not e.is_success) {
procError("Failure during handover preparation.\n");
return srslte::proc_outcome_t::error;
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;
}
void rrc::ue::rrc_mobility::handle_s1_meas_report(idle_st& s, s1_source_ho_st& d, const ho_meas_report_ev& meas_report)
{
Info("Starting S1 Handover of rnti=0x%x to 0x%x.\n", rrc_ue->rnti, meas_report.target_eci);
d.report = meas_report;
}
/*************************************
* s1_source_ho subFSM methods
*************************************/
void rrc::ue::rrc_mobility::s1_source_ho_st::wait_ho_req_ack_st::enter(s1_source_ho_st* f)
{
bool success = f->parent_fsm()->start_ho_preparation(f->report.target_eci, f->report.meas_obj->meas_obj_id, false);
if (not success) {
f->trigger(srslte::failure_ev{});
}
}
void rrc::ue::rrc_mobility::s1_source_ho_st::handle_ho_cmd(wait_ho_req_ack_st& s,
status_transfer_st& d,
const srslte::unique_byte_buffer_t& container)
{
d.is_ho_cmd_sent = false;
/* unpack RRC HOCmd struct and perform sanity checks */
asn1::rrc::ho_cmd_s rrchocmd;
{
asn1::cbit_ref bref(e.rrc_container->msg, e.rrc_container->N_bytes);
asn1::cbit_ref bref(container->msg, container->N_bytes);
if (rrchocmd.unpack(bref) != asn1::SRSASN_SUCCESS) {
procError("Unpacking of RRC HOCommand was unsuccessful\n");
parent->rrc_log->error_hex(e.rrc_container->msg, e.rrc_container->N_bytes, "Received container:\n");
return srslte::proc_outcome_t::error;
log_h->warning("Unpacking of RRC HOCommand was unsuccessful\n");
log_h->warning_hex(container->msg, container->N_bytes, "Received container:\n");
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) {
procError("Only handling r8 Handover Commands\n");
log_h->warning("Only handling r8 Handover Commands\n");
return;
}
/* unpack DL-DCCH message containing the RRCRonnectionReconf (with MobilityInfo) to be sent to the UE */
@ -812,34 +881,120 @@ srslte::proc_outcome_t rrc::ue::rrc_mobility::sourceenb_ho_proc_t::react(ho_prep
asn1::cbit_ref bref(&rrchocmd.crit_exts.c1().ho_cmd_r8().ho_cmd_msg[0],
rrchocmd.crit_exts.c1().ho_cmd_r8().ho_cmd_msg.size());
if (dl_dcch_msg.unpack(bref) != asn1::SRSASN_SUCCESS) {
procError("Unpacking of RRC DL-DCCH message with HO Command was unsuccessful.\n");
return srslte::proc_outcome_t::error;
log_h->warning("Unpacking of RRC DL-DCCH message with HO Command was unsuccessful.\n");
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) {
procError("HandoverCommand is expected to contain an RRC Connection Reconf message inside\n");
return srslte::proc_outcome_t::error;
log_h->warning("HandoverCommand is expected to contain an RRC Connection Reconf message inside\n");
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) {
procWarning("HandoverCommand is expected to have mobility control subfield\n");
return srslte::proc_outcome_t::error;
log_h->warning("HandoverCommand is expected to have mobility control subfield\n");
return;
}
// TODO: Do anything with MeasCfg info within the Msg (e.g. update ue_var_meas)?
/* Send HO Command to UE */
parent->rrc_ue->send_dl_dcch(&dl_dcch_msg);
procInfo("HandoverCommand of rnti=0x%x handled successfully.\n", parent->rrc_ue->rnti);
state = state_t::ho_execution;
/* Start S1AP eNBStatusTransfer Procedure */
if (not parent->start_enb_status_transfer()) {
return srslte::proc_outcome_t::error;
if (not parent_fsm()->rrc_ue->send_dl_dcch(&dl_dcch_msg)) {
return;
}
return srslte::proc_outcome_t::success;
d.is_ho_cmd_sent = true;
log_h->info("HandoverCommand of rnti=0x%x handled successfully.\n", parent_fsm()->rrc_ue->rnti);
}
void rrc::ue::rrc_mobility::s1_source_ho_st::status_transfer_st::enter(s1_source_ho_st* f)
{
if (not is_ho_cmd_sent) {
f->trigger(srslte::failure_ev{});
}
// 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{});
}
}
/*************************************************************************************************
* intraENB Handover sub-FSM
************************************************************************************************/
void rrc::ue::rrc_mobility::handle_intraenb_meas_report(idle_st& s,
intraenb_ho_st& d,
const ho_meas_report_ev& meas_report)
{
uint32_t cell_id = rrc_details::eci_to_cellid(meas_report.target_eci);
d.target_cell = rrc_enb->cell_common_list->get_cell_id(cell_id);
d.source_cell_ctxt = rrc_ue->cell_ded_list.get_ue_cc_idx(UE_PCELL_CC_IDX);
if (d.target_cell == nullptr) {
rrc_log->error("The target cell_id=0x%x was not found in the list of eNB cells\n", cell_id);
return;
}
Info("Starting intraeNB Handover of rnti=0x%x to 0x%x.\n", rrc_ue->rnti, meas_report.target_eci);
}
void rrc::ue::rrc_mobility::intraenb_ho_st::enter(rrc_mobility* f)
{
if (target_cell == nullptr) {
f->trigger(srslte::failure_ev{});
return;
}
/* Allocate Resources in Target Cell */
// NOTE: for intra-eNB Handover only CQI resources will change
if (not f->rrc_ue->cell_ded_list.set_cells({target_cell->enb_cc_idx})) {
f->trigger(srslte::failure_ev{});
return;
}
/* Freeze all DRBs. SRBs are needed for sending the HO Cmd */
for (const drb_to_add_mod_s& drb : f->rrc_ue->bearer_list.established_drbs()) {
f->rrc_enb->pdcp->del_bearer(f->rrc_ue->rnti, drb.drb_id + 2);
f->rrc_enb->mac->bearer_ue_rem(f->rrc_ue->rnti, drb.drb_id + 2);
}
/* Prepare RRC Reconf Message with mobility info */
dl_dcch_msg_s dl_dcch_msg;
f->fill_mobility_reconf_common(dl_dcch_msg, *target_cell);
auto& recfg_r8 = dl_dcch_msg.msg.c1().rrc_conn_recfg().crit_exts.c1().rrc_conn_recfg_r8();
// Add MeasConfig of target cell
auto prev_meas_var = f->ue_var_meas;
recfg_r8.meas_cfg_present = f->update_ue_var_meas_cfg(*f->ue_var_meas, target_cell->enb_cc_idx, &recfg_r8.meas_cfg);
// Send DL-DCCH Message via current PCell
if (not f->rrc_ue->send_dl_dcch(&dl_dcch_msg)) {
f->trigger(srslte::failure_ev{});
return;
}
}
void rrc::ue::rrc_mobility::handle_crnti_ce(intraenb_ho_st& s, idle_st& d, const user_crnti_upd_ev& ev)
{
rrc_log->info("UE performing handover updated its temp-crnti=0x%x to rnti=0x%x\n", ev.temp_crnti, ev.crnti);
// Need to reset SNs of bearers.
rrc_enb->rlc->rem_user(rrc_ue->rnti);
rrc_enb->pdcp->rem_user(rrc_ue->rnti);
rrc_enb->rlc->add_user(rrc_ue->rnti);
rrc_enb->pdcp->add_user(rrc_ue->rnti);
// Change PCell in MAC/Scheduler
rrc_ue->current_sched_ue_cfg.supported_cc_list[0].active = true;
rrc_ue->current_sched_ue_cfg.supported_cc_list[0].enb_cc_idx = s.target_cell->enb_cc_idx;
rrc_ue->apply_setup_phy_common(s.target_cell->sib2.rr_cfg_common);
rrc_enb->mac->ue_set_crnti(ev.temp_crnti, ev.crnti, &rrc_ue->current_sched_ue_cfg);
rrc_ue->ue_security_cfg.regenerate_keys_handover(s.target_cell->cell_cfg.pci, s.target_cell->cell_cfg.dl_earfcn);
rrc_ue->bearer_list.reest_bearers();
rrc_ue->bearer_list.apply_rlc_bearer_updates(rrc_enb->rlc);
rrc_ue->bearer_list.apply_pdcp_bearer_updates(rrc_enb->pdcp, rrc_ue->ue_security_cfg);
rrc_log->info("new rnti=0x%x PCell is %d\n", ev.crnti, s.target_cell->enb_cc_idx);
}
} // namespace srsenb

View File

@ -434,6 +434,64 @@ int test_s1ap_mobility(mobility_test_params test_params)
return SRSLTE_SUCCESS;
}
int test_intraenb_mobility(mobility_test_params test_params)
{
printf("\n===== TEST: test_intraenb_mobility() for event %s =====\n", test_params.to_string());
intraenb_mobility_tester tester{test_params};
srslte::unique_byte_buffer_t pdu;
TESTASSERT(tester.generate_rrc_cfg() == SRSLTE_SUCCESS);
TESTASSERT(tester.setup_rrc() == SRSLTE_SUCCESS);
TESTASSERT(tester.run_preamble() == SRSLTE_SUCCESS);
tester.pdcp.last_sdu.sdu = nullptr;
/* Receive MeasReport from UE (correct if PCI=2) */
if (test_params.fail_at == mobility_test_params::test_fail_at::wrong_measreport) {
uint8_t meas_report[] = {0x08, 0x10, 0x38, 0x74, 0x00, 0x0D, 0xBC, 0x80}; // PCI == 3
test_helpers::copy_msg_to_buffer(pdu, meas_report, sizeof(meas_report));
} else {
uint8_t meas_report[] = {0x08, 0x10, 0x38, 0x74, 0x00, 0x09, 0xBC, 0x80}; // PCI == 2
test_helpers::copy_msg_to_buffer(pdu, meas_report, sizeof(meas_report));
}
tester.rrc.write_pdu(tester.rnti, 1, std::move(pdu));
tester.tic();
TESTASSERT(tester.s1ap.last_ho_required.rrc_container == nullptr);
/* Test Case: the MeasReport is not valid */
if (test_params.fail_at == mobility_test_params::test_fail_at::wrong_measreport) {
TESTASSERT(tester.rrc_log->warn_counter == 1);
TESTASSERT(tester.pdcp.last_sdu.sdu == nullptr);
return SRSLTE_SUCCESS;
}
TESTASSERT(tester.pdcp.last_sdu.sdu != nullptr);
TESTASSERT(tester.s1ap.last_ho_required.rrc_container == nullptr);
TESTASSERT(not tester.s1ap.last_enb_status.status_present);
/* Test Case: Multiple concurrent MeasReports arrived. Only one HO procedure should be running */
if (test_params.fail_at == mobility_test_params::test_fail_at::concurrent_ho) {
tester.pdcp.last_sdu = {};
uint8_t meas_report[] = {0x08, 0x10, 0x38, 0x74, 0x00, 0x09, 0xBC, 0x80}; // PCI == 2
test_helpers::copy_msg_to_buffer(pdu, meas_report, sizeof(meas_report));
tester.rrc.write_pdu(tester.rnti, 1, std::move(pdu));
tester.tic();
TESTASSERT(tester.pdcp.last_sdu.sdu == nullptr);
return SRSLTE_SUCCESS;
}
/* Test Case: the HandoverCommand was sent to the lower layers */
TESTASSERT(tester.rrc_log->error_counter == 0);
TESTASSERT(tester.pdcp.last_sdu.rnti == tester.rnti);
TESTASSERT(tester.pdcp.last_sdu.lcid == 1); // SRB1
asn1::rrc::dl_dcch_msg_s ho_cmd;
TESTASSERT(test_helpers::unpack_asn1(ho_cmd, tester.pdcp.last_sdu.sdu));
auto& recfg_r8 = ho_cmd.msg.c1().rrc_conn_recfg().crit_exts.c1().rrc_conn_recfg_r8();
TESTASSERT(recfg_r8.mob_ctrl_info_present);
TESTASSERT(recfg_r8.mob_ctrl_info.new_ue_id.to_number() == tester.rnti);
TESTASSERT(recfg_r8.mob_ctrl_info.target_pci == 2);
return SRSLTE_SUCCESS;
}
int main(int argc, char** argv)
{
srslte::logmap::set_default_log_level(srslte::LOG_LEVEL_INFO);
@ -453,6 +511,11 @@ int main(int argc, char** argv)
TESTASSERT(test_s1ap_mobility(mobility_test_params{fail_at::ho_prep_failure}) == 0);
TESTASSERT(test_s1ap_mobility(mobility_test_params{fail_at::success}) == 0);
// intraeNB Handover
TESTASSERT(test_intraenb_mobility(mobility_test_params{fail_at::wrong_measreport}) == 0);
TESTASSERT(test_intraenb_mobility(mobility_test_params{fail_at::concurrent_ho}) == 0);
TESTASSERT(test_intraenb_mobility(mobility_test_params{fail_at::success}) == 0);
printf("\nSuccess\n");
return SRSLTE_SUCCESS;