25constexpr const char* LOG_TAG =
"ZmqConn";
26constexpr const char* IPC_BASE_DIR =
"/tmp/dapps";
27constexpr int RECV_TIMEOUT_MS = 500;
28constexpr int PUB_CONNECT_SETTLE_MS = 200;
36void set_linger0(
void* socket) {
38 zmq_setsockopt(socket, ZMQ_LINGER, &linger,
sizeof(linger));
48 uint16_t inbound_port,
49 uint16_t outbound_port,
53 , io_threads_(io_threads)
54 , setup_port_(setup_port)
55 , inbound_port_(inbound_port)
56 , outbound_port_(outbound_port)
84 context_ = zmq_ctx_new();
87 return ErrorCode::CONNECTION_FAILED;
91 if (zmq_ctx_set(context_, ZMQ_IO_THREADS,
static_cast<int>(io_threads_)) != 0) {
92 E3_LOG_ERROR(LOG_TAG) <<
"Failed to set ZMQ I/O threads: " << zmq_strerror(errno);
93 zmq_ctx_destroy(context_);
95 return ErrorCode::CONNECTION_FAILED;
101 if (stat(IPC_BASE_DIR, &st) == -1) {
105 if (mkdir(IPC_BASE_DIR, 0777) == -1 && errno != EEXIST) {
106 E3_LOG_ERROR(LOG_TAG) <<
"Failed to create IPC directory: " << strerror(errno);
107 return ErrorCode::CONNECTION_FAILED;
109 E3_LOG_INFO(LOG_TAG) <<
"Created IPC directory: " << IPC_BASE_DIR;
113 if (chmod(IPC_BASE_DIR, 0777) != 0) {
114 E3_LOG_WARN(LOG_TAG) <<
"Failed to set IPC directory permissions";
118 struct group* grp = getgrnam(
"dapp");
120 if (chown(IPC_BASE_DIR,
static_cast<uid_t
>(-1), grp->gr_gid) == -1) {
121 E3_LOG_WARN(LOG_TAG) <<
"Failed to change IPC directory group";
127 setup_socket_ = zmq_socket(context_, ZMQ_REP);
128 if (!setup_socket_) {
129 E3_LOG_ERROR(LOG_TAG) <<
"Failed to create setup socket: " << zmq_strerror(errno);
130 return ErrorCode::CONNECTION_FAILED;
134 int recv_timeout = RECV_TIMEOUT_MS;
135 zmq_setsockopt(setup_socket_, ZMQ_RCVTIMEO, &recv_timeout,
sizeof(recv_timeout));
136 set_linger0(setup_socket_);
140 E3_LOG_ERROR(LOG_TAG) <<
"Failed to bind setup socket: " << zmq_strerror(errno);
141 return ErrorCode::CONNECTION_FAILED;
152 return ErrorCode::SUCCESS;
156 if (!setup_socket_) {
157 return static_cast<int>(ErrorCode::NOT_CONNECTED);
161 int ret = zmq_recv(setup_socket_, buffer.data(), buffer.size(), 0);
163 if (errno == EAGAIN) {
167 E3_LOG_ERROR(LOG_TAG) <<
"Failed to receive setup request: " << zmq_strerror(errno);
168 reset_setup_socket();
169 return static_cast<int>(ErrorCode::TRANSPORT_ERROR);
172 buffer.resize(
static_cast<size_t>(ret));
173 E3_LOG_DEBUG(LOG_TAG) <<
"Received setup request: " << ret <<
" bytes";
178 if (!setup_socket_) {
179 return ErrorCode::NOT_CONNECTED;
182 int ret = zmq_send(setup_socket_, data.data(), data.size(), 0);
184 E3_LOG_ERROR(LOG_TAG) <<
"Failed to send response: " << zmq_strerror(errno);
185 reset_setup_socket();
186 return ErrorCode::TRANSPORT_ERROR;
189 E3_LOG_DEBUG(LOG_TAG) <<
"Sent response: " << data.size() <<
" bytes";
190 return ErrorCode::SUCCESS;
195 return ErrorCode::NOT_INITIALIZED;
198 inbound_socket_ = zmq_socket(context_, ZMQ_SUB);
199 if (!inbound_socket_) {
200 E3_LOG_ERROR(LOG_TAG) <<
"Failed to create inbound socket: " << zmq_strerror(errno);
201 return ErrorCode::CONNECTION_FAILED;
205 zmq_setsockopt(inbound_socket_, ZMQ_SUBSCRIBE,
"", 0);
211 int recv_timeout = RECV_TIMEOUT_MS;
212 zmq_setsockopt(inbound_socket_, ZMQ_RCVTIMEO, &recv_timeout,
sizeof(recv_timeout));
213 set_linger0(inbound_socket_);
217 E3_LOG_ERROR(LOG_TAG) <<
"Failed to bind inbound socket: " << zmq_strerror(errno);
218 return ErrorCode::CONNECTION_FAILED;
226 return ErrorCode::SUCCESS;
230 if (!inbound_socket_) {
231 return static_cast<int>(ErrorCode::NOT_CONNECTED);
235 int ret = zmq_recv(inbound_socket_, buffer.data(), buffer.size(), 0);
237 if (errno == EAGAIN) {
241 E3_LOG_ERROR(LOG_TAG) <<
"Failed to receive: " << zmq_strerror(errno);
242 return static_cast<int>(ErrorCode::TRANSPORT_ERROR);
245 buffer.resize(
static_cast<size_t>(ret));
246 E3_LOG_TRACE(LOG_TAG) <<
"Received: " << ret <<
" bytes";
252 return ErrorCode::NOT_INITIALIZED;
255 outbound_socket_ = zmq_socket(context_, ZMQ_PUB);
256 if (!outbound_socket_) {
257 E3_LOG_ERROR(LOG_TAG) <<
"Failed to create outbound socket: " << zmq_strerror(errno);
258 return ErrorCode::CONNECTION_FAILED;
260 set_linger0(outbound_socket_);
264 E3_LOG_ERROR(LOG_TAG) <<
"Failed to bind outbound socket: " << zmq_strerror(errno);
265 return ErrorCode::CONNECTION_FAILED;
273 return ErrorCode::SUCCESS;
279 if (shutdown_requested_.load()) {
280 return ErrorCode::CANCELLED;
282 if (!outbound_socket_) {
283 return ErrorCode::NOT_CONNECTED;
286 int ret = zmq_send(outbound_socket_, data.data(), data.size(), 0);
288 E3_LOG_ERROR(LOG_TAG) <<
"Failed to send: " << zmq_strerror(errno);
289 return ErrorCode::TRANSPORT_ERROR;
292 E3_LOG_TRACE(LOG_TAG) <<
"Sent: " << data.size() <<
" bytes";
293 return ErrorCode::SUCCESS;
299std::string to_connect_endpoint(
const std::string& bind_ep) {
300 const std::string prefix =
"tcp://*:";
301 if (bind_ep.compare(0, prefix.size(), prefix) == 0) {
302 return "tcp://127.0.0.1:" + bind_ep.substr(prefix.size());
321 context_ = zmq_ctx_new();
323 E3_LOG_ERROR(LOG_TAG) <<
"Failed to create ZMQ context";
324 return ErrorCode::CONNECTION_FAILED;
326 if (zmq_ctx_set(context_, ZMQ_IO_THREADS,
static_cast<int>(io_threads_)) != 0) {
327 E3_LOG_ERROR(LOG_TAG) <<
"Failed to set ZMQ I/O threads: " << zmq_strerror(errno);
328 zmq_ctx_destroy(context_);
330 return ErrorCode::CONNECTION_FAILED;
334 setup_socket_ = zmq_socket(context_, ZMQ_REQ);
335 if (!setup_socket_) {
336 E3_LOG_ERROR(LOG_TAG) <<
"Failed to create REQ setup socket: " << zmq_strerror(errno);
337 return ErrorCode::CONNECTION_FAILED;
340 int recv_timeout = RECV_TIMEOUT_MS;
341 zmq_setsockopt(setup_socket_, ZMQ_RCVTIMEO, &recv_timeout,
sizeof(recv_timeout));
342 set_linger0(setup_socket_);
345 int ret = zmq_connect(setup_socket_, setup_cep.c_str());
347 E3_LOG_ERROR(LOG_TAG) <<
"Failed to connect setup socket: " << zmq_strerror(errno);
348 return ErrorCode::CONNECTION_FAILED;
350 E3_LOG_INFO(LOG_TAG) <<
"Setup REQ socket connected to " << setup_cep;
352 return ErrorCode::SUCCESS;
356 if (!setup_socket_)
return ErrorCode::NOT_CONNECTED;
357 int ret = zmq_send(setup_socket_, data.data(), data.size(), 0);
359 E3_LOG_ERROR(LOG_TAG) <<
"Failed to send setup request: " << zmq_strerror(errno);
360 return ErrorCode::TRANSPORT_ERROR;
362 E3_LOG_DEBUG(LOG_TAG) <<
"Sent SetupRequest: " << data.size() <<
" bytes";
363 return ErrorCode::SUCCESS;
367 if (!setup_socket_)
return static_cast<int>(ErrorCode::NOT_CONNECTED);
369 int ret = zmq_recv(setup_socket_, buffer.data(), buffer.size(), 0);
371 if (errno == EAGAIN)
return 0;
372 E3_LOG_ERROR(LOG_TAG) <<
"Failed to receive setup response: " << zmq_strerror(errno);
373 return static_cast<int>(ErrorCode::TRANSPORT_ERROR);
375 buffer.resize(
static_cast<size_t>(ret));
376 E3_LOG_DEBUG(LOG_TAG) <<
"Received SetupResponse: " << ret <<
" bytes";
381 if (!context_)
return ErrorCode::NOT_INITIALIZED;
384 inbound_socket_ = zmq_socket(context_, ZMQ_SUB);
385 if (!inbound_socket_) {
386 E3_LOG_ERROR(LOG_TAG) <<
"Failed to create inbound SUB socket: " << zmq_strerror(errno);
387 return ErrorCode::CONNECTION_FAILED;
389 zmq_setsockopt(inbound_socket_, ZMQ_SUBSCRIBE,
"", 0);
392 int recv_timeout = RECV_TIMEOUT_MS;
393 zmq_setsockopt(inbound_socket_, ZMQ_RCVTIMEO, &recv_timeout,
sizeof(recv_timeout));
394 set_linger0(inbound_socket_);
397 int ret = zmq_connect(inbound_socket_, inbound_cep.c_str());
399 E3_LOG_ERROR(LOG_TAG) <<
"Failed to connect inbound SUB to "
400 << inbound_cep <<
": " << zmq_strerror(errno);
401 return ErrorCode::CONNECTION_FAILED;
403 E3_LOG_INFO(LOG_TAG) <<
"Inbound SUB socket connected to " << inbound_cep;
404 return ErrorCode::SUCCESS;
408 if (!context_)
return ErrorCode::NOT_INITIALIZED;
411 outbound_socket_ = zmq_socket(context_, ZMQ_PUB);
412 if (!outbound_socket_) {
413 E3_LOG_ERROR(LOG_TAG) <<
"Failed to create outbound PUB socket: " << zmq_strerror(errno);
414 return ErrorCode::CONNECTION_FAILED;
416 set_linger0(outbound_socket_);
419 int ret = zmq_connect(outbound_socket_, outbound_cep.c_str());
421 E3_LOG_ERROR(LOG_TAG) <<
"Failed to connect outbound PUB to "
422 << outbound_cep <<
": " << zmq_strerror(errno);
423 return ErrorCode::CONNECTION_FAILED;
425 E3_LOG_INFO(LOG_TAG) <<
"Outbound PUB socket connected to " << outbound_cep;
432 std::this_thread::sleep_for(std::chrono::milliseconds(PUB_CONNECT_SETTLE_MS));
433 return ErrorCode::SUCCESS;
440 zmq_close(setup_socket_);
441 setup_socket_ =
nullptr;
444 if (inbound_socket_) {
445 zmq_close(inbound_socket_);
446 inbound_socket_ =
nullptr;
449 if (outbound_socket_) {
450 zmq_close(outbound_socket_);
451 outbound_socket_ =
nullptr;
455 zmq_ctx_destroy(context_);
464 auto extract_path = [](
const std::string& endpoint) -> std::string {
465 const std::string prefix =
"ipc://";
466 if (endpoint.compare(0, prefix.length(), prefix) == 0) {
467 return endpoint.substr(prefix.length());
474 if (!path.empty()) unlink(path.c_str());
477 if (!path.empty()) unlink(path.c_str());
480 if (!path.empty()) unlink(path.c_str());
496 shutdown_requested_.store(
true);
497 E3_LOG_DEBUG(LOG_TAG) <<
"ZMQ connector shutdown requested";
500void ZmqE3Connector::setup_ipc_permissions(
const std::string& endpoint) {
502 const std::string prefix =
"ipc://";
503 if (endpoint.compare(0, prefix.length(), prefix) != 0) {
507 std::string path = endpoint.substr(prefix.length());
509 if (chmod(path.c_str(), 0666) == -1) {
510 E3_LOG_WARN(LOG_TAG) <<
"Failed to set permissions on " << path <<
": " << strerror(errno);
516bool ZmqE3Connector::reset_setup_socket() {
517 E3_LOG_WARN(LOG_TAG) <<
"Resetting setup socket after error";
520 zmq_close(setup_socket_);
521 setup_socket_ =
nullptr;
525 E3_LOG_ERROR(LOG_TAG) <<
"Cannot reset setup socket without context";
529 setup_socket_ = zmq_socket(context_, ZMQ_REP);
530 if (!setup_socket_) {
531 E3_LOG_ERROR(LOG_TAG) <<
"Failed to recreate setup socket: " << zmq_strerror(errno);
535 int recv_timeout = RECV_TIMEOUT_MS;
536 zmq_setsockopt(setup_socket_, ZMQ_RCVTIMEO, &recv_timeout,
sizeof(recv_timeout));
537 set_linger0(setup_socket_);
541 E3_LOG_ERROR(LOG_TAG) <<
"Failed to rebind setup socket: " << zmq_strerror(errno);
542 zmq_close(setup_socket_);
543 setup_socket_ =
nullptr;
std::string inbound_endpoint_
std::string outbound_endpoint_
std::string setup_endpoint_
const std::string & inbound_endpoint() const noexcept
Get inbound endpoint.
const std::string & setup_endpoint() const noexcept
Get setup endpoint.
const std::string & outbound_endpoint() const noexcept
Get outbound endpoint.
ErrorCode setup_outbound_connection_client() override
Connect the outbound channel (dApp role: PUB connect to the RAN's subscriber endpoint).
ErrorCode send_setup_request_client(const std::vector< uint8_t > &data) override
Send a SetupRequest on the setup channel (dApp role).
E3TransportLayer transport_layer() const noexcept override
Get the transport layer type.
ErrorCode setup_initial_connection() override
Set up the initial connection (setup channel).
ErrorCode setup_initial_connection_client() override
Connect the setup channel (dApp role: REQ connect).
void dispose() override
Clean up and release all resources.
~ZmqE3Connector() override
int recv_setup_request(std::vector< uint8_t > &buffer) override
Receive a setup request.
ZmqE3Connector(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)
int receive(std::vector< uint8_t > &buffer) override
Receive data from the inbound channel.
ErrorCode setup_inbound_connection_client() override
Connect the inbound channel (dApp role: SUB connect to the RAN's publisher endpoint).
ErrorCode setup_inbound_connection() override
Set up the inbound connection (SUB channel).
ErrorCode send_response(const std::vector< uint8_t > &data) override
Send a setup response.
ErrorCode send(const std::vector< uint8_t > &data) override
Send data on the outbound channel.
ErrorCode setup_outbound_connection() override
Set up the outbound connection (PUB channel).
int recv_setup_response_client(std::vector< uint8_t > &buffer) override
Receive a SetupResponse on the setup channel (dApp role).
void shutdown() override
Interrupt blocking operations for shutdown.
Lock-free per-thread latency stamp recorder (header-only, C99).
Logging facility for libe3.
#define E3_LOG_TRACE(component)
#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....
E3TransportLayer
Transport layer types (matches Python E3TransportLayer).
@ IPC
Unix Domain Sockets / IPC.
@ RAN
RAN function (binds sockets; today's only behaviour).
constexpr size_t DEFAULT_BUFFER_SIZE
ZeroMQ-based E3 Connector.