libe3 0.2.5
C++ library for E3AP RAN vendor integration
Loading...
Searching...
No Matches
subscription_manager.cpp
Go to the documentation of this file.
1/**
2 * @file subscription_manager.cpp
3 * @brief E3 Subscription Manager implementation
4 *
5 * Ported from the original C implementation e3_subscription_manager.c
6 *
7 * SPDX-FileCopyrightText: Copyright (c) 2026 Northeastern University
8 * SPDX-License-Identifier: Apache-2.0
9 */
10
12#include "libe3/logger.hpp"
13
14namespace libe3 {
15
16namespace {
17constexpr const char* LOG_TAG = "SubMgr";
18// E3-DAppID and the subscription id are both INTEGER (1..65535).
19constexpr uint32_t MIN_ID = 1;
20constexpr uint32_t MAX_ID = 65535;
21
22// Lowest id in MIN_ID..MAX_ID that `taken` does not hold, 0 when all are in use.
23template <typename Map>
24uint32_t lowest_free_id(const Map& taken) {
25 for (uint32_t id = MIN_ID; id <= MAX_ID; ++id) {
26 if (taken.count(id) == 0) return id;
27 }
28 return 0;
29}
30}
31
33 E3_LOG_DEBUG(LOG_TAG) << "Subscription manager created";
34}
35
37 std::unique_lock lock(mutex_);
38 registered_dapps_.clear();
39 dapp_subscriptions_.clear();
40 ran_function_subscribers_.clear();
41 subscription_ids_.clear();
42 subscription_id_reverse_.clear();
43 E3_LOG_DEBUG(LOG_TAG) << "Subscription manager destroyed";
44}
45
47 std::unique_lock lock(mutex_);
48 sm_lifecycle_callback_ = std::move(callback);
49}
50
51// =========================================================================
52// dApp Registration Management
53// =========================================================================
54
55std::pair<ErrorCode, uint32_t> SubscriptionManager::register_dapp() {
56 std::unique_lock lock(mutex_);
57
58 // Hand out the lowest free id, so a dApp that reconnects gets the same id back
59 // and a live id is never reused.
60 const uint32_t assigned_id = lowest_free_id(registered_dapps_);
61 if (assigned_id == 0) {
62 E3_LOG_ERROR(LOG_TAG) << "No available dApp IDs (range " << MIN_ID
63 << ".." << MAX_ID << ")";
64 return {ErrorCode::INTERNAL_ERROR, 0};
65 }
66
67 DAppEntry entry;
68 entry.dapp_identifier = assigned_id;
69 entry.registered_time = std::chrono::steady_clock::now();
70 registered_dapps_[assigned_id] = entry;
71
72 // Initialize empty subscription set for this dApp
73 dapp_subscriptions_[assigned_id] = {};
74
75 E3_LOG_INFO(LOG_TAG) << "dApp registered successfully with ID " << assigned_id;
76 return {ErrorCode::SUCCESS, assigned_id};
77}
78
80 std::unique_lock lock(mutex_);
81
82 auto it = registered_dapps_.find(dapp_id);
83 if (it == registered_dapps_.end()) {
84 E3_LOG_WARN(LOG_TAG) << "dApp " << dapp_id << " not found for unregistration";
85 return ErrorCode::DAPP_NOT_REGISTERED;
86 }
87
88 // Track affected RAN functions before removing subscriptions
89 std::vector<std::pair<uint32_t, bool>> affected_ran_functions;
90
91 auto sub_it = dapp_subscriptions_.find(dapp_id);
92 if (sub_it != dapp_subscriptions_.end()) {
93 for (uint32_t ran_func : sub_it->second) {
94 // Check if it currently has subscribers (before we remove this dApp)
95 auto& subscribers = ran_function_subscribers_[ran_func];
96 bool had_subscribers = !subscribers.empty();
97
98 // Remove this dApp from the RAN function's subscriber list
99 subscribers.erase(dapp_id);
100
101 // Track whether this RAN function still has subscribers
102 bool still_has_subscribers = !subscribers.empty();
103 if (had_subscribers && !still_has_subscribers) {
104 affected_ran_functions.emplace_back(ran_func, false); // should_start = false
105 }
106
107 // Remove subscription ID mapping and details
108 uint64_t sub_key = make_sub_key(dapp_id, ran_func);
109 auto sub_id_it = subscription_id_reverse_.find(sub_key);
110 if (sub_id_it != subscription_id_reverse_.end()) {
111 subscription_ids_.erase(sub_id_it->second);
112 subscription_id_reverse_.erase(sub_id_it);
113 }
114 subscription_details_.erase(sub_key);
115 }
116 dapp_subscriptions_.erase(sub_it);
117 }
118
119 // Remove the dApp registration
120 registered_dapps_.erase(it);
121
122 size_t subscriptions_removed = affected_ran_functions.size();
123 E3_LOG_INFO(LOG_TAG) << "dApp " << dapp_id << " unregistered, "
124 << subscriptions_removed << " subscriptions removed";
125
126 // Notify about SM lifecycle changes (outside the lock to avoid deadlock)
127 lock.unlock();
128 for (const auto& [ran_func, should_start] : affected_ran_functions) {
129 check_sm_lifecycle(ran_func, true); // had_subscribers = true
130 }
131
132 return ErrorCode::SUCCESS;
133}
134
135bool SubscriptionManager::is_dapp_registered(uint32_t dapp_id) const {
136 std::shared_lock lock(mutex_);
137 return registered_dapps_.count(dapp_id) > 0;
138}
139
140std::vector<uint32_t> SubscriptionManager::get_registered_dapps() const {
141 std::shared_lock lock(mutex_);
142 std::vector<uint32_t> result;
143 result.reserve(registered_dapps_.size());
144 for (const auto& [id, entry] : registered_dapps_) {
145 result.push_back(id);
146 }
147 return result;
148}
149
150// =========================================================================
151// Subscription Management
152// =========================================================================
153
154std::pair<ErrorCode, uint32_t> SubscriptionManager::add_subscription(
155 uint32_t dapp_id,
156 uint32_t ran_function_id,
157 const std::vector<uint32_t>& telemetry_ids,
158 const std::vector<uint32_t>& control_ids,
159 uint32_t periodicity_us) {
160 std::unique_lock lock(mutex_);
161
162 // Check if dApp is registered
163 if (registered_dapps_.count(dapp_id) == 0) {
164 E3_LOG_ERROR(LOG_TAG) << "dApp " << dapp_id << " not registered, cannot add subscription";
165 return {ErrorCode::DAPP_NOT_REGISTERED, 0};
166 }
167
168 // Check if subscription already exists
169 auto& dapp_subs = dapp_subscriptions_[dapp_id];
170 if (dapp_subs.count(ran_function_id) > 0) {
171 // Return existing subscription ID
172 uint64_t sub_key = make_sub_key(dapp_id, ran_function_id);
173 uint32_t existing_sub_id = subscription_id_reverse_[sub_key];
174 E3_LOG_WARN(LOG_TAG) << "dApp " << dapp_id << " already subscribed to RAN function "
175 << ran_function_id << " (subscription_id=" << existing_sub_id << ")";
176 return {ErrorCode::SUBSCRIPTION_EXISTS, existing_sub_id};
177 }
178
179 // Check if this is the first subscriber for this RAN function
180 bool had_subscribers = !ran_function_subscribers_[ran_function_id].empty();
181
182 // Assign subscription ID
183 const uint32_t subscription_id = lowest_free_id(subscription_ids_);
184 if (subscription_id == 0) {
185 E3_LOG_ERROR(LOG_TAG) << "No available subscription IDs (range " << MIN_ID
186 << ".." << MAX_ID << ")";
187 return {ErrorCode::INTERNAL_ERROR, 0};
188 }
189
190 // Add subscription
191 dapp_subs.insert(ran_function_id);
192 ran_function_subscribers_[ran_function_id].insert(dapp_id);
193
194 // Add subscription ID mappings
195 subscription_ids_[subscription_id] = {dapp_id, ran_function_id};
196 uint64_t sub_key = make_sub_key(dapp_id, ran_function_id);
197 subscription_id_reverse_[sub_key] = subscription_id;
198
199 // Store per-subscription metadata
200 SubscriptionDetails details;
201 details.telemetry_ids = telemetry_ids;
202 details.control_ids = control_ids;
203 details.periodicity_us = periodicity_us;
204 subscription_details_[sub_key] = std::move(details);
205
206 E3_LOG_INFO(LOG_TAG) << "Subscription added: dApp " << dapp_id
207 << " -> RAN function " << ran_function_id
208 << " (subscription_id=" << subscription_id
209 << ", periodicity_us=" << periodicity_us
210 << ", telemetry_ids=" << telemetry_ids.size()
211 << ", control_ids=" << control_ids.size() << ")";
212
213 // Notify about SM lifecycle change if this is the first subscriber
214 lock.unlock();
215 if (!had_subscribers) {
216 check_sm_lifecycle(ran_function_id, false); // had_subscribers = false
217 }
218
219 return {ErrorCode::SUCCESS, subscription_id};
220}
221
222ErrorCode SubscriptionManager::remove_subscription(uint32_t dapp_id, uint32_t ran_function_id) {
223 std::unique_lock lock(mutex_);
224
225 auto dapp_it = dapp_subscriptions_.find(dapp_id);
226 if (dapp_it == dapp_subscriptions_.end()) {
227 return ErrorCode::SUBSCRIPTION_NOT_FOUND;
228 }
229
230 auto& dapp_subs = dapp_it->second;
231 if (dapp_subs.erase(ran_function_id) == 0) {
232 E3_LOG_WARN(LOG_TAG) << "Subscription not found for dApp " << dapp_id
233 << " -> RAN function " << ran_function_id;
234 return ErrorCode::SUBSCRIPTION_NOT_FOUND;
235 }
236
237 // Remove from reverse index
238 auto& subscribers = ran_function_subscribers_[ran_function_id];
239 bool had_subscribers = !subscribers.empty();
240 subscribers.erase(dapp_id);
241 bool still_has_subscribers = !subscribers.empty();
242
243 // Remove subscription ID mappings and details
244 uint64_t sub_key = make_sub_key(dapp_id, ran_function_id);
245 auto sub_id_it = subscription_id_reverse_.find(sub_key);
246 if (sub_id_it != subscription_id_reverse_.end()) {
247 subscription_ids_.erase(sub_id_it->second);
248 subscription_id_reverse_.erase(sub_id_it);
249 }
250 subscription_details_.erase(sub_key);
251
252 E3_LOG_INFO(LOG_TAG) << "Subscription removed: dApp " << dapp_id
253 << " -> RAN function " << ran_function_id;
254
255 // Notify about SM lifecycle change if this was the last subscriber
256 lock.unlock();
257 if (had_subscribers && !still_has_subscribers) {
258 check_sm_lifecycle(ran_function_id, true); // had_subscribers = true
259 }
260
261 return ErrorCode::SUCCESS;
262}
263
264ErrorCode SubscriptionManager::remove_subscription_by_id(uint32_t dapp_id, uint32_t subscription_id) {
265 std::unique_lock lock(mutex_);
266
267 // Find the subscription by ID
268 auto sub_it = subscription_ids_.find(subscription_id);
269 if (sub_it == subscription_ids_.end()) {
270 E3_LOG_WARN(LOG_TAG) << "Subscription ID " << subscription_id << " not found";
271 return ErrorCode::SUBSCRIPTION_NOT_FOUND;
272 }
273
274 auto [sub_dapp_id, ran_function_id] = sub_it->second;
275
276 // Verify the dApp ID matches
277 if (sub_dapp_id != dapp_id) {
278 E3_LOG_WARN(LOG_TAG) << "Subscription ID " << subscription_id
279 << " does not belong to dApp " << dapp_id;
280 return ErrorCode::SUBSCRIPTION_NOT_FOUND;
281 }
282
283 // Remove from dApp subscriptions
284 auto dapp_it = dapp_subscriptions_.find(dapp_id);
285 if (dapp_it != dapp_subscriptions_.end()) {
286 dapp_it->second.erase(ran_function_id);
287 }
288
289 // Remove from reverse index
290 auto& subscribers = ran_function_subscribers_[ran_function_id];
291 bool had_subscribers = !subscribers.empty();
292 subscribers.erase(dapp_id);
293 bool still_has_subscribers = !subscribers.empty();
294
295 // Remove subscription ID mappings and details
296 uint64_t sub_key = make_sub_key(dapp_id, ran_function_id);
297 subscription_id_reverse_.erase(sub_key);
298 subscription_ids_.erase(sub_it);
299 subscription_details_.erase(sub_key);
300
301 E3_LOG_INFO(LOG_TAG) << "Subscription " << subscription_id << " removed: dApp " << dapp_id
302 << " -> RAN function " << ran_function_id;
303
304 // Notify about SM lifecycle change if this was the last subscriber
305 lock.unlock();
306 if (had_subscribers && !still_has_subscribers) {
307 check_sm_lifecycle(ran_function_id, true); // had_subscribers = true
308 }
309
310 return ErrorCode::SUCCESS;
311}
312
313bool SubscriptionManager::is_subscribed(uint32_t dapp_id, uint32_t ran_function_id) const {
314 std::shared_lock lock(mutex_);
315
316 auto it = dapp_subscriptions_.find(dapp_id);
317 if (it == dapp_subscriptions_.end()) {
318 return false;
319 }
320 return it->second.count(ran_function_id) > 0;
321}
322
323std::vector<uint32_t> SubscriptionManager::get_dapp_subscriptions(uint32_t dapp_id) const {
324 std::shared_lock lock(mutex_);
325
326 auto it = dapp_subscriptions_.find(dapp_id);
327 if (it == dapp_subscriptions_.end()) {
328 return {};
329 }
330 return std::vector<uint32_t>(it->second.begin(), it->second.end());
331}
332
333std::vector<uint32_t> SubscriptionManager::get_subscribed_dapps(uint32_t ran_function_id) const {
334 std::shared_lock lock(mutex_);
335
336 auto it = ran_function_subscribers_.find(ran_function_id);
337 if (it == ran_function_subscribers_.end()) {
338 return {};
339 }
340 return std::vector<uint32_t>(it->second.begin(), it->second.end());
341}
342
344 uint32_t dapp_id, uint32_t ran_function_id) const {
345 std::shared_lock lock(mutex_);
346
347 auto it = subscription_details_.find(make_sub_key(dapp_id, ran_function_id));
348 if (it == subscription_details_.end()) {
349 return nullptr;
350 }
351 return &it->second;
352}
353
354size_t SubscriptionManager::get_subscriber_count(uint32_t ran_function_id) const {
355 std::shared_lock lock(mutex_);
356
357 auto it = ran_function_subscribers_.find(ran_function_id);
358 if (it == ran_function_subscribers_.end()) {
359 return 0;
360 }
361 return it->second.size();
362}
363
364// =========================================================================
365// RAN Function Management
366// =========================================================================
367
369 std::shared_lock lock(mutex_);
370
371 std::vector<uint32_t> result;
372 for (const auto& [ran_func, subscribers] : ran_function_subscribers_) {
373 if (!subscribers.empty()) {
374 result.push_back(ran_func);
375 }
376 }
377 return result;
378}
379
380bool SubscriptionManager::has_subscribers(uint32_t ran_function_id) const {
381 std::shared_lock lock(mutex_);
382
383 auto it = ran_function_subscribers_.find(ran_function_id);
384 return it != ran_function_subscribers_.end() && !it->second.empty();
385}
386
387// =========================================================================
388// Statistics
389// =========================================================================
390
392 std::shared_lock lock(mutex_);
393 return registered_dapps_.size();
394}
395
397 std::shared_lock lock(mutex_);
398
399 size_t count = 0;
400 for (const auto& [dapp_id, subs] : dapp_subscriptions_) {
401 count += subs.size();
402 }
403 return count;
404}
405
407 std::unique_lock lock(mutex_);
408 registered_dapps_.clear();
409 dapp_subscriptions_.clear();
410 ran_function_subscribers_.clear();
411 subscription_ids_.clear();
412 subscription_id_reverse_.clear();
413 subscription_details_.clear();
414 E3_LOG_INFO(LOG_TAG) << "All registrations and subscriptions cleared";
415}
416
417// =========================================================================
418// Private Methods
419// =========================================================================
420
421void SubscriptionManager::check_sm_lifecycle(uint32_t ran_function_id, bool had_subscribers) {
422 if (!sm_lifecycle_callback_) {
423 return;
424 }
425
426 // Determine if we need to start or stop
427 bool has_subscribers_now = has_subscribers(ran_function_id);
428
429 if (!had_subscribers && has_subscribers_now) {
430 // First subscriber - start SM
431 E3_LOG_INFO(LOG_TAG) << "Starting SM for RAN function " << ran_function_id
432 << " (first subscriber)";
433 sm_lifecycle_callback_(ran_function_id, true);
434 } else if (had_subscribers && !has_subscribers_now) {
435 // Last subscriber gone - stop SM
436 E3_LOG_INFO(LOG_TAG) << "Stopping SM for RAN function " << ran_function_id
437 << " (no remaining subscribers)";
438 sm_lifecycle_callback_(ran_function_id, false);
439 }
440}
441
442} // namespace libe3
std::vector< uint32_t > get_active_ran_functions() const
Get all RAN functions that have at least one subscriber.
std::vector< uint32_t > get_registered_dapps() const
Get list of registered dApp IDs.
ErrorCode remove_subscription(uint32_t dapp_id, uint32_t ran_function_id)
Remove a subscription between dApp and RAN function.
bool is_subscribed(uint32_t dapp_id, uint32_t ran_function_id) const
Check if dApp is subscribed to a RAN function.
void set_sm_lifecycle_callback(SmLifecycleCallback callback)
Set callback for SM lifecycle events.
ErrorCode unregister_dapp(uint32_t dapp_id)
Unregister a dApp and clean up all its subscriptions.
std::pair< ErrorCode, uint32_t > register_dapp()
Register a dApp from E3 Setup Request.
size_t get_subscriber_count(uint32_t ran_function_id) const
Get count of subscribers for a RAN function.
const SubscriptionDetails * get_subscription_details(uint32_t dapp_id, uint32_t ran_function_id) const
Get subscription details for a specific dApp + RAN function pair.
bool has_subscribers(uint32_t ran_function_id) const
Check if any dApp is subscribed to a RAN function.
void clear()
Clear all registrations and subscriptions.
std::vector< uint32_t > get_dapp_subscriptions(uint32_t dapp_id) const
Get all RAN functions a dApp is subscribed to.
bool is_dapp_registered(uint32_t dapp_id) const
Check if a dApp is registered.
SubscriptionManager()
Construct a new Subscription Manager.
std::vector< uint32_t > get_subscribed_dapps(uint32_t ran_function_id) const
Get all dApps subscribed to a RAN function.
size_t subscription_count() const
Get total number of active subscriptions.
ErrorCode remove_subscription_by_id(uint32_t dapp_id, uint32_t subscription_id)
Remove a subscription by its ID.
std::pair< ErrorCode, uint32_t > add_subscription(uint32_t dapp_id, uint32_t ran_function_id, const std::vector< uint32_t > &telemetry_ids={}, const std::vector< uint32_t > &control_ids={}, uint32_t periodicity_us=0)
Add a subscription between dApp and RAN function.
size_t dapp_count() const
Get total number of registered dApps.
Logging facility for libe3.
#define E3_LOG_WARN(component)
Definition logger.hpp:226
#define E3_LOG_DEBUG(component)
Definition logger.hpp:228
#define E3_LOG_INFO(component)
Definition logger.hpp:227
#define E3_LOG_ERROR(component)
Definition logger.hpp:225
ErrorCode
Error codes returned by libe3 operations Use shared error-code list (see libe3/error_codes....
Definition types.hpp:141
std::function< void(uint32_t ran_function_id, bool should_start)> SmLifecycleCallback
Callback type for SM lifecycle events.
dApp registration entry
Definition types.hpp:496
Timestamp registered_time
Monotonic timestamp of when the dApp registered.
Definition types.hpp:498
uint32_t dapp_identifier
Assigned dApp identifier (1–100).
Definition types.hpp:497
Per-subscription metadata stored by the SubscriptionManager.
uint32_t periodicity_us
0 = use SM default
std::vector< uint32_t > control_ids
std::vector< uint32_t > telemetry_ids
E3 Subscription Manager for tracking dApp registrations.