libe3 0.2.5
C++ library for E3AP RAN vendor integration
Loading...
Searching...
No Matches
e3_dapp_session.cpp
Go to the documentation of this file.
1/**
2 * @file e3_dapp_session.cpp
3 * @brief Implementation of the batched dApp Python seam. See e3_dapp_session.hpp.
4 *
5 * SPDX-FileCopyrightText: Copyright (c) 2026 Northeastern University
6 * SPDX-License-Identifier: Apache-2.0
7 */
8
9#include "e3_dapp_session.hpp"
10
11#include <libe3/e3_agent.hpp>
12#include <libe3/latrec.h>
14
15#include <algorithm>
16#include <atomic>
17#include <chrono>
18#include <mutex>
19#include <optional>
20
21namespace libe3 {
22namespace py {
23
24namespace {
25// Map an optional negative sentinel to std::optional<uint32_t>.
26inline std::optional<uint32_t> opt_u32(int v) {
27 if (v < 0) return std::nullopt;
28 return static_cast<uint32_t>(v);
29}
30} // namespace
31
33 explicit Impl(libe3::E3Config config, std::size_t queue_capacity)
34 : agent(std::make_unique<libe3::E3Agent>(std::move(config))),
35 queue(queue_capacity) {}
36
37 std::unique_ptr<libe3::E3Agent> agent;
38 // Stable object (never moved) so a poll_events() reference stays valid; a
39 // prior stop() shut it down, and start() re-arms it in place (LockFreeQueue's
40 // shutdown is one-way, E3Agent itself is restartable).
42 std::atomic<unsigned long long> dropped{0};
43
44 // SetupResponse snapshot for the setup accessors (written once on setup).
45 mutable std::mutex setup_mu;
46 bool have_setup{false};
48
49 void enqueue(E3Event ev) {
50 const uint64_t seq = latrec_seq_next();
51 ev.trace_seq = seq;
52 const uint64_t kind = static_cast<uint64_t>(ev.kind);
53 // Before the push, for the same reason as the report queue: the
54 // binding's poll thread can drain this event and stamp
55 // SESSION_POLLED before this thread gets to issue its own stamp.
56 const uint64_t t_queued = latrec_tnow();
57 if (queue.push(std::move(ev)) != libe3::ErrorCode::SUCCESS) {
58 dropped.fetch_add(1, std::memory_order_relaxed);
60 } else {
61 latrec_tstamp_at(seq, LATREC_SESSION_QUEUED, kind, 0, t_queued);
62 }
63 }
64};
65
66DAppSession::DAppSession(libe3::E3Config config, std::size_t queue_capacity) {
67 config.role = libe3::E3Role::DAPP; // this seam is dApp-only
68 impl_ = std::make_unique<Impl>(std::move(config), queue_capacity);
69 Impl* impl = impl_.get();
70 auto& agent = *impl->agent;
71
72 agent.set_indication_handler([impl](const libe3::IndicationMessage& m) {
73 E3Event ev;
77 ev.payload = m.protocol_data; // one copy from libe3's const&
78 impl->enqueue(std::move(ev));
79 });
80
81 agent.set_xapp_control_handler([impl](const libe3::XAppControlAction& a) {
82 E3Event ev;
89 impl->enqueue(std::move(ev));
90 });
91
92 agent.set_subscription_response_handler([impl](const libe3::SubscriptionResponse& r) {
93 E3Event ev;
97 ev.subscription_id = r.subscription_id.value_or(0);
98 ev.response_code = static_cast<int>(r.response_code);
99 impl->enqueue(std::move(ev));
100 });
101
102 agent.set_setup_response_handler([impl](const libe3::SetupResponse& r) {
103 {
104 std::lock_guard<std::mutex> lk(impl->setup_mu);
105 impl->setup = r;
106 impl->have_setup = true;
107 }
108 E3Event ev;
110 ev.dapp_id = r.dapp_identifier.value_or(0);
111 ev.request_id = r.request_id;
112 ev.response_code = static_cast<int>(r.response_code);
113 impl->enqueue(std::move(ev));
114 });
115
116 agent.set_message_ack_handler([impl](const libe3::MessageAck& ack) {
117 E3Event ev;
119 ev.request_id = ack.request_id;
120 ev.response_code = static_cast<int>(ack.response_code);
121 impl->enqueue(std::move(ev));
122 });
123}
124
126 if (impl_) {
127 impl_->queue.shutdown();
128 if (impl_->agent) impl_->agent->stop();
129 }
130}
131
133 // Re-arm the inbound ring in place (a prior stop() shut it down; E3Agent is
134 // restartable). Re-arming the same object — rather than replacing it — keeps
135 // any reference held by a concurrent poll_events() valid.
136 impl_->queue.rearm();
137 return static_cast<int>(impl_->agent->start());
138}
139
140int DAppSession::wait_for_setup(int timeout_ms) {
141 return static_cast<int>(
142 impl_->agent->wait_for_setup(std::chrono::milliseconds(timeout_ms)));
143}
144
146 return static_cast<int>(impl_->agent->release());
147}
148
150 impl_->queue.shutdown();
151 impl_->agent->stop();
152}
153
155 auto id = impl_->agent->dapp_id();
156 return id ? static_cast<long>(*id) : -1;
157}
158
159std::string DAppSession::ran_identifier() const {
160 std::lock_guard<std::mutex> lk(impl_->setup_mu);
161 return impl_->have_setup ? impl_->setup.ran_identifier : std::string{};
162}
163
165 std::lock_guard<std::mutex> lk(impl_->setup_mu);
166 if (!impl_->have_setup) return -1;
167 return static_cast<int>(impl_->setup.response_code);
168}
169
171 std::lock_guard<std::mutex> lk(impl_->setup_mu);
172 return impl_->have_setup ? impl_->setup.ran_function_list.size() : 0;
173}
174
175uint32_t DAppSession::setup_ran_function_id(std::size_t i) const {
176 std::lock_guard<std::mutex> lk(impl_->setup_mu);
177 if (!impl_->have_setup || i >= impl_->setup.ran_function_list.size()) return 0;
178 return impl_->setup.ran_function_list[i].ran_function_identifier;
179}
180
181std::vector<uint32_t> DAppSession::setup_ran_function_telemetry(std::size_t i) const {
182 std::lock_guard<std::mutex> lk(impl_->setup_mu);
183 if (!impl_->have_setup || i >= impl_->setup.ran_function_list.size()) return {};
184 return impl_->setup.ran_function_list[i].telemetry_identifier_list;
185}
186
187std::vector<uint32_t> DAppSession::setup_ran_function_control(std::size_t i) const {
188 std::lock_guard<std::mutex> lk(impl_->setup_mu);
189 if (!impl_->have_setup || i >= impl_->setup.ran_function_list.size()) return {};
190 return impl_->setup.ran_function_list[i].control_identifier_list;
191}
192
193std::vector<uint8_t> DAppSession::setup_ran_function_data(std::size_t i) const {
194 std::lock_guard<std::mutex> lk(impl_->setup_mu);
195 if (!impl_->have_setup || i >= impl_->setup.ran_function_list.size()) return {};
196 return impl_->setup.ran_function_list[i].ran_function_data;
197}
198
199int64_t DAppSession::subscribe(uint32_t ran_function_id,
200 std::vector<uint32_t> telemetry_ids,
201 std::vector<uint32_t> control_ids,
202 int sub_time_ms,
203 int periodicity) {
204 uint32_t request_id = 0;
205 ErrorCode rc = impl_->agent->subscribe(
206 ran_function_id, std::move(telemetry_ids), std::move(control_ids),
207 opt_u32(sub_time_ms), opt_u32(periodicity), &request_id);
208 // On success return the assigned request id (positive, 1..4294967295) so Python
209 // can correlate the SubscriptionResponse by id; on failure return the
210 // ErrorCode (all error codes are negative, SUCCESS is 0).
211 if (rc == ErrorCode::SUCCESS) {
212 return static_cast<int64_t>(request_id);
213 }
214 return static_cast<int64_t>(rc);
215}
216
217int DAppSession::unsubscribe(uint32_t ran_function_id) {
218 return static_cast<int>(impl_->agent->unsubscribe(ran_function_id));
219}
220
221int64_t DAppSession::send_control(uint32_t ran_function_id, uint32_t control_id,
222 std::vector<uint8_t> action_data,
223 uint32_t sequence_id) {
224 uint32_t message_id = 0;
225 ErrorCode rc = impl_->agent->send_control(
226 ran_function_id, control_id, std::move(action_data), sequence_id, &message_id);
227 // On success return the assigned message id so Python can correlate a later
228 // ack/response by id; on failure return the ErrorCode (all error codes are
229 // negative, SUCCESS is 0).
230 if (rc == ErrorCode::SUCCESS) {
231 return static_cast<int64_t>(message_id);
232 }
233 return static_cast<int64_t>(rc);
234}
235
236int64_t DAppSession::send_report(uint32_t ran_function_id, std::vector<uint8_t> report_data,
237 uint32_t sequence_id) {
238 uint32_t message_id = 0;
239 ErrorCode rc = impl_->agent->send_report(
240 ran_function_id, std::move(report_data), sequence_id, &message_id);
241 // Same success/failure encoding as send_control() above.
242 if (rc == ErrorCode::SUCCESS) {
243 return static_cast<int64_t>(message_id);
244 }
245 return static_cast<int64_t>(rc);
246}
247
248int DAppSession::send_message_ack(uint32_t request_id, int response_code) {
249 return static_cast<int>(impl_->agent->send_message_ack(
250 request_id, static_cast<libe3::ResponseCode>(response_code)));
251}
252
253std::vector<E3Event> DAppSession::poll_events(std::size_t max_batch, int timeout_ms) {
254 std::vector<E3Event> out;
255 if (max_batch == 0) return out;
256 auto& queue = impl_->queue;
257 // libe3 does not start this thread, so it has no ring. The open happens
258 // once, on a per-batch call rather than a per-event one.
259 latrec_tls_open_as("libe3.session");
260
261 // timeout_ms <= 0 is a non-blocking poll (try_pop); a positive value blocks
262 // up to that long for the first event. (pop() spins/yields before checking
263 // the deadline, so 0 there would busy-wait.)
264 std::optional<E3Event> first =
265 (timeout_ms <= 0) ? queue.try_pop()
266 : queue.pop(std::chrono::milliseconds(timeout_ms));
267 if (!first) return out; // quiet tick, or shutdown after stop()
268
269 out.reserve(std::min(max_batch, queue.capacity()));
270 out.push_back(std::move(*first));
271 while (out.size() < max_batch) {
272 auto next = queue.try_pop();
273 if (!next) break;
274 out.push_back(std::move(*next));
275 }
276 // aux is the position in the batch and aux2 its size, so a backlog shows
277 // as batches reaching max_batch.
278 for (std::size_t i = 0; i < out.size(); ++i) {
279 latrec_tstamp(out[i].trace_seq, LATREC_SESSION_POLLED, i, out.size());
280 }
281 return out;
282}
283
284unsigned long long DAppSession::dropped_events() const {
285 return impl_->dropped.load(std::memory_order_relaxed);
286}
287
288} // namespace py
289} // namespace libe3
E3Agent - Main façade for RAN vendor integration.
Definition e3_agent.hpp:85
Lock-free bounded blocking queue.
uint32_t setup_ran_function_id(std::size_t i) const
RAN function id of advertised entry i (0 if out of range).
int setup_response_code() const
SetupResponse code: 0=positive, 1=negative, -1=not received.
int unsubscribe(uint32_t ran_function_id)
Delete a previously created subscription.
std::vector< E3Event > poll_events(std::size_t max_batch, int timeout_ms)
Drain up to max_batch inbound events, waiting up to timeout_ms for the first.
std::vector< uint8_t > setup_ran_function_data(std::size_t i) const
Opaque ranFunctionData bytes of advertised entry i.
std::vector< uint32_t > setup_ran_function_telemetry(std::size_t i) const
Telemetry ids of advertised entry i.
std::string ran_identifier() const
RAN identifier from the SetupResponse, or "" if none.
unsigned long long dropped_events() const
Total inbound events dropped due to ring overflow (backpressure).
void stop()
Stop the agent and shut down the ring so a blocked poll_events returns an empty batch (i....
int release()
Send a ReleaseMessage (session keeps running until stop).
int send_message_ack(uint32_t request_id, int response_code)
Acknowledge a request.
int start()
Start the agent, re-arming the inbound ring so a stop()->start() cycle works again.
int wait_for_setup(int timeout_ms)
Block until the setup handshake completes.
int64_t send_report(uint32_t ran_function_id, std::vector< uint8_t > report_data, uint32_t sequence_id)
Send a dApp report to the RAN.
long dapp_id() const
dApp id assigned at setup, or -1 if not yet assigned.
int64_t subscribe(uint32_t ran_function_id, std::vector< uint32_t > telemetry_ids, std::vector< uint32_t > control_ids, int sub_time_ms, int periodicity)
Subscribe to a RAN function (after a positive setup).
std::size_t setup_ran_function_count() const
Number of RAN functions advertised in the SetupResponse.
DAppSession(libe3::E3Config config, std::size_t queue_capacity=8192)
Construct the session and register all inbound handlers.
std::vector< uint32_t > setup_ran_function_control(std::size_t i) const
Control ids of advertised entry i.
int64_t send_control(uint32_t ran_function_id, uint32_t control_id, std::vector< uint8_t > action_data, uint32_t sequence_id=0)
Send a dApp control action to the RAN.
E3Agent - Main façade class for RAN vendors.
Python-facing dApp session over libe3 — batched, low-latency seam.
Lock-free per-thread latency stamp recorder (header-only, C99).
static void latrec_tstamp(uint64_t seq, uint8_t stage, uint64_t aux, uint64_t aux2)
Definition latrec.h:685
static uint64_t latrec_tnow(void)
Definition latrec.h:695
static uint64_t latrec_seq_next(void)
Definition latrec.h:684
static void latrec_tstamp_at(uint64_t seq, uint8_t stage, uint64_t aux, uint64_t aux2, uint64_t t_ns)
Definition latrec.h:689
@ LATREC_SESSION_POLLED
Definition latrec.h:209
@ LATREC_SESSION_QUEUED
Definition latrec.h:207
@ LATREC_DROP
Definition latrec.h:212
static latrec_t * latrec_tls_open_as(const char *role)
Definition latrec.h:682
@ LATREC_DROP_SESSION_QUEUE
Definition latrec.h:359
Lock-free bounded blocking queue for the E3AP hot paths.
@ E3_EVENT_SUBSCRIPTION_RESPONSE
RAN → dApp SubscriptionResponse.
@ E3_EVENT_SETUP_RESPONSE
RAN → dApp SetupResponse.
@ E3_EVENT_INDICATION
RAN → dApp IndicationMessage.
@ E3_EVENT_XAPP_CONTROL
xApp → dApp XAppControlAction
@ E3_EVENT_MESSAGE_ACK
RAN → dApp MessageAck.
ErrorCode
Error codes returned by libe3 operations Use shared error-code list (see libe3/error_codes....
Definition types.hpp:141
@ DAPP
dApp (connects to a RAN agent)
Definition types.hpp:77
ResponseCode
E3AP response codes.
Definition types.hpp:101
Configuration for E3Agent.
Definition types.hpp:390
E3AP Indication Message structure.
Definition types.hpp:263
uint32_t ran_function_identifier
RAN function identifier.
Definition types.hpp:265
std::vector< uint8_t > protocol_data
Definition types.hpp:266
E3AP Message Acknowledgment structure.
Definition types.hpp:285
ResponseCode response_code
Response code (positive/negative).
Definition types.hpp:287
uint32_t request_id
ID of the request being acknowledged.
Definition types.hpp:286
E3AP Setup Response structure.
Definition types.hpp:221
E3AP Subscription Response structure.
Definition types.hpp:253
std::optional< uint32_t > subscription_id
Subscription ID (optional).
Definition types.hpp:257
uint32_t request_id
ID of the corresponding SubscriptionRequest.
Definition types.hpp:254
uint32_t dapp_identifier
dApp identifier
Definition types.hpp:255
ResponseCode response_code
Response code (positive/negative).
Definition types.hpp:256
E3AP xApp Control Action structure.
Definition types.hpp:306
std::vector< uint8_t > xapp_control_data
Definition types.hpp:309
uint32_t ran_function_identifier
Definition types.hpp:308
std::atomic< unsigned long long > dropped
std::unique_ptr< libe3::E3Agent > agent
Impl(libe3::E3Config config, std::size_t queue_capacity)
libe3::LockFreeQueue< E3Event > queue
One inbound E3AP event, POD-flat for a clean SWIG crossing.
uint32_t ran_function_id
RAN function (indication / xApp control).
uint32_t sequence_id
loop correlation id (xApp control); echo it on the control you re-issue
int kind
one of E3EventKind
uint32_t dapp_id
dApp identifier the message targets
uint32_t request_id
request/message id (subscription response / ack / xApp control)
uint32_t subscription_id
subscription id (subscription response)
int response_code
0=positive, 1=negative, -1=n/a
uint64_t trace_seq
set when queued; keys the [latrec] LQ-stage records
std::vector< uint8_t > payload
opaque E3SM bytes (indication / xApp control)