26#include <sys/resource.h>
27#include <sys/syscall.h>
34constexpr const char* LOG_TAG =
"E3Iface";
44std::string default_log_path(
E3Role role,
const std::string&
id) {
46 san.reserve(
id.size());
48 san.push_back((std::isalnum(
static_cast<unsigned char>(c)) ||
49 c ==
'.' || c ==
'_' || c ==
'-')
53 std::string path =
"/tmp/e3_";
61 path += std::to_string(
static_cast<unsigned long>(geteuid()));
78void apply_thread_config(
int affinity,
int niceness,
const char* role)
noexcept {
83 CPU_SET(
static_cast<size_t>(affinity), &cpuset);
85 int rc = pthread_setaffinity_np(pthread_self(),
sizeof(cpu_set_t), &cpuset);
87 E3_LOG_WARN(LOG_TAG) <<
"Failed to pin " << role <<
" thread to core "
88 << affinity <<
": " << std::strerror(rc)
89 <<
" (thread left unpinned)";
91 E3_LOG_INFO(LOG_TAG) <<
"Pinned " << role <<
" thread to core " << affinity;
98 auto tid =
static_cast<id_t
>(
static_cast<pid_t
>(syscall(SYS_gettid)));
100 if (setpriority(PRIO_PROCESS, tid, niceness) == -1) {
101 E3_LOG_WARN(LOG_TAG) <<
"Failed to set niceness " << niceness <<
" on "
102 << role <<
" thread: " << std::strerror(errno);
118constexpr const char* LATREC_ROLE_OUTBOUND =
"libe3.outbound";
119constexpr const char* LATREC_ROLE_INBOUND =
"libe3.inbound";
120constexpr const char* LATREC_ROLE_REPORT =
"libe3.report";
121constexpr const char* LATREC_ROLE_SETUP =
"libe3.setup";
122#ifdef LIBE3_ENABLE_LATREC
123constexpr const char* LATREC_ROLE_CONTEXT =
"libe3.context";
132 const uint64_t n = next_message_id_.fetch_add(1, std::memory_order_relaxed);
133 return static_cast<uint32_t
>(n % 0xFFFFFFFFull) + 1;
136void E3Interface::remember_subscription_op(uint32_t request_id,
137 uint32_t ran_function_id,
139 std::lock_guard<std::mutex> lk(pending_subscriptions_mutex_);
140 pending_subscriptions_[request_id] = PendingSubscriptionOp{ran_function_id, is_delete};
143void E3Interface::forget_subscription_op(uint32_t request_id) {
144 std::lock_guard<std::mutex> lk(pending_subscriptions_mutex_);
145 pending_subscriptions_.erase(request_id);
151 if (request_id != 0) {
165 std::string log_path = config.log_path;
166 if (log_path.empty()) {
167 const std::string& id = (config.role == E3Role::DAPP)
169 : config.ran_identifier;
170 log_path = default_log_path(config.role, id);
185 E3_LOG_WARN(LOG_TAG) <<
"Interface already initialized";
186 return ErrorCode::ALREADY_INITIALIZED;
189 E3_LOG_INFO(LOG_TAG) <<
"Initializing E3Interface";
195 return ErrorCode::INTERNAL_ERROR;
200 subscription_manager_ = std::make_unique<SubscriptionManager>();
201 subscription_manager_->set_sm_lifecycle_callback(
202 [
this](uint32_t ran_function_id,
bool should_start) {
203 on_sm_lifecycle_change(ran_function_id, should_start);
207 dapp_state_ = std::make_unique<DAppSubscriptionState>();
211 response_queue_ = std::make_unique<LockFreeQueue<Pdu>>();
214 report_queue_ = std::make_unique<LockFreeQueue<DAppReport>>(1024);
219 config_.transport_layer,
220 config_.setup_endpoint,
221 config_.subscriber_endpoint,
222 config_.publisher_endpoint,
224 config_.subscriber_port,
225 config_.publisher_port,
232 return ErrorCode::INTERNAL_ERROR;
236 E3_LOG_INFO(LOG_TAG) <<
"E3Interface initialized successfully";
238 return ErrorCode::SUCCESS;
246 return ErrorCode::STATE_ERROR;
250 should_stop_.store(
false);
254 ? connector_->setup_initial_connection()
255 : connector_->setup_initial_connection_client();
256 if (conn_result != ErrorCode::SUCCESS) {
257 E3_LOG_ERROR(LOG_TAG) <<
"Failed to setup initial connection: "
268 setup_thread_ = std::make_unique<std::thread>(&E3Interface::setup_loop_ran,
this);
269 inbound_thread_ = std::make_unique<std::thread>(&E3Interface::inbound_loop_ran,
this);
270 outbound_thread_ = std::make_unique<std::thread>(&E3Interface::outbound_loop_ran,
this);
272 report_worker_thread_ = std::make_unique<std::thread>(&E3Interface::report_worker_loop,
this);
274 setup_thread_ = std::make_unique<std::thread>(&E3Interface::setup_loop_dapp,
this);
275 inbound_thread_ = std::make_unique<std::thread>(&E3Interface::inbound_loop_dapp,
this);
276 outbound_thread_ = std::make_unique<std::thread>(&E3Interface::outbound_loop_dapp,
this);
279#ifdef LIBE3_ENABLE_LATREC
280 context_thread_ = std::make_unique<std::thread>(&E3Interface::context_monitor_loop,
this);
284 E3_LOG_INFO(LOG_TAG) <<
"E3Interface started successfully";
286 return ErrorCode::SUCCESS;
297 should_stop_.store(
true);
300 if (response_queue_) {
301 response_queue_->shutdown();
306 report_queue_->shutdown();
311 connector_->shutdown();
315 if (setup_thread_ && setup_thread_->joinable()) {
316 setup_thread_->join();
318 if (inbound_thread_ && inbound_thread_->joinable()) {
319 inbound_thread_->join();
321 if (outbound_thread_ && outbound_thread_->joinable()) {
322 outbound_thread_->join();
324 if (report_worker_thread_ && report_worker_thread_->joinable()) {
325 report_worker_thread_->join();
327#ifdef LIBE3_ENABLE_LATREC
328 if (context_thread_ && context_thread_->joinable()) {
329 context_thread_->join();
335 std::lock_guard<std::mutex> lk(setup_complete_mu_);
336 setup_complete_ =
true;
338 setup_complete_cv_.notify_all();
351 connector_->dispose();
359 if (!response_queue_) {
360 return ErrorCode::NOT_INITIALIZED;
372 const ErrorCode rc = response_queue_->push(std::move(pdu));
373 if (rc != ErrorCode::SUCCESS) {
380 const uint64_t reason = (rc == ErrorCode::NOT_INITIALIZED)
393 return ErrorCode::INVALID_PARAM;
395 const uint32_t ran_function_id = sm->ran_function_id();
396 sm->set_subscribers_provider([
this, ran_function_id]() {
397 if (!subscription_manager_) {
398 return std::vector<uint32_t>{};
400 return subscription_manager_->get_subscribed_dapps(ran_function_id);
402 sm->set_outbound_emitter([
this, ran_function_id](
Pdu&& pdu) {
403 if (pdu.message_id == 0) {
424 if (dapp_status_changed_handler_) {
425 dapp_status_changed_handler_();
433void E3Interface::setup_loop_ran() {
436 E3_LOG_INFO(LOG_TAG) <<
"Setup loop (RAN) started";
440 while (!should_stop_.load()) {
441 std::vector<uint8_t> buffer;
442 int ret = connector_->recv_setup_request(buffer);
445 if (should_stop_.load())
break;
449 E3_LOG_INFO(LOG_TAG) <<
"Setup request received: " << ret <<
" bytes";
454 auto decode_result = encoder_->decode(buffer.data(),
static_cast<size_t>(ret));
455 if (!decode_result) {
456 E3_LOG_WARN(LOG_TAG) <<
"Undecodable setup message (" << ret
457 <<
" bytes, wrong encoding or garbage); rejecting";
458 send_negative_setup_reply(0);
462 Pdu& pdu = *decode_result;
463 if (pdu.type != PduType::SETUP_REQUEST) {
464 E3_LOG_WARN(LOG_TAG) <<
"Unexpected PDU type in setup: "
466 send_negative_setup_reply(pdu.message_id);
470 auto* request = std::get_if<SetupRequest>(&pdu.choice);
472 E3_LOG_WARN(LOG_TAG) <<
"Failed to get SetupRequest from PDU; rejecting";
473 send_negative_setup_reply(pdu.message_id);
477 handle_setup_request(*request, pdu.message_id);
480 E3_LOG_INFO(LOG_TAG) <<
"Setup loop (RAN) stopped";
483void E3Interface::inbound_loop_ran() {
484 apply_thread_config(config_.io_thread_affinity, config_.io_thread_niceness, LATREC_ROLE_INBOUND);
485 E3_LOG_INFO(LOG_TAG) <<
"Inbound loop (RAN) started";
487 ErrorCode result = connector_->setup_inbound_connection();
488 if (result != ErrorCode::SUCCESS) {
489 E3_LOG_ERROR(LOG_TAG) <<
"Failed to setup inbound connection";
493 std::vector<uint8_t> buffer;
495 while (!should_stop_.load()) {
496 int ret = connector_->receive(buffer);
499 if (should_stop_.load())
break;
508 auto decode_result = encoder_->decode(buffer.data(),
static_cast<size_t>(ret));
509 if (!decode_result) {
510 E3_LOG_ERROR(LOG_TAG) <<
"Failed to decode PDU in subscriber";
515 Pdu& pdu = *decode_result;
519 case PduType::SUBSCRIPTION_REQUEST: {
520 auto* request = std::get_if<SubscriptionRequest>(&pdu.choice);
522 handle_subscription_request(*request, pdu.message_id);
527 case PduType::SUBSCRIPTION_DELETE: {
528 auto* del = std::get_if<SubscriptionDelete>(&pdu.choice);
530 handle_subscription_delete(*del, pdu.message_id);
535 case PduType::DAPP_CONTROL_ACTION: {
536 auto* action = std::get_if<DAppControlAction>(&pdu.choice);
538 handle_control_action(*action, pdu.message_id, seq);
543 case PduType::DAPP_REPORT: {
544 auto* report = std::get_if<DAppReport>(&pdu.choice);
550 report->recv_seq = seq;
558 const ErrorCode report_rc = report_queue_->push(std::move(*report));
559 if (report_rc != ErrorCode::SUCCESS) {
560 E3_LOG_ERROR(LOG_TAG) <<
"Report queue full — dropping dApp report";
564 const uint64_t reason = (report_rc == ErrorCode::NOT_INITIALIZED)
575 case PduType::RELEASE_MESSAGE: {
576 auto* release = std::get_if<ReleaseMessage>(&pdu.choice);
578 handle_release_message(*release);
584 E3_LOG_WARN(LOG_TAG) <<
"Received unexpected PDU type: "
591 E3_LOG_INFO(LOG_TAG) <<
"Inbound loop (RAN) stopped";
594void E3Interface::outbound_loop_ran() {
595 apply_thread_config(config_.io_thread_affinity, config_.io_thread_niceness, LATREC_ROLE_OUTBOUND);
596 E3_LOG_INFO(LOG_TAG) <<
"Outbound loop (RAN) started";
599 signal(SIGPIPE, SIG_IGN);
605 ErrorCode result = connector_->setup_outbound_connection();
606 while (result != ErrorCode::SUCCESS && !should_stop_.load()) {
607 E3_LOG_WARN(LOG_TAG) <<
"Outbound connection setup failed ("
609 std::this_thread::sleep_for(std::chrono::seconds(1));
610 if (should_stop_.load())
return;
611 result = connector_->setup_outbound_connection();
614 if (result != ErrorCode::SUCCESS) {
615 E3_LOG_ERROR(LOG_TAG) <<
"Failed to setup outbound connection";
618 E3_LOG_INFO(LOG_TAG) <<
"Outbound connection established";
620 while (!should_stop_.load()) {
622 auto pdu_opt = response_queue_->pop(std::chrono::milliseconds(10));
630 auto encode_result = encoder_->encode(*pdu_opt);
631 if (!encode_result) {
632 E3_LOG_ERROR(LOG_TAG) <<
"Failed to encode PDU for sending";
639 ErrorCode send_result = connector_->send(encode_result->buffer);
640 if (send_result != ErrorCode::SUCCESS) {
644 const uint64_t reason = (send_result == ErrorCode::CANCELLED)
653 E3_LOG_INFO(LOG_TAG) <<
"Outbound loop (RAN) stopped";
656void E3Interface::report_worker_loop() {
657 apply_thread_config(config_.io_thread_affinity, config_.io_thread_niceness, LATREC_ROLE_REPORT);
658 E3_LOG_INFO(LOG_TAG) <<
"Report worker loop started";
660 while (!should_stop_.load()) {
663 auto report_opt = report_queue_->pop(std::chrono::milliseconds(10));
665 handle_dapp_report(*report_opt);
671 while (
auto report_opt = report_queue_->try_pop()) {
672 handle_dapp_report(*report_opt);
675 E3_LOG_INFO(LOG_TAG) <<
"Report worker loop stopped";
678#ifdef LIBE3_ENABLE_LATREC
679void E3Interface::context_monitor_loop() {
683 long prev_nivcsw = 0;
686 if (getrusage(RUSAGE_SELF, &ru) == 0) {
687 prev_nivcsw = ru.ru_nivcsw;
692 while (!should_stop_.load()) {
693 std::this_thread::sleep_for(std::chrono::seconds(1));
694 if (should_stop_.load()) {
698 uint64_t nivcsw_delta = 0;
699 uint64_t cur_freq_khz = 0;
702 if (getrusage(RUSAGE_SELF, &ru) == 0) {
703 nivcsw_delta =
static_cast<uint64_t
>(ru.ru_nivcsw - prev_nivcsw);
704 prev_nivcsw = ru.ru_nivcsw;
709 const int cpu = sched_getcpu();
712 std::snprintf(path,
sizeof(path),
713 "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq", cpu);
714 if (FILE* f = std::fopen(path,
"r")) {
716 if (std::fscanf(f,
"%ld", &khz) == 1) {
717 cur_freq_khz =
static_cast<uint64_t
>(khz);
733void E3Interface::handle_setup_request(
const SetupRequest& request, uint32_t request_message_id) {
734 request_message_id = sanitize_request_message_id(request_message_id);
736 E3_LOG_INFO(LOG_TAG) <<
"Handling setup request from dApp '" << request.dapp_name
737 <<
"' (version=" << request.dapp_version
738 <<
", vendor=" << request.vendor
739 <<
", e3ap_version=" << request.e3ap_protocol_version <<
")";
742 uint32_t assigned_dapp_id = 0;
745 auto [result, dapp_id] = subscription_manager_->register_dapp();
746 assigned_dapp_id = dapp_id;
748 if (result == ErrorCode::SUCCESS) {
749 response_code = ResponseCode::POSITIVE;
750 E3_LOG_INFO(LOG_TAG) <<
"dApp '" << request.dapp_name <<
"' registered with assigned ID " << assigned_dapp_id;
751 notify_dapp_status_changed();
758 auto available_ran_function_ids = SmRegistry::instance().get_available_ran_functions();
759 E3_LOG_DEBUG(LOG_TAG) <<
"Available RAN function ids count: " << available_ran_function_ids.size();
760 std::vector<RanFunctionDef> ran_function_list;
761 for (
auto id : available_ran_function_ids) {
763 func.ran_function_identifier = id;
764 ServiceModel* sm = SmRegistry::instance().get_by_ran_function(
id);
766 func.telemetry_identifier_list = sm->telemetry_ids();
767 func.control_identifier_list = sm->control_ids();
768 func.ran_function_data = sm->ran_function_data();
776 if (func.ran_function_data.empty()
777 || func.ran_function_data.size() > MAX_PROTOCOL_DATA_SIZE) {
779 <<
" from the setup response: "
780 << func.ran_function_data.size()
781 <<
" bytes of ran_function_data (need 1.."
785 ran_function_list.push_back(func);
787 E3_LOG_DEBUG(LOG_TAG) <<
"Constructed ran_function_list size: " << ran_function_list.size();
788 for (
const auto &rf : ran_function_list) {
789 E3_LOG_DEBUG(LOG_TAG) <<
" RAN function id=" << rf.ran_function_identifier
790 <<
", telemetry_count=" << rf.telemetry_identifier_list.size()
791 <<
", control_count=" << rf.control_identifier_list.size()
792 <<
", ran_function_data_len=" << rf.ran_function_data.size();
795 auto encode_result = encoder_->encode_setup_response(
796 generate_message_id(),
799 config_.e3ap_version,
802 response_code == ResponseCode::POSITIVE
803 ? std::optional<uint32_t>(assigned_dapp_id)
805 config_.ran_identifier,
809 if (!encode_result) {
812 E3_LOG_ERROR(LOG_TAG) <<
"Failed to encode setup response for request id "
813 << request_message_id <<
"; aborting";
817 ErrorCode send_result = connector_->send_response(encode_result->buffer);
818 if (send_result != ErrorCode::SUCCESS) {
819 E3_LOG_ERROR(LOG_TAG) <<
"Failed to send setup response for request id " << request_message_id
822 E3_LOG_INFO(LOG_TAG) <<
"Sent setup response for request id " << request_message_id;
825 send_result == ErrorCode::SUCCESS ? 1 : 0);
828void E3Interface::send_negative_setup_reply(uint32_t request_id) {
836 uint32_t message_id = generate_message_id();
837 request_id = sanitize_request_message_id(request_id);
838 auto encode_result = encoder_->encode_setup_response(
841 ResponseCode::NEGATIVE,
842 config_.e3ap_version,
844 config_.ran_identifier.empty() ?
"unknown" : config_.ran_identifier,
848 if (!encode_result) {
849 E3_LOG_ERROR(LOG_TAG) <<
"Failed to encode negative setup response for request id "
850 << request_id <<
"; setup channel may be left unusable";
854 ErrorCode send_result = connector_->send_response(encode_result->buffer);
855 if (send_result != ErrorCode::SUCCESS) {
856 E3_LOG_WARN(LOG_TAG) <<
"Failed to send negative setup response for request id "
862void E3Interface::handle_subscription_request(
const SubscriptionRequest& request, uint32_t request_message_id) {
864 E3_LOG_INFO(LOG_TAG) <<
"Handling subscription request from dApp " << request.dapp_identifier
865 <<
" for RAN function " << request.ran_function_identifier;
868 uint32_t subscription_id = 1;
871 if (!subscription_manager_->is_dapp_registered(request.dapp_identifier)) {
872 E3_LOG_ERROR(LOG_TAG) <<
"dApp " << request.dapp_identifier <<
" not registered";
874 uint32_t period = request.periodicity.value_or(0);
875 auto [result, sub_id] = subscription_manager_->add_subscription(
876 request.dapp_identifier,
877 request.ran_function_identifier,
878 request.telemetry_identifier_list,
879 request.control_identifier_list,
882 subscription_id = sub_id;
884 if (result == ErrorCode::SUCCESS || result == ErrorCode::SUBSCRIPTION_EXISTS) {
885 response_code = ResponseCode::POSITIVE;
886 E3_LOG_INFO(LOG_TAG) <<
"Subscription added: dApp " << request.dapp_identifier
887 <<
" -> RAN function " << request.ran_function_identifier
888 <<
" (subscription_id=" << subscription_id <<
")";
889 if (result == ErrorCode::SUCCESS) {
890 notify_dapp_status_changed();
899 Pdu response_pdu(PduType::SUBSCRIPTION_RESPONSE);
900 SubscriptionResponse resp;
901 response_pdu.message_id = generate_message_id();
902 resp.request_id = request_message_id;
903 resp.dapp_identifier = request.dapp_identifier;
904 resp.response_code = response_code;
905 if (response_code == ResponseCode::POSITIVE) {
906 resp.subscription_id = subscription_id;
908 response_pdu.choice = resp;
911 response_code == ResponseCode::POSITIVE ? 1 : 0);
912 queue_outbound(std::move(response_pdu));
915void E3Interface::handle_subscription_delete(
const SubscriptionDelete& del, uint32_t request_message_id) {
916 E3_LOG_INFO(LOG_TAG) <<
"Handling subscription delete from dApp " << del.dapp_identifier
917 <<
" for subscription " << del.subscription_id;
921 ErrorCode result = subscription_manager_->remove_subscription_by_id(
926 if (result == ErrorCode::SUCCESS) {
927 response_code = ResponseCode::POSITIVE;
928 E3_LOG_INFO(LOG_TAG) <<
"Subscription " << del.subscription_id <<
" removed for dApp " << del.dapp_identifier;
929 notify_dapp_status_changed();
931 E3_LOG_ERROR(LOG_TAG) <<
"Failed to remove subscription: "
936 Pdu response_pdu(PduType::SUBSCRIPTION_RESPONSE);
937 SubscriptionResponse resp;
938 response_pdu.message_id = generate_message_id();
939 resp.request_id = request_message_id;
940 resp.dapp_identifier = del.dapp_identifier;
941 resp.response_code = response_code;
942 if (response_code == ResponseCode::POSITIVE) {
943 resp.subscription_id = del.subscription_id;
945 response_pdu.choice = resp;
947 queue_outbound(std::move(response_pdu));
950void E3Interface::handle_control_action(
const DAppControlAction& action, uint32_t request_message_id,
951 uint64_t latrec_seq) {
952 E3_LOG_INFO(LOG_TAG) <<
"Handling control action from dApp " << action.dapp_identifier
953 <<
" for RAN function " << action.ran_function_identifier
954 <<
" control " << action.control_identifier
955 <<
" (" << action.action_data.size() <<
" bytes)";
957 ServiceModel* sm = SmRegistry::instance().get_by_ran_function(action.ran_function_identifier);
959 if (sm && sm->is_running()) {
968 ErrorCode result = sm->handle_control_action(request_message_id, action);
969 if (result != ErrorCode::SUCCESS) {
970 E3_LOG_ERROR(LOG_TAG) <<
"SM failed to process control action: "
974 E3_LOG_INFO(LOG_TAG) <<
"Control action processed by SM";
976 E3_LOG_ERROR(LOG_TAG) <<
"No running SM found for RAN function "
977 << action.ran_function_identifier;
982void E3Interface::handle_dapp_report(
const DAppReport& report) {
983 E3_LOG_INFO(LOG_TAG) <<
"Handling dApp report from dApp " << report.dapp_identifier
984 <<
" for RAN function " << report.ran_function_identifier;
987 if (dapp_report_handler_) {
988 dapp_report_handler_(report);
992void E3Interface::handle_release_message(
const ReleaseMessage& release) {
993 E3_LOG_INFO(LOG_TAG) <<
"Handling release message from dApp " << release.dapp_identifier;
995 handle_dapp_disconnection(release.dapp_identifier);
998void E3Interface::handle_dapp_disconnection(uint32_t dapp_id) {
999 E3_LOG_INFO(LOG_TAG) <<
"Handling dApp disconnection: " << dapp_id;
1002 auto subscriptions = subscription_manager_->get_dapp_subscriptions(dapp_id);
1005 ErrorCode result = subscription_manager_->unregister_dapp(dapp_id);
1007 if (result == ErrorCode::SUCCESS) {
1008 E3_LOG_INFO(LOG_TAG) <<
"dApp " << dapp_id <<
" unregistered, "
1009 << subscriptions.size() <<
" subscriptions removed";
1010 notify_dapp_status_changed();
1012 E3_LOG_ERROR(LOG_TAG) <<
"Failed to unregister dApp " << dapp_id <<
": "
1025void E3Interface::setup_loop_dapp() {
1030 E3_LOG_INFO(LOG_TAG) <<
"Setup loop (dApp) started";
1033 auto enc = encoder_->encode_setup_request(
1034 generate_message_id(),
1035 config_.e3ap_version,
1037 config_.dapp_version,
1041 E3_LOG_ERROR(LOG_TAG) <<
"Failed to encode SetupRequest";
1042 std::lock_guard<std::mutex> lk(setup_complete_mu_);
1043 setup_complete_ =
true;
1044 setup_succeeded_ =
false;
1045 setup_complete_cv_.notify_all();
1050 constexpr int MAX_RETRIES = 5;
1052 bool got_response =
false;
1053 while (!should_stop_.load() && retries < MAX_RETRIES) {
1054 ErrorCode rc = connector_->send_setup_request_client(enc->buffer);
1055 if (rc != ErrorCode::SUCCESS) {
1056 E3_LOG_ERROR(LOG_TAG) <<
"send_setup_request_client failed: "
1059 std::this_thread::sleep_for(std::chrono::seconds(1));
1064 for (
int attempt = 0; attempt < 4 && !should_stop_.load(); ++attempt) {
1065 std::vector<uint8_t> resp_buf;
1066 int n = connector_->recv_setup_response_client(resp_buf);
1068 auto decoded = encoder_->decode(resp_buf.data(),
static_cast<size_t>(n));
1070 E3_LOG_ERROR(LOG_TAG) <<
"Failed to decode SetupResponse";
1073 if (decoded->type != PduType::SETUP_RESPONSE) {
1074 E3_LOG_ERROR(LOG_TAG) <<
"Unexpected PDU on setup channel: "
1078 auto* resp = std::get_if<SetupResponse>(&decoded->choice);
1080 handle_setup_response(*resp);
1081 got_response =
true;
1086 E3_LOG_ERROR(LOG_TAG) <<
"recv_setup_response_client error";
1090 if (got_response)
break;
1095 std::lock_guard<std::mutex> lk(setup_complete_mu_);
1096 setup_complete_ =
true;
1097 setup_succeeded_ = got_response;
1099 setup_complete_cv_.notify_all();
1101 E3_LOG_INFO(LOG_TAG) <<
"Setup loop (dApp) finished, success="
1102 << (got_response ?
"yes" :
"no");
1105void E3Interface::inbound_loop_dapp() {
1106 apply_thread_config(config_.io_thread_affinity, config_.io_thread_niceness, LATREC_ROLE_INBOUND);
1107 E3_LOG_INFO(LOG_TAG) <<
"Inbound loop (dApp) started";
1109 ErrorCode result = connector_->setup_inbound_connection_client();
1110 if (result != ErrorCode::SUCCESS) {
1111 E3_LOG_ERROR(LOG_TAG) <<
"Failed to setup inbound (dApp) connection: "
1116 std::vector<uint8_t> buffer;
1117 while (!should_stop_.load()) {
1118 int ret = connector_->receive(buffer);
1120 if (should_stop_.load())
break;
1125 auto decoded = encoder_->decode(buffer.data(),
static_cast<size_t>(ret));
1127 E3_LOG_ERROR(LOG_TAG) <<
"Failed to decode PDU in dApp inbound";
1131 Pdu& pdu = *decoded;
1134 case PduType::SUBSCRIPTION_RESPONSE: {
1135 auto* r = std::get_if<SubscriptionResponse>(&pdu.choice);
1136 if (r) handle_subscription_response(*r);
1139 case PduType::INDICATION_MESSAGE: {
1140 auto* m = std::get_if<IndicationMessage>(&pdu.choice);
1141 if (m) handle_indication(*m, seq);
1144 case PduType::XAPP_CONTROL_ACTION: {
1145 auto* a = std::get_if<XAppControlAction>(&pdu.choice);
1147 a->message_id = pdu.message_id;
1148 handle_xapp_control_action(*a, seq);
1152 case PduType::MESSAGE_ACK: {
1153 auto* a = std::get_if<MessageAck>(&pdu.choice);
1154 if (a) handle_message_ack(*a);
1158 E3_LOG_WARN(LOG_TAG) <<
"dApp received unexpected PDU type: "
1165 E3_LOG_INFO(LOG_TAG) <<
"Inbound loop (dApp) stopped";
1168void E3Interface::outbound_loop_dapp() {
1169 apply_thread_config(config_.io_thread_affinity, config_.io_thread_niceness, LATREC_ROLE_OUTBOUND);
1170 E3_LOG_INFO(LOG_TAG) <<
"Outbound loop (dApp) started";
1172 signal(SIGPIPE, SIG_IGN);
1174 ErrorCode result = connector_->setup_outbound_connection_client();
1175 if (result != ErrorCode::SUCCESS) {
1176 E3_LOG_ERROR(LOG_TAG) <<
"Failed to setup outbound (dApp) connection: "
1181 while (!should_stop_.load()) {
1182 auto pdu_opt = response_queue_->pop(std::chrono::milliseconds(10));
1183 if (!pdu_opt)
continue;
1186 auto enc = encoder_->encode(*pdu_opt);
1188 E3_LOG_ERROR(LOG_TAG) <<
"Failed to encode dApp outbound PDU";
1194 ErrorCode rc = connector_->send(enc->buffer);
1195 if (rc != ErrorCode::SUCCESS) {
1196 E3_LOG_ERROR(LOG_TAG) <<
"Failed to send dApp outbound PDU";
1197 const uint64_t reason = (rc == ErrorCode::CANCELLED)
1207 E3_LOG_INFO(LOG_TAG) <<
"Outbound loop (dApp) stopped";
1214void E3Interface::handle_setup_response(
const SetupResponse& resp) {
1215 E3_LOG_INFO(LOG_TAG) <<
"Handling SetupResponse rc="
1217 if (dapp_state_ && resp.response_code == ResponseCode::POSITIVE) {
1218 uint32_t dapp_id = resp.dapp_identifier.value_or(0);
1219 dapp_state_->record_setup_response(dapp_id, resp.ran_function_list);
1221 if (setup_response_handler_) {
1222 setup_response_handler_(resp);
1226void E3Interface::handle_subscription_response(
const SubscriptionResponse& resp) {
1229 std::lock_guard<std::mutex> lk(dapp_state_->mu);
1230 if (!dapp_state_->assigned_dapp_id.has_value() ||
1231 *dapp_state_->assigned_dapp_id != resp.dapp_identifier) {
1235 E3_LOG_INFO(LOG_TAG) <<
"Handling SubscriptionResponse for request "
1236 << resp.request_id <<
" rc="
1241 std::optional<PendingSubscriptionOp> pending;
1243 std::lock_guard<std::mutex> lk(pending_subscriptions_mutex_);
1244 auto it = pending_subscriptions_.find(resp.request_id);
1245 if (it != pending_subscriptions_.end()) {
1246 pending = it->second;
1247 pending_subscriptions_.erase(it);
1250 if (dapp_state_ && pending && resp.response_code == ResponseCode::POSITIVE) {
1252 if (pending->is_delete) {
1253 dapp_state_->remove_subscription_by_rf(pending->ran_function_id);
1254 E3_LOG_INFO(LOG_TAG) <<
"Subscription to RAN function "
1255 << pending->ran_function_id <<
" released";
1256 }
else if (resp.subscription_id.has_value()) {
1257 dapp_state_->record_subscription(pending->ran_function_id,
1258 *resp.subscription_id);
1259 E3_LOG_INFO(LOG_TAG) <<
"Subscription " << *resp.subscription_id
1260 <<
" granted for RAN function "
1261 << pending->ran_function_id;
1264 if (subscription_response_handler_) {
1265 subscription_response_handler_(resp);
1269void E3Interface::handle_indication(
const IndicationMessage& msg, uint64_t latrec_seq) {
1275 std::lock_guard<std::mutex> lk(dapp_state_->mu);
1276 if (!dapp_state_->assigned_dapp_id.has_value() ||
1277 *dapp_state_->assigned_dapp_id != msg.dapp_identifier) {
1288 if (indication_handler_) {
1289 indication_handler_(msg);
1294void E3Interface::handle_xapp_control_action(
const XAppControlAction& action,
1295 uint64_t latrec_seq) {
1297 std::lock_guard<std::mutex> lk(dapp_state_->mu);
1298 if (!dapp_state_->assigned_dapp_id.has_value() ||
1299 *dapp_state_->assigned_dapp_id != action.dapp_identifier) {
1310 if (xapp_control_handler_) {
1311 xapp_control_handler_(action);
1316void E3Interface::handle_message_ack(
const MessageAck& ack) {
1322 static_cast<uint64_t
>(ack.response_code), 0);
1323 if (message_ack_handler_) {
1324 message_ack_handler_(ack);
1333 if (!dapp_state_)
return std::nullopt;
1334 std::lock_guard<std::mutex> lk(dapp_state_->mu);
1335 return dapp_state_->assigned_dapp_id;
1339 if (!dapp_state_)
return {};
1340 return dapp_state_->active_subscriptions();
1344 if (!dapp_state_)
return {};
1345 return dapp_state_->subscribed_ran_functions();
1349 if (!dapp_state_)
return {};
1350 std::lock_guard<std::mutex> lk(dapp_state_->mu);
1351 return dapp_state_->remote_ran_functions;
1355 uint32_t ran_function_id,
1356 std::vector<uint32_t> telemetry_ids,
1357 std::vector<uint32_t> control_ids,
1358 std::optional<uint32_t> sub_time,
1359 std::optional<uint32_t> periodicity,
1360 uint32_t* out_request_id
1362 if (!dapp_state_)
return ErrorCode::STATE_ERROR;
1364 if (!
id)
return ErrorCode::NOT_INITIALIZED;
1374 pdu.
choice = std::move(req);
1380 remember_subscription_op(mid, ran_function_id,
false);
1382 if (rc != ErrorCode::SUCCESS) {
1383 forget_subscription_op(mid);
1386 if (out_request_id) {
1387 *out_request_id = mid;
1393 if (!dapp_state_)
return ErrorCode::STATE_ERROR;
1395 if (!
id)
return ErrorCode::NOT_INITIALIZED;
1396 auto sub_id = dapp_state_->subscription_id_for(ran_function_id);
1397 if (!sub_id)
return ErrorCode::SUBSCRIPTION_NOT_FOUND;
1408 remember_subscription_op(mid, ran_function_id,
true);
1410 if (rc != ErrorCode::SUCCESS) {
1411 forget_subscription_op(mid);
1417 uint32_t ran_function_id,
1418 uint32_t control_id,
1419 std::vector<uint8_t> action_data,
1420 uint32_t sequence_id,
1421 uint32_t* out_message_id
1423 if (!dapp_state_)
return ErrorCode::STATE_ERROR;
1425 if (!
id)
return ErrorCode::NOT_INITIALIZED;
1434 pdu.
choice = std::move(a);
1443 if (rc == ErrorCode::SUCCESS && out_message_id) {
1444 *out_message_id = mid;
1450 uint32_t ran_function_id,
1451 std::vector<uint8_t> report_data,
1452 uint32_t sequence_id,
1453 uint32_t* out_message_id
1457 if (sequence_id == 0) {
1458 E3_LOG_ERROR(LOG_TAG) <<
"dApp report refused: sequence_id must be non-zero (E3-SequenceID is 1..4294967295)";
1459 return ErrorCode::INVALID_PARAM;
1462 if (!dapp_state_)
return ErrorCode::STATE_ERROR;
1464 if (!
id)
return ErrorCode::NOT_INITIALIZED;
1472 pdu.
choice = std::move(r);
1476 if (rc == ErrorCode::SUCCESS && out_message_id) {
1477 *out_message_id = mid;
1483 if (!dapp_state_)
return ErrorCode::STATE_ERROR;
1485 if (!
id)
return ErrorCode::NOT_INITIALIZED;
1496 if (config_.role !=
E3Role::DAPP)
return ErrorCode::INVALID_PARAM;
1497 std::unique_lock<std::mutex> lk(setup_complete_mu_);
1498 if (!setup_complete_cv_.wait_for(lk, timeout, [
this]() { return setup_complete_; })) {
1499 return ErrorCode::TIMEOUT;
1501 return setup_succeeded_ ? ErrorCode::SUCCESS : ErrorCode::CONNECTION_FAILED;
1504void E3Interface::on_sm_lifecycle_change(uint32_t ran_function_id,
bool should_start) {
1507 if (result != ErrorCode::SUCCESS) {
1508 E3_LOG_ERROR(LOG_TAG) <<
"Failed to start SM for RAN function "
1512 ErrorCode result = SmRegistry::instance().stop_sm(ran_function_id);
1513 if (result != ErrorCode::SUCCESS) {
1514 E3_LOG_WARN(LOG_TAG) <<
"Failed to stop SM for RAN function "
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
ErrorCode queue_outbound(Pdu pdu)
Queue a PDU for outbound transmission.
ErrorCode queue_subscription_delete(uint32_t ran_function_id)
~E3Interface()
Destructor - ensures clean shutdown.
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.
ErrorCode wait_for_setup(std::chrono::milliseconds timeout)
ErrorCode queue_release_message()
E3Interface(const E3Config &config)
Construct with configuration.
void stop()
Stop the interface.
std::vector< uint32_t > active_subscription_ids() const
std::vector< RanFunctionDef > remote_ran_functions() const
std::optional< uint32_t > dapp_id() const noexcept
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.
static Logger & instance()
Get the singleton logger instance.
void set_level(LogLevel level) noexcept
Set the log level.
static SmRegistry & instance()
Get the singleton instance.
E3Interface - Internal protocol coordination layer.
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)
static uint64_t latrec_tnow(void)
static void latrec_ctx_set(uint64_t seq)
static uint64_t latrec_seq_next(void)
static void latrec_tstamp_at(uint64_t seq, uint8_t stage, uint64_t aux, uint64_t aux2, uint64_t t_ns)
@ LATREC_ENCODE_E3AP_DONE
@ LATREC_DECODE_E3AP_DONE
static uint64_t latrec_ctx(void)
static latrec_t * latrec_tls_open_as(const char *role)
@ LATREC_DROP_REPORT_QUEUE
Logging facility for libe3.
#define E3_LOG_WARN(component)
#define E3_LOG_DEBUG(component)
#define E3_LOG_INFO(component)
#define E3_LOG_ERROR(component)
ErrorCode
Error codes returned by libe3 operations Use shared error-code list (see libe3/error_codes....
AgentState
Agent state machine states.
@ RUNNING
Main processing loop active.
@ CONNECTING
Connection in progress.
@ CONNECTED
Connected and ready for operations.
@ UNINITIALIZED
Agent not yet initialized.
@ INITIALIZED
Agent initialized but not connected.
@ STOPPING
Shutdown in progress.
std::unique_ptr< E3Encoder > create_encoder(EncodingFormat format)
Factory function to create appropriate encoder.
E3Role
Role this E3 endpoint plays in the protocol.
@ RAN
RAN function (binds sockets; today's only behaviour).
@ DAPP
dApp (connects to a RAN agent)
ResponseCode
E3AP response codes.
constexpr size_t MAX_PROTOCOL_DATA_SIZE
const char * pdu_type_to_string(PduType type) noexcept
Convert PduType to string representation.
const char * agent_state_to_string(AgentState state) noexcept
Convert AgentState to string representation.
const char * role_to_string(E3Role role) noexcept
Convert E3Role to string representation.
const char * response_code_to_string(ResponseCode code) noexcept
Convert ResponseCode to string representation.
std::unique_ptr< E3Connector > create_connector(E3LinkLayer link_layer, E3TransportLayer transport_layer, const std::string &setup_endpoint, const std::string &inbound_endpoint, const std::string &outbound_endpoint, uint16_t setup_port, uint16_t inbound_port, uint16_t outbound_port, size_t io_threads=2, E3Role role=E3Role::RAN)
Factory function to create appropriate connector.
const char * error_code_to_string(ErrorCode code) noexcept
Convert ErrorCode to string representation.
E3AP dApp Control Action structure.
uint32_t ran_function_identifier
std::vector< uint8_t > action_data
uint32_t control_identifier
Control identifier.
E3AP dApp Report structure.
std::vector< uint8_t > report_data
uint32_t ran_function_identifier
Configuration for E3Agent.
int log_level
Logging verbosity: 0=none, 1=error, 2=warn, 3=info, 4=debug, 5=trace.
Generic E3AP PDU structure.
uint64_t enqueue_seq
Set by queue_outbound; keys the [latrec] L-stage records.
uint32_t message_id
Unique message identifier.
E3AP Release Message structure.
uint32_t dapp_identifier
dApp identifier
E3AP Subscription Delete structure.
uint32_t dapp_identifier
dApp identifier
uint32_t subscription_id
Subscription ID to delete.
E3AP Subscription Request structure.
std::optional< uint32_t > periodicity
Periodicity of data delivery (0-10000 microseconds).
std::optional< uint32_t > subscription_time
How long to keep the subscription (0-3600 sec).
uint32_t ran_function_identifier
RAN function to subscribe to.
std::vector< uint32_t > control_identifier_list
List of control identifiers.
std::vector< uint32_t > telemetry_identifier_list
List of telemetry identifiers.
uint32_t dapp_identifier
dApp identifier