libe3 0.2.5
C++ library for E3AP RAN vendor integration
Loading...
Searching...
No Matches
zmq_connector.cpp
Go to the documentation of this file.
1/**
2 * @file zmq_connector.cpp
3 * @brief ZeroMQ connector implementation
4 *
5 *
6 * SPDX-FileCopyrightText: Copyright (c) 2026 Northeastern University
7 * SPDX-License-Identifier: Apache-2.0
8 */
9
10#include "zmq_connector.hpp"
11#include "libe3/latrec.h"
12#include "libe3/logger.hpp"
13#include <zmq.h>
14#include <cstring>
15#include <sys/stat.h>
16#include <unistd.h>
17#include <grp.h>
18#include <cerrno>
19#include <chrono>
20#include <thread>
21
22namespace libe3 {
23
24namespace {
25constexpr const char* LOG_TAG = "ZmqConn";
26constexpr const char* IPC_BASE_DIR = "/tmp/dapps";
27constexpr int RECV_TIMEOUT_MS = 500; // Timeout for graceful shutdown
28constexpr int PUB_CONNECT_SETTLE_MS = 200; // PUB/SUB slow-joiner settle (client outbound)
29
30// Every socket gets ZMQ_LINGER = 0: the default (-1) makes zmq_ctx_destroy
31// block until all queued messages are flushed, so a RAN with undelivered
32// indications on its PUB socket hangs forever in dispose() and the process
33// ignores SIGTERM (the signal handler runs, but stop() never returns).
34// Dropping unsent messages on teardown is the correct trade for a
35// real-time control-loop transport.
36void set_linger0(void* socket) {
37 int linger = 0;
38 zmq_setsockopt(socket, ZMQ_LINGER, &linger, sizeof(linger));
39}
40}
41
44 const std::string& setup_endpoint,
45 const std::string& inbound_endpoint,
46 const std::string& outbound_endpoint,
47 uint16_t setup_port,
48 uint16_t inbound_port,
49 uint16_t outbound_port,
50 size_t io_threads
51)
52 : transport_layer_(transport_layer)
53 , io_threads_(io_threads)
54 , setup_port_(setup_port)
55 , inbound_port_(inbound_port)
56 , outbound_port_(outbound_port)
57{
58 // For TCP, derive the endpoint strings from the port numbers. The
59 // bind/connect-direction adjustment is handled at the call sites below
60 // (RAN binds tcp://*:N; dApp connects to tcp://127.0.0.1:N). For IPC
61 // both sides use the same ipc:// string.
63 setup_endpoint_ = "tcp://*:" + std::to_string(setup_port);
64 inbound_endpoint_ = "tcp://*:" + std::to_string(inbound_port);
65 outbound_endpoint_ = "tcp://*:" + std::to_string(outbound_port);
66 } else {
70 }
71
72 E3_LOG_INFO(LOG_TAG) << "Creating ZMQ connector";
73 E3_LOG_DEBUG(LOG_TAG) << " Setup endpoint: " << setup_endpoint_;
74 E3_LOG_DEBUG(LOG_TAG) << " Inbound endpoint: " << inbound_endpoint_;
75 E3_LOG_DEBUG(LOG_TAG) << " Outbound endpoint: " << outbound_endpoint_;
76}
77
81
83 // Create ZMQ context
84 context_ = zmq_ctx_new();
85 if (!context_) {
86 E3_LOG_ERROR(LOG_TAG) << "Failed to create ZMQ context";
87 return ErrorCode::CONNECTION_FAILED;
88 }
89
90 // Set I/O threads
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_);
94 context_ = nullptr;
95 return ErrorCode::CONNECTION_FAILED;
96 }
97
98 // Create IPC directory if using IPC transport
99 if (transport_layer_ == E3TransportLayer::IPC) {
100 struct stat st{};
101 if (stat(IPC_BASE_DIR, &st) == -1) {
102 // EEXIST: another agent/test process created it between the stat
103 // and the mkdir (the directory existing is exactly what we want).
104 // The POSIX connector guards the same race the same way.
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;
108 }
109 E3_LOG_INFO(LOG_TAG) << "Created IPC directory: " << IPC_BASE_DIR;
110 }
111
112 // Set directory permissions
113 if (chmod(IPC_BASE_DIR, 0777) != 0) {
114 E3_LOG_WARN(LOG_TAG) << "Failed to set IPC directory permissions";
115 }
116
117 // Try to change group to "dapp"
118 struct group* grp = getgrnam("dapp");
119 if (grp) {
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";
122 }
123 }
124 }
125
126 // Create REP socket for setup
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;
131 }
132
133 // Set receive timeout to allow graceful shutdown
134 int recv_timeout = RECV_TIMEOUT_MS;
135 zmq_setsockopt(setup_socket_, ZMQ_RCVTIMEO, &recv_timeout, sizeof(recv_timeout));
136 set_linger0(setup_socket_);
137
138 int ret = zmq_bind(setup_socket_, setup_endpoint_.c_str());
139 if (ret != 0) {
140 E3_LOG_ERROR(LOG_TAG) << "Failed to bind setup socket: " << zmq_strerror(errno);
141 return ErrorCode::CONNECTION_FAILED;
142 }
143
144 // Set IPC permissions if needed
145 if (transport_layer_ == E3TransportLayer::IPC) {
146 setup_ipc_permissions(setup_endpoint_);
147 }
148
149 E3_LOG_INFO(LOG_TAG) << "Setup socket bound to " << setup_endpoint_;
150 connected_ = true;
151
152 return ErrorCode::SUCCESS;
153}
154
155int ZmqE3Connector::recv_setup_request(std::vector<uint8_t>& buffer) {
156 if (!setup_socket_) {
157 return static_cast<int>(ErrorCode::NOT_CONNECTED);
158 }
159
160 buffer.resize(DEFAULT_BUFFER_SIZE);
161 int ret = zmq_recv(setup_socket_, buffer.data(), buffer.size(), 0);
162 if (ret < 0) {
163 if (errno == EAGAIN) {
164 // Timeout - return 0 to indicate no data (allows shutdown check)
165 return 0;
166 }
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);
170 }
171
172 buffer.resize(static_cast<size_t>(ret));
173 E3_LOG_DEBUG(LOG_TAG) << "Received setup request: " << ret << " bytes";
174 return ret;
175}
176
177ErrorCode ZmqE3Connector::send_response(const std::vector<uint8_t>& data) {
178 if (!setup_socket_) {
179 return ErrorCode::NOT_CONNECTED;
180 }
181
182 int ret = zmq_send(setup_socket_, data.data(), data.size(), 0);
183 if (ret < 0) {
184 E3_LOG_ERROR(LOG_TAG) << "Failed to send response: " << zmq_strerror(errno);
185 reset_setup_socket();
186 return ErrorCode::TRANSPORT_ERROR;
187 }
188
189 E3_LOG_DEBUG(LOG_TAG) << "Sent response: " << data.size() << " bytes";
190 return ErrorCode::SUCCESS;
191}
192
194 if (!context_) {
195 return ErrorCode::NOT_INITIALIZED;
196 }
197
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;
202 }
203
204 // Subscribe to all messages
205 zmq_setsockopt(inbound_socket_, ZMQ_SUBSCRIBE, "", 0);
206
207 // No ZMQ_CONFLATE: multiple dApps multiplex onto this SUB; conflating would
208 // drop all but the newest message and starve all but one dApp (issue #15).
209
210 // Set receive timeout to allow graceful shutdown
211 int recv_timeout = RECV_TIMEOUT_MS;
212 zmq_setsockopt(inbound_socket_, ZMQ_RCVTIMEO, &recv_timeout, sizeof(recv_timeout));
213 set_linger0(inbound_socket_);
214
215 int ret = zmq_bind(inbound_socket_, inbound_endpoint_.c_str());
216 if (ret != 0) {
217 E3_LOG_ERROR(LOG_TAG) << "Failed to bind inbound socket: " << zmq_strerror(errno);
218 return ErrorCode::CONNECTION_FAILED;
219 }
220
221 if (transport_layer_ == E3TransportLayer::IPC) {
222 setup_ipc_permissions(inbound_endpoint_);
223 }
224
225 E3_LOG_INFO(LOG_TAG) << "Inbound socket bound to " << inbound_endpoint_;
226 return ErrorCode::SUCCESS;
227}
228
229int ZmqE3Connector::receive(std::vector<uint8_t>& buffer) {
230 if (!inbound_socket_) {
231 return static_cast<int>(ErrorCode::NOT_CONNECTED);
232 }
233
234 buffer.resize(DEFAULT_BUFFER_SIZE);
235 int ret = zmq_recv(inbound_socket_, buffer.data(), buffer.size(), 0);
236 if (ret < 0) {
237 if (errno == EAGAIN) {
238 // Timeout - return 0 to indicate no data (allows shutdown check)
239 return 0;
240 }
241 E3_LOG_ERROR(LOG_TAG) << "Failed to receive: " << zmq_strerror(errno);
242 return static_cast<int>(ErrorCode::TRANSPORT_ERROR);
243 }
244
245 buffer.resize(static_cast<size_t>(ret));
246 E3_LOG_TRACE(LOG_TAG) << "Received: " << ret << " bytes";
247 return ret;
248}
249
251 if (!context_) {
252 return ErrorCode::NOT_INITIALIZED;
253 }
254
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;
259 }
260 set_linger0(outbound_socket_);
261
262 int ret = zmq_bind(outbound_socket_, outbound_endpoint_.c_str());
263 if (ret != 0) {
264 E3_LOG_ERROR(LOG_TAG) << "Failed to bind outbound socket: " << zmq_strerror(errno);
265 return ErrorCode::CONNECTION_FAILED;
266 }
267
268 if (transport_layer_ == E3TransportLayer::IPC) {
269 setup_ipc_permissions(outbound_endpoint_);
270 }
271
272 E3_LOG_INFO(LOG_TAG) << "Outbound socket bound to " << outbound_endpoint_;
273 return ErrorCode::SUCCESS;
274}
275
276ErrorCode ZmqE3Connector::send(const std::vector<uint8_t>& data) {
277 // Checked first, same reasoning as PosixE3Connector::send(): narrows,
278 // but does not eliminate, a send racing a concurrent shutdown().
279 if (shutdown_requested_.load()) {
280 return ErrorCode::CANCELLED;
281 }
282 if (!outbound_socket_) {
283 return ErrorCode::NOT_CONNECTED;
284 }
285
286 int ret = zmq_send(outbound_socket_, data.data(), data.size(), 0);
287 if (ret < 0) {
288 E3_LOG_ERROR(LOG_TAG) << "Failed to send: " << zmq_strerror(errno);
289 return ErrorCode::TRANSPORT_ERROR;
290 }
291
292 E3_LOG_TRACE(LOG_TAG) << "Sent: " << data.size() << " bytes";
293 return ErrorCode::SUCCESS;
294}
295
296namespace {
297// Convert a bind-style endpoint ("tcp://*:9990") to a connect-style one
298// ("tcp://127.0.0.1:9990"). IPC endpoints are returned unchanged.
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());
303 }
304 return bind_ep;
305}
306} // anonymous namespace
307
308// ===========================================================================
309// Client-side (dApp role) implementations
310//
311// The dApp ROLE inverts the bind/connect direction on all three channels:
312// - setup_endpoint : REQ connect (RAN binds REP)
313// - publisher_endpoint: SUB connect (RAN binds PUB) — this is our INBOUND
314// - subscriber_endpoint: PUB connect (RAN binds SUB) — this is our OUTBOUND
315// Socket TYPES on the inbound/outbound pair are unchanged; only the
316// bind/connect direction flips and the role of the two endpoints swaps.
317// ===========================================================================
318
320 // Create ZMQ context (same as RAN-side init)
321 context_ = zmq_ctx_new();
322 if (!context_) {
323 E3_LOG_ERROR(LOG_TAG) << "Failed to create ZMQ context";
324 return ErrorCode::CONNECTION_FAILED;
325 }
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_);
329 context_ = nullptr;
330 return ErrorCode::CONNECTION_FAILED;
331 }
332
333 // REQ socket — the dApp INITIATES setup
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;
338 }
339
340 int recv_timeout = RECV_TIMEOUT_MS;
341 zmq_setsockopt(setup_socket_, ZMQ_RCVTIMEO, &recv_timeout, sizeof(recv_timeout));
342 set_linger0(setup_socket_); // Don't block on close if RAN never replied
343
344 const std::string setup_cep = to_connect_endpoint(setup_endpoint_);
345 int ret = zmq_connect(setup_socket_, setup_cep.c_str());
346 if (ret != 0) {
347 E3_LOG_ERROR(LOG_TAG) << "Failed to connect setup socket: " << zmq_strerror(errno);
348 return ErrorCode::CONNECTION_FAILED;
349 }
350 E3_LOG_INFO(LOG_TAG) << "Setup REQ socket connected to " << setup_cep;
351 connected_ = true;
352 return ErrorCode::SUCCESS;
353}
354
355ErrorCode ZmqE3Connector::send_setup_request_client(const std::vector<uint8_t>& data) {
356 if (!setup_socket_) return ErrorCode::NOT_CONNECTED;
357 int ret = zmq_send(setup_socket_, data.data(), data.size(), 0);
358 if (ret < 0) {
359 E3_LOG_ERROR(LOG_TAG) << "Failed to send setup request: " << zmq_strerror(errno);
360 return ErrorCode::TRANSPORT_ERROR;
361 }
362 E3_LOG_DEBUG(LOG_TAG) << "Sent SetupRequest: " << data.size() << " bytes";
363 return ErrorCode::SUCCESS;
364}
365
366int ZmqE3Connector::recv_setup_response_client(std::vector<uint8_t>& buffer) {
367 if (!setup_socket_) return static_cast<int>(ErrorCode::NOT_CONNECTED);
368 buffer.resize(DEFAULT_BUFFER_SIZE);
369 int ret = zmq_recv(setup_socket_, buffer.data(), buffer.size(), 0);
370 if (ret < 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);
374 }
375 buffer.resize(static_cast<size_t>(ret));
376 E3_LOG_DEBUG(LOG_TAG) << "Received SetupResponse: " << ret << " bytes";
377 return ret;
378}
379
381 if (!context_) return ErrorCode::NOT_INITIALIZED;
382
383 // dApp INBOUND = RAN's publisher_endpoint (where RAN PUB-binds)
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;
388 }
389 zmq_setsockopt(inbound_socket_, ZMQ_SUBSCRIBE, "", 0);
390 // No ZMQ_CONFLATE: the RAN broadcasts per-dApp indications over one PUB and
391 // each dApp filters its own; conflating would starve all but one (issue #15).
392 int recv_timeout = RECV_TIMEOUT_MS;
393 zmq_setsockopt(inbound_socket_, ZMQ_RCVTIMEO, &recv_timeout, sizeof(recv_timeout));
394 set_linger0(inbound_socket_);
395
396 const std::string inbound_cep = to_connect_endpoint(outbound_endpoint_);
397 int ret = zmq_connect(inbound_socket_, inbound_cep.c_str());
398 if (ret != 0) {
399 E3_LOG_ERROR(LOG_TAG) << "Failed to connect inbound SUB to "
400 << inbound_cep << ": " << zmq_strerror(errno);
401 return ErrorCode::CONNECTION_FAILED;
402 }
403 E3_LOG_INFO(LOG_TAG) << "Inbound SUB socket connected to " << inbound_cep;
404 return ErrorCode::SUCCESS;
405}
406
408 if (!context_) return ErrorCode::NOT_INITIALIZED;
409
410 // dApp OUTBOUND = RAN's subscriber_endpoint (where RAN SUB-binds)
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;
415 }
416 set_linger0(outbound_socket_);
417
418 const std::string outbound_cep = to_connect_endpoint(inbound_endpoint_);
419 int ret = zmq_connect(outbound_socket_, outbound_cep.c_str());
420 if (ret != 0) {
421 E3_LOG_ERROR(LOG_TAG) << "Failed to connect outbound PUB to "
422 << outbound_cep << ": " << zmq_strerror(errno);
423 return ErrorCode::CONNECTION_FAILED;
424 }
425 E3_LOG_INFO(LOG_TAG) << "Outbound PUB socket connected to " << outbound_cep;
426
427 // ZMQ PUB/SUB slow-joiner: a freshly connected PUB silently drops outgoing
428 // messages until the peer SUB's subscription has propagated to it. The
429 // dApp's first outbound message is the subscription request; without a
430 // brief settle it can be lost — reliably so for a second peer over TCP,
431 // where connection establishment is slower. Wait for the link to settle.
432 std::this_thread::sleep_for(std::chrono::milliseconds(PUB_CONNECT_SETTLE_MS));
433 return ErrorCode::SUCCESS;
434}
435
437 E3_LOG_DEBUG(LOG_TAG) << "Disposing ZMQ connector";
438
439 if (setup_socket_) {
440 zmq_close(setup_socket_);
441 setup_socket_ = nullptr;
442 }
443
444 if (inbound_socket_) {
445 zmq_close(inbound_socket_);
446 inbound_socket_ = nullptr;
447 }
448
449 if (outbound_socket_) {
450 zmq_close(outbound_socket_);
451 outbound_socket_ = nullptr;
452 }
453
454 if (context_) {
455 zmq_ctx_destroy(context_);
456 context_ = nullptr;
457 }
458
459 // Clean up IPC files if using IPC transport AND we own them (RAN side
460 // binds, which creates the socket files; dApp side connects, so it has
461 // nothing to unlink).
462 if (transport_layer_ == E3TransportLayer::IPC && role_ == E3Role::RAN) {
463 // Extract file paths from IPC endpoints (ipc:///path)
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());
468 }
469 return "";
470 };
471
472 std::string path;
473 path = extract_path(setup_endpoint_);
474 if (!path.empty()) unlink(path.c_str());
475
476 path = extract_path(inbound_endpoint_);
477 if (!path.empty()) unlink(path.c_str());
478
479 path = extract_path(outbound_endpoint_);
480 if (!path.empty()) unlink(path.c_str());
481
482 // Deliberately do NOT rmdir(IPC_BASE_DIR): it is a shared directory
483 // (multiple agents/tests use it concurrently). Removing it here races
484 // with another agent that has already stat()'d it and is about to
485 // bind a socket inside it, making that bind fail with ENOENT. Leaving
486 // the empty directory behind is harmless.
487 }
488
489 connected_ = false;
490 E3_LOG_INFO(LOG_TAG) << "ZMQ connector disposed";
491}
492
494 // ZMQ uses ZMQ_RCVTIMEO for timeout-based shutdown.
495 // The receive loops will wake up periodically and check should_stop_.
496 shutdown_requested_.store(true);
497 E3_LOG_DEBUG(LOG_TAG) << "ZMQ connector shutdown requested";
498}
499
500void ZmqE3Connector::setup_ipc_permissions(const std::string& endpoint) {
501 // Extract file path from IPC endpoint (ipc:///path)
502 const std::string prefix = "ipc://";
503 if (endpoint.compare(0, prefix.length(), prefix) != 0) {
504 return;
505 }
506
507 std::string path = endpoint.substr(prefix.length());
508
509 if (chmod(path.c_str(), 0666) == -1) {
510 E3_LOG_WARN(LOG_TAG) << "Failed to set permissions on " << path << ": " << strerror(errno);
511 } else {
512 E3_LOG_DEBUG(LOG_TAG) << "Set permissions on " << path;
513 }
514}
515
516bool ZmqE3Connector::reset_setup_socket() {
517 E3_LOG_WARN(LOG_TAG) << "Resetting setup socket after error";
518
519 if (setup_socket_) {
520 zmq_close(setup_socket_);
521 setup_socket_ = nullptr;
522 }
523
524 if (!context_) {
525 E3_LOG_ERROR(LOG_TAG) << "Cannot reset setup socket without context";
526 return false;
527 }
528
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);
532 return false;
533 }
534
535 int recv_timeout = RECV_TIMEOUT_MS;
536 zmq_setsockopt(setup_socket_, ZMQ_RCVTIMEO, &recv_timeout, sizeof(recv_timeout));
537 set_linger0(setup_socket_);
538
539 int ret = zmq_bind(setup_socket_, setup_endpoint_.c_str());
540 if (ret != 0) {
541 E3_LOG_ERROR(LOG_TAG) << "Failed to rebind setup socket: " << zmq_strerror(errno);
542 zmq_close(setup_socket_);
543 setup_socket_ = nullptr;
544 return false;
545 }
546
547 if (transport_layer_ == E3TransportLayer::IPC) {
548 setup_ipc_permissions(setup_endpoint_);
549 }
550
551 E3_LOG_INFO(LOG_TAG) << "Setup socket reset and bound to " << setup_endpoint_;
552 return true;
553}
554
555} // namespace libe3
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.
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)
Definition logger.hpp:229
#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
E3TransportLayer
Transport layer types (matches Python E3TransportLayer).
Definition types.hpp:83
@ IPC
Unix Domain Sockets / IPC.
Definition types.hpp:86
@ RAN
RAN function (binds sockets; today's only behaviour).
Definition types.hpp:76
constexpr size_t DEFAULT_BUFFER_SIZE
Definition types.hpp:165
ZeroMQ-based E3 Connector.