libe3 0.2.5
C++ library for E3AP RAN vendor integration
Loading...
Searching...
No Matches
e3_interface.hpp
Go to the documentation of this file.
1/**
2 * @file e3_interface.hpp
3 * @brief E3Interface - Internal protocol coordination layer
4 *
5 * This class manages the E3AP lifecycle and coordinates between
6 * the E3Agent facade and the protocol handling components. It is
7 * NOT exposed to library users directly.
8 *
9 * SPDX-FileCopyrightText: Copyright (c) 2026 Northeastern University
10 * SPDX-License-Identifier: Apache-2.0
11 */
12
13#ifndef LIBE3_E3_INTERFACE_HPP
14#define LIBE3_E3_INTERFACE_HPP
15
16#include "types.hpp"
17#include "e3_connector.hpp"
18#include "e3_encoder.hpp"
20#include "lockfree_queue.hpp"
21#include "sm_interface.hpp"
23#include <memory>
24#include <thread>
25#include <atomic>
26#include <functional>
27#include <optional>
28#include <mutex>
29#include <condition_variable>
30#include <chrono>
31#include <unordered_map>
32
33namespace libe3 {
34
35// Forward declaration
36class E3Agent;
37
38/**
39 * @brief Callback types for E3Interface events
40 */
43using DAppReportHandler = std::function<void(const DAppReport&)>;
44using DAppStatusChangedHandler = std::function<void()>;
45
46// dApp-side handlers (RAN -> dApp PDU dispatch)
47using SetupResponseHandler = std::function<void(const SetupResponse&)>;
48using SubscriptionResponseHandler = std::function<void(const SubscriptionResponse&)>;
49using IndicationHandler = std::function<void(const IndicationMessage&)>;
50using XAppControlHandler = std::function<void(const XAppControlAction&)>;
51using MessageAckHandler = std::function<void(const MessageAck&)>;
52
53/**
54 * @brief E3Interface - Internal protocol coordination
55 *
56 * This class is the internal coordination layer that:
57 * - Manages E3AP lifecycle (Setup, Subscription, Control, Release)
58 * - Owns inbound/outbound processing threads
59 * - Coordinates between connector, encoder, and subscription manager
60 * - Supports simulation mode for testing without real RAN
61 *
62 * This class is NOT directly exposed to library users. Users interact
63 * with the E3Agent facade which owns an E3Interface instance.
64 */
66public:
67 /**
68 * @brief Construct with configuration
69 */
70 explicit E3Interface(const E3Config& config);
71
72 /**
73 * @brief Destructor - ensures clean shutdown
74 */
76
77 // Non-copyable, non-movable
78 E3Interface(const E3Interface&) = delete;
82
83 /**
84 * @brief Initialize the interface
85 *
86 * Creates connector, encoder, and initializes internal state.
87 * @return ErrorCode::SUCCESS on success
88 */
90
91 /**
92 * @brief Start the interface processing loops
93 *
94 * Spawns the setup, inbound, and outbound threads, plus the report worker on the RAN role.
95 * @return ErrorCode::SUCCESS on success
96 */
98
99 /**
100 * @brief Stop the interface
101 *
102 * Signals all threads to stop and waits for them to finish.
103 */
104 void stop();
105
106 /**
107 * @brief Get current state
108 */
109 AgentState state() const noexcept { return state_.load(); }
110
111 /**
112 * @brief Check if interface is running
113 */
114 bool is_running() const noexcept {
115 return state_.load() == AgentState::RUNNING;
116 }
117
118 /**
119 * @brief Get the subscription manager
120 */
122 return *subscription_manager_;
123 }
124
125 /**
126 * @brief Get the response queue for outbound messages
127 */
129 return *response_queue_;
130 }
131
132 /**
133 * @brief Queue a PDU for outbound transmission
134 */
136
137 /**
138 * @brief Get available RAN functions
139 */
140 std::vector<uint32_t> get_available_ran_functions() const;
141
142 /**
143 * @brief Register a Service Model
144 */
145 ErrorCode register_sm(std::unique_ptr<ServiceModel> sm);
146
147 // =========================================================================
148 // Event Handlers - Set by E3Agent
149 // =========================================================================
150
152 dapp_report_handler_ = std::move(handler);
153 }
154
156 dapp_status_changed_handler_ = std::move(handler);
157 }
158
159 // dApp-side handlers (set before start() when role==DAPP)
161 setup_response_handler_ = std::move(handler);
162 }
164 subscription_response_handler_ = std::move(handler);
165 }
167 indication_handler_ = std::move(handler);
168 }
170 xapp_control_handler_ = std::move(handler);
171 }
173 message_ack_handler_ = std::move(handler);
174 }
175
176 // dApp-side accessors
177 std::optional<uint32_t> dapp_id() const noexcept;
178 std::vector<uint32_t> active_subscription_ids() const;
179 std::vector<uint32_t> subscribed_ran_functions() const;
180 std::vector<RanFunctionDef> remote_ran_functions() const;
181
182 // dApp-side outbound helpers (queue PDUs that the outbound loop will encode + send)
183 // out_request_id, when non-null, receives the assigned E3-MessageID on
184 // success, so the caller can correlate the eventual SubscriptionResponse
185 // (which echoes request_id) back to this request.
186 ErrorCode queue_subscription_request(uint32_t ran_function_id,
187 std::vector<uint32_t> telemetry_ids,
188 std::vector<uint32_t> control_ids,
189 std::optional<uint32_t> sub_time = std::nullopt,
190 std::optional<uint32_t> periodicity = std::nullopt,
191 uint32_t* out_request_id = nullptr);
192 ErrorCode queue_subscription_delete(uint32_t ran_function_id);
193 // out_message_id, when non-null, receives the assigned E3-MessageID on
194 // success, so the caller can correlate a later ack/response back to this
195 // send.
196 ErrorCode queue_dapp_control_action(uint32_t ran_function_id,
197 uint32_t control_id,
198 std::vector<uint8_t> action_data,
199 uint32_t sequence_id = 0,
200 uint32_t* out_message_id = nullptr);
201 ErrorCode queue_dapp_report(uint32_t ran_function_id,
202 std::vector<uint8_t> report_data,
203 uint32_t sequence_id,
204 uint32_t* out_message_id = nullptr);
206
207 // Block until the setup handshake completes (or times out / fails).
208 // Only meaningful on dApp role.
209 ErrorCode wait_for_setup(std::chrono::milliseconds timeout);
210
212
213private:
214 // Configuration
215 E3Config config_;
216
217 // State
218 std::atomic<AgentState> state_{AgentState::UNINITIALIZED};
219 std::atomic<bool> should_stop_{false};
220
221 // Monotonic source for E3-MessageID (1..4294967295). A counter gives
222 // unique ids, unlike the former random draw which could collide (birthday
223 // hazard) and defeat request/response correlation.
224 std::atomic<uint32_t> next_message_id_{0};
225
226 // Core components
227 std::unique_ptr<E3Connector> connector_;
228 std::unique_ptr<E3Encoder> encoder_;
229 // RAN-only state (nullptr when role==DAPP).
230 std::unique_ptr<SubscriptionManager> subscription_manager_;
231 // dApp-only state (nullptr when role==RAN).
232 std::unique_ptr<DAppSubscriptionState> dapp_state_;
233
234 // dApp-only: subscription requests awaiting their SubscriptionResponse.
235 // E3-SubscriptionResponse echoes requestId but not ranFunctionIdentifier,
236 // so the RAN function a granted subscription belongs to is only knowable
237 // from the request that asked for it. Keyed by the E3-MessageID we sent;
238 // entries are erased when the response arrives.
239 struct PendingSubscriptionOp {
240 uint32_t ran_function_id{0};
241 bool is_delete{false};
242 };
243 std::mutex pending_subscriptions_mutex_;
244 std::unordered_map<uint32_t, PendingSubscriptionOp> pending_subscriptions_;
245 std::unique_ptr<LockFreeQueue<Pdu>> response_queue_;
246
247 // dApp-report queue: subscriber/inbound thread hands reports off to the
248 // report worker thread so downstream (OAI / iApp) work never blocks reads.
249 std::unique_ptr<LockFreeQueue<DAppReport>> report_queue_;
250
251 // Threads. setup_thread_ runs the setup loop (RAN: serves; dApp: drives once).
252 // inbound_thread_ / outbound_thread_ replace the old subscriber_thread_ /
253 // publisher_thread_ names since on the dApp side what flows in is not a
254 // subscription.
255 std::unique_ptr<std::thread> setup_thread_;
256 std::unique_ptr<std::thread> inbound_thread_;
257 std::unique_ptr<std::thread> outbound_thread_;
258 std::unique_ptr<std::thread> report_worker_thread_;
259#ifdef LIBE3_ENABLE_LATREC
260 // ~1 Hz slow-lane sampler stamping LATREC_CONTEXT (involuntary context
261 // switches, current CPU frequency), to explain p99 tails. Exists only in
262 // a latrec-enabled build: a normal build spawns no such thread at all.
263 std::unique_ptr<std::thread> context_thread_;
264#endif
265
266 // RAN-side handlers
267 DAppReportHandler dapp_report_handler_;
268 DAppStatusChangedHandler dapp_status_changed_handler_;
269
270 // dApp-side handlers
271 SetupResponseHandler setup_response_handler_;
272 SubscriptionResponseHandler subscription_response_handler_;
273 IndicationHandler indication_handler_;
274 XAppControlHandler xapp_control_handler_;
275 MessageAckHandler message_ack_handler_;
276
277 // dApp-side setup-complete signal (notified by setup_loop_dapp)
278 mutable std::mutex setup_complete_mu_;
279 std::condition_variable setup_complete_cv_;
280 bool setup_complete_{false};
281 bool setup_succeeded_{false};
282
283 // =========================================================================
284 // Thread Entry Points
285 // =========================================================================
286
287 /**
288 * @brief RAN-role main setup loop - handles E3 Setup requests
289 */
290 void setup_loop_ran();
291
292 /**
293 * @brief dApp-role setup loop - send SetupRequest, recv SetupResponse, idle.
294 */
295 void setup_loop_dapp();
296
297 /**
298 * @brief RAN-role inbound loop - receives subscribe/control/report/release.
299 */
300 void inbound_loop_ran();
301
302 /**
303 * @brief dApp-role inbound loop - receives subscribe-response/indication/
304 * xapp-control/message-ack.
305 */
306 void inbound_loop_dapp();
307
308 /**
309 * @brief RAN-role outbound loop - sends indications, subscription
310 * responses and acks back to dApps.
311 */
312 void outbound_loop_ran();
313
314 /**
315 * @brief dApp-role outbound loop - sends subscribe/delete/control/
316 * report/release back to the RAN.
317 */
318 void outbound_loop_dapp();
319
320 /**
321 * @brief Report worker thread - drains report_queue_ and invokes
322 * handle_dapp_report() off the subscriber thread so ZMQ reads
323 * are never blocked by downstream (OAI / iApp) work.
324 */
325 void report_worker_loop();
326
327#ifdef LIBE3_ENABLE_LATREC
328 /**
329 * @brief ~1 Hz slow-lane sampler stamping LATREC_CONTEXT.
330 */
331 void context_monitor_loop();
332#endif
333
334 // =========================================================================
335 // Message Handlers
336 // =========================================================================
337
338 // RAN-role handlers
339 void handle_setup_request(const SetupRequest& request, uint32_t request_message_id);
340 void handle_subscription_request(const SubscriptionRequest& request, uint32_t request_message_id);
341 void handle_subscription_delete(const SubscriptionDelete& del, uint32_t request_message_id);
342 void handle_control_action(const DAppControlAction& action, uint32_t request_message_id,
343 uint64_t latrec_seq);
344 void handle_dapp_report(const DAppReport& report);
345 void handle_release_message(const ReleaseMessage &release);
346 void handle_dapp_disconnection(uint32_t dapp_id);
347
348 /**
349 * @brief Reply to a setup request that cannot be answered positively.
350 *
351 * The RAN side of the setup channel is a REQ/REP exchange: exactly one
352 * reply must be sent per received request before the next one can be
353 * received (a ZMQ REP socket enforces this in its state machine).
354 * Bailing out without replying (undecodable bytes, wrong PDU type)
355 * wedges the setup channel for every subsequent dApp until the agent
356 * restarts. The encoding is fixed and known a priori by both peers, so
357 * a negative SetupResponse can always be produced; the peer gets an
358 * explicit rejection instead of a silent timeout.
359 *
360 * @param request_id Message ID of the offending request, or any id that
361 * cannot be echoed (0 when the request bytes never
362 * decoded, out of E3-MessageID range otherwise) — a
363 * fresh id is then substituted on the wire (dApps do
364 * not correlate setup replies by ID).
365 */
366 void send_negative_setup_reply(uint32_t request_id);
367
368 // dApp-role handlers
369 void handle_setup_response(const SetupResponse& response);
370 void handle_subscription_response(const SubscriptionResponse& response);
371 void handle_indication(const IndicationMessage& msg, uint64_t latrec_seq);
372 void handle_xapp_control_action(const XAppControlAction& action, uint64_t latrec_seq);
373 void handle_message_ack(const MessageAck& ack);
374
375 // =========================================================================
376 // SM Lifecycle Management
377 // =========================================================================
378
379 /**
380 * @brief Callback for SM lifecycle changes
381 */
382 void on_sm_lifecycle_change(uint32_t ran_function_id, bool should_start);
383
384 /**
385 * @brief Record a subscribe/unsubscribe request awaiting its response.
386 *
387 * @param request_id E3-MessageID the request was sent with
388 * @param ran_function_id RAN function the request concerns
389 * @param is_delete true for a SubscriptionDelete, false for a request
390 */
391 void remember_subscription_op(uint32_t request_id, uint32_t ran_function_id,
392 bool is_delete);
393
394 /// Drop a pending record, e.g. when the request could not be queued.
395 void forget_subscription_op(uint32_t request_id);
396
397public:
398 /**
399 * @brief Generate the next message ID, monotonic within 1..4294967295.
400 *
401 * Wraps a lock-free counter into the ASN.1 E3-MessageID range, skipping 0.
402 * Monotonic (not random) so an id is unique, letting a caller correlate a
403 * response by the id it was assigned.
404 */
405 uint32_t generate_message_id();
406
407 /**
408 * @brief Return a request id that can be echoed into a response.
409 *
410 * The decoders do not enforce integer range constraints, so a
411 * peer-supplied message id of 0, outside E3-MessageID's 1..4294967295,
412 * can reach the handlers; echoing it into a response would fail the
413 * encode. Every non-zero id passes through unchanged, 0 is replaced
414 * with a fresh generated id.
415 */
416 uint32_t sanitize_request_message_id(uint32_t request_id);
417};
418
419} // namespace libe3
420
421#endif // LIBE3_E3_INTERFACE_HPP
E3Agent - Main façade for RAN vendor integration.
Definition e3_agent.hpp:85
ErrorCode queue_dapp_report(uint32_t ran_function_id, std::vector< uint8_t > report_data, uint32_t sequence_id, uint32_t *out_message_id=nullptr)
void notify_dapp_status_changed()
ErrorCode queue_dapp_control_action(uint32_t ran_function_id, uint32_t control_id, std::vector< uint8_t > action_data, uint32_t sequence_id=0, uint32_t *out_message_id=nullptr)
ErrorCode init()
Initialize the interface.
std::vector< uint32_t > subscribed_ran_functions() const
LockFreeQueue< Pdu > & response_queue() noexcept
Get the response queue for outbound messages.
ErrorCode queue_outbound(Pdu pdu)
Queue a PDU for outbound transmission.
ErrorCode queue_subscription_delete(uint32_t ran_function_id)
E3Interface & operator=(E3Interface &&)=delete
SubscriptionManager & subscription_manager() noexcept
Get the subscription manager.
void set_dapp_status_changed_handler(DAppStatusChangedHandler handler)
~E3Interface()
Destructor - ensures clean shutdown.
void set_indication_handler(IndicationHandler handler)
bool is_running() const noexcept
Check if interface is running.
ErrorCode queue_subscription_request(uint32_t ran_function_id, std::vector< uint32_t > telemetry_ids, std::vector< uint32_t > control_ids, std::optional< uint32_t > sub_time=std::nullopt, std::optional< uint32_t > periodicity=std::nullopt, uint32_t *out_request_id=nullptr)
std::vector< uint32_t > get_available_ran_functions() const
Get available RAN functions.
uint32_t generate_message_id()
Generate the next message ID, monotonic within 1..4294967295.
ErrorCode start()
Start the interface processing loops.
E3Interface(const E3Interface &)=delete
AgentState state() const noexcept
Get current state.
ErrorCode wait_for_setup(std::chrono::milliseconds timeout)
void set_subscription_response_handler(SubscriptionResponseHandler handler)
ErrorCode queue_release_message()
E3Interface(const E3Config &config)
Construct with configuration.
E3Interface & operator=(const E3Interface &)=delete
void set_xapp_control_handler(XAppControlHandler handler)
void set_message_ack_handler(MessageAckHandler handler)
void stop()
Stop the interface.
void set_setup_response_handler(SetupResponseHandler handler)
std::vector< uint32_t > active_subscription_ids() const
E3Interface(E3Interface &&)=delete
std::vector< RanFunctionDef > remote_ran_functions() const
std::optional< uint32_t > dapp_id() const noexcept
void set_dapp_report_handler(DAppReportHandler handler)
uint32_t sanitize_request_message_id(uint32_t request_id)
Return a request id that can be echoed into a response.
ErrorCode register_sm(std::unique_ptr< ServiceModel > sm)
Register a Service Model.
Lock-free bounded blocking queue.
Per-instance state for the dApp role.
Abstract E3 Connector interface for transport layer.
Abstract E3 Encoder interface for PDU encoding/decoding.
Lock-free bounded blocking queue for the E3AP hot paths.
std::function< void(const SubscriptionResponse &)> SubscriptionResponseHandler
Definition e3_agent.hpp:37
ErrorCode
Error codes returned by libe3 operations Use shared error-code list (see libe3/error_codes....
Definition types.hpp:141
std::function< void(const XAppControlAction &)> XAppControlHandler
Definition e3_agent.hpp:39
AgentState
Agent state machine states.
Definition types.hpp:126
@ RUNNING
Main processing loop active.
Definition types.hpp:131
@ UNINITIALIZED
Agent not yet initialized.
Definition types.hpp:127
std::function< void()> DAppStatusChangedHandler
Definition e3_agent.hpp:33
ResponseCode
E3AP response codes.
Definition types.hpp:101
std::function< void(const DAppReport &)> DAppReportHandler
Definition e3_agent.hpp:30
std::function< void(const SetupResponse &)> SetupResponseHandler
Definition e3_agent.hpp:36
std::function< void(const MessageAck &)> MessageAckHandler
Definition e3_agent.hpp:40
std::function< ResponseCode(const SubscriptionRequest &)> SubscriptionRequestHandler
std::function< ResponseCode(const SetupRequest &, SetupResponse &)> SetupRequestHandler
Callback types for E3Interface events.
std::function< void(const IndicationMessage &)> IndicationHandler
Definition e3_agent.hpp:38
Service Model Interface - Extension point for RAN vendors.
E3AP dApp Report structure.
Definition types.hpp:293
Configuration for E3Agent.
Definition types.hpp:390
E3AP Indication Message structure.
Definition types.hpp:263
E3AP Message Acknowledgment structure.
Definition types.hpp:285
Generic E3AP PDU structure.
Definition types.hpp:345
RAN Function Definition for Setup Response.
Definition types.hpp:211
E3AP Setup Request structure.
Definition types.hpp:201
E3AP Setup Response structure.
Definition types.hpp:221
E3AP Subscription Request structure.
Definition types.hpp:233
E3AP Subscription Response structure.
Definition types.hpp:253
E3AP xApp Control Action structure.
Definition types.hpp:306
E3 Subscription Manager for tracking dApp registrations.
E3AP Protocol Types - Vendor-neutral type definitions.