Coverage Report

Created: 2026-09-14 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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)] = &notifier;
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
}