Coverage Report

Created: 2026-09-14 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ocudu/lib/xnap/xnap_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 "xnap_impl.h"
6
#include "log_helpers.h"
7
#include "procedures/ngran_node_cfg_update_procedure.h"
8
#include "procedures/retrieve_ue_context_asn1_helpers.h"
9
#include "procedures/sn_status_transfer_asn1_helpers.h"
10
#include "procedures/xn_handover_asn1_helpers.h"
11
#include "procedures/xn_setup_asn1_helpers.h"
12
#include "procedures/xn_setup_procedure.h"
13
#include "procedures/xn_setup_procedure_asn1_helpers.h"
14
#include "procedures/xnap_new_node_retrieve_ue_context_procedure.h"
15
#include "procedures/xnap_old_node_retrieve_ue_context_procedure.h"
16
#include "procedures/xnap_sn_status_transfer_procedure.h"
17
#include "procedures/xnap_source_handover_preparation_procedure.h"
18
#include "procedures/xnap_target_handover_preparation_procedure.h"
19
#include "xnap_asn1_converters.h"
20
#include "xnap_asn1_utils.h"
21
#include "ocudu/adt/scope_exit.h"
22
#include "ocudu/asn1/xnap/common.h"
23
#include "ocudu/asn1/xnap/xnap.h"
24
#include "ocudu/asn1/xnap/xnap_ies.h"
25
#include "ocudu/asn1/xnap/xnap_pdu_contents.h"
26
#include "ocudu/support/async/async_no_op_task.h"
27
#include "ocudu/xnap/xnap_message.h"
28
#include "ocudu/xnap/xnap_types.h"
29
#include <algorithm>
30
#include <vector>
31
32
using namespace ocudu;
33
using namespace asn1::xnap;
34
using namespace ocucp;
35
36
xnap_impl::xnap_impl(xnc_peer_index_t          xnc_index_,
37
                     const xnap_configuration& xnap_cfg_,
38
                     xnap_cu_cp_notifier&      cu_cp_notifier_,
39
                     timer_manager&            timers_,
40
                     task_executor&            ctrl_exec_) :
41
0
  logger(ocudulog::fetch_basic_logger("XNAP")),
42
0
  ue_ctxt_list(timers_, ctrl_exec_, logger),
43
0
  xnc_index(xnc_index_),
44
0
  xnap_cfg(xnap_cfg_),
45
0
  cu_cp_notifier(cu_cp_notifier_),
46
0
  timers(timers_),
47
0
  ctrl_exec(ctrl_exec_),
48
0
  xn_setup_outcome(timer_factory{timers, ctrl_exec}),
49
0
  cfg_update_outcome(timer_factory{timers, ctrl_exec})
50
0
{
51
0
}
52
53
async_task<void> xnap_impl::stop()
54
0
{
55
  // Stop XN setup procedure if in progress.
56
0
  xn_setup_outcome.stop();
57
58
  // Stop NG-RAN node configuration update procedure if in progress.
59
0
  cfg_update_outcome.stop();
60
61
  // Cancel pending per-UE transactions (e.g. Handover Preparation, SN Status Transfer).
62
0
  ue_ctxt_list.cancel_all_transactions();
63
64
0
  if (nof_ue_procedures == 0) {
65
0
    return launch_no_op_task();
66
0
  }
67
68
  // Await the UE-associated procedures that are suspended on a CU-CP notifier. Cancelling the XNAP transactions
69
  // does not resume those, and they access this XNAP instance once they do resume. The caller removes this
70
  // instance as soon as this task completes.
71
0
  logger.debug("Awaiting the completion of {} UE procedures before stopping XNAP", nof_ue_procedures);
72
0
  return launch_async([this](coro_context<async_task<void>>& ctx) {
73
0
    CORO_BEGIN(ctx);
74
75
0
    while (nof_ue_procedures != 0) {
76
0
      ue_procedures_done.reset();
77
0
      CORO_AWAIT(ue_procedures_done);
78
0
    }
79
80
0
    CORO_RETURN();
81
0
  });
82
0
}
83
84
void xnap_impl::handle_message(const xnap_message& msg)
85
0
{
86
  // Run XNAP protocols in Control executor.
87
0
  if (not ctrl_exec.execute([this, msg]() {
88
0
        log_xnap_pdu(logger, logger.debug.enabled(), true, msg.pdu);
89
0
        switch (msg.pdu.type().value) {
90
0
          case xn_ap_pdu_c::types_opts::init_msg:
91
0
            handle_initiating_message(msg.pdu.init_msg());
92
0
            break;
93
0
          case xn_ap_pdu_c::types_opts::successful_outcome:
94
0
            handle_successful_outcome(msg.pdu.successful_outcome());
95
0
            break;
96
0
          case xn_ap_pdu_c::types_opts::unsuccessful_outcome:
97
0
            handle_unsuccessful_outcome(msg.pdu.unsuccessful_outcome());
98
0
            break;
99
0
          default:
100
0
            logger.error("Invalid PDU type");
101
0
            break;
102
0
        }
103
0
      })) {
104
0
    logger.error("Discarding Rx XNAP PDU. Cause: task queue is full");
105
0
  }
106
0
}
107
108
void xnap_impl::remove_ue_context(cu_cp_ue_index_t ue_index)
109
0
{
110
0
  if (!ue_ctxt_list.contains(ue_index)) {
111
0
    logger.debug("ue={}: UE context not found", ue_index);
112
0
    return;
113
0
  }
114
115
0
  ue_ctxt_list.remove_ue_context(ue_index);
116
0
}
117
118
void xnap_impl::handle_initiating_message(const init_msg_s& msg)
119
0
{
120
0
  switch (msg.value.type().value) {
121
0
    case xnap_elem_procs_o::init_msg_c::types_opts::xn_setup_request:
122
0
      handle_xn_setup_request(msg.value.xn_setup_request());
123
0
      break;
124
0
    case xnap_elem_procs_o::init_msg_c::types_opts::ngran_node_cfg_upd:
125
0
      handle_ngran_node_cfg_update(msg.value.ngran_node_cfg_upd());
126
0
      break;
127
0
    case xnap_elem_procs_o::init_msg_c::types_opts::ho_request:
128
0
      handle_handover_request(msg.value.ho_request());
129
0
      break;
130
0
    case xnap_elem_procs_o::init_msg_c::types_opts::ho_cancel:
131
0
      handle_handover_cancel(msg.value.ho_cancel());
132
0
      break;
133
0
    case xnap_elem_procs_o::init_msg_c::types_opts::sn_status_transfer:
134
0
      handle_sn_status_transfer(msg.value.sn_status_transfer());
135
0
      break;
136
0
    case xnap_elem_procs_o::init_msg_c::types_opts::ue_context_release:
137
0
      handle_ue_context_release(msg.value.ue_context_release());
138
0
      break;
139
0
    case xnap_elem_procs_o::init_msg_c::types_opts::ho_success:
140
0
      handle_handover_success(msg.value.ho_success());
141
0
      break;
142
0
    case xnap_elem_procs_o::init_msg_c::types_opts::conditional_ho_cancel:
143
0
      handle_conditional_ho_cancel(msg.value.conditional_ho_cancel());
144
0
      break;
145
0
    case xnap_elem_procs_o::init_msg_c::types_opts::retrieve_ue_context_request:
146
0
      handle_retrieve_ue_context_request(msg.value.retrieve_ue_context_request());
147
0
      break;
148
0
    default:
149
0
      logger.error("Initiating message of type {} is not supported", msg.value.type().to_string());
150
0
  }
151
0
}
152
153
void xnap_impl::handle_successful_outcome(const successful_outcome_s& outcome)
154
0
{
155
0
  switch (outcome.value.type().value) {
156
0
    case xnap_elem_procs_o::successful_outcome_c::types_opts::xn_setup_resp: {
157
0
      xn_setup_outcome.set(outcome.value.xn_setup_resp());
158
0
    } break;
159
0
    case xnap_elem_procs_o::successful_outcome_c::types_opts::ngran_node_cfg_upd_ack: {
160
0
      cfg_update_outcome.set(outcome.value.ngran_node_cfg_upd_ack());
161
0
    } break;
162
0
    case xnap_elem_procs_o::successful_outcome_c::types_opts::ho_request_ack: {
163
0
      auto* ue_ctxt = asn1_utils::get_ue_ctxt_in_ue_assoc_msg(outcome, ue_ctxt_list, logger);
164
0
      if (ue_ctxt == nullptr) {
165
0
        break;
166
0
      }
167
0
      const ho_request_ack_s& ack = outcome.value.ho_request_ack();
168
      // TS 38.423 Section 8.2.1.2: for a conditional handover the target shall echo the requested target cell, which
169
      // tells parallel preparations sharing a Source NG-RAN node UE XnAP ID apart.
170
0
      std::optional<nr_cell_global_id_t> cell;
171
0
      if (ack->ch_oinfo_ack_present) {
172
0
        if (ack->ch_oinfo_ack.requested_target_cell_global_id.type() != target_cgi_c::types_opts::nr) {
173
0
          ue_ctxt->logger.log_warning(
174
0
              "Discarding HandoverRequestAcknowledge: Requested Target Cell Global ID is not an NR cell");
175
0
          break;
176
0
        }
177
0
        cell = asn1_to_cgi(ack->ch_oinfo_ack.requested_target_cell_global_id.nr());
178
0
      }
179
0
      if (!ue_ctxt->deliver_ho_prep_outcome(cell, ack)) {
180
0
        ue_ctxt->logger.log_warning("Discarding HandoverRequestAcknowledge: no matching handover preparation");
181
0
      }
182
0
    } break;
183
0
    case xnap_elem_procs_o::successful_outcome_c::types_opts::retrieve_ue_context_resp: {
184
0
      if (auto* ue_ctxt = asn1_utils::get_ue_ctxt_in_ue_assoc_msg(outcome, ue_ctxt_list, logger)) {
185
0
        ue_ctxt->retrieve_ue_context_outcome.set(outcome.value.retrieve_ue_context_resp());
186
0
      }
187
0
    } break;
188
0
    default:
189
0
      logger.error("Successful outcome of type {} is not supported", outcome.value.type().to_string());
190
0
  }
191
0
}
192
193
void xnap_impl::handle_unsuccessful_outcome(const unsuccessful_outcome_s& outcome)
194
0
{
195
0
  switch (outcome.value.type().value) {
196
0
    case xnap_elem_procs_o::unsuccessful_outcome_c::types_opts::xn_setup_fail: {
197
0
      xn_setup_outcome.set(outcome.value.xn_setup_fail());
198
0
    } break;
199
0
    case xnap_elem_procs_o::unsuccessful_outcome_c::types_opts::ngran_node_cfg_upd_fail: {
200
0
      cfg_update_outcome.set(outcome.value.ngran_node_cfg_upd_fail());
201
0
    } break;
202
0
    case xnap_elem_procs_o::unsuccessful_outcome_c::types_opts::ho_prep_fail: {
203
0
      auto* ue_ctxt = asn1_utils::get_ue_ctxt_in_ue_assoc_msg(outcome, ue_ctxt_list, logger);
204
0
      if (ue_ctxt == nullptr) {
205
0
        break;
206
0
      }
207
0
      const ho_prep_fail_s& fail = outcome.value.ho_prep_fail();
208
      // TS 38.423 Section 8.2.1.3: the failure carries the requested target cell when it rejects one of several
209
      // parallel CHO preparations. The IE is optional, so it may have to be routed without one.
210
0
      std::optional<nr_cell_global_id_t> cell;
211
0
      if (fail->requested_target_cell_global_id_present) {
212
0
        if (fail->requested_target_cell_global_id.type() != target_cgi_c::types_opts::nr) {
213
          // The named cell cannot be one we prepared, so do not let it resolve an unrelated transaction.
214
0
          ue_ctxt->logger.log_warning(
215
0
              "Discarding HandoverPreparationFailure: Requested Target Cell Global ID is not an NR cell");
216
0
          break;
217
0
        }
218
0
        cell = asn1_to_cgi(fail->requested_target_cell_global_id.nr());
219
0
      }
220
0
      if (!ue_ctxt->deliver_ho_prep_outcome(cell, fail)) {
221
0
        ue_ctxt->logger.log_warning("Discarding HandoverPreparationFailure: no matching handover preparation");
222
0
      }
223
0
    } break;
224
0
    case xnap_elem_procs_o::unsuccessful_outcome_c::types_opts::retrieve_ue_context_fail: {
225
0
      if (auto* ue_ctxt = asn1_utils::get_ue_ctxt_in_ue_assoc_msg(outcome, ue_ctxt_list, logger)) {
226
0
        ue_ctxt->retrieve_ue_context_outcome.set(outcome.value.retrieve_ue_context_fail());
227
0
      }
228
0
    } break;
229
0
    default:
230
0
      logger.error("Unsuccessful outcome of type {} is not supported", outcome.value.type().to_string());
231
0
  }
232
0
}
233
234
async_task<bool> xnap_impl::handle_xn_setup_request_required()
235
0
{
236
0
  advertised_cells = cu_cp_notifier.on_served_cells_required();
237
238
0
  return launch_async<xn_setup_procedure>(
239
0
      xnap_cfg, advertised_cells, peer_ctxt, tx_notifier, xn_setup_outcome, timer_factory{timers, ctrl_exec}, logger);
240
0
}
241
242
async_task<bool> xnap_impl::handle_served_cells_update_required()
243
0
{
244
0
  return launch_async<ngran_node_cfg_update_procedure>(xnap_cfg,
245
0
                                                       cu_cp_notifier.on_served_cells_required(),
246
0
                                                       advertised_cells,
247
0
                                                       peer_ctxt,
248
0
                                                       tx_notifier,
249
0
                                                       cfg_update_outcome,
250
0
                                                       logger);
251
0
}
252
253
void xnap_impl::handle_ngran_node_cfg_update(const ngran_node_cfg_upd_s& msg)
254
0
{
255
0
  if (not peer_ctxt.has_value()) {
256
0
    logger.warning("Rejecting NGRANNodeConfigurationUpdate. Cause: XN setup has not been completed");
257
0
    if (not tx_notifier.on_new_message(
258
0
            generate_asn1_ngran_node_cfg_update_failure(cause_protocol_t::msg_not_compatible_with_receiver_state))) {
259
0
      logger.error("Failed to send NGRANNodeConfigurationUpdateFailure. Cause: no SCTP association available");
260
0
    }
261
0
    return;
262
0
  }
263
264
0
  const auto& asn1_init_node_choice = msg->cfg_upd_init_node_choice;
265
0
  if (asn1_init_node_choice.type() == cfg_upd_init_node_choice_c::types_opts::gnb and
266
0
      asn1_init_node_choice.gnb().served_cells_to_upd_nr_present) {
267
0
    update_peer_served_cells(peer_ctxt->list_of_served_cells_nr, asn1_init_node_choice.gnb().served_cells_to_upd_nr);
268
0
    logger.info("Xn-C peer serves {} cell(s)", peer_ctxt->list_of_served_cells_nr.size());
269
0
  }
270
271
0
  if (not tx_notifier.on_new_message(generate_asn1_ngran_node_cfg_update_ack())) {
272
0
    logger.error("Failed to send NGRANNodeConfigurationUpdateAcknowledge. Cause: no SCTP association available");
273
0
  }
274
0
}
275
276
void xnap_impl::handle_xn_setup_request(const xn_setup_request_s& request)
277
0
{
278
0
  xnap_message xn_setup_result;
279
280
  // Message content validation.
281
0
  auto msgerr = validate_xn_setup_request_response(request);
282
0
  if (not msgerr.has_value()) {
283
0
    logger.warning("Rejecting XN Setup Request. Cause: {}", msgerr.error().second);
284
0
    xn_setup_result = generate_asn1_xn_setup_failure(asn1_to_cause(msgerr.error().first));
285
0
  } else {
286
    // Store peer context information.
287
0
    peer_ctxt = create_peer_xnap_context(request);
288
    // Generate XN Setup Response.
289
0
    advertised_cells = cu_cp_notifier.on_served_cells_required();
290
0
    xn_setup_result  = generate_asn1_xn_setup_response(xnap_cfg, advertised_cells);
291
0
  }
292
293
  // Transmit XN Setup Response/Failure.
294
0
  if (not tx_notifier.on_new_message(xn_setup_result)) {
295
0
    logger.error("Failed to send XN Setup Response. Cause: no SCTP association available");
296
0
  }
297
0
}
298
299
void xnap_impl::handle_handover_request(const asn1::xnap::ho_request_s& msg)
300
0
{
301
  // Add lambda that generates and transmits Handover Preparation Failure message.
302
0
  auto send_handover_failure = [this](uint64_t peer_xnap_ue_id, xnap_cause_t cause) {
303
0
    xnap_message xnap_msg;
304
0
    xnap_msg.pdu.set_unsuccessful_outcome();
305
0
    xnap_msg.pdu.unsuccessful_outcome().load_info_obj(ASN1_XNAP_ID_HO_PREP);
306
0
    auto& ho_fail = xnap_msg.pdu.unsuccessful_outcome().value.ho_prep_fail();
307
    // This is sent from the target to the source, so the peer XNAP UE ID is the source UE ID.
308
0
    ho_fail->source_ng_ra_nnode_ue_xn_ap_id = peer_xnap_ue_id;
309
0
    ho_fail->cause                          = cause_to_asn1(cause);
310
311
0
    if (!tx_notifier.on_new_message(xnap_msg)) {
312
0
      logger.warning("Xn-C association is not set. Cannot send HandoverFailure");
313
0
      return;
314
0
    }
315
0
    logger.warning("Sending HandoverFailure");
316
0
  };
317
318
  // Convert Handover Request to common type.
319
0
  xnap_handover_request ho_request;
320
0
  if (!asn1_to_handover_request(ho_request, msg)) {
321
0
    logger.info("Received invalid HandoverRequest");
322
0
    send_handover_failure(msg->source_ng_ra_nnode_ue_xn_ap_id,
323
0
                          cause_protocol_t::abstract_syntax_error_falsely_constructed_msg);
324
325
0
    return;
326
0
  }
327
328
0
  logger.info("HandoverRequest - extracted target cell. plmn={}, target cell_id=0x{:x}",
329
0
              ho_request.nr_cgi.plmn_id,
330
0
              ho_request.nr_cgi.nci);
331
332
  // Create UE in target cell.
333
0
  ho_request.ue_index = cu_cp_notifier.request_new_ue_index_allocation(ho_request.nr_cgi, ho_request.guami.plmn);
334
0
  if (ho_request.ue_index == cu_cp_ue_index_t::invalid) {
335
0
    logger.debug("Couldn't allocate UE index for handover target cell");
336
0
    send_handover_failure(msg->source_ng_ra_nnode_ue_xn_ap_id, xnap_cause_misc_t::not_enough_user_plane_processing_res);
337
0
    return;
338
0
  }
339
340
  // Inititialize security context of target UE.
341
0
  if (!cu_cp_notifier.on_handover_request_received(
342
0
          ho_request.ue_index, ho_request.guami.plmn, ho_request.ue_context_info_ho_request.security_context)) {
343
0
    logger.debug("Failed to initialize security context for UE index {}. Rejecting handover request",
344
0
                 ho_request.ue_index);
345
0
    send_handover_failure(msg->source_ng_ra_nnode_ue_xn_ap_id, xnap_cause_misc_t::not_enough_user_plane_processing_res);
346
0
    return;
347
0
  }
348
349
0
  if (!cu_cp_notifier.schedule_async_task(ho_request.ue_index,
350
0
                                          track_ue_procedure(launch_async<xnap_target_handover_preparation_procedure>(
351
0
                                              ho_request,
352
0
                                              xnc_index,
353
0
                                              uint_to_peer_xnap_ue_id(msg->source_ng_ra_nnode_ue_xn_ap_id),
354
0
                                              ue_ctxt_list,
355
0
                                              cu_cp_notifier,
356
0
                                              tx_notifier,
357
0
                                              logger)))) {
358
0
    logger.debug("Couldn't schedule targer handover preparation procedure");
359
0
    send_handover_failure(msg->source_ng_ra_nnode_ue_xn_ap_id, xnap_cause_misc_t::not_enough_user_plane_processing_res);
360
0
    return;
361
0
  }
362
0
}
363
364
void xnap_impl::handle_handover_cancel(const asn1::xnap::ho_cancel_s& msg)
365
0
{
366
  // TS 38.423 Section 8.2.3.2: the cancelled handover is the one on the signalling connection identified by the Source
367
  // NG-RAN node UE XnAP ID and, if included, also by the Target NG-RAN node UE XnAP ID, restricted to the candidate
368
  // cells of the Candidate Cells To Be Cancelled List when that IE is present. Parallel CHO preparations from one
369
  // source share the source ID, so it alone does not identify a context here.
370
  // This is sent from the source to the target, so the source XNAP UE ID is the peer UE ID.
371
0
  const peer_xnap_ue_id_t peer_xnap_ue_id = uint_to_peer_xnap_ue_id(msg->source_ng_ra_nnode_ue_xn_ap_id);
372
373
  // Releases one prepared candidate: the CU-CP drops its UE, then the local XNAP UE context goes.
374
0
  auto release = [this](xnap_ue_context& ue_ctxt) {
375
0
    const cu_cp_ue_index_t ue_index = ue_ctxt.ue_ids.ue_index;
376
0
    cu_cp_notifier.on_handover_cancel_received(ue_index);
377
0
    ue_ctxt_list.remove_ue_context(ue_index);
378
0
  };
379
380
0
  if (msg->target_ng_ra_nnode_ue_xn_ap_id_present) {
381
0
    xnap_ue_context* ue_ctxt = ue_ctxt_list.find(uint_to_local_xnap_ue_id(msg->target_ng_ra_nnode_ue_xn_ap_id));
382
    // Both IDs name the same signalling connection, so a context whose source ID differs is not the addressed one.
383
0
    if (ue_ctxt != nullptr and ue_ctxt->ue_ids.peer_xnap_ue_id != peer_xnap_ue_id) {
384
0
      logger.info("Discarding HandoverCancel: peer_xnap_ue_id={} does not match local_xnap_ue_id={}",
385
0
                  msg->source_ng_ra_nnode_ue_xn_ap_id,
386
0
                  msg->target_ng_ra_nnode_ue_xn_ap_id);
387
0
      return;
388
0
    }
389
0
    if (ue_ctxt == nullptr) {
390
0
      logger.info("Received HandoverCancel for unknown UE. peer_xnap_ue_id={}", msg->source_ng_ra_nnode_ue_xn_ap_id);
391
0
      return;
392
0
    }
393
    // Section 8.2.3.2 scopes the cancellation to the listed cells as well as to the connection the IDs name, so a
394
    // list that does not mention this context's cell is not cancelling it.
395
0
    if (msg->target_cells_to_cancel.size() != 0 and ue_ctxt->ho_target_cell.has_value()) {
396
0
      const bool names_this_cell = std::any_of(msg->target_cells_to_cancel.begin(),
397
0
                                               msg->target_cells_to_cancel.end(),
398
0
                                               [&ue_ctxt](const asn1::xnap::target_cell_list_item_s& item) {
399
0
                                                 return item.target_cell.type() == target_cgi_c::types_opts::nr and
400
0
                                                        asn1_to_cgi(item.target_cell.nr()) == *ue_ctxt->ho_target_cell;
401
0
                                               });
402
0
      if (not names_this_cell) {
403
0
        logger.info("Discarding HandoverCancel: the candidate cell list does not name local_xnap_ue_id={}'s cell",
404
0
                    msg->target_ng_ra_nnode_ue_xn_ap_id);
405
0
        return;
406
0
      }
407
0
    }
408
0
    release(*ue_ctxt);
409
0
    return;
410
0
  }
411
412
  // Without the target ID the named candidate cells are what tell parallel CHO preparations apart. Every named cell
413
  // is cancelled, and only those: Section 8.2.3.2 scopes the procedure to the cells the list identifies.
414
0
  bool named_a_cell = false;
415
0
  for (const auto& item : msg->target_cells_to_cancel) {
416
0
    if (item.target_cell.type() != target_cgi_c::types_opts::nr) {
417
0
      continue;
418
0
    }
419
0
    named_a_cell = true;
420
0
    xnap_ue_context* cell_ctxt =
421
0
        ue_ctxt_list.find_by_peer_and_cell(peer_xnap_ue_id, asn1_to_cgi(item.target_cell.nr()));
422
0
    if (cell_ctxt == nullptr) {
423
0
      logger.info("HandoverCancel names a cell with no prepared UE. peer_xnap_ue_id={}",
424
0
                  msg->source_ng_ra_nnode_ue_xn_ap_id);
425
0
      continue;
426
0
    }
427
0
    release(*cell_ctxt);
428
0
  }
429
0
  if (named_a_cell) {
430
0
    return;
431
0
  }
432
433
  // Neither the target ID nor a candidate cell: the whole UE-associated signalling connection is cancelled.
434
0
  xnap_ue_context* ue_ctxt = ue_ctxt_list.find(peer_xnap_ue_id);
435
0
  if (ue_ctxt == nullptr) {
436
0
    logger.info("Received HandoverCancel for unknown UE. peer_xnap_ue_id={}", msg->source_ng_ra_nnode_ue_xn_ap_id);
437
0
    return;
438
0
  }
439
0
  release(*ue_ctxt);
440
0
}
441
442
void xnap_impl::handle_sn_status_transfer(const asn1::xnap::sn_status_transfer_s& msg)
443
0
{
444
  // This is sent from the source to the target, so the target XNAP UE ID is the local UE ID. Address the context by it:
445
  // parallel CHO preparations from one source share the Source NG-RAN node UE XnAP ID, so it does not identify a single
446
  // context here (TS 38.423 Section 9.1.1.4, the Target NG-RAN node UE XnAP ID is mandatory and allocated by us).
447
0
  const local_xnap_ue_id_t local_xnap_ue_id = uint_to_local_xnap_ue_id(msg->target_ng_ra_nnode_ue_xn_ap_id);
448
449
0
  if (!ue_ctxt_list.contains(local_xnap_ue_id)) {
450
0
    logger.warning("peer_xnap_ue={} local_xnap_ue={}: Dropping SNStatusTransfer. UE context does not exist",
451
0
                   msg->source_ng_ra_nnode_ue_xn_ap_id,
452
0
                   msg->target_ng_ra_nnode_ue_xn_ap_id);
453
0
    return;
454
0
  }
455
456
0
  xnap_ue_context& ue_ctxt = ue_ctxt_list[local_xnap_ue_id];
457
458
  // Both IDs name the same signalling connection, so a context whose source ID differs is not the addressed one.
459
0
  if (ue_ctxt.ue_ids.peer_xnap_ue_id != uint_to_peer_xnap_ue_id(msg->source_ng_ra_nnode_ue_xn_ap_id)) {
460
0
    logger.warning("peer_xnap_ue={} local_xnap_ue={}: Dropping SNStatusTransfer. The IDs name different contexts",
461
0
                   msg->source_ng_ra_nnode_ue_xn_ap_id,
462
0
                   msg->target_ng_ra_nnode_ue_xn_ap_id);
463
0
    return;
464
0
  }
465
466
0
  ue_ctxt.sn_status_transfer_outcome.set(msg);
467
0
}
468
469
void xnap_impl::handle_ue_context_release(const asn1::xnap::ue_context_release_s& msg)
470
0
{
471
  // This is sent from the target to the source, so the source XNAP UE ID is the local UE ID and the target XNAP UE ID
472
  // is the peer UE ID.
473
0
  local_xnap_ue_id_t local_xnap_ue_id = uint_to_local_xnap_ue_id(msg->source_ng_ra_nnode_ue_xn_ap_id);
474
475
0
  if (!ue_ctxt_list.contains(local_xnap_ue_id)) {
476
0
    logger.warning("local_xnap_ue={} peer_xnap_ue={}: Dropping UEContextRelease. UE context does not exist",
477
0
                   msg->source_ng_ra_nnode_ue_xn_ap_id,
478
0
                   msg->target_ng_ra_nnode_ue_xn_ap_id);
479
0
    return;
480
0
  }
481
482
0
  xnap_ue_context& ue_ctxt = ue_ctxt_list[local_xnap_ue_id];
483
484
  // Request CU-CP to release the UE context.
485
0
  cu_cp_notifier.on_ue_context_release_received(ue_ctxt.ue_ids.ue_index);
486
0
}
487
488
async_task<xnap_handover_preparation_response>
489
xnap_impl::handle_handover_request_required(const xnap_handover_request& request)
490
0
{
491
  // TS 38.423 Section 8.2.1.1: all CHO candidate cells served by this peer share one Source NG-RAN node UE XnAP ID,
492
  // hence one UE context, just like the successive handovers of an immediate HO.
493
0
  const local_xnap_ue_id_t local_xnap_ue_id = ue_ctxt_list.find_or_create_ue_context(request.ue_index);
494
0
  if (local_xnap_ue_id == local_xnap_ue_id_t::invalid) {
495
0
    logger.error("Failed to allocate XNAP UE ID for ue={}. Cannot transmit HandoverPreparationRequest",
496
0
                 request.ue_index);
497
0
    return launch_no_op_task(xnap_handover_preparation_response{false});
498
0
  }
499
500
0
  xnap_ue_context& ue_ctxt = ue_ctxt_list[local_xnap_ue_id];
501
502
  // The event source this preparation awaits. A conditional handover gets its own, keyed by target cell, so that
503
  // parallel candidates each await their own outcome; an immediate handover uses the one of the UE context.
504
0
  ho_prep_outcome_t* ho_outcome = &ue_ctxt.xn_handover_outcome;
505
0
  if (request.is_conditional_handover) {
506
0
    ho_outcome = ue_ctxt.add_cho_cell_prep(request.nr_cgi, timer_factory{timers, ctrl_exec});
507
0
    if (ho_outcome == nullptr) {
508
0
      ue_ctxt.logger.log_warning("CHO preparation already in flight for nci={:#x}", request.nr_cgi.nci.value());
509
0
      return launch_no_op_task(xnap_handover_preparation_response{false});
510
0
    }
511
0
    ue_ctxt.logger.log_debug("Starting CHO source preparation for nci={:#x}", request.nr_cgi.nci.value());
512
0
  } else {
513
0
    ue_ctxt.logger.log_debug("Starting HO source preparation");
514
0
  }
515
516
0
  return track_ue_procedure(launch_async<xnap_source_handover_preparation_procedure>(
517
0
      request, ue_ctxt, *ho_outcome, ue_ctxt_list, tx_notifier, cu_cp_notifier, timer_factory{timers, ctrl_exec}));
518
0
}
519
520
void xnap_impl::handle_cho_cancel_required(cu_cp_ue_index_t ue_index, const nr_cell_global_id_t& target_cgi)
521
0
{
522
0
  if (!ue_ctxt_list.contains(ue_index)) {
523
0
    logger.warning("ue={}: Cannot send HandoverCancel (CHO non-winner): UE context not found", ue_index);
524
0
    return;
525
0
  }
526
527
0
  xnap_ue_context&         ue_ctxt          = ue_ctxt_list[ue_index];
528
0
  const local_xnap_ue_id_t local_xnap_ue_id = ue_ctxt.ue_ids.local_xnap_ue_id;
529
0
  const peer_xnap_ue_id_t  peer_ue_id       = ue_ctxt_list.remove_cho_prepared(local_xnap_ue_id, target_cgi);
530
531
0
  if (peer_ue_id == peer_xnap_ue_id_t::invalid) {
532
0
    ue_ctxt.logger.log_warning("HandoverCancel (CHO non-winner) skipped: no candidate prepared for nci={:#x}",
533
0
                               target_cgi.nci.value());
534
0
  } else {
535
    // TS 38.423 Section 8.2.3: source sends HANDOVER CANCEL to release a non-winning prepared context at the target.
536
    // Include TargetCellsToCancel IE to identify the specific candidate cell being cancelled.
537
0
    xnap_message msg = {};
538
0
    msg.pdu.set_init_msg();
539
0
    msg.pdu.init_msg().load_info_obj(ASN1_XNAP_ID_HO_CANCEL);
540
0
    ho_cancel_s& ho_cancel                            = msg.pdu.init_msg().value.ho_cancel();
541
0
    ho_cancel->source_ng_ra_nnode_ue_xn_ap_id         = to_underlying(local_xnap_ue_id);
542
0
    ho_cancel->cause.set_radio_network()              = cause_radio_network_layer_opts::proc_cancelled;
543
0
    ho_cancel->target_ng_ra_nnode_ue_xn_ap_id_present = true;
544
0
    ho_cancel->target_ng_ra_nnode_ue_xn_ap_id         = to_underlying(peer_ue_id);
545
0
    ho_cancel->target_cells_to_cancel_present         = true;
546
0
    asn1::xnap::target_cell_list_item_s cell_item;
547
0
    cell_item.target_cell.set_nr() = cgi_to_asn1(target_cgi);
548
0
    ho_cancel->target_cells_to_cancel.push_back(cell_item);
549
550
0
    if (!tx_notifier.on_new_message(msg)) {
551
0
      ue_ctxt.logger.log_warning("Cannot send HandoverCancel to release non-winning CHO target");
552
0
    } else {
553
0
      ue_ctxt.logger.log_debug("HandoverCancel sent to release non-winning CHO target nci={:#x}",
554
0
                               target_cgi.nci.value());
555
0
    }
556
0
  }
557
558
  // Release the local XNAP UE context once this peer holds no candidate for this UE any more. Other candidates at
559
  // the same peer keep it alive.
560
0
  if (ue_ctxt.cho_idle()) {
561
0
    ue_ctxt_list.remove_ue_context(ue_index);
562
0
  }
563
0
}
564
565
void xnap_impl::handle_handover_success_required(cu_cp_ue_index_t ue_index, const nr_cell_global_id_t& cgi)
566
0
{
567
0
  if (!ue_ctxt_list.contains(ue_index)) {
568
0
    logger.warning("ue={}: Cannot send HandoverSuccess: UE context not found", ue_index);
569
0
    return;
570
0
  }
571
572
0
  xnap_ue_context& ue_ctxt = ue_ctxt_list[ue_index];
573
574
0
  xnap_message xnap_msg = {};
575
0
  xnap_msg.pdu.set_init_msg();
576
0
  xnap_msg.pdu.init_msg().load_info_obj(ASN1_XNAP_ID_HO_SUCCESS);
577
578
0
  ho_success_s& ho_success = xnap_msg.pdu.init_msg().value.ho_success();
579
  // HandoverSuccess: target -> source. source_id is the peer (source) XNAP UE ID; target_id is the local XNAP UE ID.
580
0
  ho_success->source_ng_ra_nnode_ue_xn_ap_id           = to_underlying(ue_ctxt.ue_ids.peer_xnap_ue_id);
581
0
  ho_success->target_ng_ra_nnode_ue_xn_ap_id           = to_underlying(ue_ctxt.ue_ids.local_xnap_ue_id);
582
0
  ho_success->requested_target_cell_global_id.set_nr() = cgi_to_asn1(cgi);
583
584
0
  if (!tx_notifier.on_new_message(xnap_msg)) {
585
0
    ue_ctxt.logger.log_warning("Xn-C association is not set. Cannot send HandoverSuccess");
586
0
    return;
587
0
  }
588
0
}
589
590
void xnap_impl::handle_sn_status_transfer_required(const cu_cp_status_transfer& sn_status_transfer)
591
0
{
592
0
  const cu_cp_ue_index_t ue_index = sn_status_transfer.ue_index;
593
0
  if (!ue_ctxt_list.contains(ue_index)) {
594
0
    logger.warning("ue={}: Cannot transmit SNStatusTransfer. UE context does not exist", ue_index);
595
0
    return;
596
0
  }
597
598
0
  xnap_ue_context& ue_ctxt = ue_ctxt_list[ue_index];
599
600
0
  xnap_message xnap_msg = {};
601
0
  xnap_msg.pdu.set_init_msg();
602
0
  xnap_msg.pdu.init_msg().load_info_obj(ASN1_XNAP_ID_S_N_STATUS_TRANSFER);
603
604
0
  sn_status_transfer_s& asn1_sn_status = xnap_msg.pdu.init_msg().value.sn_status_transfer();
605
  // This is sent from the source to the target, so the source XNAP UE ID is the local UE ID and the target XNAP UE ID
606
  // is the peer UE ID.
607
0
  asn1_sn_status->source_ng_ra_nnode_ue_xn_ap_id = to_underlying(ue_ctxt.ue_ids.local_xnap_ue_id);
608
0
  asn1_sn_status->target_ng_ra_nnode_ue_xn_ap_id = to_underlying(ue_ctxt.ue_ids.peer_xnap_ue_id);
609
610
0
  sn_status_transfer_to_asn1(asn1_sn_status, sn_status_transfer.drbs_subject_to_status_transfer_list);
611
612
  // Forward message to Xn-C peer CU-CP.
613
0
  if (!tx_notifier.on_new_message(xnap_msg)) {
614
0
    ue_ctxt.logger.log_warning("Xn-C association is not set. Cannot send SNStatusTransfer");
615
0
    return;
616
0
  }
617
0
}
618
619
async_task<expected<cu_cp_status_transfer>> xnap_impl::handle_sn_status_transfer_expected(cu_cp_ue_index_t ue_index)
620
0
{
621
0
  if (!ue_ctxt_list.contains(ue_index)) {
622
0
    logger.warning("ue={}: Cannot await SNStatusTransfer. UE context does not exist", ue_index);
623
0
    expected<cu_cp_status_transfer> ret = make_unexpected(default_error_t{});
624
0
    return launch_no_op_task(ret);
625
0
  }
626
627
0
  xnap_ue_context& ue_ctxt = ue_ctxt_list[ue_index];
628
0
  return track_ue_procedure(launch_async<xnap_sn_status_transfer_procedure>(
629
0
      xnap_cfg.procedure_timeout, ue_ctxt.sn_status_transfer_outcome, ue_ctxt.logger));
630
0
}
631
632
void xnap_impl::handle_handover_success(const asn1::xnap::ho_success_s& msg)
633
0
{
634
  // HandoverSuccess is sent from the target to the source. From the source's perspective,
635
  // Source NG-RAN node UE XnAP ID is our own (local) XNAP UE ID set in the HandoverRequest.
636
0
  local_xnap_ue_id_t local_xnap_ue_id = uint_to_local_xnap_ue_id(msg->source_ng_ra_nnode_ue_xn_ap_id);
637
0
  if (!ue_ctxt_list.contains(local_xnap_ue_id)) {
638
0
    logger.warning("Received HandoverSuccess for unknown local_xnap_ue_id={}", local_xnap_ue_id);
639
0
    return;
640
0
  }
641
642
0
  xnap_ue_context& ue_ctxt_ref = ue_ctxt_list[local_xnap_ue_id];
643
644
  // TS 38.423 Section 8.2.8: the Requested Target Cell Global ID names the cell the UE accessed. It is what
645
  // identifies the winning candidate, since XNAP UE IDs are only unique per Xn interface.
646
0
  if (msg->requested_target_cell_global_id.type() != target_cgi_c::types_opts::nr) {
647
0
    ue_ctxt_ref.logger.log_warning("Discarding HandoverSuccess: Requested Target Cell Global ID is not an NR cell");
648
0
    return;
649
0
  }
650
0
  const nr_cell_global_id_t winner_cgi = asn1_to_cgi(msg->requested_target_cell_global_id.nr());
651
652
  // Only a cell we prepared can have won. Adopting the ID first would leave the context addressed by an ID that
653
  // belongs to no candidate if the peer names a stale or unknown cell.
654
0
  if (ue_ctxt_ref.find_cho_prepared(winner_cgi) == peer_xnap_ue_id_t::invalid) {
655
0
    ue_ctxt_ref.logger.log_warning("Discarding HandoverSuccess: no CHO candidate prepared for {}", winner_cgi);
656
0
    return;
657
0
  }
658
659
  // The Target NG-RAN node UE XnAP ID is the winning target's own local ID, so adopt it: the SN Status Transfer that
660
  // follows addresses the UE by it, and the other candidates' IDs are of no further use.
661
0
  ue_ctxt_list.update_peer_xnap_ue_id(local_xnap_ue_id, uint_to_peer_xnap_ue_id(msg->target_ng_ra_nnode_ue_xn_ap_id));
662
663
0
  ue_ctxt_ref.logger.log_debug("CHO winner is {}", winner_cgi);
664
665
  // Notify CU-CP: source must now send SN Status Transfer and release UE context.
666
0
  cu_cp_notifier.on_handover_success_received(ue_ctxt_ref.ue_ids.ue_index, winner_cgi);
667
0
}
668
669
void xnap_impl::handle_conditional_ho_cancel(const asn1::xnap::conditional_ho_cancel_s& msg)
670
0
{
671
  // ConditionalHandoverCancel is sent from TARGET to SOURCE (TS 38.423 Section 8.2.9).
672
  // The target self-cancels prepared CHO contexts it no longer wants to hold.
673
  // Source NG-RAN node UE XnAP ID is our local XNAP UE ID (we allocated it when initiating HO).
674
  // Target NG-RAN node UE XnAP ID is the cancelling target's XNAP UE ID.
675
0
  local_xnap_ue_id_t local_id = uint_to_local_xnap_ue_id(msg->source_ng_ra_nnode_ue_xn_ap_id);
676
0
  if (!ue_ctxt_list.contains(local_id)) {
677
0
    logger.info("Received ConditionalHandoverCancel for unknown local_xnap_ue_id={}",
678
0
                msg->source_ng_ra_nnode_ue_xn_ap_id);
679
0
    return;
680
0
  }
681
682
0
  xnap_ue_context&        ue_ctxt         = ue_ctxt_list[local_id];
683
0
  const peer_xnap_ue_id_t cancelling_peer = uint_to_peer_xnap_ue_id(msg->target_ng_ra_nnode_ue_xn_ap_id);
684
685
  // TS 38.423 Section 8.2.9.2: when the target names cells, only those candidates are released; the remaining ones
686
  // stay prepared under the same UE context. Both lists identify candidates by NG-RAN CGI.
687
0
  std::vector<nr_cell_global_id_t> cancelled_cells;
688
0
  unsigned                         nof_non_nr_cells = 0;
689
0
  for (const auto& item : msg->target_cells_to_cancel) {
690
0
    if (item.target_cell.type() == target_cgi_c::types_opts::nr) {
691
0
      cancelled_cells.push_back(asn1_to_cgi(item.target_cell.nr()));
692
0
    } else {
693
0
      ++nof_non_nr_cells;
694
0
    }
695
0
  }
696
0
  for (const auto& item : msg->conditional_recfg_to_cancel_list) {
697
0
    if (item.pcell_id.type() == target_cgi_c::types_opts::nr) {
698
0
      cancelled_cells.push_back(asn1_to_cgi(item.pcell_id.nr()));
699
0
    } else {
700
0
      ++nof_non_nr_cells;
701
0
    }
702
0
  }
703
0
  if (nof_non_nr_cells != 0) {
704
0
    ue_ctxt.logger.log_warning("ConditionalHandoverCancel names {} cell(s) that are not NR cells; ignoring them",
705
0
                               nof_non_nr_cells);
706
0
  }
707
708
0
  if (!msg->target_cells_to_cancel_present and !msg->conditional_recfg_to_cancel_list_present) {
709
    // Neither list is present: the target withdraws every candidate it prepared for this UE.
710
0
    for (const auto& [cell, peer_xnap_ue_id] : ue_ctxt.cho_prepared) {
711
0
      if (peer_xnap_ue_id == cancelling_peer) {
712
0
        cancelled_cells.push_back(cell);
713
0
      }
714
0
    }
715
0
  }
716
717
0
  for (const nr_cell_global_id_t& cell : cancelled_cells) {
718
    // TS 38.423 Section 8.2.9.4: candidate cells that were not prepared using this UE-associated signalling
719
    // connection are ignored. The connection is named by both XNAP UE IDs (Section 8.2.9.2), so a cell prepared
720
    // under another Target NG-RAN node UE XnAP ID is not this sender's to cancel.
721
0
    const peer_xnap_ue_id_t prepared_peer = ue_ctxt.find_cho_prepared(cell);
722
0
    if (prepared_peer == peer_xnap_ue_id_t::invalid) {
723
0
      ue_ctxt.logger.log_info("ConditionalHandoverCancel names nci={:#x}, which is not prepared", cell.nci.value());
724
0
      continue;
725
0
    }
726
0
    if (prepared_peer != cancelling_peer) {
727
0
      ue_ctxt.logger.log_info("Ignoring nci={:#x} in ConditionalHandoverCancel: prepared for peer_xnap_ue={}, not {}",
728
0
                              cell.nci.value(),
729
0
                              fmt::underlying(prepared_peer),
730
0
                              fmt::underlying(cancelling_peer));
731
0
      continue;
732
0
    }
733
    // The peer XNAP UE ID lookup is released from the candidate's own record, never from the ID carried by this
734
    // message, so a stale or misaddressed one cannot unregister an unrelated UE.
735
0
    ue_ctxt_list.remove_cho_prepared(local_id, cell);
736
0
  }
737
738
  // Release the XNAP UE context once this peer holds no candidate for this UE any more.
739
0
  if (ue_ctxt.cho_idle()) {
740
0
    ue_ctxt_list.remove_ue_context(ue_ctxt.ue_ids.ue_index);
741
0
  }
742
0
}
743
744
bool xnap_impl::handle_ue_context_release_required(cu_cp_ue_index_t ue_index)
745
0
{
746
0
  if (!ue_ctxt_list.contains(ue_index)) {
747
0
    logger.warning("ue={}: Cannot transmit UEContextReleaseRequest. UE context does not exist", ue_index);
748
0
    return false;
749
0
  }
750
751
0
  xnap_ue_context& ue_ctxt = ue_ctxt_list[ue_index];
752
753
0
  xnap_message xnap_msg = {};
754
0
  xnap_msg.pdu.set_init_msg();
755
0
  xnap_msg.pdu.init_msg().load_info_obj(ASN1_XNAP_ID_U_E_CONTEXT_RELEASE);
756
757
0
  ue_context_release_s& ue_ctxt_release = xnap_msg.pdu.init_msg().value.ue_context_release();
758
  // This is sent from the target to the source, so the local XNAP UE ID is the target and the peer XNAP UE ID is the
759
  // source.
760
0
  ue_ctxt_release->source_ng_ra_nnode_ue_xn_ap_id = to_underlying(ue_ctxt.ue_ids.peer_xnap_ue_id);
761
0
  ue_ctxt_release->target_ng_ra_nnode_ue_xn_ap_id = to_underlying(ue_ctxt.ue_ids.local_xnap_ue_id);
762
763
  // Forward message to Xn-C peer CU-CP.
764
0
  if (!tx_notifier.on_new_message(xnap_msg)) {
765
0
    ue_ctxt.logger.log_warning("Xn-C association is not set. Cannot send UEContextReleaseRequest");
766
0
    return false;
767
0
  }
768
769
  // Remove UE context locally as well, as no further messages for this UE are expected.
770
0
  ue_ctxt_list.remove_ue_context(ue_index);
771
772
0
  return true;
773
0
}
774
775
std::optional<cu_cp_served_cell_info> xnap_impl::find_peer_served_cell(nr_cell_identity nci) const
776
0
{
777
0
  if (!peer_ctxt.has_value()) {
778
0
    return std::nullopt;
779
0
  }
780
781
0
  auto cell_it = std::find_if(peer_ctxt->list_of_served_cells_nr.begin(),
782
0
                              peer_ctxt->list_of_served_cells_nr.end(),
783
0
                              [nci](const cu_cp_served_cell_info& cell) { return cell.nr_cgi.nci == nci; });
784
0
  if (cell_it == peer_ctxt->list_of_served_cells_nr.end()) {
785
0
    return std::nullopt;
786
0
  }
787
788
0
  return *cell_it;
789
0
}
790
791
async_task<xnap_retrieve_ue_context_response>
792
xnap_impl::handle_retrieve_ue_context_required(const xnap_retrieve_ue_context_request& request)
793
0
{
794
0
  if (!ue_ctxt_list.contains(request.ue_index)) {
795
0
    local_xnap_ue_id_t local_xnap_ue_id = ue_ctxt_list.allocate_local_xnap_ue_id();
796
0
    if (local_xnap_ue_id == local_xnap_ue_id_t::invalid) {
797
0
      logger.warning("ue={}: No local XNAP UE ID available", request.ue_index);
798
0
      return launch_no_op_task(xnap_retrieve_ue_context_response{});
799
0
    }
800
801
0
    ue_ctxt_list.add_ue(request.ue_index, local_xnap_ue_id);
802
0
  }
803
804
0
  return track_ue_procedure(
805
0
      launch_async<xnap_new_node_retrieve_ue_context_procedure>(request, ue_ctxt_list, tx_notifier));
806
0
}
807
808
void xnap_impl::handle_retrieve_ue_context_request(const asn1::xnap::retrieve_ue_context_request_s& msg)
809
0
{
810
  // This is sent from the new to the old NG-RAN node, so the new NG-RAN node UE XnAP ID is the peer XNAP UE ID.
811
0
  const peer_xnap_ue_id_t peer_xnap_ue_id = uint_to_peer_xnap_ue_id(msg->new_ng_ra_nnode_ue_xn_ap_id);
812
813
  // Add lambda that generates and transmits a Retrieve UE Context Failure message. It is used before the procedure is
814
  // launched, so it addresses the peer by the UE XnAP ID the request carried.
815
0
  auto send_retrieve_ue_context_failure = [this, peer_xnap_ue_id](const xnap_cause_t& cause) {
816
0
    xnap_message xnap_msg;
817
0
    xnap_msg.pdu.set_unsuccessful_outcome();
818
0
    xnap_msg.pdu.unsuccessful_outcome().load_info_obj(ASN1_XNAP_ID_RETRIEVE_UE_CONTEXT);
819
0
    auto& asn1_failure                        = xnap_msg.pdu.unsuccessful_outcome().value.retrieve_ue_context_fail();
820
0
    asn1_failure->new_ng_ra_nnode_ue_xn_ap_id = to_underlying(peer_xnap_ue_id);
821
0
    asn1_failure->cause                       = cause_to_asn1(cause);
822
823
0
    if (!tx_notifier.on_new_message(xnap_msg)) {
824
0
      logger.warning("Xn-C association is not set. Cannot send RetrieveUEContextFailure");
825
0
      return;
826
0
    }
827
0
    logger.info("Sending RetrieveUEContextFailure");
828
0
  };
829
830
0
  xnap_retrieve_ue_context_request request;
831
0
  if (!asn1_to_retrieve_ue_context_request(request, msg)) {
832
0
    logger.info("Received invalid RetrieveUEContextRequest");
833
0
    send_retrieve_ue_context_failure(cause_protocol_t::abstract_syntax_error_falsely_constructed_msg);
834
0
    return;
835
0
  }
836
837
  // Resolve the UE the peer is asking for. Only this node can do so, as the UE Context ID refers to identities this
838
  // node allocated.
839
0
  request.ue_index = cu_cp_notifier.on_xnap_ue_context_id_lookup(request.ue_context_id);
840
0
  if (request.ue_index == cu_cp_ue_index_t::invalid) {
841
0
    logger.info("Received RetrieveUEContextRequest for unknown UE Context ID");
842
0
    send_retrieve_ue_context_failure(xnap_cause_radio_network_t::ue_context_id_not_known);
843
0
    return;
844
0
  }
845
846
  // Resolve the target cell, which the CU-CP needs to derive KgNB* (TS 33.501 section 6.11).
847
0
  request.target_cell = find_peer_served_cell(request.target_nci);
848
0
  if (!request.target_cell.has_value()) {
849
0
    logger.info("ue={}: Received RetrieveUEContextRequest for a target cell the peer did not advertise. nci={}",
850
0
                request.ue_index,
851
0
                request.target_nci);
852
0
    send_retrieve_ue_context_failure(xnap_cause_radio_network_t::cell_not_available);
853
0
    return;
854
0
  }
855
856
0
  if (!cu_cp_notifier.schedule_async_task(
857
0
          request.ue_index,
858
0
          track_ue_procedure(launch_async<xnap_old_node_retrieve_ue_context_procedure>(
859
0
              request, peer_xnap_ue_id, ue_ctxt_list, cu_cp_notifier, tx_notifier, logger)))) {
860
0
    logger.debug("ue={}: Couldn't schedule the retrieve UE context procedure", request.ue_index);
861
0
    send_retrieve_ue_context_failure(xnap_cause_radio_network_t::unspecified);
862
0
    return;
863
0
  }
864
0
}
865
866
template <typename Result>
867
async_task<Result> xnap_impl::track_ue_procedure(async_task<Result> proc)
868
0
{
869
0
  ++nof_ue_procedures;
870
871
  // Runs when the procedure completes, or when it is discarded without ever running.
872
0
  auto on_completion = make_scope_exit([this]() {
873
0
    if (--nof_ue_procedures == 0) {
874
0
      ue_procedures_done.set();
875
0
    }
876
0
  });
Unexecuted instantiation: ocudu::ocucp::xnap_impl::track_ue_procedure<ocudu::ocucp::xnap_handover_preparation_response>(ocudu::async_task<ocudu::ocucp::xnap_handover_preparation_response>)::{lambda()#1}::operator()() const
Unexecuted instantiation: ocudu::ocucp::xnap_impl::track_ue_procedure<tl::expected<ocudu::ocucp::cu_cp_status_transfer, ocudu::default_error_t> >(ocudu::async_task<tl::expected<ocudu::ocucp::cu_cp_status_transfer, ocudu::default_error_t> >)::{lambda()#1}::operator()() const
Unexecuted instantiation: ocudu::ocucp::xnap_impl::track_ue_procedure<ocudu::ocucp::xnap_retrieve_ue_context_response>(ocudu::async_task<ocudu::ocucp::xnap_retrieve_ue_context_response>)::{lambda()#1}::operator()() const
877
878
0
  return launch_async([proc = std::move(proc), on_completion = std::move(on_completion), res = Result{}](
879
0
                          coro_context<async_task<Result>>& ctx) mutable {
880
0
    CORO_BEGIN(ctx);
881
0
    CORO_AWAIT_VALUE(res, std::move(proc));
882
0
    CORO_RETURN(std::move(res));
883
0
  });
Unexecuted instantiation: ocudu::ocucp::xnap_impl::track_ue_procedure<ocudu::ocucp::xnap_handover_preparation_response>(ocudu::async_task<ocudu::ocucp::xnap_handover_preparation_response>)::{lambda(ocudu::detail::base_coro_frame<ocudu::async_task<ocudu::ocucp::xnap_handover_preparation_response>::promise_type>&)#1}::operator()(ocudu::detail::base_coro_frame<ocudu::async_task<ocudu::ocucp::xnap_handover_preparation_response>::promise_type>&)
Unexecuted instantiation: ocudu::ocucp::xnap_impl::track_ue_procedure<tl::expected<ocudu::ocucp::cu_cp_status_transfer, ocudu::default_error_t> >(ocudu::async_task<tl::expected<ocudu::ocucp::cu_cp_status_transfer, ocudu::default_error_t> >)::{lambda(ocudu::detail::base_coro_frame<ocudu::async_task<tl::expected<ocudu::ocucp::cu_cp_status_transfer, ocudu::default_error_t> >::promise_type>&)#1}::operator()(ocudu::detail::base_coro_frame<ocudu::async_task<tl::expected<ocudu::ocucp::cu_cp_status_transfer, ocudu::default_error_t> >::promise_type>&)
Unexecuted instantiation: ocudu::ocucp::xnap_impl::track_ue_procedure<ocudu::ocucp::xnap_retrieve_ue_context_response>(ocudu::async_task<ocudu::ocucp::xnap_retrieve_ue_context_response>)::{lambda(ocudu::detail::base_coro_frame<ocudu::async_task<ocudu::ocucp::xnap_retrieve_ue_context_response>::promise_type>&)#1}::operator()(ocudu::detail::base_coro_frame<ocudu::async_task<ocudu::ocucp::xnap_retrieve_ue_context_response>::promise_type>&)
884
0
}
Unexecuted instantiation: ocudu::async_task<ocudu::ocucp::xnap_handover_preparation_response> ocudu::ocucp::xnap_impl::track_ue_procedure<ocudu::ocucp::xnap_handover_preparation_response>(ocudu::async_task<ocudu::ocucp::xnap_handover_preparation_response>)
Unexecuted instantiation: ocudu::async_task<tl::expected<ocudu::ocucp::cu_cp_status_transfer, ocudu::default_error_t> > ocudu::ocucp::xnap_impl::track_ue_procedure<tl::expected<ocudu::ocucp::cu_cp_status_transfer, ocudu::default_error_t> >(ocudu::async_task<tl::expected<ocudu::ocucp::cu_cp_status_transfer, ocudu::default_error_t> >)
Unexecuted instantiation: ocudu::async_task<ocudu::ocucp::xnap_retrieve_ue_context_response> ocudu::ocucp::xnap_impl::track_ue_procedure<ocudu::ocucp::xnap_retrieve_ue_context_response>(ocudu::async_task<ocudu::ocucp::xnap_retrieve_ue_context_response>)
885
886
async_task<void> xnap_impl::track_ue_procedure(async_task<void> proc)
887
0
{
888
0
  ++nof_ue_procedures;
889
890
  // Runs when the procedure completes, or when it is discarded without ever running.
891
0
  auto on_completion = make_scope_exit([this]() {
892
0
    if (--nof_ue_procedures == 0) {
893
0
      ue_procedures_done.set();
894
0
    }
895
0
  });
896
897
0
  return launch_async(
898
0
      [proc = std::move(proc), on_completion = std::move(on_completion)](coro_context<async_task<void>>& ctx) mutable {
899
0
        CORO_BEGIN(ctx);
900
0
        CORO_AWAIT(std::move(proc));
901
0
        CORO_RETURN();
902
0
      });
903
0
}