Skip to content
Merged
4 changes: 4 additions & 0 deletions CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ We are grateful for every contribution, big or small, that helped shape this pro

- Filippo Olimpieri <126257005+Ninjabippo1205@users.noreply.github.com>
- Callback mechanism for dApp status reporting to the E2 Agent

- Davide Villa <dvilla@nvidia.com> (NVIDIA Corporation)
- JSON encoder: camelCase wire format with nested-JSON protocolData
- Subscription manager: per-subscription metadata, C API subscription getters
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.5
0.0.6
41 changes: 41 additions & 0 deletions include/libe3/c_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,47 @@ uint32_t* e3_agent_get_dapp_subscriptions(e3_agent_handle_t* agent, uint32_t dap
*/
uint32_t* e3_agent_get_ran_function_subscribers(e3_agent_handle_t* agent, uint32_t ran_function_id, size_t* out_len);

/**
* @brief Get the reporting periodicity a dApp requested for a RAN function.
*
* @return periodicity in microseconds, 0 if not set or subscription not found
*/
uint32_t e3_agent_get_subscription_periodicity(
e3_agent_handle_t* agent,
uint32_t dapp_id,
uint32_t ran_function_id
);

/**
* @brief Get the telemetry IDs a dApp requested for a RAN function.
*
* Caller must free the returned array with e3_agent_free_uint32_array().
*
* @param out_len receives the number of elements (set to 0 on error/empty)
* @return newly-allocated array, or NULL if empty/error
*/
uint32_t* e3_agent_get_subscription_telemetry_ids(
e3_agent_handle_t* agent,
uint32_t dapp_id,
uint32_t ran_function_id,
size_t* out_len
);

/**
* @brief Get the control IDs a dApp requested for a RAN function.
*
* Caller must free the returned array with e3_agent_free_uint32_array().
*
* @param out_len receives the number of elements (set to 0 on error/empty)
* @return newly-allocated array, or NULL if empty/error
*/
uint32_t* e3_agent_get_subscription_control_ids(
e3_agent_handle_t* agent,
uint32_t dapp_id,
uint32_t ran_function_id,
size_t* out_len
);

/**
* @brief Free an array previously returned by the e3_agent_get_* helpers.
*/
Expand Down
27 changes: 27 additions & 0 deletions include/libe3/e3_agent.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,33 @@ class E3Agent {
uint32_t ran_function_id
) const;

/**
* @brief Get the reporting periodicity a dApp requested for a RAN function
*
* @return periodicity in microseconds, 0 if not set or subscription not found
*/
uint32_t get_subscription_periodicity(
uint32_t dapp_id, uint32_t ran_function_id
) const;

/**
* @brief Get the telemetry IDs a dApp requested for a RAN function
*
* @return vector of telemetry IDs (empty if none requested or subscription not found)
*/
std::vector<uint32_t> get_subscription_telemetry_ids(
uint32_t dapp_id, uint32_t ran_function_id
) const;

/**
* @brief Get the control IDs a dApp requested for a RAN function
*
* @return vector of control IDs (empty if none requested or subscription not found)
*/
std::vector<uint32_t> get_subscription_control_ids(
uint32_t dapp_id, uint32_t ran_function_id
) const;

// =========================================================================
// Configuration
// =========================================================================
Expand Down
29 changes: 28 additions & 1 deletion include/libe3/subscription_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@

namespace libe3 {

/**
* @brief Per-subscription metadata stored by the SubscriptionManager.
*/
struct SubscriptionDetails {
Comment thread
Thecave3 marked this conversation as resolved.
std::vector<uint32_t> telemetry_ids;
std::vector<uint32_t> control_ids;
uint32_t periodicity_us{0}; ///< 0 = use SM default
};
Comment thread
Thecave3 marked this conversation as resolved.

/**
* @brief Callback type for SM lifecycle events
*
Expand Down Expand Up @@ -116,13 +125,21 @@ class SubscriptionManager {
*
* @param dapp_id dApp identifier
* @param ran_function_id RAN function identifier (0-255)
* @param telemetry_ids Telemetry IDs the dApp wants
* @param control_ids Control IDs the dApp wants
* @param periodicity_us Reporting periodicity in microseconds (0 = SM default)
* @return std::pair containing:
* - ErrorCode::SUCCESS on success, or error code on failure
* - The assigned subscription ID (valid only if ErrorCode::SUCCESS)
* @return ErrorCode::DAPP_NOT_REGISTERED if dApp not registered
* @return ErrorCode::SUBSCRIPTION_EXISTS if already subscribed
*/
std::pair<ErrorCode, uint32_t> add_subscription(uint32_t dapp_id, uint32_t ran_function_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);

/**
* @brief Remove a subscription between dApp and RAN function
Expand Down Expand Up @@ -159,6 +176,13 @@ class SubscriptionManager {
*/
std::vector<uint32_t> get_subscribed_dapps(uint32_t ran_function_id) const;

/**
* @brief Get subscription details for a specific dApp + RAN function pair
*
* @return nullptr if no such subscription exists
*/
const SubscriptionDetails* get_subscription_details(uint32_t dapp_id, uint32_t ran_function_id) const;

/**
* @brief Get count of subscribers for a RAN function
*/
Expand Down Expand Up @@ -215,6 +239,9 @@ class SubscriptionManager {

// (dApp ID, RAN function ID) -> Subscription ID reverse mapping
std::unordered_map<uint64_t, uint32_t> subscription_id_reverse_;

// (dApp ID, RAN function ID) -> per-subscription metadata
std::unordered_map<uint64_t, SubscriptionDetails> subscription_details_;

// Callback for SM lifecycle events
SmLifecycleCallback sm_lifecycle_callback_;
Expand Down
31 changes: 31 additions & 0 deletions src/c_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,37 @@ e3_error_t e3_agent_set_dapp_report_handler(
return copy_vector_u32_to_c(v, out_len);
}

uint32_t e3_agent_get_subscription_periodicity(
e3_agent_handle_t* agent,
uint32_t dapp_id,
uint32_t ran_function_id
) {
if (!agent || !agent->agent) return 0;
return agent->agent->get_subscription_periodicity(dapp_id, ran_function_id);
}

uint32_t* e3_agent_get_subscription_telemetry_ids(
e3_agent_handle_t* agent,
uint32_t dapp_id,
uint32_t ran_function_id,
size_t* out_len
) {
if (!agent || !agent->agent) { if (out_len) *out_len = 0; return nullptr; }
auto v = agent->agent->get_subscription_telemetry_ids(dapp_id, ran_function_id);
return copy_vector_u32_to_c(v, out_len);
}

uint32_t* e3_agent_get_subscription_control_ids(
e3_agent_handle_t* agent,
uint32_t dapp_id,
uint32_t ran_function_id,
size_t* out_len
) {
if (!agent || !agent->agent) { if (out_len) *out_len = 0; return nullptr; }
auto v = agent->agent->get_subscription_control_ids(dapp_id, ran_function_id);
return copy_vector_u32_to_c(v, out_len);
}

void e3_agent_free_uint32_array(uint32_t* arr) {
free(arr);
}
Expand Down
27 changes: 27 additions & 0 deletions src/core/e3_agent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,33 @@ std::vector<uint32_t> E3Agent::get_ran_function_subscribers(uint32_t ran_functio
return impl_->interface->subscription_manager().get_subscribed_dapps(ran_function_id);
}

uint32_t E3Agent::get_subscription_periodicity(uint32_t dapp_id, uint32_t ran_function_id) const {
if (!impl_->interface) {
return 0;
}
const auto* details = impl_->interface->subscription_manager()
.get_subscription_details(dapp_id, ran_function_id);
return details ? details->periodicity_us : 0;
Comment thread
Thecave3 marked this conversation as resolved.
}

std::vector<uint32_t> E3Agent::get_subscription_telemetry_ids(uint32_t dapp_id, uint32_t ran_function_id) const {
if (!impl_->interface) {
return {};
}
const auto* details = impl_->interface->subscription_manager()
.get_subscription_details(dapp_id, ran_function_id);
return details ? details->telemetry_ids : std::vector<uint32_t>{};
Comment thread
Thecave3 marked this conversation as resolved.
}

std::vector<uint32_t> E3Agent::get_subscription_control_ids(uint32_t dapp_id, uint32_t ran_function_id) const {
if (!impl_->interface) {
return {};
}
const auto* details = impl_->interface->subscription_manager()
.get_subscription_details(dapp_id, ran_function_id);
return details ? details->control_ids : std::vector<uint32_t>{};
}

// =========================================================================
// Configuration
// =========================================================================
Expand Down
22 changes: 15 additions & 7 deletions src/core/e3_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -626,9 +626,13 @@ void E3Interface::handle_subscription_request(const SubscriptionRequest& request
if (!subscription_manager_->is_dapp_registered(request.dapp_identifier)) {
E3_LOG_ERROR(LOG_TAG) << "dApp " << request.dapp_identifier << " not registered";
} else {
uint32_t period = request.periodicity.value_or(0);
auto [result, sub_id] = subscription_manager_->add_subscription(
request.dapp_identifier,
request.ran_function_identifier
request.ran_function_identifier,
request.telemetry_identifier_list,
request.control_identifier_list,
period
);
subscription_id = sub_id;

Expand Down Expand Up @@ -681,13 +685,17 @@ void E3Interface::handle_subscription_delete(const SubscriptionDelete& del, uint
<< error_code_to_string(result);
}

// Create and queue ack response
Pdu response_pdu(PduType::MESSAGE_ACK);
MessageAck ack;
// Create and queue response
Pdu response_pdu(PduType::SUBSCRIPTION_RESPONSE);
SubscriptionResponse resp;
response_pdu.message_id = generate_message_id();
ack.request_id = request_message_id;
ack.response_code = response_code;
response_pdu.choice = ack;
resp.request_id = request_message_id;
resp.dapp_identifier = del.dapp_identifier;
resp.response_code = response_code;
if (response_code == ResponseCode::POSITIVE) {
Comment thread
Thecave3 marked this conversation as resolved.
resp.subscription_id = del.subscription_id;
}
response_pdu.choice = resp;

queue_outbound(std::move(response_pdu));
}
Expand Down
45 changes: 39 additions & 6 deletions src/core/subscription_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,14 @@ ErrorCode SubscriptionManager::unregister_dapp(uint32_t dapp_id) {
affected_ran_functions.emplace_back(ran_func, false); // should_start = false
}

// Remove subscription ID mapping
// Remove subscription ID mapping and details
uint64_t sub_key = make_sub_key(dapp_id, ran_func);
auto sub_id_it = subscription_id_reverse_.find(sub_key);
if (sub_id_it != subscription_id_reverse_.end()) {
subscription_ids_.erase(sub_id_it->second);
subscription_id_reverse_.erase(sub_id_it);
}
subscription_details_.erase(sub_key);
}
dapp_subscriptions_.erase(sub_it);
}
Expand Down Expand Up @@ -155,7 +156,12 @@ std::vector<uint32_t> SubscriptionManager::get_registered_dapps() const {
// Subscription Management
// =========================================================================

std::pair<ErrorCode, uint32_t> SubscriptionManager::add_subscription(uint32_t dapp_id, uint32_t ran_function_id) {
std::pair<ErrorCode, uint32_t> SubscriptionManager::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) {
std::unique_lock lock(mutex_);

// Check if dApp is registered
Expand Down Expand Up @@ -190,11 +196,22 @@ std::pair<ErrorCode, uint32_t> SubscriptionManager::add_subscription(uint32_t da

// Add subscription ID mappings
subscription_ids_[subscription_id] = {dapp_id, ran_function_id};
subscription_id_reverse_[make_sub_key(dapp_id, ran_function_id)] = subscription_id;
uint64_t sub_key = make_sub_key(dapp_id, ran_function_id);
subscription_id_reverse_[sub_key] = subscription_id;

// Store per-subscription metadata
SubscriptionDetails details;
details.telemetry_ids = telemetry_ids;
details.control_ids = control_ids;
details.periodicity_us = periodicity_us;
subscription_details_[sub_key] = std::move(details);

E3_LOG_INFO(LOG_TAG) << "Subscription added: dApp " << dapp_id
<< " -> RAN function " << ran_function_id
<< " (subscription_id=" << subscription_id << ")";
<< " (subscription_id=" << subscription_id
<< ", periodicity_us=" << periodicity_us
<< ", telemetry_ids=" << telemetry_ids.size()
<< ", control_ids=" << control_ids.size() << ")";

// Notify about SM lifecycle change if this is the first subscriber
lock.unlock();
Expand Down Expand Up @@ -226,13 +243,14 @@ ErrorCode SubscriptionManager::remove_subscription(uint32_t dapp_id, uint32_t ra
subscribers.erase(dapp_id);
bool still_has_subscribers = !subscribers.empty();

// Remove subscription ID mappings
// Remove subscription ID mappings and details
uint64_t sub_key = make_sub_key(dapp_id, ran_function_id);
auto sub_id_it = subscription_id_reverse_.find(sub_key);
if (sub_id_it != subscription_id_reverse_.end()) {
subscription_ids_.erase(sub_id_it->second);
subscription_id_reverse_.erase(sub_id_it);
}
subscription_details_.erase(sub_key);

E3_LOG_INFO(LOG_TAG) << "Subscription removed: dApp " << dapp_id
<< " -> RAN function " << ran_function_id;
Expand Down Expand Up @@ -277,10 +295,11 @@ ErrorCode SubscriptionManager::remove_subscription_by_id(uint32_t dapp_id, uint3
subscribers.erase(dapp_id);
bool still_has_subscribers = !subscribers.empty();

// Remove subscription ID mappings
// Remove subscription ID mappings and details
uint64_t sub_key = make_sub_key(dapp_id, ran_function_id);
subscription_id_reverse_.erase(sub_key);
subscription_ids_.erase(sub_it);
subscription_details_.erase(sub_key);

E3_LOG_INFO(LOG_TAG) << "Subscription " << subscription_id << " removed: dApp " << dapp_id
<< " -> RAN function " << ran_function_id;
Expand Down Expand Up @@ -324,6 +343,17 @@ std::vector<uint32_t> SubscriptionManager::get_subscribed_dapps(uint32_t ran_fun
return std::vector<uint32_t>(it->second.begin(), it->second.end());
}

const SubscriptionDetails* SubscriptionManager::get_subscription_details(
uint32_t dapp_id, uint32_t ran_function_id) const {
std::shared_lock lock(mutex_);

auto it = subscription_details_.find(make_sub_key(dapp_id, ran_function_id));
if (it == subscription_details_.end()) {
return nullptr;
}
return &it->second;
}

size_t SubscriptionManager::get_subscriber_count(uint32_t ran_function_id) const {
std::shared_lock lock(mutex_);

Expand Down Expand Up @@ -381,6 +411,9 @@ void SubscriptionManager::clear() {
registered_dapps_.clear();
dapp_subscriptions_.clear();
ran_function_subscribers_.clear();
subscription_ids_.clear();
subscription_id_reverse_.clear();
subscription_details_.clear();
E3_LOG_INFO(LOG_TAG) << "All registrations and subscriptions cleared";
}

Expand Down
Loading
Loading