/src/ocudu/lib/rrc/ue/rrc_ue_impl.cpp
Line | Count | Source |
1 | | // SPDX-FileCopyrightText: Copyright (C) 2021-2026 Software Radio Systems Limited |
2 | | // SPDX-License-Identifier: BSD-3-Clause-Open-MPI |
3 | | // Portions of this file may implement 3GPP specifications, which may be subject to additional licensing requirements. |
4 | | |
5 | | #include "rrc_ue_impl.h" |
6 | | #include "rrc_asn1_helpers.h" |
7 | | #include "ocudu/adt/format.h" |
8 | | #include "ocudu/asn1/rrc_nr/rrc_nr.h" |
9 | | #include "ocudu/support/ocudu_assert.h" |
10 | | |
11 | | using namespace ocudu; |
12 | | using namespace ocucp; |
13 | | using namespace asn1::rrc_nr; |
14 | | |
15 | | rrc_ue_impl::rrc_ue_impl(rrc_pdu_f1ap_notifier& f1ap_pdu_notifier_, |
16 | | rrc_ue_ngap_notifier& ngap_notifier_, |
17 | | rrc_ue_context_update_notifier& cu_cp_notifier_, |
18 | | rrc_ue_measurement_notifier& measurement_notifier_, |
19 | | rrc_ue_cu_cp_ue_notifier& cu_cp_ue_notifier_, |
20 | | rrc_ue_event_notifier& metrics_notifier_, |
21 | | rrc_ue_srb_pdcp_manager& pdcp_manager_, |
22 | | const cu_cp_ue_index_t ue_index_, |
23 | | const rnti_t c_rnti_, |
24 | | const rrc_cell_context& cell_, |
25 | | const rrc_ue_cfg_t& cfg_, |
26 | | const byte_buffer& du_to_cu_container_, |
27 | | std::optional<rrc_ue_transfer_context> rrc_context, |
28 | | std::optional<rrc_resume_context_t> remote_resume_context) : |
29 | 0 | logger("RRC", {ue_index_, c_rnti_}), |
30 | 0 | context(ue_index_, c_rnti_, cell_, cfg_, rrc_context, remote_resume_context, pdcp_manager_, logger), |
31 | 0 | f1ap_pdu_notifier(f1ap_pdu_notifier_), |
32 | 0 | ngap_notifier(ngap_notifier_), |
33 | 0 | cu_cp_notifier(cu_cp_notifier_), |
34 | 0 | measurement_notifier(measurement_notifier_), |
35 | 0 | cu_cp_ue_notifier(cu_cp_ue_notifier_), |
36 | 0 | metrics_notifier(metrics_notifier_), |
37 | 0 | du_to_cu_container(du_to_cu_container_), |
38 | 0 | event_mng(std::make_unique<rrc_ue_event_manager>(cu_cp_ue_notifier.get_timer_factory())) |
39 | 0 | { |
40 | 0 | ocudu_assert(context.cell.bands.empty() == false, "Band must be present in RRC cell configuration."); |
41 | | |
42 | | // Update security context and keys. |
43 | 0 | if (rrc_context.has_value()) { |
44 | 0 | if (!rrc_context.value().is_inter_cu_handover) { |
45 | 0 | cu_cp_ue_notifier.update_security_context(rrc_context.value().sec_context); |
46 | 0 | cu_cp_ue_notifier.perform_horizontal_key_derivation(cell_.pci, cell_.ssb_arfcn.value()); |
47 | 0 | } |
48 | | |
49 | | // Create SRBs. |
50 | 0 | for (const auto& srb : rrc_context.value().srbs) { |
51 | 0 | srb_creation_message srb_msg{}; |
52 | 0 | srb_msg.ue_index = ue_index_; |
53 | 0 | srb_msg.srb_id = srb; |
54 | 0 | srb_msg.enable_security = true; |
55 | 0 | srb_msg.pdcp_cfg = {}; // TODO: add support for non-default config. |
56 | 0 | create_srb(srb_msg); |
57 | 0 | } |
58 | 0 | } |
59 | 0 | } |
60 | | |
61 | 0 | rrc_ue_impl::~rrc_ue_impl() {} |
62 | | |
63 | | void rrc_ue_impl::create_srb(const srb_creation_message& msg) |
64 | 0 | { |
65 | 0 | logger.log_debug("Creating {}", msg.srb_id); |
66 | | |
67 | | // Create adapter objects and PDCP bearer as needed. |
68 | 0 | if (msg.srb_id == srb_id_t::srb0) { |
69 | | // SRB0 is already set up. |
70 | 0 | return; |
71 | 0 | } |
72 | | |
73 | 0 | if (msg.srb_id <= srb_id_t::srb2) { |
74 | 0 | rrc_ue_pdcp_notifier& notifier = context.pdcp_manager.create_srb(msg.srb_id); |
75 | 0 | context.pdcp_notifiers[srb_id_to_uint(msg.srb_id)] = ¬ifier; |
76 | |
|
77 | 0 | if (msg.srb_id == srb_id_t::srb2 || msg.enable_security) { |
78 | 0 | security::sec_128_as_config sec_cfg = security::truncate_config(cu_cp_ue_notifier.get_rrc_as_config()); |
79 | 0 | notifier.enable_tx_security(security::integrity_enabled::on, security::ciphering_enabled::on, sec_cfg); |
80 | 0 | notifier.enable_rx_security(security::integrity_enabled::on, security::ciphering_enabled::on, sec_cfg); |
81 | 0 | } |
82 | 0 | } else { |
83 | 0 | logger.log_error("Couldn't create {}", msg.srb_id); |
84 | 0 | } |
85 | 0 | } |
86 | | |
87 | | static_vector<srb_id_t, MAX_NOF_SRBS> rrc_ue_impl::get_srbs() |
88 | 0 | { |
89 | 0 | return context.pdcp_manager.get_srb_ids(); |
90 | 0 | } |
91 | | |
92 | | rrc_state rrc_ue_impl::get_rrc_state() const |
93 | 0 | { |
94 | 0 | return context.state; |
95 | 0 | } |
96 | | |
97 | | void rrc_ue_impl::cancel_handover_reconfiguration_transaction(uint8_t transaction_id) |
98 | 0 | { |
99 | 0 | logger.log_debug("Cancelling the ongoing handover reconfiguration transaction"); |
100 | 0 | if (not event_mng->transactions.cancel_transaction(transaction_id)) { |
101 | 0 | logger.log_warning("Unexpected RRC transaction id={}", transaction_id); |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | void rrc_ue_impl::cancel_all_transactions() |
106 | 0 | { |
107 | 0 | logger.log_debug("Cancelling all ongoing RRC transactions"); |
108 | 0 | event_mng->cancel_all(); |
109 | 0 | } |
110 | | |
111 | | void rrc_ue_impl::update_c_rnti(rnti_t crnti) |
112 | 0 | { |
113 | 0 | context.c_rnti = crnti; |
114 | 0 | logger.set_prefix(rrc_ue_log_prefix{context.ue_index, crnti}); |
115 | 0 | } |
116 | | |
117 | | void rrc_ue_impl::update_cell_group_config(byte_buffer cell_group_config) |
118 | 0 | { |
119 | 0 | context.cell_group_config = std::move(cell_group_config); |
120 | 0 | } |
121 | | |
122 | | void rrc_ue_impl::on_new_dl_ccch(const asn1::rrc_nr::dl_ccch_msg_s& dl_ccch_msg) |
123 | 0 | { |
124 | 0 | send_dl_ccch(dl_ccch_msg); |
125 | 0 | } |
126 | | |
127 | | void rrc_ue_impl::on_new_dl_dcch(srb_id_t srb_id, const asn1::rrc_nr::dl_dcch_msg_s& dl_dcch_msg) |
128 | 0 | { |
129 | 0 | send_dl_dcch(srb_id, dl_dcch_msg); |
130 | 0 | } |
131 | | |
132 | | void rrc_ue_impl::on_new_as_security_context(bool security_mode_active) |
133 | 0 | { |
134 | 0 | ocudu_sanity_check(context.pdcp_manager.has_srb(srb_id_t::srb1), |
135 | 0 | "Attempted to configure security, but there is no interface to PDCP"); |
136 | |
|
137 | 0 | security::sec_128_as_config sec_cfg = cu_cp_ue_notifier.get_rrc_128_as_config(); |
138 | 0 | rrc_ue_pdcp_notifier& srb1 = *context.get_pdcp_notifier(srb_id_t::srb1); |
139 | 0 | srb1.enable_rx_security(security_mode_active ? security::integrity_enabled::on |
140 | 0 | : security::integrity_enabled::smc_transition, |
141 | 0 | security::ciphering_enabled::off, |
142 | 0 | sec_cfg); |
143 | 0 | srb1.enable_tx_security(security::integrity_enabled::on, security::ciphering_enabled::off, sec_cfg); |
144 | 0 | } |
145 | | |
146 | | // Builds the UE's current radio bearer configuration (all active DRBs across all PDU sessions) from the UP |
147 | | // context. |
148 | | static rrc_radio_bearer_config build_source_radio_bearer_config(const up_context& up_ctxt) |
149 | 0 | { |
150 | 0 | rrc_radio_bearer_config radio_bearer_config; |
151 | 0 | for (const auto& [psi, pdu_session_ctxt] : up_ctxt.pdu_sessions) { |
152 | 0 | for (const auto& [drb_id, drb_ctxt] : pdu_session_ctxt.drbs) { |
153 | 0 | rrc_drb_to_add_mod drb_to_add_mod; |
154 | 0 | drb_to_add_mod.drb_id = drb_id; |
155 | 0 | drb_to_add_mod.pdcp_cfg = drb_ctxt.pdcp_cfg; |
156 | |
|
157 | 0 | rrc_cn_assoc cn_assoc; |
158 | 0 | cn_assoc.sdap_cfg = drb_ctxt.sdap_cfg; |
159 | 0 | drb_to_add_mod.cn_assoc = cn_assoc; |
160 | |
|
161 | 0 | radio_bearer_config.drb_to_add_mod_list.emplace(drb_id, drb_to_add_mod); |
162 | 0 | } |
163 | 0 | } |
164 | | |
165 | | // TODO: Fill SRB config and security config. |
166 | |
|
167 | 0 | return radio_bearer_config; |
168 | 0 | } |
169 | | |
170 | | byte_buffer rrc_ue_impl::get_packed_handover_preparation_message() |
171 | 0 | { |
172 | 0 | struct ho_prep_info_s ho_prep; |
173 | 0 | ho_prep_info_ies_s& ies = ho_prep.crit_exts.set_c1().set_ho_prep_info(); |
174 | |
|
175 | 0 | if (not context.capabilities_list.has_value()) { |
176 | 0 | logger.log_error("No UE capabilities stored. Handover preparation message can't be generated"); |
177 | | // No capabilities present, return empty buffer. |
178 | 0 | return {}; |
179 | 0 | } |
180 | 0 | ies.ue_cap_rat_list = *context.capabilities_list; |
181 | | |
182 | | // Fill AS-Config with the UE's current radio bearer configuration. TS 38.331 Section 11.2.3 mandates that the |
183 | | // RRCReconfiguration embedded here reflects the UE's complete AS configuration, not a delta relative to prior |
184 | | // signalling, so it is built from the UP context rather than from any previously sent RRCReconfiguration. |
185 | 0 | rrc_radio_bearer_config source_radio_bearer_cfg = |
186 | 0 | build_source_radio_bearer_config(cu_cp_notifier.on_up_context_required()); |
187 | 0 | if (!source_radio_bearer_cfg.drb_to_add_mod_list.empty()) { |
188 | 0 | ies.source_cfg_present = true; |
189 | 0 | rrc_reconfiguration_procedure_request source_cfg_request; |
190 | 0 | source_cfg_request.radio_bearer_cfg = source_radio_bearer_cfg; |
191 | 0 | rrc_recfg_s source_recfg; |
192 | 0 | fill_asn1_rrc_reconfiguration_msg(source_recfg, 0, source_cfg_request); |
193 | 0 | ies.source_cfg.rrc_recfg = pack_into_pdu(source_recfg, "AS-Config RRCReconfiguration"); |
194 | 0 | } |
195 | | |
196 | | // TODO: Fill measurement configuration and MCG/SCG. |
197 | |
|
198 | 0 | return pack_into_pdu(ho_prep, "handover preparation info"); |
199 | 0 | } |
200 | | |
201 | | void rrc_ue_impl::on_ue_release_required(const ngap_cause_t& cause) |
202 | 0 | { |
203 | 0 | cu_cp_ue_notifier.schedule_async_task(launch_async([this, cause](coro_context<async_task<void>>& ctx) mutable { |
204 | 0 | CORO_BEGIN(ctx); |
205 | |
|
206 | 0 | CORO_AWAIT(cu_cp_notifier.on_ue_release_required({context.ue_index, {}, cause})); |
207 | |
|
208 | 0 | CORO_RETURN(); |
209 | 0 | })); |
210 | 0 | } |