Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
49
tools/EVerest-main/lib/everest/io/BUILD.bazel
Normal file
49
tools/EVerest-main/lib/everest/io/BUILD.bazel
Normal file
@@ -0,0 +1,49 @@
|
||||
load("@rules_cc//cc:defs.bzl", "cc_library")
|
||||
|
||||
cc_library(
|
||||
name = "io",
|
||||
srcs = glob(
|
||||
[
|
||||
"src/**/*.cpp",
|
||||
],
|
||||
exclude = [
|
||||
"src/can/*.cpp",
|
||||
"src/raw/*.cpp",
|
||||
],
|
||||
) + select({
|
||||
"@platforms//cpu:armv7": [],
|
||||
"@platforms//cpu:aarch64": [],
|
||||
"//conditions:default": glob([
|
||||
"src/can/*.cpp",
|
||||
"src/raw/*.cpp",
|
||||
]),
|
||||
}),
|
||||
hdrs = glob(
|
||||
[
|
||||
"include/everest/io/**/*.hpp",
|
||||
],
|
||||
exclude = [
|
||||
"include/everest/io/can/*.hpp",
|
||||
"include/everest/io/raw/*.hpp",
|
||||
],
|
||||
) + select({
|
||||
"@platforms//cpu:armv7": [],
|
||||
"@platforms//cpu:aarch64": [],
|
||||
"//conditions:default": glob([
|
||||
"include/everest/io/can/*.hpp",
|
||||
"include/everest/io/raw/*.hpp",
|
||||
]),
|
||||
}),
|
||||
copts = ["-std=c++17"],
|
||||
includes = ["include"],
|
||||
local_defines = select({
|
||||
"@platforms//cpu:armv7": ["EVEREST_NO_PACKET_IGNORE_OUTGOING=ON"],
|
||||
"@platforms//cpu:aarch64": ["EVEREST_NO_PACKET_IGNORE_OUTGOING=ON"],
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//lib/everest/util",
|
||||
"@mosquitto",
|
||||
],
|
||||
)
|
||||
17
tools/EVerest-main/lib/everest/io/CMakeLists.txt
Normal file
17
tools/EVerest-main/lib/everest/io/CMakeLists.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
option(BUILD_DOC "Build documentation" OFF)
|
||||
option(BUILD_EXAMPLES "Build examples" OFF)
|
||||
option(EVEREST_IO_WITH_MQTT "Build MQTT (mosquitto) support in everest::io" ON)
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
if (BUILD_TESTING)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
||||
if (BUILD_DOC)
|
||||
add_subdirectory(docs)
|
||||
endif()
|
||||
|
||||
if (BUILD_EXAMPLES)
|
||||
add_subdirectory(examples)
|
||||
endif()
|
||||
13
tools/EVerest-main/lib/everest/io/README.md
Normal file
13
tools/EVerest-main/lib/everest/io/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# everest_io {#mainpage}
|
||||
|
||||
libeverest_io provides utilities for socket based communication.
|
||||
|
||||
Currently there are clients for
|
||||
- UDP
|
||||
- SocketCAN
|
||||
- MQTT
|
||||
- PTY
|
||||
- TCP
|
||||
- TAP
|
||||
|
||||
The clients are single threaded and epoll based. Utilities for file descriptor based event handling are provided and used.
|
||||
19
tools/EVerest-main/lib/everest/io/docs/CMakeLists.txt
Normal file
19
tools/EVerest-main/lib/everest/io/docs/CMakeLists.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
find_package(Doxygen
|
||||
REQUIRED dot
|
||||
OPTIONAL_COMPONENTS mscgen dia)
|
||||
|
||||
if (DOXYGEN_FOUND)
|
||||
set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in)
|
||||
set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
|
||||
|
||||
configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)
|
||||
message("Doxygen build started")
|
||||
|
||||
add_custom_target(libeverest_io_doc_doxygen
|
||||
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMENT "Generating API documentation with Doxygen"
|
||||
VERBATIM )
|
||||
else (DOXYGEN_FOUND)
|
||||
message("Doxygen need to be installed to generate the doxygen documentation")
|
||||
endif (DOXYGEN_FOUND)
|
||||
2863
tools/EVerest-main/lib/everest/io/docs/Doxyfile.in
Normal file
2863
tools/EVerest-main/lib/everest/io/docs/Doxyfile.in
Normal file
File diff suppressed because it is too large
Load Diff
114
tools/EVerest-main/lib/everest/io/examples/CMakeLists.txt
Normal file
114
tools/EVerest-main/lib/everest/io/examples/CMakeLists.txt
Normal file
@@ -0,0 +1,114 @@
|
||||
add_executable(mosquitto_client test_mosquitto_client.cpp)
|
||||
|
||||
target_link_libraries(mosquitto_client
|
||||
PRIVATE
|
||||
everest::io
|
||||
)
|
||||
|
||||
target_compile_features(mosquitto_client PUBLIC cxx_std_17)
|
||||
target_compile_options(mosquitto_client PRIVATE -fsanitize=address -g)
|
||||
target_link_options(mosquitto_client PRIVATE -static-libasan -fsanitize=address)
|
||||
|
||||
|
||||
add_executable(test_serial test_serial.cpp)
|
||||
|
||||
target_link_libraries(test_serial
|
||||
PRIVATE
|
||||
everest::io
|
||||
)
|
||||
|
||||
target_compile_features(test_serial PUBLIC cxx_std_17)
|
||||
target_compile_options(test_serial PRIVATE -fsanitize=address -g)
|
||||
target_link_options(test_serial PRIVATE -static-libasan -fsanitize=address)
|
||||
|
||||
###
|
||||
|
||||
add_executable(test_socket_can test_socket_can.cpp)
|
||||
|
||||
target_link_libraries(test_socket_can
|
||||
PRIVATE
|
||||
everest::io
|
||||
)
|
||||
|
||||
target_compile_features(test_socket_can PUBLIC cxx_std_17)
|
||||
target_compile_options(test_socket_can PRIVATE -fsanitize=address -g)
|
||||
target_link_options(test_socket_can PRIVATE -static-libasan -fsanitize=address)
|
||||
|
||||
###
|
||||
|
||||
add_executable(test_tcp_client test_tcp_client.cpp)
|
||||
|
||||
target_link_libraries(test_tcp_client
|
||||
PRIVATE
|
||||
everest::io
|
||||
)
|
||||
|
||||
target_compile_features(test_tcp_client PUBLIC cxx_std_17)
|
||||
target_compile_options(test_tcp_client PRIVATE -fsanitize=address -g)
|
||||
target_link_options(test_tcp_client PRIVATE -static-libasan -fsanitize=address)
|
||||
|
||||
###
|
||||
|
||||
add_executable(test_tun_tap test_tun_tap.cpp)
|
||||
|
||||
target_link_libraries(test_tun_tap
|
||||
PRIVATE
|
||||
everest::io
|
||||
)
|
||||
|
||||
target_compile_features(test_tun_tap PUBLIC cxx_std_17)
|
||||
target_compile_options(test_tun_tap PRIVATE -fsanitize=address -g)
|
||||
target_link_options(test_tun_tap PRIVATE -static-libasan -fsanitize=address)
|
||||
|
||||
###
|
||||
|
||||
add_executable(test_udp_client test_udp_client.cpp)
|
||||
|
||||
target_link_libraries(test_udp_client
|
||||
PRIVATE
|
||||
everest::io
|
||||
)
|
||||
|
||||
target_compile_features(test_udp_client PUBLIC cxx_std_17)
|
||||
target_compile_options(test_udp_client PRIVATE -fsanitize=address -g)
|
||||
target_link_options(test_udp_client PRIVATE -static-libasan -fsanitize=address)
|
||||
|
||||
###
|
||||
|
||||
add_executable(test_udp_server test_udp_server.cpp)
|
||||
|
||||
target_link_libraries(test_udp_server
|
||||
PRIVATE
|
||||
everest::io
|
||||
)
|
||||
|
||||
target_compile_features(test_udp_server PUBLIC cxx_std_17)
|
||||
target_compile_options(test_udp_server PRIVATE -fsanitize=address -g)
|
||||
target_link_options(test_udp_server PRIVATE -static-libasan -fsanitize=address)
|
||||
|
||||
###
|
||||
|
||||
add_executable(test_udp_unconnected_client test_udp_unconnected_client.cpp)
|
||||
|
||||
target_link_libraries(test_udp_unconnected_client
|
||||
PRIVATE
|
||||
everest::io
|
||||
)
|
||||
|
||||
target_compile_features(test_udp_unconnected_client PUBLIC cxx_std_17)
|
||||
target_compile_options(test_udp_unconnected_client PRIVATE -fsanitize=address -g)
|
||||
target_link_options(test_udp_unconnected_client PRIVATE -static-libasan -fsanitize=address)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/**
|
||||
* @example test_mqtt_client.cpp Creates two MQTT clients that ping/pong messages to each other
|
||||
*/
|
||||
|
||||
#include "everest/io/mqtt/mosquitto_cpp.hpp"
|
||||
#include <cstdio>
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <everest/io/event/timer_fd.hpp>
|
||||
#include <everest/io/mqtt/mqtt_client.hpp>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
using namespace everest::lib::io::event;
|
||||
using namespace everest::lib::io;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
constexpr auto HOST = "localhost";
|
||||
constexpr std::uint16_t PORT = 1883;
|
||||
|
||||
using payloadT = mqtt::mosquitto_cpp::message;
|
||||
using generic_client = typename mqtt::mosquitto_cpp;
|
||||
|
||||
// Creates the error handler for the clients
|
||||
mqtt::mqtt_client::cb_error make_error_cb(mqtt::mqtt_client& client) {
|
||||
return [&](int error, std::string const& msg) {
|
||||
std::cerr << "ERROR ( " << error << " ): " << msg << std::endl;
|
||||
if (error) {
|
||||
std::cout << "What to do?" << std::endl;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Creates the RX handler for the client
|
||||
mqtt::mqtt_client::subscribe_message_callback make_rx_callback(mqtt::mosquitto_cpp&, std::string tar) {
|
||||
return [&, tar](generic_client& client, payloadT const& data) {
|
||||
std::cout << &client << "\n"
|
||||
<< "topic: " << data.topic << "\n"
|
||||
<< "msg: " << data.payload << std::endl;
|
||||
payloadT dataset;
|
||||
auto c = std::stoi(data.payload) + 1;
|
||||
dataset.payload = std::to_string(c);
|
||||
dataset.topic = tar;
|
||||
client.publish(dataset);
|
||||
};
|
||||
}
|
||||
|
||||
int main(int, char*[]) {
|
||||
std::cout << "mqtt_client ping/pong demonstration" << std::endl;
|
||||
bool server_connected = false;
|
||||
bool client_connected = false;
|
||||
|
||||
// Create first mqtt_client
|
||||
mqtt::mqtt_client client(2000);
|
||||
// Create and assign error handler
|
||||
client.set_error_handler(make_error_cb(client));
|
||||
// Create and assign rx handler
|
||||
client.set_callback_connect([&](auto& mqtt, auto, auto, auto const&) {
|
||||
client_connected = true;
|
||||
mqtt.subscribe("test/client/recv/#", make_rx_callback(mqtt, "test/server/recv"),
|
||||
mqtt::mosquitto_cpp::QoS::at_most_once);
|
||||
if (server_connected) {
|
||||
mqtt.publish("test/server/recv", "1");
|
||||
}
|
||||
});
|
||||
client.connect(HOST, PORT, 1000);
|
||||
|
||||
// Create second mqtt_client
|
||||
mqtt::mqtt_client server(2000);
|
||||
server.set_callback_connect([&](auto& mqtt, auto, auto, auto const&) {
|
||||
server_connected = true;
|
||||
mqtt.subscribe("test/server/recv/#", make_rx_callback(mqtt, "test/client/recv"),
|
||||
mqtt::mosquitto_cpp::QoS::at_most_once);
|
||||
if (client_connected) {
|
||||
mqtt.publish("test/client/recv", "1");
|
||||
}
|
||||
});
|
||||
server.set_error_handler(make_error_cb(server));
|
||||
|
||||
server.connect(HOST, PORT, 1000);
|
||||
|
||||
// Create event handler and register clients
|
||||
fd_event_handler ev_handler;
|
||||
ev_handler.register_event_handler(&client);
|
||||
ev_handler.register_event_handler(&server);
|
||||
|
||||
std::atomic_bool running = true;
|
||||
ev_handler.run(running);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/**
|
||||
* @example test_mqtt_client.cpp Creates two MQTT clients that ping/pong messages to each other
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <everest/io/mqtt/mqtt_client.hpp>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
using namespace everest::lib::io::event;
|
||||
using namespace everest::lib::io;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
constexpr auto HOST = "localhost";
|
||||
constexpr std::uint16_t PORT = 1883;
|
||||
|
||||
using payloadT = mqtt::mqtt_client::ClientPayloadT;
|
||||
using generic_client = typename mqtt::mqtt_client::interface;
|
||||
|
||||
// Creates the error handler for the clients
|
||||
mqtt::mqtt_client::cb_error make_error_cb(mqtt::mqtt_client& client) {
|
||||
return [&](int error, std::string const& msg) {
|
||||
std::cerr << "ERROR ( " << error << " ): " << msg << std::endl;
|
||||
if (error) {
|
||||
std::this_thread::sleep_for(1s);
|
||||
client.reset();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Creates the RX handler for the client
|
||||
mqtt::mqtt_client::cb_rx make_rx_callback(mqtt::mqtt_client& client, std::string tar) {
|
||||
return [&, tar](payloadT const& data, generic_client& tx) {
|
||||
std::cout << &client << "\n"
|
||||
<< "topic: " << data.topic << "\n"
|
||||
<< "msg: " << data.message << std::endl;
|
||||
payloadT dataset;
|
||||
auto c = std::stoi(data.message) + 1;
|
||||
dataset.message = std::to_string(c);
|
||||
dataset.topic = tar;
|
||||
tx.tx(dataset);
|
||||
};
|
||||
}
|
||||
|
||||
int main(int, char*[]) {
|
||||
std::cout << "mqtt_client ping/pong demonstration" << std::endl;
|
||||
|
||||
// Create first mqtt_client
|
||||
mqtt::mqtt_client client(HOST, PORT);
|
||||
// Create and assign error handler
|
||||
client.set_error_handler(make_error_cb(client));
|
||||
// Create and assign rx handler
|
||||
client.set_message_handler(make_rx_callback(client, "test/server/recv"));
|
||||
// Subscribe to topic
|
||||
client.subscribe("test/client/recv/#");
|
||||
|
||||
// Create second mqtt_client
|
||||
mqtt::mqtt_client server(HOST, PORT);
|
||||
server.set_error_handler(make_error_cb(server));
|
||||
server.set_message_handler(make_rx_callback(server, "test/client/recv"));
|
||||
server.subscribe("test/server/recv/#");
|
||||
|
||||
// Create event handler and register clients
|
||||
fd_event_handler ev_handler;
|
||||
ev_handler.register_event_handler(&client);
|
||||
ev_handler.register_event_handler(&server);
|
||||
|
||||
// Send first message to trigger ping/pong
|
||||
client.tx({"test/server/recv", "1"});
|
||||
// event loop
|
||||
while (true) {
|
||||
// Handles all the work and blocks if there is nothing to do.
|
||||
ev_handler.poll();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
64
tools/EVerest-main/lib/everest/io/examples/test_serial.cpp
Normal file
64
tools/EVerest-main/lib/everest/io/examples/test_serial.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/**
|
||||
* @example test_serial.cpp Event based PTY handling for data and status updates.
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <everest/io/event/timer_fd.hpp>
|
||||
#include <everest/io/serial/event_pty.hpp>
|
||||
#include <everest/io/serial/serial.hpp>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace everest::lib::io;
|
||||
using namespace everest::lib::io::utilities;
|
||||
using namespace everest::lib::io::event;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
int main() {
|
||||
std::cout << "This is serial test" << std::endl;
|
||||
|
||||
// Create PTY
|
||||
serial::event_pty handler;
|
||||
// Register callback for data events
|
||||
handler.set_data_handler([](auto const& pl, auto& dev) {
|
||||
auto msg = std::string(pl.begin(), pl.end());
|
||||
std::cout << " ##data update: " << msg << std::endl;
|
||||
|
||||
// generate reply and send it to the PTY
|
||||
static auto counter = 0;
|
||||
auto str = std::to_string(++counter) + "\n";
|
||||
auto ptr = reinterpret_cast<uint8_t*>(str.data());
|
||||
serial::event_pty::ClientPayloadT repl(ptr, ptr + str.size());
|
||||
dev.tx(repl);
|
||||
});
|
||||
|
||||
handler.set_status_handler([](auto const& status) {
|
||||
std::cout << " ##status update" << std::endl;
|
||||
// clang-format off
|
||||
std::cout << " - ixon -> " << status.ixon << "\n"
|
||||
<< " - ixoff -> " << status.ixoff << "\n"
|
||||
<< " - cstopb -> " << status.cstopb << "\n"
|
||||
<< " - baud -> " << status.cbaud
|
||||
<< std::endl;
|
||||
// clang-format on
|
||||
});
|
||||
|
||||
handler.set_error_handler([](int error, std::string const& err_msg) {
|
||||
std::cout << "ERRORHANDLER: " << err_msg << "(" << error << ")" << std::endl;
|
||||
});
|
||||
|
||||
// register the client with the event handler
|
||||
fd_event_handler ev_handler;
|
||||
ev_handler.register_event_handler(&handler);
|
||||
|
||||
while (true) {
|
||||
ev_handler.poll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <everest/io/can/socket_can.hpp>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
using namespace everest::lib::io;
|
||||
using namespace std::chrono_literals;
|
||||
using generic_socket_can = typename can::socket_can::interface;
|
||||
|
||||
const std::string current_date_time() {
|
||||
time_t now = time(0);
|
||||
struct tm tstruct;
|
||||
char buf[80];
|
||||
tstruct = *localtime(&now);
|
||||
|
||||
strftime(buf, sizeof(buf), "%Y-%m-%d %X", &tstruct);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::atomic_bool on_error{false};
|
||||
|
||||
void rx(can::can_dataset const& data, generic_socket_can& dev) {
|
||||
auto can_id = data.get_can_id();
|
||||
auto& payload = data.payload;
|
||||
|
||||
std::cout << "[ " << std::hex << can_id << " ] -> ";
|
||||
for (auto elem : payload) {
|
||||
std::cout << std::setw(2) << std::setfill('0') << (uint)elem << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
can::can_dataset msg;
|
||||
msg.set_can_id(002);
|
||||
msg.payload = {01, 02, 03, 04};
|
||||
dev.tx(msg);
|
||||
}
|
||||
|
||||
void error_fn(int err, std::string const& message, std::ostream& logger) {
|
||||
if (err == 0) {
|
||||
std::cerr << "ERROR: ( " << err << " ): " << message << std::endl;
|
||||
on_error.store(false);
|
||||
} else {
|
||||
std::stringstream ss;
|
||||
ss << "ERROR: ( " << err << " ): " << message << std::endl;
|
||||
|
||||
on_error.store(true);
|
||||
std::cerr << ss.str() << std::endl;
|
||||
logger << current_date_time() << " " << ss.str() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::ofstream logfile("logfile.txt", std::ofstream::out);
|
||||
std::cout << "hello socket_can test" << std::endl;
|
||||
can::socket_can can_dev("can0");
|
||||
can_dev.set_rx_handler(&rx);
|
||||
can_dev.set_error_handler([&](auto err, auto msg) {
|
||||
error_fn(err, msg, logfile);
|
||||
if (err != 0) {
|
||||
can_dev.reset();
|
||||
on_error.store(can_dev.on_error());
|
||||
std::this_thread::sleep_for(1s);
|
||||
}
|
||||
});
|
||||
while (true) {
|
||||
can_dev.sync();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
110
tools/EVerest-main/lib/everest/io/examples/test_tcp_client.cpp
Normal file
110
tools/EVerest-main/lib/everest/io/examples/test_tcp_client.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <everest/io/event/timer_fd.hpp>
|
||||
#include <everest/io/tcp/tcp_client.hpp>
|
||||
#include <everest/io/tcp/tcp_socket.hpp>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
using namespace everest::lib::io::tcp;
|
||||
using namespace everest::lib::io::utilities;
|
||||
using namespace everest::lib::io::event;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
using payload_type = everest::lib::io::event::fd_event_client<tcp_socket>::payload;
|
||||
using generic_tcp = typename tcp_client::interface;
|
||||
|
||||
auto to_string_data(const payload_type& d) {
|
||||
return std::string(d.begin(), d.end());
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, payload_type const& p) {
|
||||
os << "[TCP ( " << p.size() << " )]: " << to_string_data(p) << " ";
|
||||
return os;
|
||||
}
|
||||
|
||||
void rx_handler(payload_type const& payload, generic_tcp& client) {
|
||||
std::cout << "MSG: " << payload << std::endl;
|
||||
client.tx(payload);
|
||||
}
|
||||
|
||||
payload_type make_message(int val) {
|
||||
std::stringstream ss;
|
||||
ss << "message #" << std::to_string(val) << std::flush;
|
||||
auto tmp = ss.str();
|
||||
return {tmp.begin(), tmp.end()};
|
||||
}
|
||||
|
||||
tcp_client::cb_error make_error_cb(tcp_client& client) {
|
||||
return [&](int error, std::string const& msg) {
|
||||
std::cerr << "ERROR ( " << error << " ): " << msg << std::endl;
|
||||
if (error) {
|
||||
client.reset();
|
||||
} else {
|
||||
client.tx(make_message(1));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
tcp_client::cb_rx make_rx_callback(tcp_client& client) {
|
||||
return [&](payload_type const& payload, generic_tcp& repl) { rx_handler(payload, repl); };
|
||||
}
|
||||
|
||||
bool parse_args(int argc, char* argv[], std::string& remote, uint16_t& port) {
|
||||
if (argc != 3) {
|
||||
std::cout << "\nUSAGE: test_tcp_client {remote} {port}" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
remote = argv[1];
|
||||
std::string port_str = argv[2];
|
||||
|
||||
port = 0;
|
||||
|
||||
try {
|
||||
auto tmp = std::stoul(port_str);
|
||||
if (tmp > std::numeric_limits<uint16_t>::max()) {
|
||||
throw "";
|
||||
}
|
||||
port = tmp;
|
||||
} catch (...) {
|
||||
std::cout << "\nERROR '" << port_str << "' is not a valid port" << std::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
std::cout << "tcp echo test client" << std::endl;
|
||||
uint16_t port;
|
||||
std::string remote;
|
||||
|
||||
if (not parse_args(argc, argv, remote, port)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Connecting to -> " << remote << ":" << port << std::endl;
|
||||
tcp_client client(remote, port, 1000);
|
||||
timer_fd timer;
|
||||
timer.set_timeout_ms(100);
|
||||
|
||||
client.set_error_handler(make_error_cb(client));
|
||||
client.set_rx_handler(make_rx_callback(client));
|
||||
|
||||
std::cout << "TCP socket ok? -> " << not client.on_error() << std::endl;
|
||||
|
||||
fd_event_handler ev_handler;
|
||||
|
||||
ev_handler.register_event_handler(&timer, [&client](auto const&) {
|
||||
std::cout << "timer" << std::endl;
|
||||
// client.tx(make_message(12));
|
||||
});
|
||||
|
||||
ev_handler.register_event_handler(&client);
|
||||
std::atomic_bool running = true;
|
||||
ev_handler.run(running);
|
||||
|
||||
return 0;
|
||||
}
|
||||
71
tools/EVerest-main/lib/everest/io/examples/test_tun_tap.cpp
Normal file
71
tools/EVerest-main/lib/everest/io/examples/test_tun_tap.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <everest/io/tun_tap/tap_client.hpp>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
using namespace everest::lib::io;
|
||||
using namespace std::chrono_literals;
|
||||
using generic_tun_tap = typename tun_tap::tap_client::interface;
|
||||
|
||||
const std::string current_date_time() {
|
||||
time_t now = time(0);
|
||||
struct tm tstruct;
|
||||
char buf[80];
|
||||
tstruct = *localtime(&now);
|
||||
|
||||
strftime(buf, sizeof(buf), "%Y-%m-%d %X", &tstruct);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::atomic_bool on_error{false};
|
||||
|
||||
void rx(std::vector<uint8_t> const& data, generic_tun_tap& dev) {
|
||||
std::cout << "[ tap ] -> ";
|
||||
for (auto elem : data) {
|
||||
std::cout << std::setw(2) << std::setfill('0') << (uint)elem << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
void error_fn(int err, std::string const& message, std::ostream& logger) {
|
||||
if (err == 0) {
|
||||
std::cerr << "ERROR: ( " << err << " ): " << message << std::endl;
|
||||
on_error.store(false);
|
||||
} else {
|
||||
std::stringstream ss;
|
||||
ss << "ERROR: ( " << err << " ): " << message << std::endl;
|
||||
|
||||
on_error.store(true);
|
||||
std::cerr << ss.str() << std::endl;
|
||||
logger << current_date_time() << " " << ss.str() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::ofstream logfile("logfile.txt", std::ofstream::out);
|
||||
std::cout << "hello socket_can test" << std::endl;
|
||||
tun_tap::tap_client tap("test_tap", "172.1.1.1", "255.255.255.0", 1518);
|
||||
tap.set_rx_handler(&rx);
|
||||
tap.set_error_handler([&](auto err, auto msg) {
|
||||
error_fn(err, msg, logfile);
|
||||
if (err != 0) {
|
||||
tap.reset();
|
||||
on_error.store(tap.on_error());
|
||||
std::this_thread::sleep_for(1s);
|
||||
}
|
||||
});
|
||||
while (true) {
|
||||
tap.sync();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
124
tools/EVerest-main/lib/everest/io/examples/test_udp_client.cpp
Normal file
124
tools/EVerest-main/lib/everest/io/examples/test_udp_client.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <everest/io/event/timer_fd.hpp>
|
||||
#include <everest/io/udp/udp_client.hpp>
|
||||
#include <everest/io/udp/udp_payload.hpp>
|
||||
#include <everest/io/udp/udp_socket.hpp>
|
||||
#include <everest/io/utilities/stop_watch.hpp>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
using namespace everest::lib::io::udp;
|
||||
using namespace everest::lib::io::utilities;
|
||||
using namespace everest::lib::io::event;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
udp_payload current_message;
|
||||
stop_watch message_timer;
|
||||
using generic_udp = typename udp_client::interface;
|
||||
|
||||
auto to_string_data(const udp_payload& d) {
|
||||
return std::string(d.buffer.begin(), d.buffer.end());
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, udp_payload const& p) {
|
||||
os << "[UDP ( " << p.size() << " )]: " << to_string_data(p) << " ";
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, stop_watch const& sw) {
|
||||
os << sw.lap().count() << " us";
|
||||
return os;
|
||||
}
|
||||
|
||||
void rx_handler(udp_payload const& payload, generic_udp&) {
|
||||
if (payload == current_message) {
|
||||
std::cout << "REPLY: " << payload << message_timer << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
udp_payload make_message(int val) {
|
||||
std::stringstream ss;
|
||||
ss << "message #" << std::to_string(val) << std::flush;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
udp_client::cb_error make_error_cb(udp_client& client) {
|
||||
return [&](int error, std::string const& msg) {
|
||||
std::cerr << "ERROR ( " << error << " ): " << msg << std::endl;
|
||||
if (error) {
|
||||
std::this_thread::sleep_for(1s);
|
||||
client.reset();
|
||||
client.tx(make_message(-1));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
udp_client::cb_rx make_rx_callback(udp_client& client) {
|
||||
return [&](udp_payload const& payload, generic_udp& repl) { rx_handler(payload, repl); };
|
||||
}
|
||||
|
||||
fd_event_handler::event_handler_type make_timer_cb(udp_client& client) {
|
||||
return [&](fd_event_handler::event_list const& events) {
|
||||
static int counter = 0;
|
||||
current_message = make_message(++counter);
|
||||
message_timer.reset();
|
||||
client.tx(current_message);
|
||||
};
|
||||
}
|
||||
|
||||
bool parse_args(int argc, char* argv[], std::string& remote, uint16_t& port) {
|
||||
if (argc != 3) {
|
||||
std::cout << "\nUSAGE: test_udp_client {remote} {port}" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
remote = argv[1];
|
||||
std::string port_str = argv[2];
|
||||
|
||||
port = 0;
|
||||
|
||||
try {
|
||||
auto tmp = std::stoul(port_str);
|
||||
if (tmp > std::numeric_limits<uint16_t>::max()) {
|
||||
throw "";
|
||||
}
|
||||
port = tmp;
|
||||
} catch (...) {
|
||||
std::cout << "\nERROR '" << port_str << "' is not a valid port" << std::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
std::cout << "udp echo test client" << std::endl;
|
||||
uint16_t port;
|
||||
std::string remote;
|
||||
|
||||
if (not parse_args(argc, argv, remote, port)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Connecting to -> " << remote << ":" << port << std::endl;
|
||||
udp_client client(remote, port, 1000);
|
||||
client.set_error_handler(make_error_cb(client));
|
||||
client.set_rx_handler(make_rx_callback(client));
|
||||
|
||||
std::cout << "UDP socket ok? -> " << not client.on_error() << std::endl;
|
||||
|
||||
fd_event_handler ev_handler;
|
||||
timer_fd msg_timer;
|
||||
msg_timer.set_timeout(100ms);
|
||||
|
||||
ev_handler.register_event_handler(client.get_poll_fd(), [&](auto&) { client.sync(); }, {poll_events::read});
|
||||
ev_handler.register_event_handler(&msg_timer, make_timer_cb(client));
|
||||
|
||||
while (true) {
|
||||
ev_handler.poll();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include <everest/io/udp/udp_socket.hpp>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
using namespace everest::lib::io::udp;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
auto to_string_data(const udp_payload& d) {
|
||||
return std::string(d.buffer.begin(), d.buffer.end());
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, udp_payload const& p) {
|
||||
os << "[UDP ( " << p.size() << " )]: " << to_string_data(p) << " ";
|
||||
return os;
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "udp server" << std::endl;
|
||||
udp_client_socket socket;
|
||||
|
||||
socket.open_as_server(7766);
|
||||
|
||||
std::cout << "UDP socket open? -> " << socket.is_open() << std::endl;
|
||||
|
||||
while (socket.is_open()) {
|
||||
udp_payload payload;
|
||||
auto source = socket.rx(payload);
|
||||
|
||||
if (source) {
|
||||
std::cout << payload << std::endl;
|
||||
socket.tx(payload);
|
||||
} else {
|
||||
std::cout << "no value" << std::endl;
|
||||
}
|
||||
std::this_thread::sleep_for(500ms);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
//
|
||||
// Example: how to use udp_unconnected_client (an unconnected UDP datagram
|
||||
// client, IPv4 or IPv6 auto-selected from the target). Sends a text message
|
||||
// once per second to <dest>:<port> and prints any datagram received back,
|
||||
// together with its source endpoint.
|
||||
//
|
||||
// test_udp_unconnected_client <dest> <port> [iface]
|
||||
//
|
||||
// <dest> may be an IPv4 address, an IPv6 address, or a hostname. For IPv6
|
||||
// link-local / multicast destinations pass the optional <iface> (used as the
|
||||
// scope id and as a best-effort SO_BINDTODEVICE hint).
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <everest/io/event/timer_fd.hpp>
|
||||
#include <everest/io/udp/endpoint.hpp>
|
||||
#include <everest/io/udp/udp_payload.hpp>
|
||||
#include <everest/io/udp/udp_unconnected_client.hpp>
|
||||
|
||||
using namespace everest::lib::io::udp;
|
||||
using namespace everest::lib::io::event;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
using generic_udp = typename udp_unconnected_client::interface;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string to_text(udp_payload const& payload) {
|
||||
return std::string(payload.buffer.begin(), payload.buffer.end());
|
||||
}
|
||||
|
||||
bool parse_args(int argc, char* argv[], std::string& dest, uint16_t& port, std::string& iface) {
|
||||
if (argc != 3 && argc != 4) {
|
||||
std::cout << "USAGE: test_udp_unconnected_client <dest> <port> [iface]\n";
|
||||
return false;
|
||||
}
|
||||
dest = argv[1];
|
||||
try {
|
||||
auto tmp = std::stoul(argv[2]);
|
||||
if (tmp > std::numeric_limits<uint16_t>::max()) {
|
||||
throw std::out_of_range("port");
|
||||
}
|
||||
port = static_cast<uint16_t>(tmp);
|
||||
} catch (...) {
|
||||
std::cout << "ERROR: '" << argv[2] << "' is not a valid port\n";
|
||||
return false;
|
||||
}
|
||||
iface = argc == 4 ? argv[3] : "";
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
std::string dest;
|
||||
std::string iface;
|
||||
uint16_t port = 0;
|
||||
if (not parse_args(argc, argv, dest, port, iface)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
endpoint target;
|
||||
try {
|
||||
target = endpoint(dest, port, iface);
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "ERROR: " << e.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
udp_unconnected_client client(target, iface);
|
||||
|
||||
client.set_error_handler([](int err, std::string const& msg) {
|
||||
if (err) {
|
||||
std::cerr << "ERROR (" << err << "): " << msg << "\n";
|
||||
}
|
||||
});
|
||||
client.set_rx_handler([&](udp_payload const& payload, generic_udp&) {
|
||||
std::cout << "RX (" << payload.size() << "): " << to_text(payload);
|
||||
if (const auto src = client.get_raw_handler()->last_source()) {
|
||||
std::cout << " from [" << src->addr_str() << "]:" << src->port();
|
||||
}
|
||||
std::cout << "\n";
|
||||
});
|
||||
|
||||
fd_event_handler ev;
|
||||
ev.register_event_handler(client.get_poll_fd(), [&](auto&) { client.sync(); }, {poll_events::read});
|
||||
|
||||
timer_fd ticker;
|
||||
ticker.set_timeout(1s);
|
||||
int counter = 0;
|
||||
fd_event_handler::event_handler_type on_tick = [&](fd_event_handler::event_list const&) {
|
||||
ticker.set_timeout(1s); // re-arm
|
||||
std::ostringstream msg;
|
||||
msg << "ping #" << ++counter;
|
||||
udp_payload payload;
|
||||
payload.set_message(msg.str());
|
||||
std::cout << "TX: " << msg.str() << " -> [" << target.addr_str() << "]:" << target.port() << "\n";
|
||||
client.tx(payload);
|
||||
};
|
||||
ev.register_event_handler(&ticker, on_tick);
|
||||
|
||||
std::cout << "udp unconnected client: sending to [" << target.addr_str() << "]:" << target.port()
|
||||
<< (iface.empty() ? "" : (" via " + iface)) << " every 1s\n";
|
||||
while (true) {
|
||||
ev.poll();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace everest::lib::io::can {
|
||||
|
||||
/**
|
||||
* @var can_payload
|
||||
* @brief Payload of a CAN message
|
||||
*/
|
||||
using can_payload = std::vector<uint8_t>;
|
||||
|
||||
/**
|
||||
* @struct can_dataset
|
||||
* Dataset for socket_can. Includes CAN ID, optional DLC8, and the actual message / payload
|
||||
*/
|
||||
class can_dataset {
|
||||
public:
|
||||
/**
|
||||
* @brief Create a dataset from CAN id, DLC8 and the message
|
||||
* @details Creates a dataset with 11bit SSF (standard frame format) can_id. Any flags
|
||||
* implicitly given by the can_id are discarded.
|
||||
* @param[in] can_id_ CAN id
|
||||
* @param[in] len8_dlc_ optional for payloads of size 8 but higher dlc
|
||||
* @param[in] payload_ message to be transmitted
|
||||
*/
|
||||
can_dataset(uint32_t can_id_, uint8_t len8_dlc_, can_payload const& payload_);
|
||||
|
||||
can_dataset() = default;
|
||||
|
||||
/**
|
||||
* @brief Get the can_id without any implicit flags
|
||||
* @details Get the can_id with EFF, ERR, RTR flags cleared. This takes the objects EFF
|
||||
* flag into account and strips any extra bits.
|
||||
* @return The plain id.
|
||||
*/
|
||||
uint32_t get_can_id() const;
|
||||
/**
|
||||
* @brief Get the can_id with with all implicit flags included
|
||||
* @details Get the can_id including EFF, ERR, RTR flags. This takes the objects EFF
|
||||
* flag into account and strips any extra bits.
|
||||
* @return The plain id.
|
||||
*/
|
||||
uint32_t get_can_id_with_flags() const;
|
||||
|
||||
/**
|
||||
* @brief Set the can_id without any implicit flags.
|
||||
* @details This take the objects EFF flag into account and strips data accordingly.
|
||||
* @param[in] id The can_id
|
||||
*/
|
||||
void set_can_id(uint32_t id);
|
||||
/**
|
||||
* @brief Set the can_id including all implicit flags.
|
||||
* @details This take the implicit EFF flag into account and strips data accordingly.
|
||||
* Implicit EFF, RTR and ERR flags are extracted and set acoordingly.
|
||||
* @param[in] id The can_id
|
||||
*/
|
||||
void set_can_id_with_flags(uint32_t id);
|
||||
/**
|
||||
* @brief Set can_id and explicit flags
|
||||
* @details This sets the can_id and the EFF, RTR and ERR flags explicitly. Implicitly flags
|
||||
* from the can_id are discarded and the id is stripped according to the EFF flag given.
|
||||
* @param[in] id Description
|
||||
* @param[in] eff Description
|
||||
* @param[in] rtr Description
|
||||
* @param[in] err Description
|
||||
*/
|
||||
void set_can_id_with_flags(uint32_t id, bool eff, bool rtr, bool err);
|
||||
|
||||
/**
|
||||
* @brief Status of the extended frame format (EFF) flag;
|
||||
* @return EFF flag;
|
||||
*/
|
||||
bool eff_flag() const;
|
||||
/**
|
||||
* @brief Status of the remote transmission request (RTR) flag;
|
||||
* @return RTR flag;
|
||||
*/
|
||||
bool rtr_flag() const;
|
||||
/**
|
||||
* @brief Status of the error (ERR) flag.
|
||||
* @return The ERR flag;
|
||||
*/
|
||||
bool err_flag() const;
|
||||
|
||||
/**
|
||||
* @brief payload of up to 8 bytes
|
||||
*/
|
||||
can_payload payload{};
|
||||
/**
|
||||
* @brief optional DLC for payloads of size 8
|
||||
*/
|
||||
uint8_t len8_dlc{0};
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief CAN ID
|
||||
*/
|
||||
uint32_t can_id{0};
|
||||
/**
|
||||
* @brief If 'true' use EFF (extended frame format). SFF (standard frame format otherwise)
|
||||
*/
|
||||
bool eff{false};
|
||||
/**
|
||||
* @brief Remote transmission flag (RTR flag) is set.
|
||||
*/
|
||||
bool rtr{false};
|
||||
/**
|
||||
* @brief Error flag (ERR) is set.
|
||||
*/
|
||||
bool err{false};
|
||||
};
|
||||
} // namespace everest::lib::io::can
|
||||
@@ -0,0 +1,38 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace everest::lib::io::can {
|
||||
|
||||
/**
|
||||
* @brief Userspace description of a SocketCAN \c CAN_RAW_FILTER rule.
|
||||
*
|
||||
* Rules are applied on the raw socket via \c setsockopt(SOL_CAN_RAW, CAN_RAW_FILTER).
|
||||
* A frame is delivered when it matches at least one rule (OR). Use \p invert on a rule
|
||||
* to drop matching frames instead (\c CAN_INV_FILTER).
|
||||
*
|
||||
* \p can_id and \p can_mask are the 29-bit (EFF) or 11-bit identifier bits without
|
||||
* \c CAN_EFF_FLAG / \c CAN_RTR_FLAG / \c CAN_ERR_FLAG. When \p extended is true,
|
||||
* \c CAN_EFF_FLAG is added to both fields when installing the kernel filter.
|
||||
*/
|
||||
struct can_recv_filter {
|
||||
uint32_t can_id{0};
|
||||
uint32_t can_mask{0};
|
||||
bool extended{true};
|
||||
bool invert{false};
|
||||
|
||||
/**
|
||||
* @brief Drop frames whose identifier matches \p id under \p mask.
|
||||
* @param[in] id Plain CAN identifier bits (no SocketCAN flags).
|
||||
* @param[in] mask Bits to compare; set bits are significant.
|
||||
* @param[in] extended If true, match extended (29-bit) frames only.
|
||||
*/
|
||||
static can_recv_filter reject_match(uint32_t id, uint32_t mask, bool extended = true);
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::can
|
||||
@@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <everest/io/can/socket_can_handler.hpp>
|
||||
#include <everest/io/event/fd_event_client.hpp>
|
||||
|
||||
namespace everest::lib::io::can {
|
||||
|
||||
/**
|
||||
* @var socket_can
|
||||
* @brief Client for socket_can implemented in terms of \ref event::fd_event_client
|
||||
* and \ref can::socket_can_handler
|
||||
*/
|
||||
using socket_can = event::fd_event_client<socket_can_handler>::type;
|
||||
|
||||
} // namespace everest::lib::io::can
|
||||
@@ -0,0 +1,146 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <everest/io/can/can_payload.hpp>
|
||||
#include <everest/io/can/can_recv_filter.hpp>
|
||||
#include <everest/io/event/unique_fd.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace everest::lib::io {
|
||||
|
||||
namespace can {
|
||||
|
||||
/**
|
||||
* socket_can_handler bundles basic <a href="https://docs.kernel.org/networking/can.html">socket_can</a>
|
||||
* related functionality. This includes lifetime management, reading, writing and fundamental
|
||||
* error checking. It also addresses the common issue of
|
||||
* <a href=" https://rtime.felk.cvut.cz/can/socketcan-qdisc-final.pdf">failing writes</a>,
|
||||
* after write ([E]POLLOUT) notifications. <br>
|
||||
* Although this class can be used on its own, the main purpose is to implement the
|
||||
* \p ClientPolicy of \ref event::fd_event_client
|
||||
*/
|
||||
class socket_can_handler {
|
||||
public:
|
||||
/**
|
||||
* @var PayloadT
|
||||
* @brief Type of the payload for TX and RX operations
|
||||
*/
|
||||
using PayloadT = can_dataset;
|
||||
|
||||
/**
|
||||
* @brief The class is default constructed
|
||||
*/
|
||||
socket_can_handler() = default;
|
||||
~socket_can_handler() = default;
|
||||
|
||||
/**
|
||||
* @brief Raw implementation for writing data to the socket
|
||||
* @details Prior to sending the function checks the device status and the payload
|
||||
* @param[in] can_id ID of the target device on the CAN bus.
|
||||
* @param[in] len8_dlc Optional (9..15) if \p payload size is 8 but DLC is higher (ISO 11898-1)
|
||||
* @param[in] payload Payload of up to 8 bytes. Implicitly defines DLC
|
||||
* @return The errno of <a href="https://man7.org/linux/man-pages/man2/write.2.html">write</a>.
|
||||
* Zero indicates success.
|
||||
*/
|
||||
int tx(uint32_t can_id, uint8_t len8_dlc, can_payload const& payload);
|
||||
|
||||
/**
|
||||
* @brief Raw implementation for reading data from the socket.
|
||||
* @param[in] can_id ID of the target device on the CAN bus
|
||||
* @param[in] len8_dlc Optional (9..15) if \p payload size is 8 but DLC is higher (ISO 11898-1)
|
||||
* @param[in] payload Payload of up to 8 bytes. Implicitly defines DLC
|
||||
* @return The errno of <a href="https://man7.org/linux/man-pages/man2/read.2.html">read</a>.
|
||||
* Zero indicates success
|
||||
*/
|
||||
int rx(uint32_t& can_id, uint8_t& len8_dlc, can_payload& payload);
|
||||
|
||||
/**
|
||||
* @brief Write a \ref can_dataset to the socket
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @param[in] data Payload
|
||||
* @return True on success, False otherwise
|
||||
*/
|
||||
bool tx(can_dataset const& data);
|
||||
/**
|
||||
* @brief Read a \ref can_dataset from the socket
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @param[out] data Payload
|
||||
* @return True on success, False otherwise
|
||||
*/
|
||||
bool rx(can_dataset& data);
|
||||
|
||||
/**
|
||||
* @brief Open the socket_can device.
|
||||
* @details Sets the socket non blocking and reduces send buffer. Kernel receive
|
||||
* filters from the last \ref set_recv_filters call or \p recv_filters are installed
|
||||
* after bind. <br>
|
||||
* Implementation for \p ClientPolicy
|
||||
* @param[in] can_dev The device to bind the socket to.
|
||||
* @param[in] recv_filters Optional filters applied on open (replaces stored filters).
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool open(std::string const& can_dev, std::vector<can_recv_filter> const& recv_filters = {});
|
||||
|
||||
/**
|
||||
* @brief Set kernel receive filters for this handler.
|
||||
* @details Stored filters are applied on the next \ref open and on \ref reset via
|
||||
* \ref fd_event_client. If the socket is already open, filters are applied immediately.
|
||||
* An empty list clears filtering (receive all frames).
|
||||
* @param[in] recv_filters Filter rules (OR semantics unless \p invert is set on a rule).
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool set_recv_filters(std::vector<can_recv_filter> const& recv_filters);
|
||||
|
||||
/**
|
||||
* @brief Get the currently configured receive filters.
|
||||
*/
|
||||
std::vector<can_recv_filter> const& get_recv_filters() const;
|
||||
|
||||
/**
|
||||
* @brief Check if the objects owns a device
|
||||
* @return True if a device is owned, false otherwise
|
||||
*/
|
||||
bool is_open() const;
|
||||
|
||||
/**
|
||||
* @brief Close the owned device
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* @brief Get the file descriptor of the socket
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @return The file descriptor of the socket
|
||||
*/
|
||||
int get_fd() const;
|
||||
|
||||
/**
|
||||
* @brief Get pending errors on the socket.
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @return The current errno of the socket. Zero with no pending error.
|
||||
*/
|
||||
int get_error() const;
|
||||
|
||||
/**
|
||||
* @brief Check if the payload is valid
|
||||
* @details Internally checks is the size of the message is smaller than the maximum for CAN
|
||||
* @param[in] payload The payload
|
||||
* @return True if valid, false otherwise
|
||||
*/
|
||||
static bool data_valid(can_payload const& payload);
|
||||
|
||||
private:
|
||||
int open_device();
|
||||
bool apply_recv_filters();
|
||||
|
||||
event::unique_fd m_owned_can_fd;
|
||||
std::string m_can_dev;
|
||||
std::vector<can_recv_filter> m_recv_filters;
|
||||
};
|
||||
} // namespace can
|
||||
} // namespace everest::lib::io
|
||||
@@ -0,0 +1,107 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "unique_fd.hpp"
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace everest::lib::io::event {
|
||||
|
||||
/**
|
||||
* event_fd_base creates an <a href="https://man7.org/linux/man-pages/man2/eventfd.2.html">event</a>.
|
||||
* The lifetime of the event is bound to the lifetime of this object.
|
||||
*/
|
||||
class event_fd_base {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] initval The initial value for the internal counter of the eventfd
|
||||
* @param[in] flags for the creation of the internal eventfd.
|
||||
*/
|
||||
event_fd_base(unsigned int initval, int flags);
|
||||
|
||||
/**
|
||||
* @brief Declared virtual to ensure proper cleanup via base class pointer,
|
||||
* but defaulted to use the compiler-generated implementation.
|
||||
*/
|
||||
virtual ~event_fd_base() = default;
|
||||
|
||||
event_fd_base(const event_fd_base&) = delete;
|
||||
event_fd_base& operator=(const event_fd_base&) = delete;
|
||||
|
||||
event_fd_base(event_fd_base&&) = default;
|
||||
event_fd_base& operator=(event_fd_base&&) = default;
|
||||
|
||||
/**
|
||||
* @brief Explicit conversion to file descriptor
|
||||
* @return The internal file descriptor
|
||||
*/
|
||||
explicit operator int() const;
|
||||
|
||||
/**
|
||||
* @brief Access to internal file descriptor
|
||||
* @return The internal file descriptor
|
||||
*/
|
||||
int get_raw_fd() const;
|
||||
/**
|
||||
* @brief Check if an event is held by this object
|
||||
* @details Compares internally to \ref unique_fd::NO_DESCRIPTOR_SENTINEL
|
||||
* @return True if an event filedescriptor is held, false otherwise
|
||||
*/
|
||||
bool valid() const;
|
||||
|
||||
/**
|
||||
* @brief Read from the eventfd
|
||||
* @details This returns the value of the eventfd internal counter.
|
||||
* Return immediately is the counter is non-zero. Depending on wether the eventfd
|
||||
* is used as a semaphore or not, calling this function either decrements the internal counter
|
||||
* (semaphore) or resets it to zero.
|
||||
* @return The value of the event counter read from the eventfd.
|
||||
* If the event cannot be read, the optional is a 'nullopt'
|
||||
*/
|
||||
std::optional<std::uint64_t> read();
|
||||
|
||||
/**
|
||||
* @brief Write to the eventfd
|
||||
* @details Adds 'data' to the eventfds internal counter.
|
||||
* This call blocks if adding 'data' to the internal counter would exceed the maximum value.
|
||||
* A call to read() is necessary to unblock.
|
||||
* @param[in] data Payload of the event
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool write(std::uint64_t data);
|
||||
|
||||
/**
|
||||
* @brief Add a single event with default payload '1' to the event queue
|
||||
* @details Calles \ref write(1) internally.
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool notify();
|
||||
|
||||
private:
|
||||
unique_fd m_fd;
|
||||
};
|
||||
|
||||
/**
|
||||
* event_fd creates a blocking eventfd with initial value '0'
|
||||
* The lifetime of the event is bound to the lifetime of this object.
|
||||
*/
|
||||
class event_fd : public event_fd_base {
|
||||
public:
|
||||
event_fd();
|
||||
};
|
||||
|
||||
/**
|
||||
* event_fd creates a blocking eventfd as semaphore with initial value '0'
|
||||
* The lifetime of the event is bound to the lifetime of this object.
|
||||
*/
|
||||
class semaphore_fd : public event_fd_base {
|
||||
public:
|
||||
semaphore_fd();
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::event
|
||||
@@ -0,0 +1,491 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <everest/io/event/event_fd.hpp>
|
||||
#include <everest/io/event/fd_event_sync_interface.hpp>
|
||||
#include <everest/io/event/unique_fd.hpp>
|
||||
#include <everest/io/socket/socket.hpp>
|
||||
#include <everest/io/utilities/event_client_async_policy.hpp>
|
||||
#include <everest/io/utilities/generic_error_state.hpp>
|
||||
#include <everest/util/async/monitor.hpp>
|
||||
#include <future>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
|
||||
namespace everest::lib::io::event {
|
||||
class fd_event_handler;
|
||||
|
||||
/**
|
||||
* @enum action_status
|
||||
* @brief Possible outcomes of actions. Internal usage only.
|
||||
*/
|
||||
enum class action_status {
|
||||
empty,
|
||||
success,
|
||||
fail
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct client_status_internal
|
||||
* Accumulation fo client status for internal usage
|
||||
*/
|
||||
struct client_status_internal {
|
||||
/** filedescriptor */
|
||||
int fd{-1};
|
||||
/** status */
|
||||
bool ok{false};
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements the type independant part of the fd_event_client. This includes all event handling
|
||||
* with \ref fd_event_handler. Not to be used on its own.
|
||||
*/
|
||||
class generic_fd_event_client_impl : public fd_event_sync_interface, protected utilities::generic_error_state {
|
||||
public:
|
||||
/**
|
||||
* @var cb_error
|
||||
* @brief Prototype for an error callback
|
||||
*/
|
||||
using cb_error = generic_error_state::cb_error;
|
||||
/**
|
||||
* @var action
|
||||
* @brief Prototype for action_status
|
||||
*/
|
||||
using action = std::function<action_status()>;
|
||||
/**
|
||||
* @var error_status
|
||||
* @brief Prototype for error_status
|
||||
*/
|
||||
using error_status = std::function<int()>;
|
||||
|
||||
/**
|
||||
* @var ready_action
|
||||
* @brief Prototype for the callback to be called on client ready
|
||||
*/
|
||||
using ready_action = std::function<void()>;
|
||||
|
||||
/**
|
||||
* @brief Access to the internal event handler
|
||||
* @details Call \ref sync on read (POLLIN/EPOLLIN).
|
||||
* Override if an additional layer of event handler is necessary.
|
||||
* @return The file descriptor of the internal event handler
|
||||
*/
|
||||
int get_poll_fd() override;
|
||||
|
||||
/**
|
||||
* @name Syncing the internal event handler
|
||||
* Description
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Sync without a timeout. May not be called in any registered callback.
|
||||
* @details Blocks until an event occurs. Override if any special handling for sync is needed.
|
||||
* @return Result of sync operation
|
||||
*/
|
||||
sync_status sync() override;
|
||||
|
||||
/**
|
||||
* @brief Sync with timeout. May not be called in any registered callback
|
||||
* @details Blocks until an event occurs or the timeout is reached
|
||||
* @param[in] timeout
|
||||
* @return Result of sync operation
|
||||
*/
|
||||
template <class Rep, class Period> sync_status sync(std::chrono::duration<Rep, Period> timeout) {
|
||||
return sync_impl(std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sync with timeout. May not be called in any registered callback
|
||||
* @details Blocks until an event occurs ot the timeout is reached
|
||||
* @return Result of sync operation
|
||||
*/
|
||||
sync_status sync_impl(int timeout_ms);
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Register an error handler
|
||||
* @details The error handler is called when an error occurs or is cleared
|
||||
* @param[in] handler The callback to be used as error handler
|
||||
*/
|
||||
void set_error_handler(cb_error const& handler);
|
||||
|
||||
/**
|
||||
* @brief Register a function, that is called once the client is ready
|
||||
* @details This callback is performed immediately, if the client is in a ready state
|
||||
* or the client is ready. The action is persistent and called whenever the client
|
||||
* is ready after reset.
|
||||
* @param[in] item The callback
|
||||
*/
|
||||
void set_on_ready_action(std::function<void()>&& item);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Constructor.
|
||||
* @details Functionality passed in by functors
|
||||
*/
|
||||
generic_fd_event_client_impl(action const& send_one, action const& receive_one, action const& reset_client,
|
||||
error_status const& get_error);
|
||||
~generic_fd_event_client_impl();
|
||||
|
||||
/**
|
||||
* @brief Handling of RX operations
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool rx_handler();
|
||||
|
||||
/**
|
||||
* @brief Handling of TX operations
|
||||
* @details Writing one queued message at a time to the file descriptor \p fd.
|
||||
* When there are no more queued messages, notifications for writing [(E)POLLOUT] are disabled.
|
||||
* @param[in] fd The filedescriptor for writing
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool tx_handler(int fd);
|
||||
|
||||
/**
|
||||
* @brief Handle errors errors.
|
||||
* @details This triggers a reset of the client and calls the \p error_handler
|
||||
* with the current error.
|
||||
*/
|
||||
void error_handler();
|
||||
|
||||
/**
|
||||
* @brief Setup io event handling.
|
||||
* @details This registers a generic event handler for reading, writing and error handling.
|
||||
* Errors and reading will be handled continuously, writing on demand.
|
||||
* @param[in] fd Filedescriptor to be monitored.
|
||||
*/
|
||||
void setup_io_event_handler(int fd);
|
||||
|
||||
/**
|
||||
* @brief Register setup of I/O event handling for client ready
|
||||
* @details This prepares the I/O event handling to be setup once the client is ready.
|
||||
* This includes preparation of the I/O event handler and checking for errors
|
||||
* as well as adding the on_ready_action to the action queue.
|
||||
*/
|
||||
void prepare_io_event_handler();
|
||||
|
||||
/**
|
||||
* @brief Setup error event handling.
|
||||
* @details This registers the error event_status event with the event handler, which is independent of
|
||||
* the io event handling.
|
||||
*/
|
||||
bool setup_error_event_handler();
|
||||
|
||||
/**
|
||||
* @brief Remove event handler
|
||||
* @details Stop event monitoring and event handling for \p fd
|
||||
* @param[in] fd The file descriptor
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool unregister_source(int fd);
|
||||
|
||||
/**
|
||||
* @brief Set the internal error status and notify the error_status event handler
|
||||
* @param[in] error_code The current error code
|
||||
* @return False on error, true otherwise
|
||||
*/
|
||||
bool set_error_status_and_notify(int error_code);
|
||||
|
||||
/**
|
||||
* @brief To be called on client ready (internally)
|
||||
* @details This set's the client_status and notifies the internal event
|
||||
* @param[in] ok Client status. 'true' on ready/success, 'false' otherwise
|
||||
* @param[in] fd The filedescriptor of the client's socket
|
||||
*/
|
||||
void on_client_ready(bool ok, int fd);
|
||||
|
||||
/**
|
||||
* @brief Add an action to the action queue of the event handler
|
||||
* @details This adds a new action to the action queue of the event handler. Action's are called
|
||||
* after all event handling is done. For this reason it is safe register/unregister for
|
||||
* events and other do other things, that effect the event handler within an action.
|
||||
* @param[in] item
|
||||
*/
|
||||
void add_action(std::function<void()>&& item);
|
||||
|
||||
/// @cond
|
||||
std::unique_ptr<event::fd_event_handler> m_event_handler;
|
||||
event::event_fd m_io_event_fd;
|
||||
event::event_fd m_error_status_event_fd;
|
||||
event::event_fd m_connected_event_fd;
|
||||
|
||||
cb_error m_error;
|
||||
action m_send_one;
|
||||
action m_receive_one;
|
||||
action m_reset_client;
|
||||
error_status m_get_error;
|
||||
util::monitor<client_status_internal> m_client_status;
|
||||
ready_action m_on_ready_action;
|
||||
/// @endcond
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements the type dependant part of the fd_event_client. Best to be constructed via \ref fd_event_client
|
||||
* Refer to it also for the contraints of the @tparam ClientPolicy
|
||||
* @tparam ClientInterface Provides the interface used for rx callbacks. This is the policy definition
|
||||
* \code{.cpp}
|
||||
* class ClientInterfacePolicy {
|
||||
* public:
|
||||
* using cb_rx = std::function<void(payload const& payload, interface& device)>; // type of rx callback
|
||||
* virtual ~interface() = default;
|
||||
* virtual bool tx(payload const& payload) = 0; // definition fo payload funcion
|
||||
* };
|
||||
* \endcode
|
||||
*/
|
||||
template <class ClientPolicy, class ClientInterfacePolicy>
|
||||
class generic_fd_event_client : public ClientInterfacePolicy, public generic_fd_event_client_impl {
|
||||
public:
|
||||
/**
|
||||
* @var cb_rx
|
||||
* @brief Prototype of the receive callback
|
||||
*/
|
||||
using cb_rx = typename ClientInterfacePolicy::cb_rx;
|
||||
|
||||
/**
|
||||
* @var ClientPayloadT
|
||||
* @brief Type of the payload to be handled by the client
|
||||
*/
|
||||
using ClientPayloadT = typename ClientPolicy::PayloadT;
|
||||
|
||||
/**
|
||||
* @brief Construction of the generic event handler
|
||||
* @details All parameters will be forwarded to the open(...) function of the \p ClientPolicy
|
||||
*/
|
||||
template <class... ArgsT>
|
||||
generic_fd_event_client(ArgsT... args) :
|
||||
generic_fd_event_client_impl([this]() { return send_one(); }, [this]() { return receive_one(); },
|
||||
[this]() { return reset_client(); }, [this]() { return m_handle->get_error(); }) {
|
||||
init<ClientPolicy>(args...);
|
||||
}
|
||||
|
||||
~generic_fd_event_client() = default;
|
||||
|
||||
/**
|
||||
* @brief Register a callback for RX.
|
||||
* @details This handler will be called with all new data available
|
||||
* @param[in] handler The callback used as RX handler
|
||||
*/
|
||||
void set_rx_handler(cb_rx const& handler) {
|
||||
add_action([this, handler]() { m_rx = std::move(handler); });
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send data
|
||||
* @details This buffers the incoming data and notifies. It will be send as soon as the file descriptor
|
||||
* is ready for transmission (POLLOUT / EPOLLOUT)
|
||||
* @param[in] payload The data to be transmitted.
|
||||
* @return False if client is \ref on_error. False otherwise
|
||||
*/
|
||||
bool tx(ClientPayloadT const& payload) override {
|
||||
if (on_error()) {
|
||||
return false;
|
||||
}
|
||||
m_tx_buffer.emplace(payload);
|
||||
m_io_event_fd.notify();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset the client.
|
||||
* @details Use this function to reset the client if on error or any other reason.
|
||||
* This destructs the client as defined by \p ClientPolicy and opens it again.
|
||||
* @return True if the client could be successfully created. False otherwise
|
||||
*/
|
||||
bool reset() {
|
||||
add_action([this]() { reset_impl(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the current error state
|
||||
* @return True if on error, false otherwise
|
||||
*/
|
||||
bool on_error() {
|
||||
return generic_error_state::on_error();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Access to the raw client.
|
||||
* @details The reference is bound to the lifetime of this object. The object pointed to
|
||||
* changes with each call to 'reset' and may be 'nullptr'
|
||||
* @return Reference to the raw client.
|
||||
*/
|
||||
std::unique_ptr<ClientPolicy> const& get_raw_handler() {
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
private:
|
||||
template <class T, class... ArgsT> std::enable_if_t<utilities::event_client_async_policy_v<T>> init(ArgsT... args) {
|
||||
m_open_device = [this, args...]() {
|
||||
auto setup_ok = m_handle->setup(args...);
|
||||
if (setup_ok) {
|
||||
std::thread t([this]() { m_handle->connect([this](bool ok, int fd) { on_client_ready(ok, fd); }); });
|
||||
t.detach();
|
||||
} else {
|
||||
on_client_ready(false, -1);
|
||||
}
|
||||
};
|
||||
reset();
|
||||
}
|
||||
|
||||
template <class T, class... ArgsT>
|
||||
std::enable_if_t<not utilities::event_client_async_policy_v<T>> init(ArgsT... args) {
|
||||
m_open_device = [this, args...]() {
|
||||
auto result = m_handle->open(args...);
|
||||
on_client_ready(result, m_handle->get_fd());
|
||||
};
|
||||
reset_impl();
|
||||
}
|
||||
|
||||
void reset_impl() {
|
||||
m_client_status.handle()->fd = false;
|
||||
setup_error_event_handler();
|
||||
init_device();
|
||||
m_tx_buffer = {};
|
||||
prepare_io_event_handler();
|
||||
}
|
||||
|
||||
action_status send_one() {
|
||||
if (m_tx_buffer.empty()) {
|
||||
return action_status::empty;
|
||||
}
|
||||
auto& elem = m_tx_buffer.front();
|
||||
auto success = m_handle->tx(elem);
|
||||
if (success) {
|
||||
m_tx_buffer.pop();
|
||||
return action_status::success;
|
||||
}
|
||||
return action_status::fail;
|
||||
}
|
||||
|
||||
action_status receive_one() {
|
||||
auto status = m_handle->rx(m_data);
|
||||
if (status and m_rx) {
|
||||
m_rx(m_data, *this);
|
||||
return action_status::success;
|
||||
}
|
||||
return action_status::fail;
|
||||
}
|
||||
|
||||
action_status reset_client() {
|
||||
auto client_status = m_client_status.handle();
|
||||
unregister_source(client_status->fd);
|
||||
unregister_source(m_io_event_fd.get_raw_fd());
|
||||
m_handle.reset();
|
||||
client_status->fd = -1;
|
||||
return action_status::success;
|
||||
}
|
||||
|
||||
bool init_device() {
|
||||
m_handle = std::make_unique<ClientPolicy>();
|
||||
m_open_device();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<ClientPolicy> m_handle{nullptr};
|
||||
cb_rx m_rx;
|
||||
std::function<void()> m_open_device;
|
||||
std::queue<ClientPayloadT> m_tx_buffer;
|
||||
ClientPayloadT m_data;
|
||||
};
|
||||
|
||||
/**
|
||||
* fd_event_client bootstraps the creation of of the event client by creating the interface needed
|
||||
* for \ref generic_fd_event_client and then specifying the actual client type via this template.
|
||||
* @tparam ClientPolicy Handles specific tasks on the filedescriptor, e.g. open, connect, send, receive,...
|
||||
* When ClientPolicy offers the optional functions setup/connect these are used for an async connection
|
||||
* process instead of the syncronous and potentially blocking call to open.
|
||||
* The policy is the same as for \ref generic_fd_event_client.
|
||||
* \code{.cpp}
|
||||
* class ClientPolicy{
|
||||
* public:
|
||||
* // callback = std::function<void(bool success, int filedescriptor)>
|
||||
* using PayloadT = [type of data] // the type of the payload
|
||||
* ClientPolicy(); // must be default contructiable
|
||||
* bool open(ArgsT... args); // Opens the device. Parameters given by fd_event_client's constructor.
|
||||
* // Return true on success, false otherwise
|
||||
* bool setup(ArgsT... args); // [optional] Setup the device. Parameters given by fd_event_client's constructor.
|
||||
* // This function is the first step in a two part connection process.
|
||||
* // It may not block for any subtantial amount of time.
|
||||
* // Return true on success, false otherwise
|
||||
* void connect(callback [const&]); // [optional] Bring the device in a ready state. This is the second step in a two
|
||||
* part
|
||||
* // connection process. This part shall bundle a blocking operations and call the
|
||||
* callback
|
||||
* // with the correct status when done. A filedescriptor of -1 is considered
|
||||
* invalid.
|
||||
* // This function may be called from a separate thread.
|
||||
* bool tx(PayloadT [const]& data); // transmit a dataset. Return true on success, false otherwise
|
||||
* // data could be passed as non-const reference. The reference remains valid as
|
||||
* long
|
||||
* // false is returned. Useful for partial writes.
|
||||
* bool rx(PayloadT& data); // receive a dataset. Make no assumptions about the content of data
|
||||
* // It may contain any data. rx is responsible to bring data a consistent state.
|
||||
* // Return true on success, false otherwise
|
||||
* int get_fd(); // returns the file discriptor to be monitored
|
||||
* int get_error(); // return the current error, 0 with no error.
|
||||
* };
|
||||
* // ClientPolicy owns the file descriptor
|
||||
* \endcode
|
||||
* \details A specific client is created this way
|
||||
* \code{.cpp}
|
||||
* using specific_client = event::fd_event_client<specific_policy>::type;
|
||||
*
|
||||
* \endcode
|
||||
*/
|
||||
template <class ClientPolicy> class fd_event_client {
|
||||
public:
|
||||
/**
|
||||
* @var payload
|
||||
* @brief The type of the payload is infered from the policy \p ClientPolicy
|
||||
*/
|
||||
using payload = typename ClientPolicy::PayloadT;
|
||||
|
||||
/**
|
||||
* This is the interface to the client as seen in the RX callback.
|
||||
*/
|
||||
class interface {
|
||||
public:
|
||||
/**
|
||||
* @var cb_rx
|
||||
* @brief Prototype for the callback function to be registered for RX with the client
|
||||
*/
|
||||
using cb_rx = std::function<void(payload const& payload, interface& device)>;
|
||||
virtual ~interface() = default;
|
||||
/**
|
||||
* @brief Interface function for TX.
|
||||
* @details Implemented by generic_fd_event_client
|
||||
*/
|
||||
virtual bool tx(payload const& payload) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @var type
|
||||
* @brief Type of the specific client
|
||||
*/
|
||||
using type = generic_fd_event_client<ClientPolicy, interface>;
|
||||
|
||||
/**
|
||||
* @var callback_rx
|
||||
* @brief Type of the RX callback
|
||||
*/
|
||||
using callback_rx = typename interface::cb_rx;
|
||||
|
||||
/**
|
||||
* @var callback_error
|
||||
* @brief Type of the error callback
|
||||
*/
|
||||
using callback_error = typename type::cb_error;
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::event
|
||||
@@ -0,0 +1,323 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <everest/io/event/event_fd.hpp>
|
||||
#include <everest/io/event/fd_event_client.hpp>
|
||||
#include <everest/io/event/fd_event_register_interface.hpp>
|
||||
#include <everest/util/queue/thread_safe_queue.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
namespace everest::lib::io::event {
|
||||
|
||||
/**
|
||||
* @enum poll_events
|
||||
* @brief Collection of types of polling events
|
||||
*/
|
||||
enum class poll_events {
|
||||
read = 0,
|
||||
priority = 1,
|
||||
write = 2,
|
||||
error = 3,
|
||||
hungup = 4,
|
||||
};
|
||||
|
||||
std::set<poll_events> operator|(poll_events lhs, poll_events rhs);
|
||||
std::set<poll_events>& operator|(std::set<poll_events>& lhs, poll_events rhs);
|
||||
bool operator&(std::set<poll_events> const& lhs, poll_events rhs);
|
||||
|
||||
/**
|
||||
* @enum event_modification
|
||||
* @brief Possible modifications to the \p poll_events list of an event handler
|
||||
*/
|
||||
enum class event_modification {
|
||||
add,
|
||||
remove,
|
||||
replace,
|
||||
};
|
||||
|
||||
// forward declaration
|
||||
class EventHandlerMap;
|
||||
class event_fd;
|
||||
class timer_fd;
|
||||
class generic_fd_event_client_impl;
|
||||
/**
|
||||
* fd_event_handler implements a general event handling mechanism based on file descriptors
|
||||
* and <a href="https://man7.org/linux/man-pages/man7/epoll.7.html">epoll</a>. Any file descriptor can
|
||||
* be registered together with a list of the events of interest and a callback.
|
||||
* This class provides itself a filedescriptor that can be added to other event handlers. This way
|
||||
* concerns may be separated and event handlers nested.
|
||||
*/
|
||||
class fd_event_handler {
|
||||
public:
|
||||
/**
|
||||
* @var event_list
|
||||
* @brief A disjoint list of events.
|
||||
*/
|
||||
using event_list = std::set<poll_events>;
|
||||
|
||||
/**
|
||||
* @var event_handler_type
|
||||
* @brief Prototype of the callbacks that can be registered for event handling
|
||||
*/
|
||||
using event_handler_type = std::function<void(event_list const& event)>;
|
||||
/**
|
||||
* @var event_handler_type
|
||||
* @brief Simplified prototype of the callbacks that can be registered for event handling
|
||||
*/
|
||||
using event_handler_simple_type = std::function<void()>;
|
||||
|
||||
/**
|
||||
* @var task
|
||||
* @brief Prototype of a callback that is added to the tasks queue
|
||||
*/
|
||||
using task = std::function<void()>;
|
||||
|
||||
/**
|
||||
* @brief fd_event_event is default constructed
|
||||
*/
|
||||
fd_event_handler();
|
||||
/**
|
||||
* @brief On destruction all event handling is stopped and resources are cleaned up.
|
||||
* The file descriptor returned by \ref get_poll_fd is invalidated at this point.
|
||||
*/
|
||||
~fd_event_handler();
|
||||
|
||||
/**
|
||||
* @name Event registration
|
||||
* Register events or special objects for event handling
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Register an arbitrary file descriptor for event handling
|
||||
* @details The file descriptor will be monitored for all listed events. The handler is called when any of the
|
||||
* events in the list occur. The actual list of events will be passed as argument to the handler. It is the
|
||||
* users responsibility to read from the file descriptor as necessary to acknowledge the event handling. Otherwise
|
||||
* the same event will fire again.
|
||||
* @param[in] fd The file descriptor to be monitored
|
||||
* @param[in] handler Callback for the handling of the events on \p fd
|
||||
* @param[in] events The list of events to me monitored of \p fd
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool register_event_handler(int fd, event_handler_type const& handler, event_list const& events);
|
||||
|
||||
/**
|
||||
* @brief Register an arbitrary file descriptor for event handling
|
||||
* @details The file descriptor will be monitored for the event. The handler is called the
|
||||
* event in the list occur. The actual list of events will be passed as argument to the handler. It is the
|
||||
* users responsibility to read from the file descriptor as necessary to acknowledge the event handling. Otherwise
|
||||
* the same event will fire again.
|
||||
* @param[in] fd The file descriptor to be monitored
|
||||
* @param[in] handler Callback for the handling of the events on \p fd
|
||||
* @param[in] event The event to me monitored of \p fd
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool register_event_handler(int fd, event_handler_type const& handler, poll_events event);
|
||||
|
||||
/**
|
||||
* @brief Register an \ref event_fd for event handling
|
||||
* @details Reading from the event happens internally to acknowledge event handling.
|
||||
* If manual handling is necessary use \ref event_fd filedescriptor directly
|
||||
* @param[inout] obj The object to be registerd for event handling
|
||||
* @param[in] handler Callback for handling the events on \p obj
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool register_event_handler(event_fd* obj, event_handler_type const& handler);
|
||||
/**
|
||||
* @copydoc register_event_handler(event_fd*, event_handler_type const&)
|
||||
* @param[in] handler Simplified callback for handling events on \p obj
|
||||
*/
|
||||
bool register_event_handler(event_fd* obj, event_handler_simple_type const& handler);
|
||||
/**
|
||||
* @brief Register an \ref timer_fd for event handling
|
||||
* @details Reading from the event happens internally to acknowledge event handling.
|
||||
* If manual handling is necessary use \ref timer_fd filedecriptor directly
|
||||
* @param[in] obj The object to be registerd for event handling
|
||||
* @param[in] handler Callback for handling the events on \p obj
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool register_event_handler(timer_fd* obj, event_handler_type const& handler);
|
||||
/**
|
||||
* @copydoc register_event_handler(timer_fd*, event_handler_type const&)
|
||||
* @param[in] handler Simplified callback for handling events on \p obj
|
||||
*/
|
||||
bool register_event_handler(timer_fd* obj, event_handler_simple_type const& handler);
|
||||
|
||||
/**
|
||||
* @brief Register a client implementing \ref fd_event_sync_interface for event handling
|
||||
* @details On notification from the file descriptor of the client, its sync method is called
|
||||
* If manual handling is necessary use \ref fd_event_client filedescriptor directly
|
||||
* @param[in] obj The object to be registerd for event handling
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool register_event_handler(fd_event_sync_interface* obj);
|
||||
|
||||
/**
|
||||
* @brief Register client evens of \ref fd_event_register_interface for event handling
|
||||
* @param[in] obj The object that needs to register its events registerd for event handling
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool register_event_handler(fd_event_register_interface* obj);
|
||||
|
||||
/**
|
||||
* @brief Register an \ref fd_event_handler for event handling
|
||||
* @details This will call \p poll on the event_handler whenever any of its handles events
|
||||
need attention.
|
||||
* @param[in] obj The fd_event_handler to be registerd for event handling
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool register_event_handler(fd_event_handler* obj);
|
||||
|
||||
/**
|
||||
* @brief Unregister client events of \ref fd_event_register_interface from event handling
|
||||
* @param[in] obj The object that needs to unregister its events registerd from
|
||||
* event handling
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool unregister_event_handler(fd_event_register_interface* obj);
|
||||
|
||||
/**
|
||||
* @brief Unregister object implementing \ref fd_event_sync_interface from event handling
|
||||
* @param[in] obj The object to be removed from event handling
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
|
||||
bool unregister_event_handler(fd_event_sync_interface* obj);
|
||||
|
||||
/**
|
||||
* @brief Unregister timer_fd from event handling
|
||||
* @param[in] obj The timer to be removed
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool unregister_event_handler(timer_fd* obj);
|
||||
|
||||
/**
|
||||
* @brief Unregister event_fd from event handling
|
||||
* @param[in] obj The event to be removed
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool unregister_event_handler(event_fd* obj);
|
||||
|
||||
/**
|
||||
* @brief Unregister a file descriptor from event handling
|
||||
* @param[in] fd The filedescriptor to be removed
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool unregister_event_handler(int fd);
|
||||
|
||||
/**
|
||||
* @brief Modify the handling of an already registered file descriptor
|
||||
* @details This allows to change the handled events on a file descriptor. The most obvious
|
||||
* usecase is to add modify the handling of writability (EPOLLOUT).
|
||||
* You want to be notified if there is something to write only.
|
||||
* @param[in] fd The file descriptor to perform the modification on
|
||||
* @param[in] events List of events, that should be changed
|
||||
* @param[in] change The kind of modification to perform
|
||||
* @return Description
|
||||
*/
|
||||
bool modify_event_handler(int fd, event_list const& events, event_modification change);
|
||||
|
||||
/**
|
||||
* @brief Modify the handling of an already registered file descriptor
|
||||
* @details This allows to change the handled events on a file descriptor. The most obvious
|
||||
* usecase is to add modify the handling of writability (EPOLLOUT).
|
||||
* You want to be notified if there is something to write only.
|
||||
* @param[in] fd The file descriptor to perform the modification on
|
||||
* @param[in] event The event, that should be changed
|
||||
* @param[in] change The kind of modification to perform
|
||||
* @return Description
|
||||
*/
|
||||
bool modify_event_handler(int fd, poll_events event, event_modification change);
|
||||
|
||||
/**
|
||||
* @brief Stop monitoring of events on the file descriptor.
|
||||
* @param[in] fd The file descriptor
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool remove_event_handler(int fd);
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Wait for any of the registered events to occur
|
||||
* @param[in] timeout Maximum time to wait for any event
|
||||
* @return false in case of timeout, true otherwise
|
||||
*/
|
||||
template <class Rep, class Period> bool poll(std::chrono::duration<Rep, Period> timeout) {
|
||||
return poll_impl(std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Wait for any of the registered events to occur
|
||||
* @details This function polls the internal file descriptor. Blocks until an event occurs.
|
||||
*/
|
||||
void poll();
|
||||
|
||||
/**
|
||||
* @brief Get the event handlers own file descriptor.
|
||||
* @details If any of the registered file descriptors have an update for any event,
|
||||
* this filedescriptors notifies a read event (POLLIN/EPOLLIN).
|
||||
* @return The file descriptor of the event handler.
|
||||
*/
|
||||
int get_poll_fd();
|
||||
|
||||
/**
|
||||
* @brief Add a task to the task queue
|
||||
* @details Adds a task to the task queue
|
||||
* @param[in] item The task
|
||||
*/
|
||||
void add_action(task&& item);
|
||||
|
||||
/**
|
||||
* @brief Add a task to the task queue
|
||||
* @details Adds a task to the task queue
|
||||
* @param[in] item The task
|
||||
*/
|
||||
void add_action(task const& item);
|
||||
|
||||
/**
|
||||
* @brief Run the tasks in the task queue
|
||||
*/
|
||||
void run_actions();
|
||||
|
||||
/**
|
||||
* @brief Run the event loop
|
||||
* @details This runs the two step event loop. First step is to call \ref poll, the second step is to call
|
||||
* \ref run_actions. This loop continues while online is 'true'. Beware, just setting online to 'false'
|
||||
* Does not stop the queue immediately, it just prevents an other cycle from running. For this reason
|
||||
* it is advisable to register an event that can manually be notified on a cancellation request.
|
||||
* @param[in] online Description
|
||||
*/
|
||||
void run(std::atomic_bool& online);
|
||||
|
||||
/**
|
||||
* @brief Run a single iteration of the event loop
|
||||
* @details This runs a single iteration of the two step event loop.
|
||||
* First step is to call \ref poll, the second step is to call
|
||||
* \ref run_actions.
|
||||
*/
|
||||
void run_once();
|
||||
|
||||
private:
|
||||
/// Wait with timeout for any of the registered events to occur
|
||||
/**
|
||||
* \param timeout_ms Maximum time in ms to wait for any event
|
||||
* \return false in case of timeout, true otherwise
|
||||
*/
|
||||
bool poll_impl(int timeout_ms);
|
||||
std::unique_ptr<EventHandlerMap> m_handlers{nullptr};
|
||||
util::thread_safe_queue<task> task_pool;
|
||||
event_fd m_action_event;
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::event
|
||||
@@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace everest::lib::io::event {
|
||||
|
||||
class fd_event_handler;
|
||||
|
||||
/**
|
||||
* Interface for classes implementing registration with existing fd_event_handler
|
||||
*/
|
||||
class fd_event_register_interface {
|
||||
public:
|
||||
virtual ~fd_event_register_interface() = default;
|
||||
|
||||
/**
|
||||
* @brief Register with existing event handler
|
||||
* @param[in] handler The event handler to register with
|
||||
* @return true on success, false otherwise
|
||||
*/
|
||||
virtual bool register_events(fd_event_handler& handler) = 0;
|
||||
/**
|
||||
* @brief Unregister from existing event handler
|
||||
* @param[in] handler The event handler to unregister from
|
||||
* @return true on success, false otherwise
|
||||
*/
|
||||
virtual bool unregister_events(fd_event_handler& handler) = 0;
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::event
|
||||
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace everest::lib::io::event {
|
||||
|
||||
/**
|
||||
* @brief Possible outcomes of syncing
|
||||
*/
|
||||
enum class sync_status {
|
||||
/** Sync was successful */
|
||||
ok,
|
||||
/** Sync timed out */
|
||||
timeout,
|
||||
/** Sync was not successful*/
|
||||
error
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for classes implementing default syncing (E/POLLIN only)for fd_event_handler
|
||||
*/
|
||||
class fd_event_sync_interface {
|
||||
public:
|
||||
virtual ~fd_event_sync_interface() = default;
|
||||
|
||||
/**
|
||||
* @brief Access to the internal event handler
|
||||
* @details Call \ref sync on read (E/POLLIN).
|
||||
* Override if an additional layer of event handler is necessary.
|
||||
* @return The file descriptor of the internal event handler
|
||||
*/
|
||||
virtual int get_poll_fd() = 0;
|
||||
|
||||
/**
|
||||
* @brief Sync internal event handler
|
||||
* @details Blocks until an event occurs.
|
||||
* @return Result of sync operation
|
||||
*/
|
||||
virtual everest::lib::io::event::sync_status sync() = 0;
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::event
|
||||
@@ -0,0 +1,115 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "unique_fd.hpp"
|
||||
#include <chrono>
|
||||
|
||||
namespace everest::lib::io::event {
|
||||
|
||||
/**
|
||||
* timer_fd creates and configures a <a href="https://man7.org/linux/man-pages/man2/timerfd_create.2.html">timer</a>.
|
||||
* The lifetime of the timer is bound to the lifetime of this object.
|
||||
*/
|
||||
class timer_fd {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @details After construction the timeout is undefined. It must be set manually.
|
||||
*/
|
||||
timer_fd();
|
||||
|
||||
/**
|
||||
* @brief Explicit conversion to file descriptor
|
||||
* @return The internal file descriptor
|
||||
*/
|
||||
explicit operator int() const;
|
||||
|
||||
/**
|
||||
* @brief Access to internal file descriptor
|
||||
* @return The internal file descriptor
|
||||
*/
|
||||
int get_raw_fd() const;
|
||||
|
||||
/**
|
||||
* @brief Check if a timer is held by this object
|
||||
* @details Compares internally to \ref unique_fd::NO_DESCRIPTOR_SENTINEL
|
||||
* @return True if a timer is held, false otherwise
|
||||
*/
|
||||
bool valid() const;
|
||||
|
||||
/**
|
||||
* @brief Read from timer
|
||||
* @details This acknowledges, that the timer event has been handled. Poll on this object will
|
||||
* return immediately until this function has been called after a timeout event.
|
||||
* @return The value read from the timer
|
||||
*/
|
||||
int read();
|
||||
|
||||
/**
|
||||
* @brief Resets the timer
|
||||
* @details This starts a new timeout period for an already set running timer.
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool reset();
|
||||
|
||||
/**
|
||||
* @brief Select one-shot or periodic mode for subsequent \ref set_timeout_ns calls.
|
||||
* @details One-shot: \c it_value is the delay, \c it_interval is zero. Periodic (default):
|
||||
* both \c it_value and \c it_interval use the configured timeout.
|
||||
*/
|
||||
void set_single_shot(bool on);
|
||||
|
||||
/**
|
||||
* @brief Stop the timer (no pending expiry until armed again).
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool disarm();
|
||||
|
||||
/**
|
||||
* @name Configuring the notification timeout
|
||||
* This set of functions allows to set the time after which the timer file fire
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* @brief Milliseconds
|
||||
* @param[in] to timeout in milliseconds
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool set_timeout_ms(long long to);
|
||||
/**
|
||||
* @brief Microseconds
|
||||
* @param[in] to timeout in microseconds
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool set_timeout_us(long long to);
|
||||
/**
|
||||
* @brief Nanoseconds
|
||||
* @param[in] to timeout in nanoseconds
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool set_timeout_ns(long long to);
|
||||
|
||||
/**
|
||||
* @brief <a href="https://en.cppreference.com/w/cpp/chrono/duration"> std::chrono::duration </a>
|
||||
* @param[in] timeout timeout in abitrary units
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
template <class Rep, class Period> bool set_timeout(std::chrono::duration<Rep, Period> timeout) {
|
||||
auto interval = std::chrono::duration_cast<std::chrono::nanoseconds>(timeout);
|
||||
return set_timeout_ns(interval.count());
|
||||
}
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
private:
|
||||
unique_fd m_fd;
|
||||
long long m_to_ns{0};
|
||||
bool m_single_shot{false};
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::event
|
||||
@@ -0,0 +1,78 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace everest::lib::io::event {
|
||||
|
||||
/**
|
||||
* unique_fd manages the lifetime of a file descriptor. It takes ownership of the held filedescriptor and * closes it on
|
||||
* destruction. Objects of this class can be moved but not copied.
|
||||
*/
|
||||
class unique_fd {
|
||||
public:
|
||||
unique_fd() = default;
|
||||
|
||||
/**
|
||||
* @brief Construct from file descriptor
|
||||
* @details Takes ownership of the file descriptor. Implicit conversion is disabled.
|
||||
* @param[in] fd A valid filedescriptor.
|
||||
*/
|
||||
explicit unique_fd(int fd);
|
||||
unique_fd(const unique_fd&) = delete;
|
||||
unique_fd& operator=(const unique_fd&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Construct from other unique_fd.
|
||||
* @param[in, out] other Transfer ownership from \p other to this.
|
||||
*/
|
||||
unique_fd(unique_fd&& other);
|
||||
/**
|
||||
* @brief Assign from other unique_fd.
|
||||
* @param[in, out] other Transfer ownership from \p other to this.
|
||||
*/
|
||||
unique_fd& operator=(unique_fd&& other);
|
||||
|
||||
/**
|
||||
* @brief Destructor calls \ref close internally
|
||||
*/
|
||||
~unique_fd();
|
||||
|
||||
/**
|
||||
* @brief Conversion to file descriptor
|
||||
* @details The conversion is implicit
|
||||
* @return The managed file descriptor
|
||||
*/
|
||||
operator int() const;
|
||||
|
||||
/**
|
||||
* @brief Check if a file descriptor is held
|
||||
* @details Compares to \ref NO_DESCRIPTOR_SENTINEL
|
||||
* @return True if a value is held, false otherwise
|
||||
*/
|
||||
bool is_fd() const;
|
||||
|
||||
/**
|
||||
* @brief Give up ownership of the file descriptor
|
||||
* @return The previously managed file descriptor
|
||||
*/
|
||||
int release();
|
||||
|
||||
/**
|
||||
* @brief Close the file descriptor
|
||||
* @details \ref is_fd return false after this call.
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* @var NO_DESCRIPTOR_SENTINEL
|
||||
* @brief Special value representing an invalid file descriptor
|
||||
*/
|
||||
constexpr static int NO_DESCRIPTOR_SENTINEL = -1;
|
||||
|
||||
private:
|
||||
int m_fd{NO_DESCRIPTOR_SENTINEL};
|
||||
};
|
||||
} // namespace everest::lib::io::event
|
||||
@@ -0,0 +1,53 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace everest::lib::io::mdns {
|
||||
|
||||
struct mDNS_discovery {
|
||||
using txt_field = std::map<std::string, std::string>;
|
||||
|
||||
void add_string(std::string const& key, std::string const& value) {
|
||||
txt[key] = value;
|
||||
}
|
||||
|
||||
std::string ip;
|
||||
std::uint16_t port;
|
||||
std::string hostname;
|
||||
std::string service_instance;
|
||||
txt_field txt;
|
||||
|
||||
static const std::uint16_t txt_string_limit{255};
|
||||
static const std::uint16_t txt_record_limit{1500};
|
||||
};
|
||||
|
||||
std::optional<mDNS_discovery> parse_mdns_packet(std::vector<std::uint8_t> const& packet);
|
||||
std::vector<std::uint8_t> create_mdns_query(std::string const& name);
|
||||
|
||||
/// Build an mDNS response packet advertising the given service.
|
||||
/// Includes PTR, SRV, TXT, and A records.
|
||||
std::vector<std::uint8_t> create_mdns_response(mDNS_discovery const& service, std::string const& service_type);
|
||||
|
||||
/// Check if an mDNS packet is a query for the given service type.
|
||||
bool is_query_for(std::vector<std::uint8_t> const& packet, std::string const& service_type);
|
||||
|
||||
class mDNS_registry {
|
||||
public:
|
||||
using registry = std::map<std::string, mDNS_discovery>;
|
||||
bool update(mDNS_discovery const& update);
|
||||
void remove(const std::string& instance);
|
||||
void clear();
|
||||
registry const& get();
|
||||
|
||||
private:
|
||||
registry data;
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::mdns
|
||||
@@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <everest/io/event/fd_event_client.hpp>
|
||||
#include <everest/io/mdns/mdns_socket.hpp>
|
||||
|
||||
namespace everest::lib::io::mdns {
|
||||
|
||||
/**
|
||||
* @var mdns_client
|
||||
* @brief Client for MDNS discovery implemented in terms of \ref event::fd_event_client
|
||||
* and \ref mdns::mdns_socket
|
||||
*/
|
||||
using mdns_client = event::fd_event_client<mdns_socket>::type;
|
||||
|
||||
} // namespace everest::lib::io::mdns
|
||||
@@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <everest/io/event/unique_fd.hpp>
|
||||
#include <everest/io/mdns/mdns.hpp>
|
||||
#include <everest/io/udp/udp_payload.hpp>
|
||||
#include <everest/io/udp/udp_socket.hpp>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace everest::lib::io::mdns {
|
||||
|
||||
struct udp_info {
|
||||
/** Adress */
|
||||
uint32_t addr;
|
||||
/** Port */
|
||||
uint16_t port;
|
||||
/** Family */
|
||||
uint16_t family;
|
||||
};
|
||||
|
||||
class mdns_socket : public udp::udp_socket_base {
|
||||
public:
|
||||
using PayloadT = udp::udp_payload;
|
||||
|
||||
mdns_socket() = default;
|
||||
~mdns_socket() = default;
|
||||
|
||||
bool open(std::string const& interface);
|
||||
bool tx(udp::udp_payload const& payload);
|
||||
bool rx(udp::udp_payload& payload);
|
||||
|
||||
bool query(std::string const& what);
|
||||
|
||||
/// Send an mDNS response advertising the given service
|
||||
bool announce(mDNS_discovery const& service, std::string const& service_type);
|
||||
|
||||
private:
|
||||
std::string m_remote;
|
||||
uint16_t m_port{0};
|
||||
int m_timeout_ms{0};
|
||||
|
||||
std::array<uint8_t, udp::udp_payload::max_size> rx_buffer;
|
||||
udp::udp_info target;
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::mdns
|
||||
@@ -0,0 +1,296 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
struct mosquitto;
|
||||
struct mosquitto_message;
|
||||
struct mqtt5__property;
|
||||
|
||||
namespace everest::lib::io::mqtt {
|
||||
|
||||
enum class ErrorCode : std::uint8_t {
|
||||
Success = 0,
|
||||
InvalidArgument,
|
||||
NoMemory,
|
||||
Errno, // check errno for more details
|
||||
NoConnection,
|
||||
ConnectionLost,
|
||||
ConnectionRefused,
|
||||
Protocol,
|
||||
PayloadSize,
|
||||
MalformedUTF8,
|
||||
DuplicateProperty,
|
||||
QoSNotSupported,
|
||||
OversizePacket,
|
||||
NotSupported,
|
||||
HostnameLookup,
|
||||
Tls,
|
||||
TlsHandshake,
|
||||
Unknown,
|
||||
// additional errors (non-mosquitto)
|
||||
MapInsert,
|
||||
};
|
||||
|
||||
std::string_view to_string(ErrorCode ec);
|
||||
|
||||
class PropertiesBase {
|
||||
protected:
|
||||
mqtt5__property* props;
|
||||
bool is_const;
|
||||
|
||||
public:
|
||||
PropertiesBase() : props(nullptr), is_const(true) {
|
||||
}
|
||||
PropertiesBase(const mqtt5__property* ptr) : props(const_cast<mqtt5__property*>(ptr)), is_const(true) {
|
||||
}
|
||||
PropertiesBase(mqtt5__property* ptr) : props(ptr), is_const(false) {
|
||||
}
|
||||
~PropertiesBase();
|
||||
|
||||
enum class property_t : std::uint8_t {
|
||||
AssignedClientId = 18,
|
||||
AuthenticationData = 22,
|
||||
AuthenticationMethod = 21,
|
||||
ContentType = 3,
|
||||
CorrelationData = 9,
|
||||
MaximumPacketSize = 39,
|
||||
MaximumQoS = 36,
|
||||
MessageExpiryInterval = 2,
|
||||
PayloadFormatIndicator = 1,
|
||||
ReasonString = 31,
|
||||
ReceiveMaximum = 33,
|
||||
RequestProblemInfo = 23,
|
||||
RequestResponseInfo = 25,
|
||||
ResponseInfo = 26,
|
||||
ResponseTopic = 8,
|
||||
RetainAvailable = 37,
|
||||
SubscriptionId = 11,
|
||||
SubscriptionIdAvailable = 41,
|
||||
ServerKeepAlive = 19,
|
||||
ServerRef = 28,
|
||||
SessionExpiryInterval = 17,
|
||||
SharedSubAvailable = 42,
|
||||
TopicAlias = 35,
|
||||
TopicAliasMaximum = 34,
|
||||
UserProperty = 38,
|
||||
WildcardSubAvailable = 40,
|
||||
WillDelayInterval = 24,
|
||||
};
|
||||
|
||||
constexpr operator const mqtt5__property*() const {
|
||||
return props;
|
||||
}
|
||||
|
||||
constexpr mqtt5__property* release() {
|
||||
mqtt5__property* ptr{nullptr};
|
||||
if (!is_const) {
|
||||
ptr = props;
|
||||
props = nullptr;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// useful after release() has been used
|
||||
static void free_property(mqtt5__property** ptr);
|
||||
|
||||
const mqtt5__property* get_property(property_t prop) const;
|
||||
std::string get_response_topic() const;
|
||||
std::string get_correlation_data() const;
|
||||
};
|
||||
|
||||
class Properties : public PropertiesBase {
|
||||
public:
|
||||
using PropertiesBase::PropertiesBase;
|
||||
|
||||
ErrorCode set_response_topic(const std::string& topic);
|
||||
ErrorCode set_correlation_data(const std::string& data);
|
||||
};
|
||||
|
||||
class PropertiesAccess : public PropertiesBase {
|
||||
public:
|
||||
using PropertiesBase::PropertiesBase;
|
||||
};
|
||||
|
||||
using subscribe_callback = std::function<void(const std::string_view& topic, const std::string_view& payload)>;
|
||||
|
||||
class mosquitto_cpp {
|
||||
public:
|
||||
enum class QoS : std::uint8_t {
|
||||
at_most_once = 0,
|
||||
at_least_once = 1,
|
||||
exactly_once = 2,
|
||||
};
|
||||
|
||||
static constexpr QoS default_QoS = QoS::exactly_once;
|
||||
static constexpr QoS telemetry_QoS = QoS::at_most_once;
|
||||
static constexpr bool default_publish_retain = false;
|
||||
|
||||
enum class LogLevel : std::uint8_t {
|
||||
info,
|
||||
notice,
|
||||
warning,
|
||||
error,
|
||||
debug,
|
||||
};
|
||||
|
||||
enum class ResponseCode : std::uint8_t {
|
||||
Success = 0x00,
|
||||
|
||||
v3_ProtocolError = 0x01,
|
||||
v3_IdentifierRejected = 0x02,
|
||||
v3_ServerUnavailable = 0x03,
|
||||
v3_BadUserNamePassword = 0x04,
|
||||
v3_NotAuthorized = 0x05,
|
||||
|
||||
UnspecifiedError = 0x80,
|
||||
MalformedPacket = 0x81,
|
||||
ProtocolError = 0x82,
|
||||
ImplementationSpecificError = 0x83,
|
||||
UnsupportedProtocolVersion = 0x84,
|
||||
ClientIdentifierInvalid = 0x85,
|
||||
BadUserNamePassword = 0x86,
|
||||
NotAuthorized = 0x87,
|
||||
ServerUnavailable = 0x88,
|
||||
ServerBusy = 0x89,
|
||||
Banned = 0x8A,
|
||||
BadAuthenticationmethod = 0x8C,
|
||||
TopicNameInvalid = 0x90,
|
||||
PacketTooLarge = 0x95,
|
||||
QuotaExceeded = 0x97,
|
||||
PayloadFormatInvalid = 0x99,
|
||||
RetainNotSupported = 0x9A,
|
||||
QoSNotSupported = 0x9B,
|
||||
UseAnotherServer = 0x9C,
|
||||
ServerMoved = 0x9D,
|
||||
ConnectionRateExceeded = 0x9F,
|
||||
};
|
||||
|
||||
struct message {
|
||||
std::string topic;
|
||||
std::string payload;
|
||||
QoS qos{default_QoS};
|
||||
};
|
||||
|
||||
using connect_callback =
|
||||
std::function<void(mosquitto_cpp& client, ResponseCode rc, int flags, const PropertiesAccess& props)>;
|
||||
|
||||
using disconnect_callback = std::function<void(mosquitto_cpp& client, ErrorCode ec, const PropertiesAccess& props)>;
|
||||
|
||||
using publish_callback =
|
||||
std::function<void(mosquitto_cpp& client, int mid, ResponseCode rc, const PropertiesAccess& props)>;
|
||||
|
||||
using subscribe_callback =
|
||||
std::function<void(mosquitto_cpp& client, const std::string_view& topic, const std::string_view& payload,
|
||||
QoS qos, const PropertiesAccess& props)>;
|
||||
|
||||
using subscribe_message_callback = std::function<void(mosquitto_cpp&, message const&)>;
|
||||
|
||||
private:
|
||||
std::unique_ptr<mosquitto, void (*)(mosquitto*)> client;
|
||||
std::map<std::string, subscribe_callback> callbacks;
|
||||
connect_callback connect_cb;
|
||||
disconnect_callback disconnect_cb;
|
||||
publish_callback publish_cb;
|
||||
|
||||
static void cb_connect(mosquitto* mosq, void* obj, int rc, int flags, const mqtt5__property* props);
|
||||
static void cb_disconnect(mosquitto* mosq, void* obj, int rc, const mqtt5__property* props);
|
||||
static void cb_log(mosquitto* mosq, void* obj, int level, const char* str);
|
||||
static void cb_message(mosquitto* mosq, void* obj, const mosquitto_message* msg, const mqtt5__property* props);
|
||||
static void cb_publish(mosquitto* mosq, void* obj, int mid, int rc, const mqtt5__property* props);
|
||||
|
||||
void connect_ccb(ResponseCode rc, int flags, const PropertiesAccess& props);
|
||||
void disconnect_ccb(ErrorCode ec, const PropertiesAccess& props);
|
||||
void message_ccb(const std::string_view& topic, const std::string_view& payload, QoS qos,
|
||||
const PropertiesAccess& props);
|
||||
void publish_ccb(int mid, ResponseCode rc, const PropertiesAccess& props);
|
||||
|
||||
protected:
|
||||
virtual ErrorCode connect_impl(const std::string_view& bind_address, const std::string_view& host,
|
||||
std::uint16_t port, std::uint16_t keepalive_seconds);
|
||||
virtual ErrorCode set_will_impl(const std::string_view& topic, const std::string_view& payload, QoS qos,
|
||||
bool retain, PropertiesBase&& props);
|
||||
|
||||
virtual ErrorCode publish_impl(int* mid, const std::string_view& topic, const std::string_view& payload, QoS qos,
|
||||
bool retain, const PropertiesBase& props);
|
||||
virtual ErrorCode subscribe_impl(const std::string_view& topic, QoS qos, int options, const PropertiesBase& props,
|
||||
subscribe_callback cb);
|
||||
virtual ErrorCode unsubscribe_impl(const std::string_view& topic, const PropertiesBase& props);
|
||||
|
||||
virtual void set_callback_connect_impl(connect_callback cb);
|
||||
virtual void set_callback_disconnect_impl(disconnect_callback cb);
|
||||
virtual void set_callback_publish_impl(publish_callback cb);
|
||||
|
||||
bool is_connect_callback_set();
|
||||
bool is_disconnect_callback_set();
|
||||
|
||||
public:
|
||||
mosquitto_cpp();
|
||||
mosquitto_cpp(const char* id);
|
||||
|
||||
virtual ~mosquitto_cpp();
|
||||
|
||||
virtual void log(LogLevel level, const char* str);
|
||||
|
||||
void set_callback_connect(connect_callback cb);
|
||||
void set_callback_disconnect(disconnect_callback cb);
|
||||
void set_callback_publish(publish_callback cb);
|
||||
|
||||
ErrorCode tls(const char* ca_file, const char* ca_path, const char* cert_file, const char* key_file);
|
||||
ErrorCode tls(const ::std::string& ca_file, const ::std::string& ca_path, const ::std::string& cert_file,
|
||||
const ::std::string& key_file);
|
||||
ErrorCode connect(const std::string_view& host, std::uint16_t port, std::uint16_t keepalive_seconds);
|
||||
ErrorCode connect(const std::string_view& bind_address, const std::string_view& host, std::uint16_t port,
|
||||
std::uint16_t keepalive_seconds);
|
||||
|
||||
ErrorCode connect(const std::string_view& unix_domain_socket, std::uint16_t keepalive_seconds);
|
||||
|
||||
ErrorCode reconnect();
|
||||
ErrorCode disconnect();
|
||||
|
||||
ErrorCode loop_forever();
|
||||
|
||||
ErrorCode loop_read();
|
||||
ErrorCode loop_write();
|
||||
ErrorCode loop_misc();
|
||||
|
||||
bool want_write();
|
||||
int socket() const;
|
||||
|
||||
void set_option_threaded(bool val);
|
||||
void set_option_tcpnodelay(bool val);
|
||||
|
||||
// must be called before connect()
|
||||
ErrorCode set_will(const std::string_view& topic, const std::string_view& payload, QoS qos, bool retain,
|
||||
PropertiesBase&& props);
|
||||
|
||||
ErrorCode publish(int* mid, const std::string_view& topic, const std::string_view& payload, QoS qos, bool retain,
|
||||
const PropertiesBase& props);
|
||||
|
||||
ErrorCode publish(const std::string_view& topic, const std::string_view& payload, QoS qos, bool retain,
|
||||
const PropertiesBase& props);
|
||||
|
||||
ErrorCode publish(const std::string_view& topic, const std::string_view& payload);
|
||||
|
||||
ErrorCode publish(message const& data);
|
||||
|
||||
ErrorCode subscribe(const std::string_view& topic, QoS qos, int options, const PropertiesBase& props,
|
||||
subscribe_callback cb);
|
||||
ErrorCode subscribe(const std::string_view& topic, subscribe_callback cb);
|
||||
|
||||
ErrorCode subscribe(std::string_view const& topic, subscribe_message_callback const& cb, QoS qos);
|
||||
|
||||
ErrorCode unsubscribe(const std::string_view& topic, const PropertiesBase& props);
|
||||
ErrorCode unsubscribe(const std::string_view& topic);
|
||||
|
||||
static bool library_init();
|
||||
static void library_cleanup();
|
||||
};
|
||||
} // namespace everest::lib::io::mqtt
|
||||
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <everest/io/event/fd_event_sync_interface.hpp>
|
||||
#include <everest/io/event/timer_fd.hpp>
|
||||
#include <everest/io/mqtt/mosquitto_cpp.hpp>
|
||||
|
||||
namespace everest::lib::io::mqtt {
|
||||
|
||||
class mqtt_client : public mosquitto_cpp, public event::fd_event_sync_interface {
|
||||
public:
|
||||
using event_list = event::fd_event_handler::event_list;
|
||||
using cb_error = std::function<void(int, std::string const)>;
|
||||
|
||||
mqtt_client(std::uint32_t reconnect_to_ms, std::string client_id = "");
|
||||
~mqtt_client();
|
||||
|
||||
std::string get_client_id() const;
|
||||
|
||||
everest::lib::io::event::sync_status sync() override;
|
||||
int get_poll_fd() override;
|
||||
|
||||
void set_error_handler(cb_error const& handler);
|
||||
|
||||
protected:
|
||||
ErrorCode set_will_impl(std::string_view const& topic, std::string_view const& payload, QoS qos, bool retain,
|
||||
PropertiesBase&& props) override;
|
||||
|
||||
ErrorCode publish_impl(int* mid, std::string_view const& topic, std::string_view const& payload, QoS qos,
|
||||
bool retain, PropertiesBase const& props) override;
|
||||
|
||||
ErrorCode subscribe_impl(std::string_view const& topic, QoS qos, int options, PropertiesBase const& props,
|
||||
subscribe_callback cb) override;
|
||||
|
||||
ErrorCode unsubscribe_impl(std::string_view const& topic, PropertiesBase const& props) override;
|
||||
ErrorCode connect_impl(std::string_view const& bind_adress, std::string_view const& host, std::uint16_t port,
|
||||
std::uint16_t keepalive_seconds) override;
|
||||
|
||||
void set_callback_connect_impl(connect_callback cb) override;
|
||||
void set_callback_disconnect_impl(disconnect_callback cb) override;
|
||||
void set_callback_publish_impl(publish_callback cb) override;
|
||||
|
||||
private:
|
||||
void handle_socket(event_list const& events);
|
||||
void handle_reconnect_timer();
|
||||
void handle_sync_timer();
|
||||
ErrorCode handle_error(ErrorCode error);
|
||||
|
||||
void listen_to_reconnect_timer(bool enable);
|
||||
void listen_to_sync_timer(bool enable);
|
||||
void listen_to_write_events(bool enable);
|
||||
void listen_to_write_events_if_wanted();
|
||||
|
||||
event::fd_event_handler m_handler;
|
||||
event::timer_fd m_reconnect_timer;
|
||||
event::timer_fd m_sync_timer;
|
||||
|
||||
cb_error m_error_handler;
|
||||
std::string m_client_id;
|
||||
ErrorCode m_last_error{ErrorCode::Unknown};
|
||||
int m_last_socket{-1};
|
||||
bool m_connected{false};
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::mqtt
|
||||
@@ -0,0 +1,125 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
extern "C" struct nlmsghdr;
|
||||
extern "C" struct ifinfomsg;
|
||||
|
||||
namespace everest::lib::io::netlink {
|
||||
|
||||
/**
|
||||
* @brief vcan_netlink_manager manages setup of
|
||||
* <a href=https://docs.kernel.org/networking/can.html>virtual CAN</a>
|
||||
* devices via <a href=https://man7.org/linux/man-pages/man7/netlink.7.html>NETLINK</a>.
|
||||
* The class is designed as a singleton.
|
||||
*/
|
||||
class vcan_netlink_manager {
|
||||
public:
|
||||
struct NetlinkMessage;
|
||||
|
||||
vcan_netlink_manager(vcan_netlink_manager const&) = delete;
|
||||
vcan_netlink_manager& operator=(vcan_netlink_manager const&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Creates a new virtual CAN interface.
|
||||
* @param interface_name The desired name for the new interface (e.g., "vcan0").
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool create(std::string const& interface_name);
|
||||
|
||||
/**
|
||||
* @brief Brings a virtual CAN interface up (activates it).
|
||||
* @param interface_name The name of the interface.
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool bring_up(std::string const& interface_name);
|
||||
|
||||
/**
|
||||
* @brief Brings a virtual CAN interface down (deactivates it).
|
||||
* @param interface_name The name of the interface.
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool bring_down(std::string const& interface_name);
|
||||
|
||||
/**
|
||||
* @brief Destroys (deletes) a virtual CAN interface.
|
||||
* @param interface_name The name of the interface.
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool destroy(std::string const& interface_name);
|
||||
|
||||
/**
|
||||
* @brief Access the single instance of this object.
|
||||
* @details The underlying object is created on the first call to this function.
|
||||
* @return Reference to the object instance
|
||||
*/
|
||||
static vcan_netlink_manager& Instance();
|
||||
|
||||
private:
|
||||
using cb_type = std::function<void(nlmsghdr*, ifinfomsg*, int)>;
|
||||
|
||||
vcan_netlink_manager();
|
||||
~vcan_netlink_manager();
|
||||
|
||||
int m_nl_socket_fd{-1};
|
||||
uint32_t s_netlink_seq_counter{1};
|
||||
|
||||
/**
|
||||
* @brief Adds a Netlink attribute to the message buffer.
|
||||
* This helper handles both top-level and nested attributes by always appending
|
||||
* to the current end of the message and updating the overall message length.
|
||||
* The parent attribute's length must be updated manually after its nested attributes are added.
|
||||
* @param nlh The Netlink message header.
|
||||
* @param maxlen The maximum allowed length of the message buffer.
|
||||
* @param type The attribute type (e.g., IFLA_IFNAME, IFLA_INFO_KIND).
|
||||
* @param data Pointer to the attribute data.
|
||||
* @param len Length of the attribute data.
|
||||
* @throws std::runtime_error if attribute exceeds buffer maxlen.
|
||||
*/
|
||||
void add_attribute(nlmsghdr* nlh, int maxlen, int type, const void* data, int len);
|
||||
|
||||
/**
|
||||
* @brief Sends a Netlink message to the kernel.
|
||||
* @param msg The Netlink message to send.
|
||||
* @param flags Send flags (e.g., MSG_DONTWAIT).
|
||||
* @throws std::runtime_error if sendmsg fails.
|
||||
*/
|
||||
void send_message(NetlinkMessage const& msg, int flags = 0);
|
||||
|
||||
/**
|
||||
* @brief Receives a Netlink message response from the kernel.
|
||||
* @param msg Output parameter to store the received message.
|
||||
* @throws std::runtime_error if recvmsg fails or no valid message is received.
|
||||
*/
|
||||
void receive_message(NetlinkMessage& msg);
|
||||
|
||||
/**
|
||||
* @brief Sends a Netlink request and waits for an ACK from the kernel.
|
||||
* @param msg_type The type of Netlink message (e.g., RTM_NEWLINK).
|
||||
* @param flags Netlink message flags (e.g., NLM_F_CREATE | NLM_F_EXCL).
|
||||
* @param callback A lambda or function to populate the `ifinfomsg` and add `rtattr`s.
|
||||
* @throws std::runtime_error if the operation fails or ACK is not received.
|
||||
*/
|
||||
void send_netlink_request_impl(int msg_type, int flags, cb_type const& callback);
|
||||
|
||||
/**
|
||||
* @brief Wraps exception handling for \p send_netlink_request_impl
|
||||
* @param msg_type The type of Netlink message (e.g., RTM_NEWLINK).
|
||||
* @param flags Netlink message flags (e.g., NLM_F_CREATE | NLM_F_EXCL).
|
||||
* @param callback A lambda or function to populate the `ifinfomsg` and add `rtattr`s.
|
||||
* @param interface_name For error reporting.
|
||||
* @param[in] caller The name of the caller
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool send_netlink_request(int msg_type, int flags, cb_type const& callback, std::string const& interface_name,
|
||||
std::string const& caller);
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::netlink
|
||||
@@ -0,0 +1,101 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
|
||||
namespace everest::lib::io::queue {
|
||||
|
||||
/**
|
||||
* Simplified interface for a queue based
|
||||
* on <a href="https://en.cppreference.com/w/cpp/container/queue">std::queue</a>
|
||||
* @tparam T Datatype held by the queue
|
||||
*/
|
||||
template <class T> class simple_queue {
|
||||
public:
|
||||
/**
|
||||
* @var reference
|
||||
* @brief Inherited type definition from STL
|
||||
*/
|
||||
using reference = typename std::queue<T>::reference;
|
||||
|
||||
/**
|
||||
* @var const_reference
|
||||
* @brief Inherited type definition from STL
|
||||
*/
|
||||
using const_reference = typename std::queue<T>::const_reference;
|
||||
|
||||
/**
|
||||
* @var value_type
|
||||
* @brief Inherited type definition from STL
|
||||
*/
|
||||
using value_type = typename std::queue<T>::value_type;
|
||||
|
||||
/**
|
||||
* @var size_type
|
||||
* @brief Inherited type definition from STL
|
||||
*/
|
||||
using size_type = typename std::queue<T>::size_type;
|
||||
|
||||
/**
|
||||
* @brief Maps to std::queue::front()
|
||||
*/
|
||||
const_reference front() const {
|
||||
return m_queue.front();
|
||||
}
|
||||
/**
|
||||
* @brief Maps to std::queue::back()
|
||||
*/
|
||||
const_reference back() const {
|
||||
return m_queue.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Maps to std::queue::push(const value_type&)
|
||||
*/
|
||||
void push(const value_type& value) {
|
||||
m_queue.push(value);
|
||||
}
|
||||
/**
|
||||
* @brief Maps to std::queue::push(value_type&&)
|
||||
*/
|
||||
void push(value_type&& value) {
|
||||
m_queue.push(std::forward<value_type>(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the front element of the queue if available.
|
||||
* @return Maybe the front element
|
||||
*/
|
||||
std::optional<value_type> pop() {
|
||||
if (m_queue.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto tmp = front();
|
||||
m_queue.pop();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Maps to std::queue::empty()
|
||||
*/
|
||||
bool empty() const {
|
||||
return m_queue.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Maps to std::queue::size()
|
||||
*/
|
||||
size_type size() const {
|
||||
return m_queue.size();
|
||||
}
|
||||
|
||||
private:
|
||||
std::queue<T> m_queue;
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::queue
|
||||
@@ -0,0 +1,92 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "simple_queue.hpp"
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
namespace everest::lib::io::queue {
|
||||
|
||||
/**
|
||||
* A thread safe queue implemented on top of \ref queue::simple_queue. <br>
|
||||
* The common resource \ref simple_queue is guarded by a mutex in every member function. A caller blocking on \p pop
|
||||
* or \p try_pop will be unblocked new data made available via \p push
|
||||
* @tparam T Datatype held by the queue
|
||||
*/
|
||||
template <class T> class thread_safe_queue {
|
||||
public:
|
||||
/**
|
||||
* @var value_type
|
||||
* @brief Inherited type definition.
|
||||
*/
|
||||
using value_type = typename simple_queue<T>::value_type;
|
||||
|
||||
/**
|
||||
* @brief Push new data into the queue
|
||||
* @param[in] value data
|
||||
*/
|
||||
void push(const value_type& value) {
|
||||
std::unique_lock lock(m_mtx);
|
||||
m_queue.push(value);
|
||||
lock.unlock();
|
||||
m_cv.notify_one();
|
||||
}
|
||||
/**
|
||||
* @brief Push new data into the queue
|
||||
* @param[in] value data
|
||||
*/
|
||||
void push(value_type&& value) {
|
||||
std::unique_lock lock(m_mtx);
|
||||
m_queue.push(std::forward<value_type>(value));
|
||||
lock.unlock();
|
||||
m_cv.notify_one();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Try to get an element from the queue.
|
||||
* @details Returns immediately.
|
||||
* @return An element from the queue, if one is available. \p std::nullopt otherwise
|
||||
*/
|
||||
std::optional<value_type> try_pop() {
|
||||
return pop_impl(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Try to get an element from the queue.
|
||||
* @details Returns as soon as data is availble or after timeout.
|
||||
* @param[in] timeout as <a href="https://en.cppreference.com/w/cpp/chrono/duration">std::chrono::duration</a>.
|
||||
* Smallest unit acceptable is milliseconds.
|
||||
* @return An element from the queue, if one is available. \p std::nullopt otherwise
|
||||
*/
|
||||
template <class Rep, class Period> std::optional<value_type> try_pop(std::chrono::duration<Rep, Period> timeout) {
|
||||
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(timeout);
|
||||
return pop_impl(ms.count());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get an element from the queue
|
||||
* @details Only returns, when data is available
|
||||
* @return An element from the queue.
|
||||
*/
|
||||
value_type pop() {
|
||||
return pop_impl(-1).value();
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<value_type> pop_impl(int timeout_ms) {
|
||||
std::unique_lock lock(m_mtx);
|
||||
m_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), [this]() { return not m_queue.empty(); });
|
||||
return m_queue.pop();
|
||||
}
|
||||
|
||||
simple_queue<T> m_queue;
|
||||
std::mutex m_mtx;
|
||||
std::condition_variable m_cv;
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::queue
|
||||
@@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <everest/io/event/fd_event_client.hpp>
|
||||
#include <everest/io/raw/raw_socket.hpp>
|
||||
|
||||
namespace everest::lib::io::raw {
|
||||
|
||||
/**
|
||||
* @var tcp_client
|
||||
* @brief Client for RAW ETHERNET implemented in terms of \ref event::fd_event_client
|
||||
* and \ref raw::raw_socket
|
||||
*/
|
||||
using raw_client = event::fd_event_client<raw_socket>::type;
|
||||
|
||||
} // namespace everest::lib::io::raw
|
||||
@@ -0,0 +1,96 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <everest/io/event/unique_fd.hpp>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace everest::lib::io::raw {
|
||||
|
||||
/**
|
||||
* raw_socket bundles basic <a href="https://man7.org/linux/man-pages/man7/raw.7.html">RAW SOCKET</a>
|
||||
* related functionality. This includes lifetime management, reading, writing and fundamental
|
||||
* error checking.
|
||||
* Although this class can be used on its own, the main purpose is to be used as base class for
|
||||
* implementation the \p ClientPolicy of \ref event::fd_event_client
|
||||
*/
|
||||
class raw_socket {
|
||||
public:
|
||||
/**
|
||||
* @var PayloadT
|
||||
* @brief Type of the payload for TX and RX operations
|
||||
*/
|
||||
using PayloadT = std::vector<uint8_t>;
|
||||
|
||||
/**
|
||||
* @brief The class is default constructed
|
||||
*/
|
||||
raw_socket() = default;
|
||||
~raw_socket() = default;
|
||||
|
||||
/**
|
||||
* @brief Open a RAW socket.
|
||||
* @details Sets the socket non blocking. <br>
|
||||
* Implementation for \p ClientPolicy
|
||||
* @param[in] if_name Name of the ethernet device
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool open(std::string const& if_name);
|
||||
|
||||
/**
|
||||
* @brief Write data to the socket. Partial writes may occur.
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @param[inout] payload Payload. Will be modified on partial writes to hold only the remaining data.
|
||||
* @return True on complete transmission of the payload, False otherwise (including partial writes)
|
||||
*/
|
||||
bool tx(PayloadT& payload);
|
||||
|
||||
/**
|
||||
* @brief Read as much data as available from the socket
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @param[out] buffer Buffer to write to
|
||||
* @return True on success, False otherwise
|
||||
*/
|
||||
bool rx(PayloadT& buffer);
|
||||
|
||||
/**
|
||||
* @brief Get the file descriptor of the socket
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @return The file descriptor of the socket
|
||||
*/
|
||||
int get_fd() const;
|
||||
|
||||
/**
|
||||
* @brief Get the file descriptor of the socket
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @return The file descriptor of the socket
|
||||
*/
|
||||
/**
|
||||
* @brief Get pending errors on the socket.
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @return The current errno of the socket. Zero with no pending error.
|
||||
*/
|
||||
int get_error() const;
|
||||
|
||||
/**
|
||||
* @brief Check if the objects owns a socket
|
||||
* @return True if a object owns a socket, false otherwise
|
||||
*/
|
||||
bool is_open() const;
|
||||
|
||||
/**
|
||||
* @brief Close the owned socket
|
||||
*/
|
||||
void close();
|
||||
|
||||
private:
|
||||
event::unique_fd m_fd;
|
||||
static constexpr size_t default_buffer_size{65536};
|
||||
};
|
||||
} // namespace everest::lib::io::raw
|
||||
@@ -0,0 +1,64 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
#include <everest/io/event/fd_event_client.hpp>
|
||||
#include <everest/io/serial/pty_handler.hpp>
|
||||
|
||||
namespace everest::lib::io::serial {
|
||||
|
||||
/**
|
||||
* @var event_pty_base
|
||||
* @brief Base type for a client for PTY implemented in terms of \ref event::fd_event_client
|
||||
* and \ref serial::pty_handler
|
||||
*/
|
||||
using event_pty_base = event::fd_event_client<pty_handler>::type;
|
||||
|
||||
/**
|
||||
* event_pty extends \ref event_pty_base for the special handling needed
|
||||
* for data and status, which are both received via RX
|
||||
*/
|
||||
class event_pty : public event_pty_base {
|
||||
/**
|
||||
* @var status
|
||||
* @brief Type for status information
|
||||
*/
|
||||
using status = pty_status;
|
||||
|
||||
/**
|
||||
* @var cb_status
|
||||
* @brief Prototype for status callback functions
|
||||
*/
|
||||
using cb_status = std::function<void(status const&)>;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Register a callback for status RX
|
||||
* @details This handler will be called, when there are changes on settings of the slave, e.g. via
|
||||
* <a href="https://man7.org/linux/man-pages/man1/stty.1.html">stty</a>
|
||||
* @param[in] handler The callback to be used as status handler
|
||||
*/
|
||||
void set_status_handler(cb_status const& handler);
|
||||
|
||||
/**
|
||||
* @brief Register a callback for data RX
|
||||
* @details This handler will be called when new data is available on the slave
|
||||
* @param[in] handler The callback to be used as data handler
|
||||
*/
|
||||
void set_data_handler(cb_rx const& handler);
|
||||
|
||||
/**
|
||||
* @brief Get the path of the slave in the filesystem
|
||||
* @details The file at this path represents the slave and is to be used by the outside world
|
||||
* @return The path
|
||||
*/
|
||||
std::string get_slave_path();
|
||||
|
||||
private:
|
||||
cb_status m_status;
|
||||
cb_rx m_data;
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::serial
|
||||
@@ -0,0 +1,116 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <everest/io/serial/serial.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace everest::lib::io::serial {
|
||||
|
||||
/**
|
||||
* @struct pty_status
|
||||
* Simplified status of a <a href="https://man7.org/linux/man-pages/man7/pty.7.html">PTY</a>.
|
||||
* Values map to the values defined <a href="https://linux.die.net/man/3/tcgetattr">here</a>
|
||||
*/
|
||||
struct pty_status {
|
||||
/**
|
||||
* @brief Software flow control for the output
|
||||
*/
|
||||
bool ixon{false};
|
||||
/**
|
||||
* @brief Software flow control for the input
|
||||
*/
|
||||
bool ixoff{false};
|
||||
/**
|
||||
* @brief Two stopbits instead of one
|
||||
*/
|
||||
bool cstopb{false};
|
||||
/**
|
||||
* @brief The Baud reate
|
||||
*/
|
||||
unsigned int cbaud{0};
|
||||
};
|
||||
|
||||
/**
|
||||
* pty_handler bundles basic <a href="https://man7.org/linux/man-pages/man7/pty.7.html">PTY</a>
|
||||
* related functionality. This includes lifetime management, reading, writing and fundamental
|
||||
* error checking. <br>
|
||||
* Although this class can be used on its own, the main purpose is to implement the
|
||||
* \p ClientPolicy of \ref event::fd_event_client
|
||||
*/
|
||||
class pty_handler {
|
||||
public:
|
||||
/**
|
||||
* @var PayloadT
|
||||
* @brief Type of the payload for tX and RX operations
|
||||
*/
|
||||
using PayloadT = std::vector<uint8_t>;
|
||||
|
||||
/**
|
||||
* @brief The class is default constructed
|
||||
*/
|
||||
pty_handler() = default;
|
||||
~pty_handler() = default;
|
||||
|
||||
/**
|
||||
* @brief Write a dataset to the PTY
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @param[in] data Payload
|
||||
* @return True on success, False on failure and partial writes.
|
||||
*/
|
||||
bool tx(PayloadT& data);
|
||||
/**
|
||||
* @brief Read a dataset from the PTY
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @param[in] data Payload
|
||||
* @return True on success, False otherwise.
|
||||
*/
|
||||
bool rx(PayloadT& data);
|
||||
|
||||
/**
|
||||
* @brief Open the PTY
|
||||
* @details Activates <a href="https://lists.gnu.org/archive/html/bug-readline/2011-01/msg00004.html">EXTPROC</a>
|
||||
* an <a href="https://man7.org/linux/man-pages/man2/TIOCPKT.2const.html">TIOCPKT</a>
|
||||
* via \ref make_pty_mode_aware. <br>
|
||||
* Implementation for \p ClientPolicy
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool open();
|
||||
|
||||
/**
|
||||
* @brief Get the master file descriptor
|
||||
* @details Implementation for ClientPolicy
|
||||
* @return master file descriptor
|
||||
*/
|
||||
int get_fd() const;
|
||||
|
||||
/**
|
||||
* @brief Get the current error
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @return The last errno. Zero if there is no error.
|
||||
*/
|
||||
int get_error() const;
|
||||
|
||||
/**
|
||||
* @brief Get the current status of the PTY
|
||||
* @details Read the status information as set on the slave
|
||||
* @return Simplified status
|
||||
*/
|
||||
pty_status get_status();
|
||||
|
||||
/**
|
||||
* @brief The path of the slave in the file system
|
||||
* @return Path
|
||||
*/
|
||||
std::string get_slave_path() const;
|
||||
|
||||
private:
|
||||
pty m_dev;
|
||||
int error_id{0};
|
||||
static constexpr size_t buffer_size_limit = 1400;
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::serial
|
||||
@@ -0,0 +1,77 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
#include <everest/io/event/unique_fd.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace everest::lib::io::serial {
|
||||
|
||||
/**
|
||||
* @struct pty
|
||||
* Bundle of data necessary to manage a <a href="https://man7.org/linux/man-pages/man7/pty.7.html">PTY</a>
|
||||
*/
|
||||
struct pty {
|
||||
/** @brief The master file descriptor */
|
||||
event::unique_fd master_fd;
|
||||
/** @brief The slave file descriptor */
|
||||
event::unique_fd slave_fd;
|
||||
/** @brief The path to the slave in the filesystem*/
|
||||
std::string slave_path;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Opens a PTY
|
||||
* @details The follows the steps defined in <a href="https://man7.org/linux/man-pages/man7/pty.7.html">PTY</a>,
|
||||
* which are:
|
||||
* -# Open the master via /dev/ptmx <a href="https://man7.org/linux/man-pages/man3/posix_openpt.3.html">posix_openpt</a>
|
||||
* -# Change ownership via <a href="https://man7.org/linux/man-pages/man3/grantpt.3.html">grantpt</a>
|
||||
* -# Unlock the slave via <a href="https://man7.org/linux/man-pages/man3/unlockpt.3.html">unlockpt</a>
|
||||
* -# Get the filename of the slave via <a href="https://man7.org/linux/man-pages/man3/ptsname.3.html">ptsname</a>
|
||||
* -# Open the slave via <a href="https://man7.org/linux/man-pages/man2/open.2.html">open</a>
|
||||
* @return Rerturns a everest::lib::io::serial::pty with valid data on success, \p std::nullopt otherwise.
|
||||
*/
|
||||
std::optional<pty> openpty();
|
||||
|
||||
/**
|
||||
* @brief Enable packet mode for the PTY
|
||||
* @details Refer to <a href="https://man7.org/linux/man-pages/man2/TIOCPKT.2const.html">TIOCPKT</a>
|
||||
* @param[in] fd This is supposed to be called on the master file descriptor
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool set_packet_mode(int fd);
|
||||
|
||||
/**
|
||||
* @brief Enabled EXTPROC for the PTY
|
||||
* @details Refer to <a href="https://lists.gnu.org/archive/html/bug-readline/2011-01/msg00004.html">EXTPROC</a>
|
||||
* This will trigger (E)POLLIN on the master, if there are changes on the slave status
|
||||
* @param[in] fd This is supposed to be called on the slave file descriptor
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool set_extproc_flag(int fd);
|
||||
|
||||
/**
|
||||
* @brief Set RAW/Binary mode on a PTY
|
||||
* @details Refer to <a href="https://man7.org/linux/man-pages/man3/termios.3.html">termios</a>
|
||||
* This will disable any flow control any special handling for characters for the input modes and any
|
||||
* implementation specific output handling. For the output modes any signals and echoing as well as
|
||||
* the canonical mode are disabled. Settings for control mode are 8-bit characters, no parity, 1 stop bit.
|
||||
* For the non canonical mode used here, the settings are a minimum of 1 character and no timeout. This uses
|
||||
* \p cfmakeraw internally
|
||||
*/
|
||||
bool set_binary_mode(int fd);
|
||||
|
||||
/**
|
||||
* @brief Setup a <a href="https://man7.org/linux/man-pages/man7/pty.7.html">PTY</a> for (e)poll usage
|
||||
* @details This enabled packet mode via \ref everest::lib::io::serial::set_packet_mode and EXTPROC
|
||||
* via \ref everest::lib::io::serial::set_extproc_flag
|
||||
* In combination these settings allow integration into an event queue for data and settings updates on the slave
|
||||
* @param[in] item The PTY
|
||||
* @return True on success, False otherwise
|
||||
*/
|
||||
bool make_pty_mode_aware(pty const& item);
|
||||
|
||||
} // namespace everest::lib::io::serial
|
||||
@@ -0,0 +1,416 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <everest/io/event/unique_fd.hpp>
|
||||
#include <everest/io/udp/endpoint.hpp>
|
||||
|
||||
namespace everest::lib::io::socket {
|
||||
|
||||
/**
|
||||
* @brief Open a UDP socket in server mode
|
||||
* @param[in] port The port to listen to
|
||||
* @param[in] device Optional interface name (e.g. "eth0"). When non-empty the socket is bound
|
||||
* to that device via SO_BINDTODEVICE. Requires CAP_NET_RAW or root.
|
||||
* @return The managed file descriptor of the socket
|
||||
* @throws std::runtime_error if the operation fails.
|
||||
*/
|
||||
event::unique_fd open_udp_server_socket(std::uint16_t port, std::string const& device = {});
|
||||
|
||||
/**
|
||||
* @brief Open a UDP socket in client mode
|
||||
* @param[in] host The host to connect to
|
||||
* @param[in] port The port to listen to
|
||||
* @param[in] device Optional interface name (e.g. "eth0"). When non-empty the socket is bound
|
||||
* to that device via SO_BINDTODEVICE before connect. Requires CAP_NET_RAW or root.
|
||||
* @return The managed file descriptor of the socket
|
||||
* @throws std::runtime_error if the operation fails.
|
||||
*/
|
||||
event::unique_fd open_udp_client_socket(std::string const& host, std::uint16_t port, std::string const& device = {});
|
||||
|
||||
/**
|
||||
* @brief Open an unconnected UDP datagram socket (IPv4 or IPv6) for a target.
|
||||
* @details Socket family is taken from @p target. SOCK_DGRAM, non-blocking,
|
||||
* bound to the family wildcard address on an ephemeral port. For a multicast
|
||||
* @p target the multicast egress interface is pinned (IP_MULTICAST_IF /
|
||||
* IPV6_MULTICAST_IF) to @p iface (falling back to the interface carried by
|
||||
* @p target); SO_BINDTODEVICE is applied best-effort for extra link
|
||||
* confinement (tolerated to fail unprivileged). Performs neither ::connect()
|
||||
* nor any multicast group join, so a unicast reply from an address other than
|
||||
* the configured target is delivered on this same fd by destination address
|
||||
* and port.
|
||||
* @param[in] target Destination address; selects the socket family.
|
||||
* @param[in] iface Optional interface name. Empty uses @p target's iface hint.
|
||||
* @return The managed file descriptor of the socket.
|
||||
* @throws std::runtime_error if the operation fails.
|
||||
*/
|
||||
event::unique_fd open_udp_unconnected_socket(udp::endpoint const& target, std::string const& iface = {});
|
||||
|
||||
/**
|
||||
* @brief Open a dual-stack (IPv6 + IPv4-mapped) UDP server socket.
|
||||
* @details AF_INET6/SOCK_DGRAM, IPV6_V6ONLY=0, non-blocking, SO_REUSEADDR,
|
||||
* bound to [::]:@p port so it receives both native IPv6 and IPv4-mapped
|
||||
* traffic. @p device (optional) is applied best-effort via
|
||||
* bind_socket_to_device (SO_BINDTODEVICE → IPV6_UNICAST_IF); the wildcard
|
||||
* bind is always kept. If IPv6 is unavailable (socket(AF_INET6) fails with
|
||||
* EAFNOSUPPORT) it falls back to AF_INET bound to 0.0.0.0:@p port.
|
||||
* @param[in] port Local UDP port; 0 picks an ephemeral port.
|
||||
* @param[in] device Optional interface name. Empty = no binding.
|
||||
* @return The managed file descriptor of the socket.
|
||||
* @throws std::runtime_error on any failure other than the EAFNOSUPPORT
|
||||
* v6→v4 fallback.
|
||||
*/
|
||||
event::unique_fd open_udp_dualstack_server_socket(std::uint16_t port, std::string const& device = {});
|
||||
|
||||
/**
|
||||
* @brief Open a UDP socket with <a href="https://man7.org/linux/man-pages/man7/ip.7.html">multicast</a>
|
||||
* enabled
|
||||
* @details Description
|
||||
* @param[in] multicast_group The multicast group to join
|
||||
* @param[in] port Port to listen to.
|
||||
* @param[in] interface_address The IP address of the interface
|
||||
* @param[in] listen_address IP adress to listen for incoming traffic
|
||||
* @param[in] reuse_address 'True' if reuse of address is required
|
||||
* @param[in] reuse_port 'True' if reuse of port is required
|
||||
* @return The managed file descriptor of the socket
|
||||
* @throws std::runtime_error if the operation fails.
|
||||
*/
|
||||
event::unique_fd open_udp_multicast_socket(std::string const& multicast_group, std::uint16_t port,
|
||||
std::string interface_address, std::string listen_address,
|
||||
bool reuse_address, bool reuse_port);
|
||||
|
||||
/**
|
||||
* @brief Open a UDP socket for
|
||||
* <a href="https://datatracker.ietf.org/doc/html/rfc6762">Multicast DNS</a>
|
||||
* @details This creates a UDP socket for mDNS discovery. The socket is bound
|
||||
* to the specified interface and messages are only send on that interface.
|
||||
* @param[in] interface_name The name of interface
|
||||
* @return The managed file descriptor of the socket
|
||||
* @throws std::runtime_error if the operation fails.
|
||||
*/
|
||||
event::unique_fd open_mdns_socket(std::string const& interface_name);
|
||||
|
||||
/**
|
||||
* @brief Open a TCP socket in client mode
|
||||
* @param[in] host The host to connect to
|
||||
* @param[in] port The port to listen to
|
||||
* @return The managed file descriptor of the socket
|
||||
* @throws std::runtime_error if the operation fails.
|
||||
*/
|
||||
event::unique_fd open_tcp_socket(const std::string& host, std::uint16_t port, const std::string& device = {});
|
||||
|
||||
/**
|
||||
* @brief Open a TCP socket in client mode
|
||||
* @param[in] host The host to connect to
|
||||
* @param[in] port The port to listen to.
|
||||
* @param[in] timeout_ms Timeout for the operation in ms
|
||||
* @param[in] device Optional interface name (e.g. "eth0"). When non-empty the socket is bound
|
||||
* to that device via SO_BINDTODEVICE before connect. If the caller lacks CAP_NET_RAW, falls back
|
||||
* to source-IP bind using the interface's IPv4 address (no privilege needed).
|
||||
* @return The managed file descriptor of the socket
|
||||
* @throws std::runtime_error if the operation fails.
|
||||
*/
|
||||
event::unique_fd open_tcp_socket_with_timeout(const std::string& host, std::uint16_t port, unsigned int timeout_ms,
|
||||
const std::string& device = {});
|
||||
|
||||
/**
|
||||
* @brief Bind a socket to a specific network interface.
|
||||
* @details Tries SO_BINDTODEVICE first (needs CAP_NET_RAW). On EPERM/EACCES, falls back to:
|
||||
* - IP_UNICAST_IF (AF_INET) or IPV6_UNICAST_IF (AF_INET6) to restrict the outgoing
|
||||
* interface for unicast packets — no privilege required, works for both v4 and v6
|
||||
* client sockets.
|
||||
* - As a last resort for AF_INET, a source-IP bind() to the interface's IPv4 address.
|
||||
* The IPV6 source-IP fallback is not implemented; IPv6 sockets must succeed via
|
||||
* SO_BINDTODEVICE or IPV6_UNICAST_IF. No-op when @p device is empty.
|
||||
* @param[in] fd Open socket file descriptor
|
||||
* @param[in] device Interface name (e.g. "eth0"). Empty string is a no-op.
|
||||
* @throws std::runtime_error if all viable strategies fail.
|
||||
*/
|
||||
void bind_socket_to_device(int fd, std::string const& device);
|
||||
|
||||
#ifndef EVEREST_NO_PACKET_IGNORE_OUTGOING
|
||||
/**
|
||||
* @brief Open a raw socket in promiscuous mode
|
||||
* @param[in] if_name The name of the interface
|
||||
* @return The managed file descriptor of the socket
|
||||
* @throws std::runtime_error if the operation fails.
|
||||
*/
|
||||
event::unique_fd open_raw_promiscuous_socket(std::string const& if_name);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Enable <a href="https://man7.org/linux/man-pages/man7/tcp.7.html">TCP_NODELAY</a> on a socket
|
||||
* @details If set, disable the Nagle algorithm. This means that
|
||||
* segments are always sent as soon as possible, even if there
|
||||
* is only a small amount of data. When not set, data is
|
||||
* buffered until there is a sufficient amount to send out,
|
||||
* thereby avoiding the frequent sending of small packets,
|
||||
* which results in poor utilization of the network. This
|
||||
* option is overridden by TCP_CORK; however, setting this
|
||||
* option forces an explicit flush of pending output, even if
|
||||
* TCP_CORK is currently set.
|
||||
* @param[in] fd Filedescriptor of the socket
|
||||
* @throws std::runtime_error if the operation fails.
|
||||
*/
|
||||
void enable_tcp_no_delay(int fd);
|
||||
|
||||
/**
|
||||
* @brief Set the port <a href="https://man7.org/linux/man-pages/man3/errno.3.html">non blocking</a>
|
||||
* @details Operations on this socket will not block but instead return an error EAGAIN or EWOULDBLOCK
|
||||
* @param[in] fd Filedescriptor of the socket
|
||||
* @throws std::runtime_error if the operation fails.
|
||||
*/
|
||||
void set_non_blocking(int fd);
|
||||
|
||||
/**
|
||||
* @brief Set the <a href="https://man7.org/linux/man-pages/man7/socket.7.html">keepalive</a> option.
|
||||
* @details Enable sending of keep-alive messages on connection-oriented sockets.
|
||||
* @param[in] fd Description
|
||||
* @param[in] enable enabled if 'true'
|
||||
* @throws std::runtime_error if the operation fails.
|
||||
*/
|
||||
void set_keepalive(int fd, bool enable);
|
||||
|
||||
/**
|
||||
* @brief Set the parameters for <a href="https://man7.org/linux/man-pages/man7/tcp.7.html">TCP keepalive</a>
|
||||
* @details This implicitly calls @p set_keepalive
|
||||
* @param[in] fd Description
|
||||
* @param[in] count The maximum number of keepalive probes TCP should send
|
||||
* before dropping the connection.
|
||||
* @param[in] idle_s The time (in seconds) the connection needs to remain idle
|
||||
* before TCP starts sending keepalive probes.
|
||||
* @param[in] intval_s The time (in seconds) between individual keepalive probes.
|
||||
* @throws std::runtime_error if the operation fails.
|
||||
*/
|
||||
void set_tcp_keepalive(int fd, uint32_t count, uint32_t idle_s, uint32_t intval_s);
|
||||
|
||||
/**
|
||||
* @brief Sets the maximum transmission timeout befor error
|
||||
* @details
|
||||
* Increasing user timeouts allows a TCP connection to survive
|
||||
* extended periods without end-to-end connectivity.
|
||||
* Decreasing user timeouts allows applications to "fail
|
||||
* fast", if so desired. Otherwise, failure may take up to 20
|
||||
* minutes with the current system defaults in a normal WAN environment.
|
||||
* @param[in] fd Description
|
||||
* @param[in] to_ms timeout in milliseconds
|
||||
* @throws std::runtime_error if the operation fails.
|
||||
*/
|
||||
void set_tcp_user_timeout(int fd, uint32_t to_ms);
|
||||
|
||||
/**
|
||||
* @brief Set socket send buffer to minimum value
|
||||
* @details
|
||||
* This transforms ENOBUFS to EAGAIN and allows to wait for
|
||||
* the socket with select/poll/epoll to be really ready to write
|
||||
* This is especially helpfull for
|
||||
* <a href="https://rtime.felk.cvut.cz/can/socketcan-qdisc-final.pdf">socket_can</a>.
|
||||
* @param[in] fd Filedescriptor of the socket
|
||||
* @throws std::runtime_error if the operation fails.
|
||||
*/
|
||||
void set_socket_send_buffer_to_min(int fd);
|
||||
|
||||
/**
|
||||
* @brief Get the pending errors from a socket
|
||||
* @details Gets <a href="https://man7.org/linux/man-pages/man7/socket.7.html">SO_ERROR</a>
|
||||
* with <a href="https://man7.org/linux/man-pages/man2/getsockopt.2.html">getsockopt</a>
|
||||
* @param[in] fd Description
|
||||
* @return Description
|
||||
*/
|
||||
int get_pending_error(int fd);
|
||||
|
||||
/**
|
||||
* @brief Checks if a TCP socket is still connected and operational without consuming any incoming data.
|
||||
*
|
||||
* @details By peeking into the socket's receive buffer the connection status is determinded.
|
||||
* The function is guaranteed to be non blocking and not removing any data from the buffer
|
||||
* by using `MSG_PEEK` and `MSG_DONTWAIT` flags.
|
||||
* @param fd The file descriptor of the TCP socket to check.
|
||||
* @return `true` if the socket is considered alive, `false` otherwise.
|
||||
*/
|
||||
bool is_tcp_socket_alive(int fd);
|
||||
|
||||
/**
|
||||
* @brief Brings a network device up (activates it).
|
||||
* @param sock_fd The file descriptor of a control socket (AF_INET, SOCK_DGRAM).
|
||||
* @param dev_name The name of the network device (e.g., "tap0", "eth0").
|
||||
* @throws std::runtime_error if the operation fails.
|
||||
*/
|
||||
void bring_device_up(int sock_fd, std::string const& dev_name);
|
||||
|
||||
/**
|
||||
* @brief Sets the IP address for a network device.
|
||||
* @param sock_fd The file descriptor of a control socket (AF_INET, SOCK_DGRAM).
|
||||
* @param dev_name The name of the network device.
|
||||
* @param ip_address_str The IP address string (e.g., "192.168.1.1").
|
||||
* @throws std::runtime_error if the operation fails or IP address is invalid.
|
||||
*/
|
||||
void set_ip_address(int sock_fd, std::string const& dev_name, std::string const& ip_address_str);
|
||||
|
||||
/**
|
||||
* @brief Sets the netmask for a network device.
|
||||
* @param sock_fd The file descriptor of a control socket (AF_INET, SOCK_DGRAM).
|
||||
* @param dev_name The name of the network device.
|
||||
* @param netmask_str The netmask string (e.g., "255.255.255.0").
|
||||
* @throws std::runtime_error if the operation fails or netmask is invalid.
|
||||
*/
|
||||
void set_netmask(int sock_fd, std::string const& dev_name, std::string const& netmask_str);
|
||||
|
||||
/**
|
||||
* @brief Sets the MTU (Maximum Transmission Unit) for a network device.
|
||||
* @param sock_fd The file descriptor of a control socket (AF_INET, SOCK_DGRAM).
|
||||
* @param dev_name The name of the network device.
|
||||
* @param mtu The MTU value to set.
|
||||
* @throws std::runtime_error if the operation fails.
|
||||
*/
|
||||
void set_mtu(int sock_fd, std::string const& dev_name, int mtu);
|
||||
|
||||
/**
|
||||
* @brief Creates a new TAP device with a *specific* desired name.
|
||||
* This function opens /dev/net/tun and performs the TUNSETIFF ioctl.
|
||||
* It will fail if the desired_device_name is empty or already in use.
|
||||
*
|
||||
* @param desired_device_name The exact name for the TAP device (e.g., "mytap0").
|
||||
* Must NOT be empty.
|
||||
* @return The managed filedescriptor for the device.
|
||||
* @throws std::runtime_error if operation fails or if desired_device_name is empty.
|
||||
*/
|
||||
event::unique_fd create_tap_device(std::string const& desired_device_name);
|
||||
|
||||
/**
|
||||
* @brief Opens a control socket for network interface configuration.
|
||||
* This socket is used to issue SIOC* ioctl commands.
|
||||
* @return An event::unique_fd for the opened control socket.
|
||||
* @throws std::runtime_error if the operation fails.
|
||||
*/
|
||||
event::unique_fd open_control_socket();
|
||||
|
||||
/**
|
||||
* @brief Configures a previously created TAP device with IP, netmask, MTU, and non-blocking mode.
|
||||
* This function now internally creates and manages the control socket.
|
||||
*
|
||||
* @param tap_fd The file descriptor of the TAP device.
|
||||
* @param dev_name The actual name of the TAP device.
|
||||
* @param ip The IP address to assign (e.g., "192.168.1.1").
|
||||
* @param netmask The netmask to assign (e.g., "255.255.255.0").
|
||||
* @param mtu The MTU value to set (defaults to 1500).
|
||||
* @return bool on success, false otherwise
|
||||
*/
|
||||
bool configure_tap_device_properties(int tap_fd, std::string const& dev_name, std::string const& ip,
|
||||
std::string const& netmask, int mtu = 1500);
|
||||
|
||||
/**
|
||||
* @brief Get the flags of a socket
|
||||
* @param[in] fd The file descriptor of a socket
|
||||
* @return The flags of the socket
|
||||
* @throws std::runtime_error if the flags cannot be read
|
||||
*/
|
||||
int get_socket_flags(int fd);
|
||||
|
||||
/**
|
||||
* @brief Set the flags of a socket
|
||||
* @param[in] fd The file descirptor of a socket
|
||||
* @param[in] flags The flags to be set
|
||||
* @throws std::runtime_error if the flags cannot be set
|
||||
*/
|
||||
void set_socket_flags(int fd, int flags);
|
||||
|
||||
/**
|
||||
* @brief <a href="https://man7.org/linux/man-pages/man2/poll.2.html">poll</a> the socket until timeout
|
||||
* @details No special care is taken in the error case to check if poll was interrupted.
|
||||
* @param[in] fd The file descirptor of a socket
|
||||
* @param[in] timeout_ms The timeout for poll
|
||||
*/
|
||||
int poll_for_timeout_once(int fd, uint32_t timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief <a href="https://man7.org/linux/man-pages/man2/poll.2.html">poll</a> the socket until timeout
|
||||
* @details The function checks in the error case if poll was interrupted. If it was, poll is called again with the
|
||||
* remaining portion of the timeout
|
||||
* @param[in] fd The file descirptor of a socket
|
||||
* @param[in] timeout_ms The timeout for poll
|
||||
*/
|
||||
int poll_until_timeout(int fd, uint32_t timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief Connect with timeout
|
||||
* @details This function adds a timeout to
|
||||
* <a href="https://man7.org/linux/man-pages/man2/connect.2.html">connect</a>.
|
||||
* The behavior of the original function is mimicked as closely as possible by
|
||||
* forwarding the original 'errno' extended with a possible 'ETIMEDOUT'
|
||||
* @param[in] fd The file descriptor of a socket to be connected
|
||||
* @param[in] addr Address information
|
||||
* @param[in] addrlen The length of addr
|
||||
* @param[in] timeout_ms Timeout in milliseconds
|
||||
* @return '0' on success, '-1' on error. In the error case, errno is set accordingly
|
||||
*/
|
||||
int connect_with_timeout(int fd, const struct sockaddr* addr, uint32_t addrlen, unsigned int timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief Set <a href="https://man7.org/linux/man-pages/man7/socket.7.html">reuse address<a>
|
||||
* flag on socket
|
||||
* @param[in] fd The filedescriptor of a socket.
|
||||
*/
|
||||
void set_reuse_address(int fd);
|
||||
|
||||
/**
|
||||
* @brief Set <a href="https://man7.org/linux/man-pages/man7/socket.7.html">reuse port<a>
|
||||
* flag on socket
|
||||
* @param[in] fd The filedescriptor of a socket.
|
||||
*/
|
||||
void set_reuse_port(int fd);
|
||||
|
||||
/**
|
||||
* @brief Uses <a href="https://man7.org/linux/man-pages/man2/bind.2.html">bind</a> to
|
||||
* to adding an address and port to the socket.
|
||||
* @details Description
|
||||
* @param[in] fd The filedescriptor of the socket
|
||||
* @param[in] ip IP address
|
||||
* @param[in] port Port
|
||||
*/
|
||||
void bind_socket_ip4(int fd, std::string const& ip, std::uint16_t port);
|
||||
|
||||
/**
|
||||
* @brief Enable <a href="https://man7.org/linux/man-pages/man7/ip.7.html">multicast</a> for a UDP socket
|
||||
* @details This sets the local device for the multicast socket and joins the multicast group
|
||||
* @param[in] fd The filedescriptor of the socket
|
||||
* @param[in] multicast_ip The IP of the multicast group to be joined.
|
||||
* @param[in] interface_ip The IP of the local interface
|
||||
*/
|
||||
void set_udp_multicast(int fd, std::string const& multicast_ip, std::string const& interface_ip);
|
||||
|
||||
/**
|
||||
* @brief Convert string representation of an IP to numeric representation
|
||||
* @param[in] ip The string representation
|
||||
* @return The numeric representation
|
||||
*/
|
||||
std::uint32_t ip_to_s_addr(std::string const& ip);
|
||||
|
||||
struct if_info {
|
||||
std::string name;
|
||||
std::string ipv4;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get the address of the supplied interface
|
||||
* @param[in] name The name of the interface
|
||||
* @return The IP adress of the interface
|
||||
* @throws std::runtime_error if the operation fails.
|
||||
*/
|
||||
std::string get_interface_address(std::string const& name);
|
||||
|
||||
/**
|
||||
* @brief Get all interfaces and their adresses
|
||||
* @return The list of interfaces and adresses.
|
||||
* @throws std::runtime_error if the operation fails.
|
||||
*/
|
||||
std::vector<if_info> get_all_interaces();
|
||||
|
||||
} // namespace everest::lib::io::socket
|
||||
@@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <everest/io/event/fd_event_client.hpp>
|
||||
#include <everest/io/tcp/tcp_socket.hpp>
|
||||
|
||||
namespace everest::lib::io::tcp {
|
||||
|
||||
/**
|
||||
* @var tcp_client
|
||||
* @brief Client for TCP implemented in terms of \ref event::fd_event_client
|
||||
* and \ref tcp::tcp_socket
|
||||
*/
|
||||
using tcp_client = event::fd_event_client<tcp_socket>::type;
|
||||
|
||||
} // namespace everest::lib::io::tcp
|
||||
@@ -0,0 +1,137 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <everest/io/event/unique_fd.hpp>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace everest::lib::io::tcp {
|
||||
|
||||
/**
|
||||
* tcp_socket bundles basic <a href="https://man7.org/linux/man-pages/man7/tcp.7.html">TCP</a>
|
||||
* related functionality. This includes lifetime management, reading, writing and fundamental
|
||||
* error checking.
|
||||
* Although this class can be used on its own, the main purpose is to be used as base class for
|
||||
* implementation the \p ClientPolicy of \ref event::fd_event_client
|
||||
*/
|
||||
class tcp_socket {
|
||||
public:
|
||||
/**
|
||||
* @var PayloadT
|
||||
* @brief Type of the payload for TX and RX operations
|
||||
*/
|
||||
using PayloadT = std::vector<uint8_t>;
|
||||
|
||||
/**
|
||||
* @brief The class is default constructed
|
||||
*/
|
||||
tcp_socket() = default;
|
||||
~tcp_socket() = default;
|
||||
|
||||
/**
|
||||
* @brief Open a TCP socket.
|
||||
* @details Sets the socket non blocking. <br>
|
||||
* Implementation for \p ClientPolicy optional async capabilities
|
||||
* @param[in] remote The host to connect to
|
||||
* @param[in] port The port on host
|
||||
* @param[in] device Optional interface name to bind to via SO_BINDTODEVICE. Empty = no binding.
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool open(std::string const& remote, uint16_t port, std::string const& device = {});
|
||||
|
||||
/**
|
||||
* @brief Prepare the setup a TCP socket.
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @param[in] remote The host to connect to
|
||||
* @param[in] port The port on host
|
||||
* @param[in] timeout_ms Timeout for connecting to the remote
|
||||
* @param[in] device Optional interface name to bind to via SO_BINDTODEVICE. Empty = no binding.
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool setup(std::string const& remote, uint16_t port, int timeout_ms, std::string const& device = {});
|
||||
|
||||
/**
|
||||
* @brief Long running part of the TCP connection process
|
||||
* @details Implementation for \p ClientPolicy optional async capabilities
|
||||
*/
|
||||
void connect(std::function<void(bool, int)> const& setup_cb);
|
||||
|
||||
/**
|
||||
* @brief Write data to the socket. Partial writes may occur.
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @param[inout] payload Payload. Will be modified on partial writes to hold only the remaining data.
|
||||
* @return True on complete transmission of the payload, False otherwise (including partial writes)
|
||||
*/
|
||||
bool tx(PayloadT& payload);
|
||||
|
||||
/**
|
||||
* @brief Read as much data as available from the socket
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @param[out] buffer Buffer to write to
|
||||
* @return True on success, False otherwise
|
||||
*/
|
||||
bool rx(PayloadT& buffer);
|
||||
|
||||
/**
|
||||
* @brief Get the file descriptor of the socket
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @return The file descriptor of the socket
|
||||
*/
|
||||
int get_fd() const;
|
||||
|
||||
/**
|
||||
* @brief Get the file descriptor of the socket
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @return The file descriptor of the socket
|
||||
*/
|
||||
/**
|
||||
* @brief Get pending errors on the socket.
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @return The current errno of the socket. Zero with no pending error.
|
||||
*/
|
||||
int get_error() const;
|
||||
|
||||
/**
|
||||
* @brief Check if the objects owns a socket
|
||||
* @return True if a object owns a socket, false otherwise
|
||||
*/
|
||||
bool is_open() const;
|
||||
|
||||
/**
|
||||
* @brief Close the owned socket
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* @brief Enable KEEPALIVE for the connection
|
||||
* @param[in] count The maximum number of keepalive probes TCP should send
|
||||
* before dropping the connection.
|
||||
* @param[in] idle_s The time (in seconds) the connection needs to remain idle
|
||||
* before TCP starts sending keepalive probes.
|
||||
* @param[in] intval_s The time (in seconds) between individual keepalive probes.
|
||||
* @return 'true' on success, 'false otherwise'
|
||||
*/
|
||||
bool set_keep_alive(uint32_t count, uint32_t idle_s, uint32_t intval_s);
|
||||
|
||||
/**
|
||||
* @brief Set transmission timeout
|
||||
* @param[in] to_ms The timeout in milliseconds
|
||||
* @return 'true' on success 'false' otherwise
|
||||
*/
|
||||
bool set_user_timeout(uint32_t to_ms);
|
||||
|
||||
private:
|
||||
std::string m_remote;
|
||||
uint16_t m_port{0};
|
||||
event::unique_fd m_fd;
|
||||
int m_timeout_ms{0};
|
||||
std::string m_device;
|
||||
static constexpr size_t default_buffer_size{1500};
|
||||
};
|
||||
} // namespace everest::lib::io::tcp
|
||||
@@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <everest/io/event/fd_event_client.hpp>
|
||||
#include <everest/io/tun_tap/tap_handler.hpp>
|
||||
|
||||
namespace everest::lib::io::tun_tap {
|
||||
|
||||
/**
|
||||
* @var tap_client
|
||||
* @brief Client for names piped and tap devices implemented in terms of \ref event::fd_event_client
|
||||
* and \ref tun_tap::tap_handler;
|
||||
*/
|
||||
using tap_client = event::fd_event_client<tap_handler>::type;
|
||||
|
||||
} // namespace everest::lib::io::tun_tap
|
||||
@@ -0,0 +1,79 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <everest/io/event/unique_fd.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace everest::lib::io::tun_tap {
|
||||
|
||||
/**
|
||||
* tap_handler bundles basic <a href="https://docs.kernel.org/networking/tuntap.html">TAP device</a>
|
||||
* related functionality. This includes setup of the device, ip settings, lifetime management,
|
||||
* reading, writing and fundamental error checking.
|
||||
* Although this class can be used on its own, the main purpose is to be used as base class for
|
||||
* implementation the \p ClientPolicy of \ref event::fd_event_client
|
||||
*/
|
||||
class tap_handler {
|
||||
public:
|
||||
/**
|
||||
* @var PayloadT
|
||||
* @brief The type of the payload
|
||||
*/
|
||||
using PayloadT = std::vector<uint8_t>;
|
||||
/**
|
||||
* The class is default constructed
|
||||
*/
|
||||
tap_handler() = default;
|
||||
|
||||
/**
|
||||
* @brief Create and open a TAP device
|
||||
* @details This functions tries to create and bring up a new TAP device with the
|
||||
* given name and IP settings. A possible reason for failure is if the \p device name is not availble.
|
||||
* @param[in] device The requested name for the TAP device.
|
||||
* @param[in] ip IP address to be assigned for the TAP device
|
||||
* @param[in] netmask Netmask for the TAP device
|
||||
* @param[in] mtu The Maximum transmission unit, i.e. the maximum size of a message in bytes.
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool open(std::string const& device, std::string const& ip, std::string const& netmask, int mtu);
|
||||
|
||||
/**
|
||||
* @brief Write a dataset to the TAP
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @param[in] data Payload
|
||||
* @return True on success, False otherwise.
|
||||
*/
|
||||
bool tx(PayloadT const& data);
|
||||
/**
|
||||
* @brief Read a dataset from the TAP
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @param[in] data Payload
|
||||
* @return True on success, False otherwise.
|
||||
*/
|
||||
bool rx(PayloadT& data);
|
||||
/**
|
||||
* @brief Get the current error
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @return The last errno. Zero if there is no error.
|
||||
*/
|
||||
int get_fd() const;
|
||||
/**
|
||||
* @brief Get the current error
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @return The last errno. Zero if there is no error.
|
||||
*/
|
||||
int get_error() const;
|
||||
|
||||
private:
|
||||
event::unique_fd m_fd;
|
||||
int m_error{0};
|
||||
int m_mtu;
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::tun_tap
|
||||
@@ -0,0 +1,115 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
namespace everest::lib::io::udp {
|
||||
|
||||
/**
|
||||
* @class endpoint
|
||||
* @brief A protocol-agnostic (IPv4 or IPv6) UDP address/port pair.
|
||||
*
|
||||
* Wraps a @c sockaddr_storage plus its valid length. The family is selected
|
||||
* automatically from the host string: numeric IPv4 first, then numeric IPv6,
|
||||
* then a name resolution via @c getaddrinfo (first result). The port is kept
|
||||
* in host byte order at the API surface; @c htons / @c ntohs are applied only
|
||||
* at the syscall boundary inside this class.
|
||||
*
|
||||
* For IPv6 link-local or multicast targets a non-empty @p iface is resolved to
|
||||
* @c sin6_scope_id and additionally retained as a SO_BINDTODEVICE hint
|
||||
* retrievable via @ref iface.
|
||||
*
|
||||
* Scoped to the unconnected UDP client; not a general networking primitive.
|
||||
*/
|
||||
class endpoint {
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructed endpoint (family AF_UNSPEC, no address).
|
||||
*/
|
||||
endpoint() = default;
|
||||
|
||||
/**
|
||||
* @brief Construct from a host string and port.
|
||||
* @param[in] host Numeric IPv4, numeric IPv6, or a resolvable name.
|
||||
* @param[in] port Port in host byte order.
|
||||
* @param[in] iface Optional interface name. Used as the scope id for IPv6
|
||||
* link-local / multicast addresses and retained as a bind hint.
|
||||
* @throws std::runtime_error if @p host cannot be parsed or resolved, or if
|
||||
* @p iface is non-empty but unknown for a scoped IPv6 address.
|
||||
*/
|
||||
endpoint(std::string const& host, std::uint16_t port, std::string iface = {});
|
||||
|
||||
/**
|
||||
* @brief Construct from a raw source address (e.g. from @c recvfrom).
|
||||
* @param[in] src The source address storage.
|
||||
* @param[in] len The valid length of @p src.
|
||||
*/
|
||||
endpoint(sockaddr_storage const& src, socklen_t len);
|
||||
|
||||
/**
|
||||
* @brief Address family.
|
||||
* @return AF_INET, AF_INET6, or AF_UNSPEC when default constructed.
|
||||
*/
|
||||
sa_family_t family() const;
|
||||
|
||||
/**
|
||||
* @brief Port in host byte order.
|
||||
*/
|
||||
std::uint16_t port() const;
|
||||
|
||||
/**
|
||||
* @brief Numeric address string (inet_ntop).
|
||||
*/
|
||||
std::string addr_str() const;
|
||||
|
||||
/**
|
||||
* @brief Pointer to the raw address, usable directly by @c sendto.
|
||||
*/
|
||||
sockaddr const* sa() const;
|
||||
|
||||
/**
|
||||
* @brief Valid length of the address returned by @ref sa.
|
||||
*/
|
||||
socklen_t sa_len() const;
|
||||
|
||||
/**
|
||||
* @brief Interface name hint supplied at construction (may be empty).
|
||||
*/
|
||||
std::string const& iface() const;
|
||||
|
||||
/**
|
||||
* @brief True iff this is an IPv4-mapped IPv6 address (::ffff:a.b.c.d).
|
||||
*/
|
||||
bool is_v4_mapped() const;
|
||||
|
||||
/**
|
||||
* @brief IPv4 view of a v4-mapped address.
|
||||
* @return If @ref is_v4_mapped(): an AF_INET endpoint with the embedded
|
||||
* IPv4 address and the same port. Otherwise a default endpoint
|
||||
* (family AF_UNSPEC, sa_len()==0).
|
||||
*/
|
||||
endpoint as_v4() const;
|
||||
|
||||
/**
|
||||
* @brief Value equality (family, address, port, IPv6 scope id).
|
||||
*/
|
||||
bool operator==(endpoint const& other) const;
|
||||
bool operator!=(endpoint const& other) const {
|
||||
return not(*this == other);
|
||||
}
|
||||
|
||||
private:
|
||||
sockaddr_storage m_storage{};
|
||||
socklen_t m_len{0};
|
||||
std::string m_iface;
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::udp
|
||||
@@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <everest/io/event/fd_event_client.hpp>
|
||||
#include <everest/io/udp/udp_socket.hpp>
|
||||
|
||||
namespace everest::lib::io::udp {
|
||||
|
||||
/**
|
||||
* @var udp_client
|
||||
* @brief Client for UDP implemented in terms of \ref event::fd_event_client
|
||||
* and \ref udp::udp_client_socket
|
||||
*/
|
||||
using udp_client = event::fd_event_client<udp_client_socket>::type;
|
||||
|
||||
} // namespace everest::lib::io::udp
|
||||
@@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <everest/io/event/fd_event_client.hpp>
|
||||
#include <everest/io/udp/udp_dualstack_server_socket.hpp>
|
||||
|
||||
namespace everest::lib::io::udp {
|
||||
|
||||
/**
|
||||
* @var udp_dualstack_server
|
||||
* @brief Dual-stack (IPv6 + IPv4-mapped) UDP server implemented in terms of
|
||||
* \ref event::fd_event_client and \ref udp::udp_dualstack_server_socket
|
||||
*/
|
||||
using udp_dualstack_server = event::fd_event_client<udp_dualstack_server_socket>::type;
|
||||
|
||||
} // namespace everest::lib::io::udp
|
||||
@@ -0,0 +1,65 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <everest/io/event/unique_fd.hpp>
|
||||
#include <everest/io/udp/endpoint.hpp>
|
||||
#include <everest/io/udp/udp_payload.hpp>
|
||||
|
||||
namespace everest::lib::io::udp {
|
||||
|
||||
/**
|
||||
* @class udp_dualstack_server_socket
|
||||
* @brief A dual-stack (IPv6 + IPv4-mapped) unconnected UDP *server* socket.
|
||||
*
|
||||
* Binds [::]:port via socket::open_udp_dualstack_server_socket, records the
|
||||
* last datagram's source, and replies to it. Synchronous ClientPolicy of
|
||||
* event::fd_event_client.
|
||||
*/
|
||||
class udp_dualstack_server_socket {
|
||||
public:
|
||||
using PayloadT = udp_payload;
|
||||
|
||||
udp_dualstack_server_socket() = default;
|
||||
|
||||
/**
|
||||
* @brief Open the server. Never throws (sync policy).
|
||||
* @param[in] port Local UDP port; 0 picks an ephemeral port.
|
||||
* @param[in] device Optional interface name. Empty = no binding.
|
||||
* @return True on success.
|
||||
*/
|
||||
bool open(std::uint16_t port, std::string device = {});
|
||||
|
||||
/**
|
||||
* @brief Reply to the most recent source.
|
||||
* @return False if nothing has been received yet.
|
||||
*/
|
||||
bool tx(udp_payload const& payload);
|
||||
|
||||
/**
|
||||
* @brief Receive a datagram; records last_source().
|
||||
*/
|
||||
bool rx(udp_payload& payload);
|
||||
|
||||
int get_fd() const;
|
||||
int get_error() const;
|
||||
|
||||
std::optional<endpoint> last_source() const {
|
||||
return m_last_source;
|
||||
}
|
||||
|
||||
private:
|
||||
event::unique_fd m_owned_udp_fd;
|
||||
std::optional<endpoint> m_last_source;
|
||||
std::array<uint8_t, udp_payload::max_size> m_rx_buffer;
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::udp
|
||||
@@ -0,0 +1,75 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace everest::lib::io::udp {
|
||||
|
||||
/**
|
||||
* @struct udp_payload
|
||||
* Dataset for UDP with maximum size of 64kBit
|
||||
*/
|
||||
struct udp_payload {
|
||||
/**
|
||||
* @brief udp_payload can be default constructed
|
||||
*/
|
||||
udp_payload() = default;
|
||||
/**
|
||||
* @brief Create message from a std::string
|
||||
* @param[in] msg Payload as string
|
||||
*/
|
||||
udp_payload(std::string const& msg);
|
||||
/**
|
||||
* @brief Create message from a const char*
|
||||
* @param[in] msg Payload as string. Needs to be null terminated
|
||||
*/
|
||||
udp_payload(const char* msg);
|
||||
|
||||
/**
|
||||
* @brief Compare to other object
|
||||
* @details Two objects are equal, if the buffers hold the same data
|
||||
* @param[in] other object to compare to
|
||||
*/
|
||||
bool operator==(udp_payload const& other) const;
|
||||
|
||||
/**
|
||||
* @brief Size of the message
|
||||
* @return Size
|
||||
*/
|
||||
size_t size() const;
|
||||
|
||||
/**
|
||||
* @brief Replace content
|
||||
* @details The replaces the internal buffer with a new message
|
||||
* @param[in] msg New message
|
||||
* @return True on succes, False if message is too long.
|
||||
*/
|
||||
bool set_message(std::string const& msg);
|
||||
|
||||
/**
|
||||
* @brief Replace content
|
||||
* @details The replaces the internal buffer with a new message
|
||||
* @param[in] buffer Pointer to new message
|
||||
* @param[in] size Bytes to copy
|
||||
* @return True on success, False is size is too big
|
||||
*/
|
||||
bool set_message(void const* buffer, size_t size);
|
||||
/**
|
||||
* @brief Buffer holding all information
|
||||
*/
|
||||
std::vector<uint8_t> buffer;
|
||||
|
||||
/**
|
||||
* @var max_size
|
||||
* @brief The maximum size for UDP is limited to 64kBit
|
||||
*/
|
||||
static constexpr size_t max_size = 64 * 1024;
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::udp
|
||||
@@ -0,0 +1,27 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <everest/io/event/fd_event_client.hpp>
|
||||
#include <everest/io/udp/udp_socket.hpp>
|
||||
|
||||
namespace everest::lib::io::udp {
|
||||
|
||||
/**
|
||||
* @var udp_client
|
||||
* @brief Client for UDP implemented in terms of \ref event::fd_event_client
|
||||
* and \ref udp::udp_client_socket
|
||||
*/
|
||||
using udp_client = event::fd_event_client<udp_client_socket>::type;
|
||||
|
||||
/**
|
||||
* @var udp_server
|
||||
* @brief Server for UDP implemented in terms of \ref event::fd_event_client
|
||||
* and \ref udp::udp_server_socket
|
||||
*/
|
||||
using udp_server = event::fd_event_client<udp_server_socket>::type;
|
||||
|
||||
} // namespace everest::lib::io::udp
|
||||
@@ -0,0 +1,247 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <everest/io/event/unique_fd.hpp>
|
||||
#include <everest/io/udp/udp_payload.hpp>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace everest::lib::io::udp {
|
||||
|
||||
/**
|
||||
* @struct udp_info
|
||||
* Collection of information needed to send/receive via UDP
|
||||
*/
|
||||
struct udp_info {
|
||||
/** Adress */
|
||||
uint32_t addr;
|
||||
/** Port */
|
||||
uint16_t port;
|
||||
/** Family */
|
||||
uint16_t family;
|
||||
};
|
||||
|
||||
/**
|
||||
* udp_socket_base bundles basic <a href="https://man7.org/linux/man-pages/man7/udp.7.html">UDP</a>
|
||||
* related functionality. This includes lifetime management, reading, writing and fundamental
|
||||
* error checking.
|
||||
* Although this class can be used on its own, the main purpose is to be used as base class for
|
||||
* implementation the \p ClientPolicy of \ref event::fd_event_client
|
||||
*/
|
||||
class udp_socket_base {
|
||||
public:
|
||||
/**
|
||||
* The class is default constructed
|
||||
*/
|
||||
udp_socket_base() = default;
|
||||
virtual ~udp_socket_base() = default;
|
||||
|
||||
/**
|
||||
* @brief Open the socket as a server
|
||||
* @details Sets the socket non blocking
|
||||
* @param[in] port The port to listen to
|
||||
* @param[in] device Optional interface name to bind to via SO_BINDTODEVICE. Empty = no binding.
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool open_as_server(uint16_t port, std::string const& device = {});
|
||||
|
||||
/**
|
||||
* @brief Open the socket as a server
|
||||
* @details Sets the socket non blocking
|
||||
* @param[in] remote The host to connect to
|
||||
* @param[in] port The port to listen to
|
||||
* @param[in] device Optional interface name to bind to via SO_BINDTODEVICE. Empty = no binding.
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool open_as_client(std::string const& remote, uint16_t port, std::string const& device = {});
|
||||
|
||||
/**
|
||||
* @brief Check if the objects owns a socket
|
||||
* @return True if a object owns a socket, false otherwise
|
||||
*/
|
||||
bool is_open();
|
||||
|
||||
/**
|
||||
* @brief Close the owned socket
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* @brief Get the file descriptor of the socket
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @return The file descriptor of the socket
|
||||
*/
|
||||
int get_fd() const;
|
||||
/**
|
||||
* @brief Get pending errors on the socket.
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @return The current errno of the socket. Zero with no pending error.
|
||||
*/
|
||||
int get_error() const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Send data to the default destination.
|
||||
* @details To be used in sever mode. The operation will fail is the device is not open or is the
|
||||
* data could not be send for a different reason.
|
||||
* @param[in] payload Payload
|
||||
* @param[in] size Size of the payload.
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool tx_impl(void const* payload, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Send data to the default destination.
|
||||
* @details Can be used in server and client mode. The operation will fail if the device is not open or if the
|
||||
* data could not be send for a different reason.
|
||||
* @param[in] payload Payload
|
||||
* @param[in] size Size of the payload.
|
||||
* @param[in] destination The destination for the message
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool tx_impl(void const* payload, size_t size, udp_info const& destination);
|
||||
|
||||
/**
|
||||
* @brief Receive data from the socket
|
||||
* @details Can be used in server and client mode.
|
||||
* @param[inout] buffer Destination for the data
|
||||
* @param[in] buffer_size Size of the buffer
|
||||
* @param[in] payload_size The size of the payload
|
||||
* @return std::nullopt when \p payload_size is zero, the dataset otherwise
|
||||
*/
|
||||
std::optional<udp_info> rx_impl(void* buffer, size_t buffer_size, ssize_t& payload_size);
|
||||
|
||||
event::unique_fd m_owned_udp_fd;
|
||||
};
|
||||
/**
|
||||
* A basic <a href="https://man7.org/linux/man-pages/man7/udp.7.html">UDP</a> client.
|
||||
* Although this class can be used on its own, the main purpose is to be used as base class for
|
||||
* implementation the \p ClientPolicy of \ref event::fd_event_client
|
||||
*/
|
||||
class udp_client_socket : public udp_socket_base {
|
||||
public:
|
||||
/**
|
||||
* @var PayloadT
|
||||
* @brief Type of the payload for TX and RX operations
|
||||
*/
|
||||
using PayloadT = udp_payload;
|
||||
|
||||
/**
|
||||
* @brief The class is default constructed
|
||||
*/
|
||||
udp_client_socket() = default;
|
||||
~udp_client_socket() = default;
|
||||
|
||||
/**
|
||||
* @brief Open a UDP client socket.
|
||||
* @details Sets the socket non blocking. <br>
|
||||
* Implementation for \p ClientPolicy
|
||||
* @param[in] remote The host to connect to
|
||||
* @param[in] port The port on host
|
||||
* @param[in] device Optional interface name to bind to via SO_BINDTODEVICE. Empty = no binding.
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool open(std::string const& remote, uint16_t port, std::string const& device = {});
|
||||
|
||||
/**
|
||||
* @brief Prepare the setup a UDP socket.
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @param[in] remote The host to connect to
|
||||
* @param[in] port The port on host
|
||||
* @param[in] timeout_ms Timeout for connecting to the remote
|
||||
* @param[in] device Optional interface name to bind to via SO_BINDTODEVICE. Empty = no binding.
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool setup(std::string const& remote, uint16_t port, int timeout_ms, std::string const& device = {});
|
||||
|
||||
/**
|
||||
* @brief Long running part of the UDP connection process
|
||||
* @details Implementation for \p ClientPolicy optional async capabilities
|
||||
*/
|
||||
void connect(std::function<void(bool, int)> const& setup_cb);
|
||||
|
||||
/**
|
||||
* @brief Write a \ref udp_payload to the socket
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @param[in] payload Payload
|
||||
* @return True on success, False otherwise
|
||||
*/
|
||||
bool tx(udp_payload const& payload);
|
||||
|
||||
/**
|
||||
* @brief Read a \ref udp_payload from the socket
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @param[out] payload Payload
|
||||
* @return True on success, False otherwise
|
||||
*/
|
||||
bool rx(udp_payload& payload);
|
||||
|
||||
private:
|
||||
std::string m_remote;
|
||||
uint16_t m_port{0};
|
||||
int m_timeout_ms{0};
|
||||
std::string m_device;
|
||||
|
||||
std::array<uint8_t, udp_payload::max_size> rx_buffer;
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* A basic <a href="https://man7.org/linux/man-pages/man7/udp.7.html">UDP</a> server.
|
||||
* Although this class can be used on its own, the main purpose is to be used as base class for
|
||||
* implementation the \p ClientPolicy of \ref event::fd_event_client
|
||||
*/
|
||||
class udp_server_socket : public udp_socket_base {
|
||||
public:
|
||||
/**
|
||||
* @var PayloadT
|
||||
* @brief Type of the payload for TX and RX operations
|
||||
*/
|
||||
using PayloadT = udp_payload;
|
||||
|
||||
/**
|
||||
* @brief The class is default constructed
|
||||
*/
|
||||
udp_server_socket() = default;
|
||||
~udp_server_socket() = default;
|
||||
|
||||
/**
|
||||
* @brief Open a UDP server socket.
|
||||
* @details Sets the socket non blocking. <br>
|
||||
* Implementation for \p ClientPolicy
|
||||
* @param[in] port The port on host
|
||||
* @param[in] device Optional interface name to bind to via SO_BINDTODEVICE. Empty = no binding.
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool open(uint16_t port, std::string const& device = {});
|
||||
|
||||
/**
|
||||
* @brief Write a \ref udp_payload to the socket
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @param[in] payload Payload
|
||||
* @return True on success, False otherwise
|
||||
*/
|
||||
bool tx(udp_payload const& payload);
|
||||
|
||||
/**
|
||||
* @brief Read a \ref udp_payload from the socket
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @param[out] payload Payload
|
||||
* @return True on success, False otherwise
|
||||
*/
|
||||
bool rx(udp_payload& payload);
|
||||
|
||||
private:
|
||||
std::optional<udp_info> m_last_source;
|
||||
std::array<uint8_t, udp_payload::max_size> rx_buffer;
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::udp
|
||||
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <everest/io/event/fd_event_client.hpp>
|
||||
#include <everest/io/udp/udp_unconnected_socket.hpp>
|
||||
|
||||
namespace everest::lib::io::udp {
|
||||
|
||||
/**
|
||||
* @var udp_unconnected_client
|
||||
* @brief Client for an unconnected UDP datagram socket (IPv4 or IPv6,
|
||||
* auto-selected from the target endpoint) implemented in terms of
|
||||
* \ref event::fd_event_client and \ref udp::udp_unconnected_socket
|
||||
*/
|
||||
using udp_unconnected_client = event::fd_event_client<udp_unconnected_socket>::type;
|
||||
|
||||
} // namespace everest::lib::io::udp
|
||||
@@ -0,0 +1,93 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <everest/io/event/unique_fd.hpp>
|
||||
#include <everest/io/udp/endpoint.hpp>
|
||||
#include <everest/io/udp/udp_payload.hpp>
|
||||
|
||||
namespace everest::lib::io::udp {
|
||||
|
||||
/**
|
||||
* An unconnected UDP datagram socket (IPv4 or IPv6, selected automatically from
|
||||
* the target \ref endpoint). It sends to a fixed destination configured at
|
||||
* \ref open and receives datagrams from any source (no ::connect, no multicast
|
||||
* group join), recording the last sender. Designed to be used as the
|
||||
* \p ClientPolicy of \ref event::fd_event_client (synchronous variant).
|
||||
*/
|
||||
class udp_unconnected_socket {
|
||||
public:
|
||||
/**
|
||||
* @var PayloadT
|
||||
* @brief Type of the payload for TX and RX operations
|
||||
*/
|
||||
using PayloadT = udp_payload;
|
||||
|
||||
/**
|
||||
* @brief The class is default constructed
|
||||
*/
|
||||
udp_unconnected_socket() = default;
|
||||
|
||||
/**
|
||||
* @brief Open the socket and set the fixed send destination.
|
||||
* @details Implementation for \p ClientPolicy (sync variant). Never throws.
|
||||
* @param[in] target Destination for \ref tx; selects the socket family.
|
||||
* @param[in] iface Optional interface name. Empty uses @p target's hint.
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool open(endpoint target, std::string iface = {});
|
||||
|
||||
/**
|
||||
* @brief Send a \ref udp_payload to the configured destination.
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @param[in] payload Payload
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool tx(udp_payload const& payload);
|
||||
|
||||
/**
|
||||
* @brief Receive a \ref udp_payload from the socket.
|
||||
* @details Implementation for \p ClientPolicy. On success the sender
|
||||
* endpoint is recorded and retrievable via \ref last_source.
|
||||
* @param[out] payload Payload
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool rx(udp_payload& payload);
|
||||
|
||||
/**
|
||||
* @brief Get the file descriptor of the socket
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @return The file descriptor of the socket
|
||||
*/
|
||||
int get_fd() const;
|
||||
|
||||
/**
|
||||
* @brief Get pending errors on the socket.
|
||||
* @details Implementation for \p ClientPolicy
|
||||
* @return The current errno of the socket. Zero with no pending error.
|
||||
*/
|
||||
int get_error() const;
|
||||
|
||||
/**
|
||||
* @brief Source endpoint of the most recently received datagram.
|
||||
* @return The endpoint, or std::nullopt if nothing was received yet.
|
||||
*/
|
||||
std::optional<endpoint> last_source() const {
|
||||
return m_last_source;
|
||||
}
|
||||
|
||||
private:
|
||||
event::unique_fd m_owned_udp_fd;
|
||||
endpoint m_target;
|
||||
std::optional<endpoint> m_last_source;
|
||||
std::array<uint8_t, udp_payload::max_size> m_rx_buffer;
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::udp
|
||||
@@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
namespace everest::lib::io::utilities {
|
||||
|
||||
/**
|
||||
* @brief Primary template for the trait to check for the existence of a member function 'setup'.
|
||||
* @tparam T The type to check.
|
||||
*/
|
||||
template <typename T, typename V = void> struct has_member_setup : std::false_type {};
|
||||
|
||||
/**
|
||||
* @brief Specialization of has_member_setup.
|
||||
* This checks for existence and accessibility of a member function 'setup', accepting any signature.
|
||||
* @tparam T The type to check.
|
||||
*/
|
||||
template <typename T> struct has_member_setup<T, std::void_t<decltype(&T::setup)>> : std::true_type {};
|
||||
|
||||
/**
|
||||
* @brief Primary template for the trait to check if a type T has a member function
|
||||
* 'connect(std::function<void(bool, int)> const&)' with any return type.
|
||||
* @tparam T The type to check.
|
||||
*/
|
||||
template <typename T, typename V = void> struct has_member_connect : std::false_type {};
|
||||
|
||||
/**
|
||||
* @brief Specialization of has_member_connect.
|
||||
* This checks the existence of a member function connect with 'std::function<void(bool, int)>' as parameter.
|
||||
* @tparam T The type to check.
|
||||
*/
|
||||
template <typename T>
|
||||
struct has_member_connect<
|
||||
T, std::void_t<decltype(std::declval<T>().connect(std::declval<std::function<void(bool, int)> const&>()))>>
|
||||
: std::true_type {};
|
||||
|
||||
/**
|
||||
* @brief Defines the policy trait for an asynchronous event client.
|
||||
* A type T satisfies this policy if it has:
|
||||
* 1. An accessible member 'setup' (any signature).
|
||||
* 2. A callable member function 'connect(const std::function<void(bool, int)>&)' (any return type).
|
||||
* @tparam T The type to check.
|
||||
*/
|
||||
template <typename T>
|
||||
struct event_client_async_policy
|
||||
: std::integral_constant<bool, has_member_setup<T>::value && has_member_connect<T>::value> {};
|
||||
|
||||
/**
|
||||
* @brief Convenience variable template for the event_client_async_policy trait's value.
|
||||
* @tparam T The type to check.
|
||||
*/
|
||||
template <typename T> inline constexpr bool event_client_async_policy_v = event_client_async_policy<T>::value;
|
||||
|
||||
} // namespace everest::lib::io::utilities
|
||||
@@ -0,0 +1,77 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace everest::lib::io::utilities {
|
||||
|
||||
/**
|
||||
* This class bundles some handling logic for <a href="https://man7.org/linux/man-pages/man3/errno.3.html">errno</a>.
|
||||
* <br> Its main purpose is the factor out functionality from \ref event::generic_fd_event_client_impl
|
||||
*/
|
||||
class generic_error_state {
|
||||
public:
|
||||
/**
|
||||
* @var cb_error
|
||||
* @brief Prototype for an on_error handler callback. It receives the current errno
|
||||
* and its string representation
|
||||
*/
|
||||
using cb_error = std::function<void(int error, std::string const& msg)>;
|
||||
virtual ~generic_error_state() = default;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Update the error state
|
||||
* @details Compares with internal error state and update it.
|
||||
* @param[in] error_code The error state to be set
|
||||
* @return False if error status is set, True otherwise
|
||||
*/
|
||||
bool set_error_status(int error_code);
|
||||
|
||||
/**
|
||||
* @brief Check if error handling is still needed.
|
||||
* @return True if there is an uncleared error, false otherwise
|
||||
*/
|
||||
bool clear_error_pending() const;
|
||||
|
||||
/**
|
||||
* @brief Check the error state
|
||||
* @return True if on error, false otherwise
|
||||
*/
|
||||
bool on_error() const;
|
||||
|
||||
/**
|
||||
* @brief Get the current error code
|
||||
* @return Current error code
|
||||
*/
|
||||
int current_error() const;
|
||||
|
||||
/**
|
||||
* @brief Call the error handler with current errno, if registered.
|
||||
* @param[in] handler The handler to be called
|
||||
*/
|
||||
void call_error_handler(cb_error& handler) const;
|
||||
|
||||
/**
|
||||
* @brief Call the error handler with errno=0 (success)
|
||||
* @param[in] handler The handler to be called
|
||||
*/
|
||||
void clear_error_handler(cb_error& handler);
|
||||
|
||||
/**
|
||||
* @brief Mark the current error as cleared
|
||||
*/
|
||||
void set_error_cleared();
|
||||
|
||||
private:
|
||||
bool m_on_error{true};
|
||||
bool m_clear_error_pending{false};
|
||||
int m_current_error{0};
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::utilities
|
||||
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
namespace everest::lib::io::utilities {
|
||||
/**
|
||||
* A simple utility class for time measurements. It's based on RAII principle.
|
||||
*/
|
||||
class stop_watch {
|
||||
public:
|
||||
/**
|
||||
* @var clock
|
||||
* @brief Convenience definition
|
||||
*/
|
||||
using clock = std::chrono::high_resolution_clock;
|
||||
|
||||
/**
|
||||
* @var tp
|
||||
* @brief convenience definition
|
||||
*/
|
||||
using tp = clock::time_point;
|
||||
|
||||
/**
|
||||
* @var us
|
||||
* @brief Convenience definition
|
||||
*/
|
||||
using us = std::chrono::microseconds;
|
||||
|
||||
/**
|
||||
* @brief Create a stop_watch with start time now
|
||||
* @param[in] id Give the stop_watch an id.
|
||||
*/
|
||||
stop_watch(std::string const& id = "");
|
||||
/**
|
||||
* @brief If the \p id is not empty a message with the last
|
||||
* measurement will be printed on destruction.
|
||||
* @details Duration is bewenn start_time and stop_time
|
||||
*/
|
||||
~stop_watch();
|
||||
|
||||
/**
|
||||
* @brief Reset the start_time to now
|
||||
* @return The new start_time
|
||||
*/
|
||||
tp reset();
|
||||
|
||||
/**
|
||||
* @brief Set the end_time to now
|
||||
* @return The duration between start_time and end_time in microseconds
|
||||
*/
|
||||
us stop();
|
||||
|
||||
/**
|
||||
* @brief Get the current duration
|
||||
* @return The duration between start_time and now in microseconds
|
||||
*/
|
||||
us lap() const;
|
||||
|
||||
private:
|
||||
tp m_start_time;
|
||||
tp m_end_time;
|
||||
std::string m_id;
|
||||
};
|
||||
|
||||
} // namespace everest::lib::io::utilities
|
||||
135
tools/EVerest-main/lib/everest/io/src/CMakeLists.txt
Normal file
135
tools/EVerest-main/lib/everest/io/src/CMakeLists.txt
Normal file
@@ -0,0 +1,135 @@
|
||||
|
||||
add_library(everest_io SHARED)
|
||||
add_library(everest::io ALIAS everest_io)
|
||||
|
||||
set_target_properties(everest_io PROPERTIES
|
||||
VERSION 0.1.0
|
||||
SOVERSION 0
|
||||
)
|
||||
|
||||
target_sources(everest_io
|
||||
PRIVATE
|
||||
event/fd_event_handler.cpp
|
||||
event/unique_fd.cpp
|
||||
event/event_fd.cpp
|
||||
event/timer_fd.cpp
|
||||
can/socket_can_handler.cpp
|
||||
can/can_recv_filter.cpp
|
||||
can/can_payload.cpp
|
||||
socket/socket.cpp
|
||||
utilities/generic_error_state.cpp
|
||||
event/fd_event_client.cpp
|
||||
udp/udp_socket.cpp
|
||||
udp/udp_unconnected_socket.cpp
|
||||
udp/udp_dualstack_server_socket.cpp
|
||||
udp/endpoint.cpp
|
||||
udp/udp_payload.cpp
|
||||
utilities/stop_watch.cpp
|
||||
serial/serial.cpp
|
||||
serial/pty_handler.cpp
|
||||
serial/event_pty.cpp
|
||||
mdns/mdns.cpp
|
||||
mdns/mdns_socket.cpp
|
||||
tun_tap/tap_handler.cpp
|
||||
tcp/tcp_socket.cpp
|
||||
netlink/vcan_netlink_manager.cpp
|
||||
raw/raw_socket.cpp
|
||||
)
|
||||
|
||||
target_include_directories(everest_io
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
|
||||
if(EVEREST_IO_WITH_MQTT)
|
||||
target_sources(everest_io
|
||||
PRIVATE
|
||||
mqtt/mosquitto_cpp.cpp
|
||||
mqtt/mqtt_client.cpp
|
||||
)
|
||||
target_include_directories(everest_io
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${mosquitto_SOURCE_DIR}/include>
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(everest_io
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:everest_util>
|
||||
$<INSTALL_INTERFACE:everest::util>
|
||||
)
|
||||
|
||||
include(CheckIncludeFile)
|
||||
check_include_file(net/ethernet.h HAVE_NET_ETHERNET_H)
|
||||
if(HAVE_NET_ETHERNET_H AND USING_MUSL)
|
||||
target_compile_definitions(everest_io PRIVATE _NETINET_IF_ETHER_H)
|
||||
endif()
|
||||
|
||||
if(EVEREST_NO_PACKET_IGNORE_OUTGOING)
|
||||
target_compile_definitions(everest_io PRIVATE EVEREST_NO_PACKET_IGNORE_OUTGOING)
|
||||
endif()
|
||||
|
||||
target_compile_features(everest_io PUBLIC cxx_std_17)
|
||||
|
||||
target_compile_options(everest_io
|
||||
PRIVATE
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wshadow
|
||||
-Wnon-virtual-dtor
|
||||
-Wunused
|
||||
-Wpedantic
|
||||
-Wformat=2
|
||||
-Werror
|
||||
)
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
set_target_properties(everest_io
|
||||
PROPERTIES
|
||||
EXPORT_NAME io
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
)
|
||||
|
||||
if(EVEREST_IO_WITH_MQTT)
|
||||
if(DISABLE_EDM)
|
||||
target_link_libraries(everest_io
|
||||
PRIVATE
|
||||
mosquitto
|
||||
)
|
||||
else()
|
||||
target_link_libraries(everest_io
|
||||
PRIVATE
|
||||
libmosquitto
|
||||
)
|
||||
install(TARGETS libmosquitto
|
||||
EXPORT everest-core-targets
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
install(TARGETS everest_io
|
||||
EXPORT everest-core-targets
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
)
|
||||
|
||||
if(EVEREST_IO_WITH_MQTT)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../include/everest
|
||||
DESTINATION include
|
||||
FILES_MATCHING PATTERN "*.hpp"
|
||||
)
|
||||
else()
|
||||
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../include/everest
|
||||
DESTINATION include
|
||||
FILES_MATCHING PATTERN "*.hpp"
|
||||
PATTERN "mqtt" EXCLUDE
|
||||
)
|
||||
endif()
|
||||
65
tools/EVerest-main/lib/everest/io/src/can/can_payload.cpp
Normal file
65
tools/EVerest-main/lib/everest/io/src/can/can_payload.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/io/can/can_payload.hpp>
|
||||
#include <linux/can.h>
|
||||
|
||||
namespace everest::lib::io::can {
|
||||
|
||||
can_dataset::can_dataset(uint32_t can_id_, uint8_t len8_dlc_, can_payload const& payload_) {
|
||||
payload = payload_;
|
||||
len8_dlc = len8_dlc_;
|
||||
set_can_id(can_id_);
|
||||
}
|
||||
|
||||
uint32_t can_dataset::get_can_id() const {
|
||||
if (eff) {
|
||||
return can_id & CAN_EFF_MASK;
|
||||
} else {
|
||||
return can_id & CAN_SFF_MASK;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t can_dataset::get_can_id_with_flags() const {
|
||||
auto tmp = get_can_id();
|
||||
if (eff) {
|
||||
tmp |= CAN_EFF_FLAG;
|
||||
}
|
||||
if (rtr) {
|
||||
tmp |= CAN_RTR_FLAG;
|
||||
}
|
||||
if (err) {
|
||||
tmp |= CAN_ERR_FLAG;
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
void can_dataset::set_can_id(uint32_t id) {
|
||||
can_id = id;
|
||||
}
|
||||
|
||||
void can_dataset::set_can_id_with_flags(uint32_t id) {
|
||||
eff = id & CAN_EFF_FLAG;
|
||||
rtr = id & CAN_RTR_FLAG;
|
||||
err = id & CAN_ERR_FLAG;
|
||||
set_can_id(id);
|
||||
}
|
||||
|
||||
void can_dataset::set_can_id_with_flags(uint32_t id, bool eff_, bool rtr_, bool err_) {
|
||||
eff = eff_;
|
||||
rtr = rtr_;
|
||||
err = err_;
|
||||
set_can_id(id);
|
||||
}
|
||||
|
||||
bool can_dataset::eff_flag() const {
|
||||
return eff;
|
||||
}
|
||||
bool can_dataset::rtr_flag() const {
|
||||
return rtr;
|
||||
}
|
||||
bool can_dataset::err_flag() const {
|
||||
return err;
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::can
|
||||
@@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/io/can/can_recv_filter.hpp>
|
||||
|
||||
namespace everest::lib::io::can {
|
||||
|
||||
can_recv_filter can_recv_filter::reject_match(uint32_t id, uint32_t mask, bool extended) {
|
||||
return can_recv_filter{id, mask, extended, true};
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::can
|
||||
214
tools/EVerest-main/lib/everest/io/src/can/socket_can_handler.cpp
Normal file
214
tools/EVerest-main/lib/everest/io/src/can/socket_can_handler.cpp
Normal file
@@ -0,0 +1,214 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <algorithm>
|
||||
#include <everest/io/can/can_recv_filter.hpp>
|
||||
#include <everest/io/can/socket_can_handler.hpp>
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <everest/io/event/unique_fd.hpp>
|
||||
#include <everest/io/socket/socket.hpp>
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/raw.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// has to be after sys/types.h for musl
|
||||
#include <net/if.h>
|
||||
|
||||
namespace everest::lib::io::can {
|
||||
|
||||
namespace {
|
||||
|
||||
canid_t to_linux_can_id(can_recv_filter const& filter) {
|
||||
auto id = filter.can_id & CAN_EFF_MASK;
|
||||
if (filter.extended) {
|
||||
id |= CAN_EFF_FLAG;
|
||||
}
|
||||
if (filter.invert) {
|
||||
id |= CAN_INV_FILTER;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
canid_t to_linux_can_mask(can_recv_filter const& filter) {
|
||||
auto mask = filter.can_mask & CAN_EFF_MASK;
|
||||
if (filter.extended) {
|
||||
mask |= CAN_EFF_FLAG;
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool socket_can_handler::data_valid(can_payload const& payload) {
|
||||
return payload.size() <= CAN_MAX_DLEN;
|
||||
}
|
||||
|
||||
int socket_can_handler::tx(uint32_t can_id, uint8_t len8_dlc, can_payload const& payload) {
|
||||
if (not is_open()) {
|
||||
return ENETDOWN;
|
||||
}
|
||||
if (not data_valid(payload)) {
|
||||
return EINVAL;
|
||||
}
|
||||
struct can_frame frame;
|
||||
|
||||
frame.can_id = can_id;
|
||||
auto const max_dlc_value = 15;
|
||||
frame.len8_dlc = std::min<uint8_t>(len8_dlc, max_dlc_value);
|
||||
frame.len = std::min<uint8_t>(CAN_MAX_DLEN, payload.size());
|
||||
memcpy(frame.data, payload.data(), frame.len);
|
||||
|
||||
auto bytes_written = write(m_owned_can_fd, &frame, sizeof(can_frame));
|
||||
if (bytes_written != sizeof(can_frame)) {
|
||||
if (bytes_written == -1) {
|
||||
return errno;
|
||||
} else {
|
||||
return EAGAIN;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int socket_can_handler::rx(uint32_t& can_id, uint8_t& len8_dlc, can_payload& payload) {
|
||||
if (is_open()) {
|
||||
static auto const framesize = sizeof(struct can_frame);
|
||||
can_frame frame;
|
||||
auto nbytes = read(m_owned_can_fd, &frame, framesize);
|
||||
|
||||
if (nbytes == framesize) {
|
||||
payload.clear();
|
||||
payload.assign(frame.data, frame.data + frame.can_dlc);
|
||||
can_id = frame.can_id;
|
||||
len8_dlc = frame.len8_dlc;
|
||||
return 0;
|
||||
} else if (nbytes == -1) {
|
||||
return errno;
|
||||
} else {
|
||||
return EINVAL;
|
||||
}
|
||||
}
|
||||
return ENETDOWN;
|
||||
}
|
||||
|
||||
bool socket_can_handler::tx(can_dataset const& data) {
|
||||
auto can_id = data.get_can_id_with_flags();
|
||||
auto status = tx(can_id, data.len8_dlc, data.payload);
|
||||
return status == 0;
|
||||
}
|
||||
|
||||
bool socket_can_handler::rx(can_dataset& data) {
|
||||
uint32_t can_id = 0;
|
||||
auto status = rx(can_id, data.len8_dlc, data.payload);
|
||||
if (status == 0)
|
||||
data.set_can_id_with_flags(can_id);
|
||||
return status == 0;
|
||||
}
|
||||
|
||||
bool socket_can_handler::open(std::string const& can_device, std::vector<can_recv_filter> const& recv_filters) {
|
||||
// IFNAMSIZ is the size of the buffer to write the name to.
|
||||
// This situation is special concerning null termination,
|
||||
// The name can occupy the fill buffer. If it does not, nulltermination is necessary
|
||||
// Since most Linux systems enforce 15 chars as limit plus 1 for nulltermination
|
||||
// we do the same thing here.
|
||||
if (can_device.size() >= IFNAMSIZ) {
|
||||
return false;
|
||||
}
|
||||
m_recv_filters = recv_filters;
|
||||
m_can_dev = can_device;
|
||||
return open_device() == 0;
|
||||
}
|
||||
|
||||
bool socket_can_handler::set_recv_filters(std::vector<can_recv_filter> const& recv_filters) {
|
||||
m_recv_filters = recv_filters;
|
||||
if (!is_open()) {
|
||||
return true;
|
||||
}
|
||||
return apply_recv_filters();
|
||||
}
|
||||
|
||||
std::vector<can_recv_filter> const& socket_can_handler::get_recv_filters() const {
|
||||
return m_recv_filters;
|
||||
}
|
||||
|
||||
bool socket_can_handler::apply_recv_filters() {
|
||||
if (!is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_recv_filters.empty()) {
|
||||
can_filter accept_all{};
|
||||
return ::setsockopt(m_owned_can_fd, SOL_CAN_RAW, CAN_RAW_FILTER, &accept_all, sizeof(accept_all)) == 0;
|
||||
}
|
||||
|
||||
std::vector<can_filter> linux_filters;
|
||||
linux_filters.reserve(m_recv_filters.size());
|
||||
for (auto const& filter : m_recv_filters) {
|
||||
linux_filters.push_back({to_linux_can_id(filter), to_linux_can_mask(filter)});
|
||||
}
|
||||
|
||||
return ::setsockopt(m_owned_can_fd, SOL_CAN_RAW, CAN_RAW_FILTER, linux_filters.data(),
|
||||
linux_filters.size() * sizeof(can_filter)) == 0;
|
||||
}
|
||||
|
||||
int socket_can_handler::open_device() {
|
||||
auto can_fd = event::unique_fd(::socket(PF_CAN, SOCK_RAW, CAN_RAW));
|
||||
if (can_fd < 0) {
|
||||
perror("Socket");
|
||||
return errno;
|
||||
}
|
||||
struct ifreq ifr;
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
// We know m_can_dev fits because of the check in open().
|
||||
// strncpy will copy the string and the null terminator.
|
||||
// The previous memset handles any trailing bytes in the 16-byte buffer.
|
||||
strncpy(ifr.ifr_name, m_can_dev.c_str(), IFNAMSIZ);
|
||||
if (ioctl(can_fd, SIOCGIFINDEX, &ifr) < 0) {
|
||||
perror(m_can_dev.c_str());
|
||||
return errno;
|
||||
}
|
||||
|
||||
struct sockaddr_can addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = ifr.ifr_ifindex;
|
||||
|
||||
if (bind(can_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
|
||||
perror("Bind");
|
||||
return errno;
|
||||
}
|
||||
|
||||
socket::set_non_blocking(can_fd);
|
||||
socket::set_socket_send_buffer_to_min(can_fd);
|
||||
m_owned_can_fd = std::move(can_fd);
|
||||
|
||||
if (!apply_recv_filters()) {
|
||||
m_owned_can_fd.close();
|
||||
return errno != 0 ? errno : EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool socket_can_handler::is_open() const {
|
||||
return m_owned_can_fd.is_fd();
|
||||
}
|
||||
|
||||
void socket_can_handler::close() {
|
||||
m_owned_can_fd.close();
|
||||
}
|
||||
|
||||
int socket_can_handler::get_fd() const {
|
||||
return m_owned_can_fd;
|
||||
}
|
||||
|
||||
int socket_can_handler::get_error() const {
|
||||
return socket::get_pending_error(m_owned_can_fd);
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::can
|
||||
51
tools/EVerest-main/lib/everest/io/src/event/event_fd.cpp
Normal file
51
tools/EVerest-main/lib/everest/io/src/event/event_fd.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/io/event/event_fd.hpp>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <sys/eventfd.h>
|
||||
|
||||
namespace everest::lib::io::event {
|
||||
|
||||
event_fd_base::event_fd_base(unsigned int initval, int flags) : m_fd(::eventfd(initval, flags)) {
|
||||
if (m_fd == -1) {
|
||||
throw std::runtime_error("failed to create an eventfd");
|
||||
}
|
||||
}
|
||||
|
||||
event_fd_base::operator int() const {
|
||||
return get_raw_fd();
|
||||
}
|
||||
|
||||
int event_fd_base::get_raw_fd() const {
|
||||
return m_fd;
|
||||
}
|
||||
|
||||
bool event_fd_base::valid() const {
|
||||
return m_fd != unique_fd::NO_DESCRIPTOR_SENTINEL;
|
||||
}
|
||||
|
||||
std::optional<uint64_t> event_fd_base::read() {
|
||||
eventfd_t eventfd_buffer{0};
|
||||
if (eventfd_read(m_fd, &eventfd_buffer) == 0) {
|
||||
return eventfd_buffer;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool event_fd_base::write(std::uint64_t data) {
|
||||
return eventfd_write(m_fd, data) == 0;
|
||||
}
|
||||
|
||||
bool event_fd_base::notify() {
|
||||
return write(1);
|
||||
}
|
||||
|
||||
event_fd::event_fd() : event_fd_base(0, 0) {
|
||||
}
|
||||
|
||||
semaphore_fd::semaphore_fd() : event_fd_base(0, EFD_SEMAPHORE) {
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::event
|
||||
164
tools/EVerest-main/lib/everest/io/src/event/fd_event_client.cpp
Normal file
164
tools/EVerest-main/lib/everest/io/src/event/fd_event_client.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/io/event/fd_event_client.hpp>
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
|
||||
namespace everest::lib::io::event {
|
||||
|
||||
generic_fd_event_client_impl::generic_fd_event_client_impl(action const& send_one, action const& receive_one,
|
||||
action const& reset_client, error_status const& get_error) :
|
||||
m_send_one(send_one), m_receive_one(receive_one), m_reset_client(reset_client), m_get_error(get_error) {
|
||||
m_event_handler = std::make_unique<event::fd_event_handler>();
|
||||
}
|
||||
|
||||
generic_fd_event_client_impl::~generic_fd_event_client_impl() = default;
|
||||
|
||||
int generic_fd_event_client_impl::get_poll_fd() {
|
||||
return m_event_handler->get_poll_fd();
|
||||
}
|
||||
|
||||
sync_status generic_fd_event_client_impl::sync() {
|
||||
return sync_impl(-1);
|
||||
}
|
||||
|
||||
sync_status generic_fd_event_client_impl::sync_impl(int timeout_ms) {
|
||||
auto result = m_event_handler->poll(std::chrono::milliseconds(timeout_ms));
|
||||
m_event_handler->run_actions();
|
||||
|
||||
// The error handler must be called after all event handlers have run.
|
||||
// Removing handlers during error processing is likely to result in
|
||||
// inconsistent state and and segmentations fault.
|
||||
return result ? sync_status::ok : sync_status::timeout;
|
||||
}
|
||||
|
||||
bool generic_fd_event_client_impl::setup_error_event_handler() {
|
||||
return m_event_handler->register_event_handler(&m_error_status_event_fd, [this](auto) {
|
||||
if (on_error()) {
|
||||
error_handler();
|
||||
call_error_handler(m_error);
|
||||
return sync_status::error;
|
||||
} else if (clear_error_pending()) {
|
||||
clear_error_handler(m_error);
|
||||
}
|
||||
return sync_status::ok;
|
||||
});
|
||||
}
|
||||
|
||||
void generic_fd_event_client_impl::setup_io_event_handler(int fd) {
|
||||
using namespace everest::lib::io::event;
|
||||
m_event_handler->register_event_handler(
|
||||
fd,
|
||||
[this, fd](auto events) {
|
||||
auto success = true;
|
||||
if (events.count(poll_events::error)) {
|
||||
auto error = m_get_error();
|
||||
if (error) {
|
||||
set_error_status_and_notify(error);
|
||||
}
|
||||
success = false;
|
||||
}
|
||||
if (events.count(poll_events::hungup)) {
|
||||
success = false;
|
||||
}
|
||||
if (success && events.count(poll_events::read)) {
|
||||
success = rx_handler();
|
||||
}
|
||||
if (success && events.count(poll_events::write)) {
|
||||
success = tx_handler(fd);
|
||||
}
|
||||
},
|
||||
poll_events::read);
|
||||
m_event_handler->register_event_handler(&m_io_event_fd, [this, fd](auto) {
|
||||
m_event_handler->modify_event_handler(fd, poll_events::write, event_modification::add);
|
||||
});
|
||||
}
|
||||
|
||||
void generic_fd_event_client_impl::set_error_handler(cb_error const& handler) {
|
||||
add_action([this, handler]() { m_error = handler; });
|
||||
}
|
||||
|
||||
bool generic_fd_event_client_impl::unregister_source(int fd) {
|
||||
return m_event_handler->remove_event_handler(fd);
|
||||
}
|
||||
|
||||
bool generic_fd_event_client_impl::set_error_status_and_notify(int error_code) {
|
||||
auto result = set_error_status(error_code);
|
||||
m_error_status_event_fd.notify();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool generic_fd_event_client_impl::rx_handler() {
|
||||
auto status = m_receive_one();
|
||||
auto error_code = status == action_status::success ? 0 : m_get_error();
|
||||
auto result = set_error_status_and_notify(error_code);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool generic_fd_event_client_impl::tx_handler(int fd) {
|
||||
// We send one message only, even if more data is queued.
|
||||
// This prevents the kernel buffer from filling up
|
||||
auto status = m_send_one();
|
||||
switch (status) {
|
||||
case action_status::empty: {
|
||||
// if there are no more message we no longer listen to writeable events
|
||||
// otherwise we wait for the socket to become writeable again.
|
||||
m_event_handler->modify_event_handler(fd, event::poll_events::write, event::event_modification::remove);
|
||||
return true;
|
||||
}
|
||||
case action_status::fail: {
|
||||
auto error_code = m_get_error();
|
||||
set_error_status_and_notify(error_code);
|
||||
return false;
|
||||
}
|
||||
case action_status::success: {
|
||||
set_error_status_and_notify(0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void generic_fd_event_client_impl::error_handler() {
|
||||
add_action([this]() { m_reset_client(); });
|
||||
}
|
||||
|
||||
void generic_fd_event_client_impl::prepare_io_event_handler() {
|
||||
m_event_handler->register_event_handler(&m_connected_event_fd, [this](auto) {
|
||||
auto client_status = m_client_status.handle();
|
||||
if (client_status->ok) {
|
||||
auto error_code = m_get_error();
|
||||
set_error_status_and_notify(error_code);
|
||||
setup_io_event_handler(client_status->fd);
|
||||
if (m_on_ready_action) {
|
||||
m_event_handler->add_action(m_on_ready_action);
|
||||
}
|
||||
} else {
|
||||
auto error_code = m_get_error();
|
||||
set_error_status_and_notify(error_code);
|
||||
}
|
||||
|
||||
return sync_status::ok;
|
||||
});
|
||||
}
|
||||
|
||||
void generic_fd_event_client_impl::on_client_ready(bool ok, int fd) {
|
||||
auto client_status = m_client_status.handle();
|
||||
client_status->ok = ok;
|
||||
client_status->fd = fd;
|
||||
m_connected_event_fd.notify();
|
||||
}
|
||||
|
||||
void generic_fd_event_client_impl::add_action(fd_event_handler::task&& item) {
|
||||
m_event_handler->add_action(std::forward<fd_event_handler::task>(item));
|
||||
}
|
||||
|
||||
void generic_fd_event_client_impl::set_on_ready_action(ready_action&& item) {
|
||||
m_on_ready_action = std::move(item);
|
||||
auto client_status = m_client_status.handle();
|
||||
if (client_status->ok) {
|
||||
m_event_handler->add_action(m_on_ready_action);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::event
|
||||
379
tools/EVerest-main/lib/everest/io/src/event/fd_event_handler.cpp
Normal file
379
tools/EVerest-main/lib/everest/io/src/event/fd_event_handler.cpp
Normal file
@@ -0,0 +1,379 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "everest/io/event/fd_event_sync_interface.hpp"
|
||||
#include <everest/io/event/event_fd.hpp>
|
||||
#include <everest/io/event/fd_event_client.hpp>
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <everest/io/event/timer_fd.hpp>
|
||||
#include <everest/io/event/unique_fd.hpp>
|
||||
|
||||
#include <cstdio>
|
||||
#include <fcntl.h>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include <poll.h>
|
||||
#include <sys/epoll.h>
|
||||
|
||||
namespace everest::lib::io::event {
|
||||
|
||||
namespace {
|
||||
uint32_t poll_event_to_bitmask(poll_events e) {
|
||||
switch (e) {
|
||||
case poll_events::read:
|
||||
return EPOLLIN;
|
||||
case poll_events::priority:
|
||||
return EPOLLPRI;
|
||||
case poll_events::write:
|
||||
return EPOLLOUT;
|
||||
case poll_events::error:
|
||||
return EPOLLERR;
|
||||
case poll_events::hungup:
|
||||
return EPOLLHUP;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::set<poll_events> bitmask_to_poll_events(uint32_t bitmask) {
|
||||
std::set<poll_events> result;
|
||||
if (bitmask & EPOLLIN) {
|
||||
result.insert(poll_events::read);
|
||||
}
|
||||
if (bitmask & EPOLLPRI) {
|
||||
result.insert(poll_events::priority);
|
||||
}
|
||||
if (bitmask & EPOLLOUT) {
|
||||
result.insert(poll_events::write);
|
||||
}
|
||||
if (bitmask & EPOLLERR) {
|
||||
result.insert(poll_events::error);
|
||||
}
|
||||
if (bitmask & EPOLLHUP) {
|
||||
result.insert(poll_events::hungup);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t sum_events(std::set<poll_events> const& events) {
|
||||
uint32_t result = 0;
|
||||
for (auto e : events) {
|
||||
result = result | poll_event_to_bitmask(e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::set<poll_events> operator|(poll_events lhs, poll_events rhs) {
|
||||
return {lhs, rhs};
|
||||
}
|
||||
|
||||
std::set<poll_events>& operator|(std::set<poll_events>& lhs, poll_events rhs) {
|
||||
lhs.insert(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
bool operator&(std::set<poll_events> const& lhs, poll_events rhs) {
|
||||
return lhs.count(rhs) == 1;
|
||||
}
|
||||
|
||||
class EventHandlerMap {
|
||||
public:
|
||||
EventHandlerMap() : m_epoll_fd(epoll_create1(0)) {
|
||||
if (not m_epoll_fd.is_fd()) {
|
||||
::perror("epoll_create");
|
||||
}
|
||||
}
|
||||
|
||||
bool add(int fd, fd_event_handler::event_handler_type handler, fd_event_handler::event_list const& events) {
|
||||
epoll_event event;
|
||||
event.events = sum_events(events);
|
||||
event.data.fd = fd;
|
||||
auto result = epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, fd, &event) == 0;
|
||||
if (result) {
|
||||
m_event_map[fd] = {std::move(handler), event};
|
||||
m_pollfds.resize(m_pollfds.size() + 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool remove(int fd) {
|
||||
auto epoll_result = epoll_ctl(m_epoll_fd, EPOLL_CTL_DEL, fd, nullptr);
|
||||
auto handler_result = m_event_map.count(fd);
|
||||
if (handler_result) {
|
||||
m_event_map.erase(fd);
|
||||
m_pollfds.resize(m_pollfds.size() - 1);
|
||||
}
|
||||
return epoll_result or handler_result;
|
||||
}
|
||||
bool modify_remove(int fd, fd_event_handler::event_list const& events) {
|
||||
auto action = [](uint32_t current, fd_event_handler::event_list const& change) {
|
||||
auto raw_change = sum_events(change);
|
||||
auto result = current & (~raw_change);
|
||||
return result;
|
||||
};
|
||||
return modify(fd, events, action);
|
||||
}
|
||||
|
||||
bool modify_add(int fd, fd_event_handler::event_list const& events) {
|
||||
auto action = [](uint32_t current, fd_event_handler::event_list const& change) {
|
||||
auto raw_change = sum_events(change);
|
||||
auto result = current | raw_change;
|
||||
return result;
|
||||
};
|
||||
return modify(fd, events, action);
|
||||
}
|
||||
|
||||
bool modify_replace(int fd, fd_event_handler::event_list const& events) {
|
||||
auto action = [](uint32_t, fd_event_handler::event_list const& change) {
|
||||
auto raw_change = sum_events(change);
|
||||
auto result = raw_change;
|
||||
return result;
|
||||
};
|
||||
return modify(fd, events, action);
|
||||
}
|
||||
|
||||
const auto& get(int fd) const {
|
||||
return std::get<fd_event_handler::event_handler_type>(m_event_map.at(fd));
|
||||
}
|
||||
|
||||
bool exists(int fd) const {
|
||||
return m_event_map.count(fd);
|
||||
}
|
||||
|
||||
auto& get_pollfds() {
|
||||
return m_pollfds;
|
||||
}
|
||||
|
||||
int get_epoll_fd() {
|
||||
return static_cast<int>(m_epoll_fd);
|
||||
}
|
||||
|
||||
private:
|
||||
using event_state = std::tuple<fd_event_handler::event_handler_type, epoll_event>;
|
||||
bool modify(int fd, fd_event_handler::event_list const& events,
|
||||
std::function<uint32_t(uint32_t, fd_event_handler::event_list const&)> const& event_action) {
|
||||
auto result = false;
|
||||
if (m_event_map.count(fd)) {
|
||||
auto& event = std::get<epoll_event>(m_event_map.at(fd));
|
||||
auto backup = event.events;
|
||||
event.events = event_action(backup, events);
|
||||
result = epoll_ctl(m_epoll_fd, EPOLL_CTL_MOD, fd, &event) == 0;
|
||||
if (not result) {
|
||||
event.events = backup;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<epoll_event> m_pollfds;
|
||||
std::map<int, event_state> m_event_map;
|
||||
unique_fd m_epoll_fd;
|
||||
};
|
||||
|
||||
fd_event_handler::~fd_event_handler() = default;
|
||||
|
||||
fd_event_handler::fd_event_handler() {
|
||||
m_handlers = std::make_unique<EventHandlerMap>();
|
||||
register_event_handler(&m_action_event, [](auto&&) {});
|
||||
}
|
||||
|
||||
bool fd_event_handler::register_event_handler(int fd, event_handler_type const& handler, event_list const& events) {
|
||||
if (fd == -1 or not handler or m_handlers->exists(fd)) {
|
||||
return false;
|
||||
}
|
||||
m_handlers->add(fd, handler, events);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool fd_event_handler::register_event_handler(int fd, event_handler_type const& handler, poll_events event) {
|
||||
return register_event_handler(fd, handler, event_list{event});
|
||||
}
|
||||
|
||||
bool fd_event_handler::register_event_handler(event_fd* fd, event_handler_type const& handler) {
|
||||
if (not fd) {
|
||||
return false;
|
||||
}
|
||||
auto raw = fd->get_raw_fd();
|
||||
return register_event_handler(
|
||||
raw,
|
||||
[handler, fd](event_list const& e) {
|
||||
fd->read();
|
||||
handler(e);
|
||||
},
|
||||
poll_events::read);
|
||||
}
|
||||
|
||||
bool fd_event_handler::register_event_handler(event_fd* fd, event_handler_simple_type const& handler) {
|
||||
return register_event_handler(fd, [handler](event_list const&) { handler(); });
|
||||
}
|
||||
|
||||
bool fd_event_handler::register_event_handler(timer_fd* fd, event_handler_type const& handler) {
|
||||
if (not fd) {
|
||||
return false;
|
||||
}
|
||||
auto raw = fd->get_raw_fd();
|
||||
return register_event_handler(
|
||||
raw,
|
||||
[handler, fd](event_list const& e) {
|
||||
fd->read();
|
||||
handler(e);
|
||||
},
|
||||
poll_events::read);
|
||||
}
|
||||
|
||||
bool fd_event_handler::register_event_handler(timer_fd* fd, event_handler_simple_type const& handler) {
|
||||
return register_event_handler(fd, [handler](event_list const&) { handler(); });
|
||||
}
|
||||
|
||||
bool fd_event_handler::register_event_handler(fd_event_sync_interface* obj) {
|
||||
if (not obj) {
|
||||
return false;
|
||||
}
|
||||
auto raw = obj->get_poll_fd();
|
||||
return register_event_handler(
|
||||
raw, [obj](event_list const&) { obj->sync(); }, poll_events::read);
|
||||
}
|
||||
|
||||
bool fd_event_handler::register_event_handler(fd_event_register_interface* obj) {
|
||||
if (not obj) {
|
||||
return false;
|
||||
}
|
||||
return obj->register_events(*this);
|
||||
}
|
||||
|
||||
bool fd_event_handler::register_event_handler(fd_event_handler* obj) {
|
||||
if (not obj or obj == this) {
|
||||
return false;
|
||||
}
|
||||
auto raw = obj->get_poll_fd();
|
||||
return register_event_handler(
|
||||
raw,
|
||||
[obj](event_list const&) {
|
||||
obj->poll();
|
||||
obj->run_actions();
|
||||
},
|
||||
poll_events::read);
|
||||
}
|
||||
|
||||
bool fd_event_handler::unregister_event_handler(fd_event_register_interface* obj) {
|
||||
if (not obj) {
|
||||
return false;
|
||||
}
|
||||
return obj->unregister_events(*this);
|
||||
}
|
||||
|
||||
bool fd_event_handler::unregister_event_handler(fd_event_sync_interface* obj) {
|
||||
if (not obj) {
|
||||
return false;
|
||||
}
|
||||
return remove_event_handler(obj->get_poll_fd());
|
||||
}
|
||||
|
||||
bool fd_event_handler::unregister_event_handler(timer_fd* obj) {
|
||||
if (not obj) {
|
||||
return false;
|
||||
}
|
||||
return remove_event_handler(obj->get_raw_fd());
|
||||
}
|
||||
|
||||
bool fd_event_handler::unregister_event_handler(event_fd* obj) {
|
||||
if (not obj) {
|
||||
return false;
|
||||
}
|
||||
return remove_event_handler(obj->get_raw_fd());
|
||||
}
|
||||
|
||||
bool fd_event_handler::unregister_event_handler(int fd) {
|
||||
if (fd == -1) {
|
||||
return false;
|
||||
}
|
||||
return remove_event_handler(fd);
|
||||
}
|
||||
|
||||
bool fd_event_handler::modify_event_handler(int fd, event_list const& events, event_modification change) {
|
||||
if (fd == -1) {
|
||||
return false;
|
||||
}
|
||||
switch (change) {
|
||||
case event_modification::add:
|
||||
return m_handlers->modify_add(fd, events);
|
||||
case event_modification::remove:
|
||||
return m_handlers->modify_remove(fd, events);
|
||||
case event_modification::replace:
|
||||
return m_handlers->modify_replace(fd, events);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool fd_event_handler::modify_event_handler(int fd, poll_events event, event_modification change) {
|
||||
return modify_event_handler(fd, event_list{event}, change);
|
||||
}
|
||||
|
||||
bool fd_event_handler::remove_event_handler(int fd) {
|
||||
if (fd == -1) {
|
||||
return false;
|
||||
}
|
||||
return m_handlers->remove(fd);
|
||||
}
|
||||
|
||||
void fd_event_handler::poll() {
|
||||
poll_impl(-1);
|
||||
}
|
||||
|
||||
bool fd_event_handler::poll_impl(int timeout_ms) {
|
||||
auto& pollfds = m_handlers->get_pollfds();
|
||||
auto status = ::epoll_wait(m_handlers->get_epoll_fd(), pollfds.data(), pollfds.size(), timeout_ms);
|
||||
|
||||
if (status > 0) {
|
||||
for (int i = 0; i < status; ++i) {
|
||||
auto& item = pollfds[i];
|
||||
m_handlers->get(item.data.fd)(bitmask_to_poll_events(item.events));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int fd_event_handler::get_poll_fd() {
|
||||
return m_handlers->get_epoll_fd();
|
||||
}
|
||||
|
||||
void fd_event_handler::add_action(task&& item) {
|
||||
task_pool.push(std::forward<task>(item));
|
||||
m_action_event.notify();
|
||||
}
|
||||
|
||||
void fd_event_handler::add_action(task const& item) {
|
||||
task_pool.push(std::move(item));
|
||||
m_action_event.notify();
|
||||
}
|
||||
|
||||
void fd_event_handler::run_actions() {
|
||||
while (true) {
|
||||
auto item = task_pool.try_pop();
|
||||
if (item.has_value()) {
|
||||
try {
|
||||
item.value()();
|
||||
} catch (...) {
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fd_event_handler::run_once() {
|
||||
poll();
|
||||
run_actions();
|
||||
}
|
||||
|
||||
void fd_event_handler::run(std::atomic_bool& online) {
|
||||
while (online.load()) {
|
||||
poll();
|
||||
run_actions();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::event
|
||||
75
tools/EVerest-main/lib/everest/io/src/event/timer_fd.cpp
Normal file
75
tools/EVerest-main/lib/everest/io/src/event/timer_fd.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <ctime>
|
||||
#include <everest/io/event/timer_fd.hpp>
|
||||
#include <stdexcept>
|
||||
#include <sys/timerfd.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace everest::lib::io::event {
|
||||
|
||||
timer_fd::timer_fd() : m_fd(::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK)) {
|
||||
if (m_fd == -1) {
|
||||
throw std::runtime_error("failed to create an timerfd");
|
||||
}
|
||||
}
|
||||
|
||||
timer_fd::operator int() const {
|
||||
return get_raw_fd();
|
||||
}
|
||||
|
||||
int timer_fd::get_raw_fd() const {
|
||||
return m_fd;
|
||||
}
|
||||
|
||||
bool timer_fd::valid() const {
|
||||
return m_fd != unique_fd::NO_DESCRIPTOR_SENTINEL;
|
||||
}
|
||||
|
||||
int timer_fd::read() {
|
||||
uint64_t buffer;
|
||||
return ::read(m_fd, &buffer, sizeof(buffer));
|
||||
}
|
||||
|
||||
bool timer_fd::reset() {
|
||||
return set_timeout_ns(m_to_ns);
|
||||
}
|
||||
|
||||
void timer_fd::set_single_shot(bool on) {
|
||||
m_single_shot = on;
|
||||
}
|
||||
|
||||
bool timer_fd::disarm() {
|
||||
struct itimerspec timer {};
|
||||
return ::timerfd_settime(m_fd, 0, &timer, nullptr) == 0;
|
||||
}
|
||||
|
||||
bool timer_fd::set_timeout_ms(long long to) {
|
||||
return set_timeout_ns(1000 * 1000 * to);
|
||||
}
|
||||
|
||||
bool timer_fd::set_timeout_us(long long to) {
|
||||
return set_timeout_ns(1000 * to);
|
||||
}
|
||||
|
||||
bool timer_fd::set_timeout_ns(long long to) {
|
||||
m_to_ns = to;
|
||||
struct itimerspec timer {};
|
||||
auto const sec = to / 1000000000LL;
|
||||
auto const nano = to % 1000000000LL;
|
||||
|
||||
timer.it_value.tv_sec = sec;
|
||||
timer.it_value.tv_nsec = nano;
|
||||
if (m_single_shot) {
|
||||
timer.it_interval.tv_sec = 0;
|
||||
timer.it_interval.tv_nsec = 0;
|
||||
} else {
|
||||
timer.it_interval.tv_sec = sec;
|
||||
timer.it_interval.tv_nsec = nano;
|
||||
}
|
||||
|
||||
return ::timerfd_settime(m_fd, 0, &timer, nullptr) == 0;
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::event
|
||||
53
tools/EVerest-main/lib/everest/io/src/event/unique_fd.cpp
Normal file
53
tools/EVerest-main/lib/everest/io/src/event/unique_fd.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/io/event/unique_fd.hpp>
|
||||
#include <unistd.h>
|
||||
#include <utility>
|
||||
|
||||
namespace everest::lib::io::event {
|
||||
|
||||
namespace {
|
||||
void close_descriptor_if_valid(int fd) {
|
||||
if (fd != unique_fd::NO_DESCRIPTOR_SENTINEL) {
|
||||
// NOTE (aw): according to the close(2) man page, close might return an error but it should not be retried
|
||||
::close(fd);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
unique_fd::unique_fd(int fd) : m_fd(fd){};
|
||||
|
||||
unique_fd::operator int() const {
|
||||
return m_fd;
|
||||
}
|
||||
|
||||
bool unique_fd::is_fd() const {
|
||||
return m_fd != NO_DESCRIPTOR_SENTINEL;
|
||||
}
|
||||
|
||||
int unique_fd::release() {
|
||||
return std::exchange(m_fd, NO_DESCRIPTOR_SENTINEL);
|
||||
}
|
||||
|
||||
void unique_fd::close() {
|
||||
close_descriptor_if_valid(m_fd);
|
||||
std::exchange(m_fd, NO_DESCRIPTOR_SENTINEL);
|
||||
}
|
||||
|
||||
unique_fd::unique_fd(unique_fd&& other) : m_fd(std::exchange(other.m_fd, NO_DESCRIPTOR_SENTINEL)) {
|
||||
}
|
||||
|
||||
unique_fd& unique_fd::operator=(unique_fd&& other) {
|
||||
if (this != &other) {
|
||||
close_descriptor_if_valid(m_fd);
|
||||
m_fd = std::exchange(other.m_fd, NO_DESCRIPTOR_SENTINEL);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
unique_fd::~unique_fd() {
|
||||
close_descriptor_if_valid(m_fd);
|
||||
}
|
||||
} // namespace everest::lib::io::event
|
||||
377
tools/EVerest-main/lib/everest/io/src/mdns/mdns.cpp
Normal file
377
tools/EVerest-main/lib/everest/io/src/mdns/mdns.cpp
Normal file
@@ -0,0 +1,377 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "everest/io/mdns/mdns.hpp"
|
||||
#include <arpa/inet.h>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
namespace everest::lib::io::mdns {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string parse_name(const std::uint8_t* buffer, int size, int* offset) {
|
||||
std::string name = "";
|
||||
int curr = *offset;
|
||||
bool moved = false;
|
||||
int next_offset = -1;
|
||||
|
||||
while (curr < size && buffer[curr] != 0) {
|
||||
if ((buffer[curr] & 0xC0) == 0xC0) {
|
||||
int pointer_offset = ((buffer[curr] & 0x3F) << 8) | buffer[curr + 1];
|
||||
if (!moved) {
|
||||
next_offset = curr + 2;
|
||||
}
|
||||
curr = pointer_offset;
|
||||
moved = true;
|
||||
} else {
|
||||
int len = buffer[curr];
|
||||
curr++;
|
||||
for (int i = 0; i < len && curr < size; ++i) {
|
||||
name += static_cast<char>(buffer[curr]);
|
||||
curr++;
|
||||
}
|
||||
if (curr < size && buffer[curr] != 0) {
|
||||
name += ".";
|
||||
}
|
||||
}
|
||||
}
|
||||
*offset = moved ? next_offset : curr + 1;
|
||||
return name;
|
||||
}
|
||||
|
||||
void parse_mdns_A(const std::uint8_t* buffer, mDNS_discovery& mdns) {
|
||||
auto size_of_ip_string = 16;
|
||||
mdns.ip.resize(size_of_ip_string);
|
||||
std::snprintf(mdns.ip.data(), mdns.ip.size(), "%d.%d.%d.%d", buffer[0], buffer[1], buffer[2], buffer[3]);
|
||||
}
|
||||
|
||||
void parse_mdns_SRV(const std::uint8_t* base, int record_data_offset, mDNS_discovery& mdns, int size) {
|
||||
mdns.port = (base[record_data_offset + 4] << 8) | base[record_data_offset + 5];
|
||||
int name_offset = record_data_offset + 6;
|
||||
mdns.hostname = parse_name(base, size, &name_offset);
|
||||
}
|
||||
|
||||
void parse_mdns_TXT(const std::uint8_t* buffer, mDNS_discovery& mdns, int rdlen) {
|
||||
int txt_ptr = 0;
|
||||
int end_of_record = rdlen;
|
||||
|
||||
while (txt_ptr < end_of_record) {
|
||||
std::uint8_t txt_len = buffer[txt_ptr];
|
||||
txt_ptr++;
|
||||
if (txt_ptr + txt_len <= end_of_record) {
|
||||
const char* entry_cstr = reinterpret_cast<const char*>(&buffer[txt_ptr]);
|
||||
std::string entry(entry_cstr, txt_len);
|
||||
|
||||
std::size_t sep = entry.find('=');
|
||||
if (sep != std::string::npos) {
|
||||
std::string key = entry.substr(0, sep);
|
||||
std::string val = entry.substr(sep + 1);
|
||||
mdns.add_string(key, val);
|
||||
}
|
||||
|
||||
txt_ptr += txt_len;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void parse_mdns_PTR(const std::uint8_t* base, int record_data_offset, mDNS_discovery& mdns, int size) {
|
||||
std::string service_instance = parse_name(base, size, &record_data_offset);
|
||||
mdns.service_instance = service_instance;
|
||||
}
|
||||
|
||||
void encode_dns_name(std::vector<std::uint8_t>& packet, std::string const& name) {
|
||||
std::size_t start = 0;
|
||||
std::size_t end;
|
||||
while ((end = name.find('.', start)) != std::string::npos) {
|
||||
auto label_len = end - start;
|
||||
if (label_len > 63) {
|
||||
label_len = 63;
|
||||
}
|
||||
packet.push_back(static_cast<std::uint8_t>(label_len));
|
||||
for (std::size_t i = start; i < start + label_len; ++i) {
|
||||
packet.push_back(static_cast<std::uint8_t>(name[i]));
|
||||
}
|
||||
start = end + 1;
|
||||
}
|
||||
if (start < name.length()) {
|
||||
auto label_len = name.length() - start;
|
||||
if (label_len > 63) {
|
||||
label_len = 63;
|
||||
}
|
||||
packet.push_back(static_cast<std::uint8_t>(label_len));
|
||||
for (std::size_t i = start; i < start + label_len; ++i) {
|
||||
packet.push_back(static_cast<std::uint8_t>(name[i]));
|
||||
}
|
||||
}
|
||||
packet.push_back(0);
|
||||
}
|
||||
|
||||
void append_uint16(std::vector<std::uint8_t>& packet, std::uint16_t value) {
|
||||
packet.push_back(static_cast<std::uint8_t>((value >> 8) & 0xFF));
|
||||
packet.push_back(static_cast<std::uint8_t>(value & 0xFF));
|
||||
}
|
||||
|
||||
void append_uint32(std::vector<std::uint8_t>& packet, std::uint32_t value) {
|
||||
packet.push_back(static_cast<std::uint8_t>((value >> 24) & 0xFF));
|
||||
packet.push_back(static_cast<std::uint8_t>((value >> 16) & 0xFF));
|
||||
packet.push_back(static_cast<std::uint8_t>((value >> 8) & 0xFF));
|
||||
packet.push_back(static_cast<std::uint8_t>(value & 0xFF));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
[[maybe_unused]] std::optional<mDNS_discovery> parse_mdns_packet(std::vector<std::uint8_t> const& packet) {
|
||||
int size = packet.size();
|
||||
const auto* buf = packet.data();
|
||||
auto const mdns_packet_min_size = 12;
|
||||
if (size < mdns_packet_min_size) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if ((buf[2] & 0x80) == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
mDNS_discovery result;
|
||||
std::size_t questions = (buf[4] << 8) | buf[5];
|
||||
std::size_t answers = (buf[6] << 8) | buf[7];
|
||||
std::size_t authority = (buf[8] << 8) | buf[9];
|
||||
std::size_t additional = (buf[10] << 8) | buf[11];
|
||||
int curr = mdns_packet_min_size;
|
||||
|
||||
for (size_t i = 0; i < questions && curr < size; ++i) {
|
||||
parse_name(buf, size, &curr);
|
||||
curr += 4;
|
||||
}
|
||||
|
||||
auto const mdns_record_header_size = 10;
|
||||
int total_records = static_cast<int>(answers + authority + additional);
|
||||
for (int i = 0; i < total_records && curr < size; ++i) {
|
||||
std::string name = parse_name(buf, size, &curr);
|
||||
if (curr + mdns_record_header_size > size) {
|
||||
break;
|
||||
}
|
||||
std::uint16_t type = (buf[curr] << 8) | buf[curr + 1];
|
||||
std::uint16_t rdlen = (buf[curr + 8] << 8) | buf[curr + 9];
|
||||
curr += mdns_record_header_size;
|
||||
|
||||
if (type == 0x01 && rdlen == 4) {
|
||||
parse_mdns_A(buf + curr, result);
|
||||
} else if (type == 0x21) {
|
||||
parse_mdns_SRV(buf, curr, result, size);
|
||||
} else if (type == 0x10) {
|
||||
parse_mdns_TXT(buf + curr, result, rdlen);
|
||||
} else if (type == 0x0C) {
|
||||
parse_mdns_PTR(buf, curr, result, size);
|
||||
}
|
||||
curr += rdlen;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[maybe_unused]] std::vector<std::uint8_t> create_mdns_query(std::string const& name) {
|
||||
std::vector<uint8_t> encoded;
|
||||
std::size_t start = 0, end;
|
||||
while ((end = name.find('.', start)) != std::string::npos) {
|
||||
encoded.push_back(static_cast<uint8_t>(end - start));
|
||||
for (std::size_t i = start; i < end; ++i) {
|
||||
encoded.push_back(name[i]);
|
||||
}
|
||||
start = end + 1;
|
||||
}
|
||||
encoded.push_back(static_cast<uint8_t>(name.length() - start));
|
||||
for (std::size_t i = start; i < name.length(); ++i) {
|
||||
encoded.push_back(name[i]);
|
||||
}
|
||||
encoded.push_back(0);
|
||||
|
||||
std::vector<std::uint8_t> packet = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
#if defined(__GNUC__) && (__GNUC__ < 12)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wstringop-overread"
|
||||
#endif
|
||||
packet.insert(packet.end(), encoded.begin(), encoded.end());
|
||||
#if defined(__GNUC__) && (__GNUC__ < 12)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
packet.push_back(0x00);
|
||||
packet.push_back(0x0c);
|
||||
packet.push_back(0x00);
|
||||
packet.push_back(0x01);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
bool apply_value(std::string const& src, std::string& tar) {
|
||||
if (src == tar) {
|
||||
return false;
|
||||
}
|
||||
if (src.empty()) {
|
||||
return false;
|
||||
}
|
||||
tar = src;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool apply_value(std::uint16_t const src, std::uint16_t& tar) {
|
||||
if (src == 0 or src == tar) {
|
||||
return false;
|
||||
}
|
||||
tar = src;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mDNS_registry::update(const mDNS_discovery& update) {
|
||||
if (update.service_instance.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool something_new = data.count(update.service_instance) == 0;
|
||||
|
||||
auto& item = data[update.service_instance];
|
||||
item.service_instance = update.service_instance;
|
||||
|
||||
something_new = apply_value(update.port, item.port) || something_new;
|
||||
something_new = apply_value(update.hostname, item.hostname) || something_new;
|
||||
something_new = apply_value(update.ip, item.ip) || something_new;
|
||||
|
||||
for (auto const& [key, val] : update.txt) {
|
||||
something_new = apply_value(val, item.txt[key]) || something_new;
|
||||
}
|
||||
return something_new;
|
||||
}
|
||||
|
||||
void mDNS_registry::remove(const std::string& instance) {
|
||||
data.erase(instance);
|
||||
}
|
||||
|
||||
void mDNS_registry::clear() {
|
||||
data.clear();
|
||||
}
|
||||
|
||||
mDNS_registry::registry const& mDNS_registry::get() {
|
||||
return data;
|
||||
}
|
||||
|
||||
[[maybe_unused]] std::vector<std::uint8_t> create_mdns_response(mDNS_discovery const& service,
|
||||
std::string const& service_type) {
|
||||
std::string service_fqdn = service_type + ".local";
|
||||
std::string instance_fqdn = service.service_instance;
|
||||
if (instance_fqdn.empty()) {
|
||||
instance_fqdn = service.hostname + "." + service_fqdn;
|
||||
}
|
||||
std::string host_fqdn = service.hostname + ".local";
|
||||
|
||||
std::vector<std::uint8_t> packet;
|
||||
append_uint16(packet, 0x0000); // Transaction ID
|
||||
append_uint16(packet, 0x8400); // Flags: response, authoritative
|
||||
append_uint16(packet, 0x0000); // Questions
|
||||
append_uint16(packet, 0x0004); // Answer RRs (PTR + SRV + TXT + A)
|
||||
append_uint16(packet, 0x0000); // Authority RRs
|
||||
append_uint16(packet, 0x0000); // Additional RRs
|
||||
|
||||
auto const default_ttl = static_cast<std::uint32_t>(120);
|
||||
|
||||
// PTR record: service_type.local -> instance_fqdn
|
||||
encode_dns_name(packet, service_fqdn);
|
||||
append_uint16(packet, 0x000C);
|
||||
append_uint16(packet, 0x0001);
|
||||
append_uint32(packet, default_ttl);
|
||||
std::vector<std::uint8_t> ptr_rdata;
|
||||
encode_dns_name(ptr_rdata, instance_fqdn);
|
||||
append_uint16(packet, static_cast<std::uint16_t>(ptr_rdata.size()));
|
||||
packet.insert(packet.end(), ptr_rdata.begin(), ptr_rdata.end());
|
||||
|
||||
// SRV record: instance_fqdn -> host:port
|
||||
encode_dns_name(packet, instance_fqdn);
|
||||
append_uint16(packet, 0x0021);
|
||||
append_uint16(packet, 0x8001);
|
||||
append_uint32(packet, default_ttl);
|
||||
std::vector<std::uint8_t> srv_rdata;
|
||||
append_uint16(srv_rdata, 0x0000); // Priority
|
||||
append_uint16(srv_rdata, 0x0000); // Weight
|
||||
append_uint16(srv_rdata, service.port);
|
||||
encode_dns_name(srv_rdata, host_fqdn);
|
||||
append_uint16(packet, static_cast<std::uint16_t>(srv_rdata.size()));
|
||||
packet.insert(packet.end(), srv_rdata.begin(), srv_rdata.end());
|
||||
|
||||
// TXT record: instance_fqdn -> key=value pairs
|
||||
encode_dns_name(packet, instance_fqdn);
|
||||
append_uint16(packet, 0x0010);
|
||||
append_uint16(packet, 0x8001);
|
||||
append_uint32(packet, default_ttl);
|
||||
std::vector<std::uint8_t> txt_rdata;
|
||||
for (auto const& [key, val] : service.txt) {
|
||||
std::string entry = key + "=" + val;
|
||||
txt_rdata.push_back(static_cast<std::uint8_t>(entry.size()));
|
||||
for (char ch : entry) {
|
||||
txt_rdata.push_back(static_cast<std::uint8_t>(ch));
|
||||
}
|
||||
}
|
||||
if (txt_rdata.empty()) {
|
||||
txt_rdata.push_back(0);
|
||||
}
|
||||
append_uint16(packet, static_cast<std::uint16_t>(txt_rdata.size()));
|
||||
packet.insert(packet.end(), txt_rdata.begin(), txt_rdata.end());
|
||||
|
||||
// A record: host_fqdn -> IP
|
||||
encode_dns_name(packet, host_fqdn);
|
||||
append_uint16(packet, 0x0001);
|
||||
append_uint16(packet, 0x8001);
|
||||
append_uint32(packet, default_ttl);
|
||||
append_uint16(packet, 0x0004);
|
||||
struct in_addr addr;
|
||||
if (inet_pton(AF_INET, service.ip.c_str(), &addr) == 1) {
|
||||
auto* bytes = reinterpret_cast<std::uint8_t*>(&addr.s_addr);
|
||||
packet.push_back(bytes[0]);
|
||||
packet.push_back(bytes[1]);
|
||||
packet.push_back(bytes[2]);
|
||||
packet.push_back(bytes[3]);
|
||||
} else {
|
||||
packet.push_back(0);
|
||||
packet.push_back(0);
|
||||
packet.push_back(0);
|
||||
packet.push_back(0);
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
[[maybe_unused]] bool is_query_for(std::vector<std::uint8_t> const& packet, std::string const& service_type) {
|
||||
int size = static_cast<int>(packet.size());
|
||||
auto const* buf = packet.data();
|
||||
auto const mdns_packet_min_size = 12;
|
||||
if (size < mdns_packet_min_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((buf[2] & 0x80) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t questions = (buf[4] << 8) | buf[5];
|
||||
if (questions == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int curr = mdns_packet_min_size;
|
||||
std::string service_fqdn = service_type + ".local";
|
||||
|
||||
for (std::size_t i = 0; i < questions && curr < size; ++i) {
|
||||
std::string qname = parse_name(buf, size, &curr);
|
||||
if (curr + 4 > size) {
|
||||
break;
|
||||
}
|
||||
std::uint16_t qtype = (buf[curr] << 8) | buf[curr + 1];
|
||||
curr += 4;
|
||||
|
||||
if ((qtype == 0x0C || qtype == 0xFF) && qname == service_fqdn) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::mdns
|
||||
66
tools/EVerest-main/lib/everest/io/src/mdns/mdns_socket.cpp
Normal file
66
tools/EVerest-main/lib/everest/io/src/mdns/mdns_socket.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "everest/io/udp/udp_payload.hpp"
|
||||
#include "everest/io/udp/udp_socket.hpp"
|
||||
#include <arpa/inet.h>
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <everest/io/event/unique_fd.hpp>
|
||||
#include <everest/io/mdns/mdns.hpp>
|
||||
#include <everest/io/mdns/mdns_socket.hpp>
|
||||
#include <everest/io/socket/socket.hpp>
|
||||
#include <iostream>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <optional>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace everest::lib::io::mdns {
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
bool mdns_socket::open(std::string const& interface) {
|
||||
auto const mdns_port = 5353;
|
||||
auto const* const mdns_ip = "224.0.0.251";
|
||||
|
||||
auto socket = socket::open_mdns_socket(interface);
|
||||
m_owned_udp_fd = std::move(socket);
|
||||
|
||||
target.addr = inet_addr(mdns_ip);
|
||||
target.port = htons(mdns_port);
|
||||
target.family = AF_INET;
|
||||
|
||||
return socket::get_pending_error(m_owned_udp_fd) == 0;
|
||||
}
|
||||
|
||||
bool mdns_socket::tx(PayloadT const& payload) {
|
||||
return tx_impl(payload.buffer.data(), payload.size(), target);
|
||||
}
|
||||
|
||||
bool mdns_socket::rx(PayloadT& payload) {
|
||||
ssize_t msg_size = 0;
|
||||
auto result = rx_impl(rx_buffer.data(), rx_buffer.size(), msg_size);
|
||||
if (result) {
|
||||
payload.set_message(rx_buffer.data(), msg_size);
|
||||
}
|
||||
return result.has_value();
|
||||
}
|
||||
|
||||
bool mdns_socket::query(std::string const& what) {
|
||||
PayloadT payload;
|
||||
payload.buffer = everest::lib::io::mdns::create_mdns_query(what);
|
||||
return tx(payload);
|
||||
}
|
||||
|
||||
bool mdns_socket::announce(mDNS_discovery const& service, std::string const& service_type) {
|
||||
PayloadT payload;
|
||||
payload.buffer = create_mdns_response(service, service_type);
|
||||
return tx(payload);
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::mdns
|
||||
521
tools/EVerest-main/lib/everest/io/src/mqtt/mosquitto_cpp.cpp
Normal file
521
tools/EVerest-main/lib/everest/io/src/mqtt/mosquitto_cpp.cpp
Normal file
@@ -0,0 +1,521 @@
|
||||
/*
|
||||
* Licensor: Pionix GmbH, 2025
|
||||
* License: BaseCamp - License Version 1.0
|
||||
*
|
||||
* Licensed under the terms and conditions of the BaseCamp License contained in the "LICENSE" file, also available
|
||||
* under: https://pionix.com/pionix-license-terms
|
||||
* You may not use this file/code except in compliance with said License.
|
||||
*/
|
||||
|
||||
#include <everest/io/mqtt/mosquitto_cpp.hpp>
|
||||
// #include <companion/utilities/String.hpp>
|
||||
// #include <utils/logging.hpp>
|
||||
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <mosquitto.h>
|
||||
#include <stdexcept>
|
||||
/**
|
||||
* \file MQTT v5 client
|
||||
*/
|
||||
|
||||
namespace {
|
||||
using namespace everest::lib::io::mqtt;
|
||||
constexpr int loop_timeout_ms = -1; // use default
|
||||
|
||||
int password_callback(char* buf, int size, [[maybe_unused]] int rwflag, [[maybe_unused]] void* userdata) {
|
||||
if ((buf != nullptr) && (size > 0)) {
|
||||
*buf = '\0';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
constexpr mosquitto_cpp::QoS convert_to_qos(int qos) {
|
||||
switch (qos) {
|
||||
case static_cast<int>(mosquitto_cpp::QoS::at_least_once):
|
||||
case static_cast<int>(mosquitto_cpp::QoS::at_most_once):
|
||||
case static_cast<int>(mosquitto_cpp::QoS::exactly_once):
|
||||
return static_cast<mosquitto_cpp::QoS>(qos);
|
||||
}
|
||||
|
||||
throw std::out_of_range("QoS from int: " + std::to_string(qos));
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
#define FOR_ALL_ERROR_CODES(apply) \
|
||||
apply(SUCCESS, Success) \
|
||||
apply(INVAL, InvalidArgument) \
|
||||
apply(NOMEM, NoMemory) \
|
||||
apply(ERRNO, Errno) \
|
||||
apply(NO_CONN, NoConnection) \
|
||||
apply(CONN_LOST, ConnectionLost) \
|
||||
apply(CONN_REFUSED, ConnectionRefused) \
|
||||
apply(PROTOCOL, Protocol) \
|
||||
apply(PAYLOAD_SIZE, PayloadSize) \
|
||||
apply(MALFORMED_UTF8, MalformedUTF8) \
|
||||
apply(DUPLICATE_PROPERTY, DuplicateProperty) \
|
||||
apply(QOS_NOT_SUPPORTED, QoSNotSupported) \
|
||||
apply(OVERSIZE_PACKET, OversizePacket) \
|
||||
apply(NOT_SUPPORTED, NotSupported) \
|
||||
apply(EAI, HostnameLookup) \
|
||||
apply(TLS, Tls) \
|
||||
apply(TLS_HANDSHAKE, TlsHandshake)
|
||||
|
||||
// clang-format off
|
||||
#define VALUES(a, b) \
|
||||
case MOSQ_ERR_##a: return ErrorCode::b;
|
||||
// clang-format on
|
||||
|
||||
constexpr ErrorCode convertEC(int rc) {
|
||||
switch (rc) {
|
||||
FOR_ALL_ERROR_CODES(VALUES)
|
||||
default:
|
||||
// LogError << "Unrecognised mosquitto error: " << rc;
|
||||
return ErrorCode::Unknown;
|
||||
}
|
||||
}
|
||||
#undef VALUES
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace everest::lib::io::mqtt {
|
||||
|
||||
PropertiesBase::~PropertiesBase() {
|
||||
if (!is_const) {
|
||||
mosquitto_property_free_all(&props);
|
||||
}
|
||||
}
|
||||
|
||||
void PropertiesBase::free_property(mqtt5__property** ptr) {
|
||||
mosquitto_property_free_all(ptr);
|
||||
}
|
||||
|
||||
const mqtt5__property* PropertiesBase::get_property(property_t prop) const {
|
||||
const mosquitto_property* result{nullptr};
|
||||
for (const auto* p = props; p != nullptr; p = mosquitto_property_next(p)) {
|
||||
if (mosquitto_property_identifier(p) == static_cast<int>(prop)) {
|
||||
result = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string PropertiesBase::get_response_topic() const {
|
||||
std::string result;
|
||||
char* topic{nullptr};
|
||||
if (mosquitto_property_read_string(props, static_cast<int>(property_t::ResponseTopic), &topic, false) != nullptr) {
|
||||
result = topic;
|
||||
}
|
||||
::free(topic);
|
||||
// LogDebug << "response_topic (" << props << "): " << result;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string PropertiesBase::get_correlation_data() const {
|
||||
std::string result;
|
||||
void* data{nullptr};
|
||||
std::uint16_t len{0};
|
||||
if (mosquitto_property_read_binary(props, static_cast<int>(property_t::CorrelationData), &data, &len, false) !=
|
||||
nullptr) {
|
||||
result = std::string{static_cast<char*>(data), len};
|
||||
}
|
||||
::free(data);
|
||||
// LogDebug << "correlation_data (" << props << "): " << result;
|
||||
return result;
|
||||
}
|
||||
|
||||
ErrorCode Properties::set_response_topic(const std::string& topic) {
|
||||
const auto result =
|
||||
convertEC(mosquitto_property_add_string(&props, static_cast<int>(property_t::ResponseTopic), topic.data()));
|
||||
if (result != ErrorCode::Success) {
|
||||
// LogError << "set_response_topic: " << to_string(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ErrorCode Properties::set_correlation_data(const std::string& data) {
|
||||
const auto result = convertEC(
|
||||
mosquitto_property_add_binary(&props, static_cast<int>(property_t::CorrelationData), data.data(), data.size()));
|
||||
if (result != ErrorCode::Success) {
|
||||
// LogError << "set_correlation_data: " << to_string(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
#define VALUES(a, b) \
|
||||
case ErrorCode::b: return {#b};
|
||||
// clang-format on
|
||||
|
||||
std::string_view to_string(ErrorCode ec) {
|
||||
switch (ec) {
|
||||
FOR_ALL_ERROR_CODES(VALUES)
|
||||
default:
|
||||
return {"<unknown>"};
|
||||
}
|
||||
}
|
||||
#undef VALUES
|
||||
|
||||
void mosquitto_cpp::cb_connect([[maybe_unused]] mosquitto* mosq, void* obj, int rc, int flags,
|
||||
const mqtt5__property* props) {
|
||||
if (obj != nullptr) {
|
||||
auto& client = *reinterpret_cast<mosquitto_cpp*>(obj);
|
||||
const PropertiesAccess p(props);
|
||||
const auto r = static_cast<ResponseCode>(rc);
|
||||
client.connect_ccb(r, flags, p);
|
||||
}
|
||||
}
|
||||
|
||||
void mosquitto_cpp::cb_disconnect([[maybe_unused]] mosquitto* mosq, void* obj, int rc, const mqtt5__property* props) {
|
||||
if (obj != nullptr) {
|
||||
auto& client = *reinterpret_cast<mosquitto_cpp*>(obj);
|
||||
const PropertiesAccess p(props);
|
||||
const auto ec = convertEC(rc);
|
||||
client.disconnect_ccb(ec, p);
|
||||
client.callbacks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void mosquitto_cpp::cb_log([[maybe_unused]] mosquitto* mosq, void* obj, int level, const char* str) {
|
||||
if (obj != nullptr) {
|
||||
auto& client = *reinterpret_cast<mosquitto_cpp*>(obj);
|
||||
LogLevel l;
|
||||
switch (level) {
|
||||
case MOSQ_LOG_INFO:
|
||||
l = LogLevel::info;
|
||||
break;
|
||||
case MOSQ_LOG_NOTICE:
|
||||
l = LogLevel::notice;
|
||||
break;
|
||||
case MOSQ_LOG_WARNING:
|
||||
l = LogLevel::warning;
|
||||
break;
|
||||
case MOSQ_LOG_ERR:
|
||||
l = LogLevel::error;
|
||||
break;
|
||||
case MOSQ_LOG_DEBUG:
|
||||
default:
|
||||
l = LogLevel::debug;
|
||||
break;
|
||||
}
|
||||
client.log(l, str);
|
||||
}
|
||||
}
|
||||
|
||||
void mosquitto_cpp::cb_message([[maybe_unused]] mosquitto* mosq, void* obj, const mosquitto_message* msg,
|
||||
const mqtt5__property* props) {
|
||||
if ((obj != nullptr) && (msg != nullptr)) {
|
||||
auto& client = *reinterpret_cast<mosquitto_cpp*>(obj);
|
||||
const PropertiesAccess p(props);
|
||||
const std::string_view topic(msg->topic);
|
||||
const std::string_view payload(static_cast<const char*>(msg->payload), msg->payloadlen);
|
||||
const QoS qos = convert_to_qos(msg->qos);
|
||||
client.message_ccb(topic, payload, qos, p);
|
||||
}
|
||||
}
|
||||
|
||||
void mosquitto_cpp::cb_publish([[maybe_unused]] mosquitto* mosq, void* obj, int mid, int rc,
|
||||
const mqtt5__property* props) {
|
||||
if (obj != nullptr) {
|
||||
auto& client = *reinterpret_cast<mosquitto_cpp*>(obj);
|
||||
const PropertiesAccess p(props);
|
||||
const auto r = static_cast<ResponseCode>(rc);
|
||||
client.publish_ccb(mid, r, p);
|
||||
}
|
||||
}
|
||||
|
||||
void mosquitto_cpp::connect_ccb(ResponseCode rc, int flags, const PropertiesAccess& props) {
|
||||
if (connect_cb) {
|
||||
connect_cb(*this, rc, flags, props);
|
||||
}
|
||||
}
|
||||
|
||||
void mosquitto_cpp::disconnect_ccb(ErrorCode ec, const PropertiesAccess& props) {
|
||||
if (disconnect_cb) {
|
||||
disconnect_cb(*this, ec, props);
|
||||
}
|
||||
}
|
||||
|
||||
void mosquitto_cpp::message_ccb(const std::string_view& topic, const std::string_view& payload, QoS qos,
|
||||
const PropertiesAccess& props) {
|
||||
// LogDebug << "MQTT msg " << topic << ' ' << bc_utils::hex_string(payload);
|
||||
for (const auto& [sub_topic, callback] : callbacks) {
|
||||
bool matches{false};
|
||||
if ((mosquitto_topic_matches_sub2(sub_topic.data(), sub_topic.size(), topic.data(), topic.size(), &matches) ==
|
||||
MOSQ_ERR_SUCCESS) &&
|
||||
matches) {
|
||||
callback(*this, topic, payload, qos, props);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mosquitto_cpp::publish_ccb(int mid, ResponseCode rc, const PropertiesAccess& props) {
|
||||
if (publish_cb) {
|
||||
publish_cb(*this, mid, rc, props);
|
||||
}
|
||||
}
|
||||
|
||||
mosquitto_cpp::mosquitto_cpp() : mosquitto_cpp(nullptr) {
|
||||
}
|
||||
|
||||
mosquitto_cpp::mosquitto_cpp(const char* id) : client(nullptr, nullptr) {
|
||||
mosquitto* ptr = mosquitto_new(id, true, this);
|
||||
std::unique_ptr<mosquitto, decltype(&mosquitto_destroy)> tmp(ptr, &mosquitto_destroy);
|
||||
client.swap(tmp);
|
||||
// use v5
|
||||
mosquitto_int_option(client.get(), MOSQ_OPT_PROTOCOL_VERSION, MQTT_PROTOCOL_V5);
|
||||
|
||||
// TODO(james-ctc): select sensible defaults
|
||||
// mosquitto_int_option(client.get(), MOSQ_OPT_RECEIVE_MAXIMUM, 20);
|
||||
// mosquitto_int_option(client.get(), MOSQ_OPT_SEND_MAXIMUM, 20);
|
||||
|
||||
// Enable TCP no delay
|
||||
// mosquitto_int_option(client.get(), MOSQ_OPT_TCP_NODELAY, 1);
|
||||
|
||||
// multiple threads are likely
|
||||
mosquitto_threaded_set(client.get(), true);
|
||||
mosquitto_connect_v5_callback_set(client.get(), cb_connect);
|
||||
mosquitto_disconnect_v5_callback_set(client.get(), cb_disconnect);
|
||||
mosquitto_log_callback_set(client.get(), cb_log);
|
||||
mosquitto_message_v5_callback_set(client.get(), cb_message);
|
||||
mosquitto_publish_v5_callback_set(client.get(), cb_publish);
|
||||
// mosquitto_subscribe_v5_callback_set(client.get(), cb_subscribe);
|
||||
}
|
||||
|
||||
mosquitto_cpp::~mosquitto_cpp() {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
void mosquitto_cpp::log(LogLevel level, [[maybe_unused]] const char* str) {
|
||||
switch (level) {
|
||||
case LogLevel::info:
|
||||
case LogLevel::notice:
|
||||
// LogInfo << str;
|
||||
break;
|
||||
case LogLevel::warning:
|
||||
// LogWarning << str;
|
||||
break;
|
||||
case LogLevel::error:
|
||||
// LogError << str;
|
||||
break;
|
||||
case LogLevel::debug:
|
||||
// LogDebug << str;
|
||||
break;
|
||||
default:
|
||||
// should not occur
|
||||
// LogCritical << str;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void mosquitto_cpp::set_callback_connect_impl(connect_callback cb) {
|
||||
connect_cb = std::move(cb);
|
||||
}
|
||||
|
||||
void mosquitto_cpp::set_callback_disconnect_impl(disconnect_callback cb) {
|
||||
disconnect_cb = std::move(cb);
|
||||
}
|
||||
|
||||
void mosquitto_cpp::set_callback_publish_impl(publish_callback cb) {
|
||||
publish_cb = std::move(cb);
|
||||
}
|
||||
|
||||
void mosquitto_cpp::set_callback_connect(connect_callback cb) {
|
||||
set_callback_connect_impl(std::move(cb));
|
||||
}
|
||||
|
||||
void mosquitto_cpp::set_callback_disconnect(disconnect_callback cb) {
|
||||
set_callback_disconnect_impl(std::move(cb));
|
||||
}
|
||||
|
||||
void mosquitto_cpp::set_callback_publish(publish_callback cb) {
|
||||
set_callback_publish_impl(std::move(cb));
|
||||
}
|
||||
|
||||
bool mosquitto_cpp::is_connect_callback_set() {
|
||||
return static_cast<bool>(connect_cb);
|
||||
}
|
||||
bool mosquitto_cpp::is_disconnect_callback_set() {
|
||||
return static_cast<bool>(disconnect_cb);
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::tls(const char* ca_file, const char* ca_path, const char* cert_file, const char* key_file) {
|
||||
return convertEC(mosquitto_tls_set(client.get(), ca_file, ca_path, cert_file, key_file, password_callback));
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::tls(const ::std::string& ca_file, const ::std::string& ca_path, const ::std::string& cert_file,
|
||||
const ::std::string& key_file) {
|
||||
const char* ca_file_ptr{nullptr};
|
||||
const char* ca_path_ptr{nullptr};
|
||||
if (!ca_file.empty()) {
|
||||
ca_file_ptr = ca_file.c_str();
|
||||
}
|
||||
if (!ca_path.empty()) {
|
||||
ca_path_ptr = ca_path.c_str();
|
||||
}
|
||||
return tls(ca_file_ptr, ca_path_ptr, cert_file.c_str(), key_file.c_str());
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::connect(const std::string_view& host, std::uint16_t port, std::uint16_t keepalive_seconds) {
|
||||
return connect_impl("", host, port, keepalive_seconds);
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::connect(const std::string_view& bind_address, const std::string_view& host, std::uint16_t port,
|
||||
std::uint16_t keepalive_seconds) {
|
||||
return connect_impl(bind_address, host, port, keepalive_seconds);
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::connect(const std::string_view& unix_domain_socket, std::uint16_t keepalive_seconds) {
|
||||
return connect_impl("", unix_domain_socket, 0, keepalive_seconds);
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::connect_impl(const std::string_view& bind_address, const std::string_view& host,
|
||||
std::uint16_t port, std::uint16_t keepalive_seconds) {
|
||||
const char* bind_to = bind_address.empty() ? nullptr : bind_address.data();
|
||||
return convertEC(mosquitto_connect_bind_async(client.get(), host.data(), port, keepalive_seconds, bind_to));
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::reconnect() {
|
||||
return convertEC(mosquitto_reconnect_async(client.get()));
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::disconnect() {
|
||||
return convertEC(mosquitto_disconnect(client.get()));
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::loop_forever() {
|
||||
return convertEC(mosquitto_loop_forever(client.get(), loop_timeout_ms, 1));
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::loop_read() {
|
||||
return convertEC(mosquitto_loop_read(client.get(), 1));
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::loop_write() {
|
||||
return convertEC(mosquitto_loop_write(client.get(), 1));
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::loop_misc() {
|
||||
return convertEC(mosquitto_loop_misc(client.get()));
|
||||
}
|
||||
|
||||
bool mosquitto_cpp::want_write() {
|
||||
return mosquitto_want_write(client.get());
|
||||
}
|
||||
|
||||
int mosquitto_cpp::socket() const {
|
||||
return mosquitto_socket(client.get());
|
||||
}
|
||||
|
||||
void mosquitto_cpp::set_option_threaded(bool val) {
|
||||
(void)mosquitto_threaded_set(client.get(), val);
|
||||
}
|
||||
|
||||
void mosquitto_cpp::set_option_tcpnodelay(bool val) {
|
||||
(void)mosquitto_int_option(client.get(), MOSQ_OPT_TCP_NODELAY, val);
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::set_will(const std::string_view& topic, const std::string_view& payload, QoS qos, bool retain,
|
||||
PropertiesBase&& props) {
|
||||
return set_will_impl(topic, payload, qos, retain, std::move(props));
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::set_will_impl(const std::string_view& topic, const std::string_view& payload, QoS qos,
|
||||
bool retain, PropertiesBase&& props) {
|
||||
// must be called before connect()
|
||||
|
||||
// On success only, the property list becomes the property of libmosquitto
|
||||
// once this function is called and will be freed by the library.
|
||||
// The property list must be freed by the application on error.
|
||||
|
||||
ErrorCode result;
|
||||
auto* prop_p = props.release();
|
||||
result = convertEC(mosquitto_will_set_v5(client.get(), topic.data(), payload.size(), payload.data(),
|
||||
static_cast<int>(qos), retain, prop_p));
|
||||
if (result != ErrorCode::Success) {
|
||||
PropertiesBase::free_property(&prop_p);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::publish(int* mid, const std::string_view& topic, const std::string_view& payload, QoS qos,
|
||||
bool retain, const PropertiesBase& props) {
|
||||
return publish_impl(mid, topic, payload, qos, retain, props);
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::publish(const std::string_view& topic, const std::string_view& payload, QoS qos, bool retain,
|
||||
const PropertiesBase& props) {
|
||||
return publish(nullptr, topic, payload, qos, retain, props);
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::publish(const std::string_view& topic, const std::string_view& payload) {
|
||||
return publish(nullptr, topic, payload, default_QoS, false, {});
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::publish(message const& data) {
|
||||
return publish(data.topic, data.payload, data.qos, false, {});
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::publish_impl(int* mid, const std::string_view& topic, const std::string_view& payload, QoS qos,
|
||||
bool retain, const PropertiesBase& props) {
|
||||
// LogDebug << "MQTT pub " << topic << ' ' << bc_utils::hex_string(payload);
|
||||
return convertEC(mosquitto_publish_v5(client.get(), mid, topic.data(), payload.size(), payload.data(),
|
||||
static_cast<int>(qos), retain, props));
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::subscribe(const std::string_view& topic, QoS qos, int options, const PropertiesBase& props,
|
||||
subscribe_callback cb) {
|
||||
return subscribe_impl(topic, qos, options, props, std::move(cb));
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::subscribe(const std::string_view& topic, subscribe_callback cb) {
|
||||
return subscribe(topic, default_QoS, 0, {}, std::move(cb));
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::subscribe(std::string_view const& topic, subscribe_message_callback const& cb, QoS qos) {
|
||||
return subscribe(topic, qos, 0, {},
|
||||
[cb = std::move(cb)](auto& cb_client, auto const& cb_topic, auto const& cb_payload, QoS cb_qos,
|
||||
[[maybe_unused]] auto const& props) {
|
||||
message data;
|
||||
data.topic = cb_topic;
|
||||
data.payload = cb_payload;
|
||||
data.qos = cb_qos;
|
||||
cb(cb_client, data);
|
||||
});
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::subscribe_impl(const std::string_view& topic, QoS qos, int options,
|
||||
const PropertiesBase& props, subscribe_callback cb) {
|
||||
// LogDebug << "MQTT sub " << topic;
|
||||
auto result =
|
||||
convertEC(mosquitto_subscribe_v5(client.get(), nullptr, topic.data(), static_cast<int>(qos), options, props));
|
||||
if (result == ErrorCode::Success) {
|
||||
const auto [it, success] = callbacks.insert({std::string{topic}, std::move(cb)});
|
||||
if (!success) {
|
||||
result = ErrorCode::MapInsert;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::unsubscribe(const std::string_view& topic, const PropertiesBase& props) {
|
||||
return unsubscribe_impl(topic, props);
|
||||
}
|
||||
|
||||
ErrorCode mosquitto_cpp::unsubscribe_impl(const std::string_view& topic, const PropertiesBase& props) {
|
||||
auto result = convertEC(mosquitto_unsubscribe_v5(client.get(), nullptr, topic.data(), props));
|
||||
if (result == ErrorCode::Success) {
|
||||
callbacks.erase(std::string{topic});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool mosquitto_cpp::library_init() {
|
||||
return mosquitto_lib_init() == MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
void mosquitto_cpp::library_cleanup() {
|
||||
(void)mosquitto_lib_cleanup();
|
||||
}
|
||||
} // namespace everest::lib::io::mqtt
|
||||
235
tools/EVerest-main/lib/everest/io/src/mqtt/mqtt_client.cpp
Normal file
235
tools/EVerest-main/lib/everest/io/src/mqtt/mqtt_client.cpp
Normal file
@@ -0,0 +1,235 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/** \file */
|
||||
|
||||
#include "everest/io/event/fd_event_handler.hpp"
|
||||
#include "everest/io/event/fd_event_sync_interface.hpp"
|
||||
#include "everest/io/mqtt/mosquitto_cpp.hpp"
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <everest/io/mqtt/mqtt_client.hpp>
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
constexpr std::uint32_t sync_interval = 1000;
|
||||
} // namespace
|
||||
|
||||
namespace everest::lib::io::mqtt {
|
||||
|
||||
mqtt_client::mqtt_client(std::uint32_t reconnect_to_ms, std::string client_id) :
|
||||
mosquitto_cpp(client_id.empty() ? nullptr : client_id.c_str()), m_client_id(std::move(client_id)) {
|
||||
set_option_tcpnodelay(true);
|
||||
m_reconnect_timer.set_timeout_ms(reconnect_to_ms);
|
||||
m_sync_timer.set_timeout_ms(sync_interval);
|
||||
}
|
||||
|
||||
std::string mqtt_client::get_client_id() const {
|
||||
return m_client_id;
|
||||
}
|
||||
|
||||
mqtt_client::~mqtt_client() {
|
||||
// This fixes late adding actions to the event_handler
|
||||
mosquitto_cpp::set_callback_disconnect_impl([](auto&, auto, auto const&) {});
|
||||
}
|
||||
|
||||
void mqtt_client::handle_socket(event_list const& events) {
|
||||
ErrorCode ec{ErrorCode::Success};
|
||||
if (events.count(event::poll_events::read)) {
|
||||
ec = loop_read();
|
||||
}
|
||||
if (ec == ErrorCode::Success and events.count(event::poll_events::write)) {
|
||||
ec = loop_write();
|
||||
if (not want_write()) {
|
||||
listen_to_write_events(false);
|
||||
}
|
||||
}
|
||||
if (ec == ErrorCode::Success) {
|
||||
ec = loop_misc();
|
||||
}
|
||||
if (ec == ErrorCode::Success and want_write()) {
|
||||
listen_to_write_events(true);
|
||||
}
|
||||
if (ec not_eq ErrorCode::Success) {
|
||||
}
|
||||
}
|
||||
|
||||
void mqtt_client::handle_sync_timer() {
|
||||
loop_misc();
|
||||
if (want_write()) {
|
||||
listen_to_write_events(true);
|
||||
}
|
||||
}
|
||||
|
||||
void mqtt_client::handle_reconnect_timer() {
|
||||
m_handler.add_action([this] {
|
||||
if (m_last_socket not_eq -1) {
|
||||
m_handler.unregister_event_handler(m_last_socket);
|
||||
m_last_socket = -1;
|
||||
}
|
||||
auto result = reconnect();
|
||||
if (result == ErrorCode::Success) {
|
||||
m_last_socket = socket();
|
||||
if (m_last_socket not_eq -1) {
|
||||
m_handler.register_event_handler(
|
||||
socket(), [this](auto const& events) { handle_socket(events); }, event::poll_events::read);
|
||||
listen_to_write_events_if_wanted();
|
||||
listen_to_reconnect_timer(false);
|
||||
}
|
||||
}
|
||||
listen_to_write_events_if_wanted();
|
||||
handle_error(result);
|
||||
});
|
||||
}
|
||||
|
||||
void mqtt_client::listen_to_reconnect_timer(bool enable) {
|
||||
m_handler.add_action([this, enable]() {
|
||||
if (enable) {
|
||||
m_reconnect_timer.reset();
|
||||
m_handler.register_event_handler(&m_reconnect_timer, [this](auto const&) { handle_reconnect_timer(); });
|
||||
} else {
|
||||
m_handler.unregister_event_handler(&m_reconnect_timer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void mqtt_client::listen_to_sync_timer(bool enable) {
|
||||
m_handler.add_action([this, enable]() {
|
||||
if (enable) {
|
||||
m_sync_timer.reset();
|
||||
m_handler.register_event_handler(&m_sync_timer, [this](auto const&) { handle_sync_timer(); });
|
||||
} else {
|
||||
m_handler.unregister_event_handler(&m_sync_timer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ErrorCode mqtt_client::handle_error(ErrorCode error) {
|
||||
if (not m_error_handler) {
|
||||
return error;
|
||||
}
|
||||
if (m_last_error == error) {
|
||||
return error;
|
||||
}
|
||||
m_last_error = error;
|
||||
if (error == ErrorCode::Errno) {
|
||||
m_error_handler(errno, strerror(errno));
|
||||
} else {
|
||||
m_error_handler(static_cast<int>(error), static_cast<std::string>(to_string(error)));
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
void mqtt_client::listen_to_write_events(bool enable) {
|
||||
m_handler.add_action([this, enable]() {
|
||||
auto action = enable ? event::event_modification::add : event::event_modification::remove;
|
||||
m_handler.modify_event_handler(socket(), event::poll_events::write, action);
|
||||
});
|
||||
}
|
||||
|
||||
void mqtt_client::listen_to_write_events_if_wanted() {
|
||||
if (want_write()) {
|
||||
listen_to_write_events(true);
|
||||
}
|
||||
}
|
||||
|
||||
void mqtt_client::set_error_handler(const cb_error& handler) {
|
||||
m_error_handler = handler;
|
||||
// setting dummy callbacks. Actual error_handling callbacks are integrated in the called functions
|
||||
// The check ensures that set_error_handler and set_callback_xxxxx can be called in any order
|
||||
if (not is_connect_callback_set()) {
|
||||
set_callback_connect([](auto&, auto, auto, auto const&) {});
|
||||
}
|
||||
if (not is_disconnect_callback_set()) {
|
||||
set_callback_disconnect([](auto&, auto, auto const&) {});
|
||||
}
|
||||
}
|
||||
|
||||
///// fd_event_sync_interface implementation
|
||||
|
||||
int mqtt_client::get_poll_fd() {
|
||||
return m_handler.get_poll_fd();
|
||||
}
|
||||
|
||||
everest::lib::io::event::sync_status mqtt_client::sync() {
|
||||
m_handler.run_once();
|
||||
return everest::lib::io::event::sync_status::ok;
|
||||
};
|
||||
|
||||
///// mosquitto_cpp override
|
||||
|
||||
ErrorCode mqtt_client::connect_impl(std::string_view const& bind_address, std::string_view const& host,
|
||||
std::uint16_t port, std::uint16_t keepalive_seconds) {
|
||||
auto result = mosquitto_cpp::connect_impl(bind_address, host, port, keepalive_seconds);
|
||||
if (result == ErrorCode::Success) {
|
||||
m_last_socket = socket();
|
||||
m_handler.register_event_handler(
|
||||
socket(), [this](auto const& events) { handle_socket(events); }, event::poll_events::read);
|
||||
listen_to_write_events_if_wanted();
|
||||
listen_to_sync_timer(true);
|
||||
} else {
|
||||
listen_to_reconnect_timer(true);
|
||||
listen_to_sync_timer(false);
|
||||
}
|
||||
return handle_error(result);
|
||||
}
|
||||
|
||||
ErrorCode mqtt_client::set_will_impl(const std::string_view& topic, const std::string_view& payload, QoS qos,
|
||||
bool retain, PropertiesBase&& props) {
|
||||
auto result = mosquitto_cpp::set_will_impl(topic, payload, qos, retain, std::move(props));
|
||||
listen_to_write_events_if_wanted();
|
||||
return handle_error(result);
|
||||
}
|
||||
|
||||
ErrorCode mqtt_client::publish_impl(int* mid, const std::string_view& topic, const std::string_view& payload, QoS qos,
|
||||
bool retain, const PropertiesBase& props) {
|
||||
auto result = ErrorCode::NoConnection;
|
||||
if (m_connected) {
|
||||
result = mosquitto_cpp::publish_impl(mid, topic, payload, qos, retain, props);
|
||||
listen_to_write_events_if_wanted();
|
||||
handle_error(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ErrorCode mqtt_client::subscribe_impl(const std::string_view& topic, QoS qos, int options, const PropertiesBase& props,
|
||||
subscribe_callback cb) {
|
||||
auto result = mosquitto_cpp::subscribe_impl(topic, qos, options, props, std::move(cb));
|
||||
listen_to_write_events_if_wanted();
|
||||
return handle_error(result);
|
||||
}
|
||||
|
||||
ErrorCode mqtt_client::unsubscribe_impl(const std::string_view& topic, const PropertiesBase& props) {
|
||||
auto result = mosquitto_cpp::unsubscribe_impl(topic, props);
|
||||
listen_to_write_events_if_wanted();
|
||||
return handle_error(result);
|
||||
}
|
||||
|
||||
void mqtt_client::set_callback_connect_impl(connect_callback cb) {
|
||||
mosquitto_cpp::set_callback_connect_impl(
|
||||
[this, cb = std::move(cb)](auto& cb_client, auto rc, auto flags, auto const& props) {
|
||||
m_connected = true;
|
||||
if (cb) {
|
||||
cb(cb_client, rc, flags, props);
|
||||
}
|
||||
handle_error(ErrorCode::Success);
|
||||
});
|
||||
}
|
||||
|
||||
void mqtt_client::set_callback_disconnect_impl(disconnect_callback cb) {
|
||||
mosquitto_cpp::set_callback_disconnect_impl(
|
||||
[this, cb = std::move(cb)](auto& cb_client, auto ec, auto const& props) {
|
||||
m_connected = false;
|
||||
listen_to_reconnect_timer(true);
|
||||
if (cb) {
|
||||
cb(cb_client, ec, props);
|
||||
}
|
||||
handle_error(ec);
|
||||
});
|
||||
}
|
||||
|
||||
void mqtt_client::set_callback_publish_impl(publish_callback cb) {
|
||||
mosquitto_cpp::set_callback_publish_impl(cb);
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::mqtt
|
||||
@@ -0,0 +1,247 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
// clang-format off
|
||||
#include <everest/io/netlink/vcan_netlink_manager.hpp>
|
||||
#include <linux/netlink.h>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <net/if.h>
|
||||
#include <linux/if_arp.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
// clang-format on
|
||||
|
||||
namespace everest::lib::io::netlink {
|
||||
|
||||
struct vcan_netlink_manager::NetlinkMessage {
|
||||
struct nlmsghdr header;
|
||||
char buffer[4096];
|
||||
};
|
||||
|
||||
void vcan_netlink_manager::add_attribute(nlmsghdr* nlh, int maxlen, int type, const void* data, int len) {
|
||||
int attrlen = RTA_LENGTH(len);
|
||||
if (NLMSG_ALIGN(nlh->nlmsg_len) + attrlen > static_cast<uint32_t>(maxlen)) {
|
||||
throw std::runtime_error("Netlink attribute too long for message buffer.");
|
||||
}
|
||||
struct rtattr* rta = (struct rtattr*)(((char*)nlh) + NLMSG_ALIGN(nlh->nlmsg_len));
|
||||
rta->rta_type = type;
|
||||
rta->rta_len = attrlen;
|
||||
memcpy(RTA_DATA(rta), data, len);
|
||||
nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + attrlen;
|
||||
}
|
||||
|
||||
vcan_netlink_manager::vcan_netlink_manager() {
|
||||
m_nl_socket_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
|
||||
if (m_nl_socket_fd < 0) {
|
||||
throw std::runtime_error("Failed to create shared Netlink socket: " + std::string(strerror(errno)));
|
||||
}
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 5;
|
||||
tv.tv_usec = 0;
|
||||
if (setsockopt(m_nl_socket_fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)) < 0) {
|
||||
::close(m_nl_socket_fd);
|
||||
m_nl_socket_fd = -1;
|
||||
throw std::runtime_error("Could not set receive timeout on shared Netlink socket: " +
|
||||
std::string(strerror(errno)));
|
||||
}
|
||||
}
|
||||
|
||||
vcan_netlink_manager::~vcan_netlink_manager() {
|
||||
if (m_nl_socket_fd != -1) {
|
||||
::close(m_nl_socket_fd);
|
||||
m_nl_socket_fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
vcan_netlink_manager& vcan_netlink_manager::Instance() {
|
||||
static vcan_netlink_manager obj;
|
||||
return obj;
|
||||
}
|
||||
|
||||
void vcan_netlink_manager::send_message(NetlinkMessage const& msg, int flags) {
|
||||
iovec iov;
|
||||
iov.iov_base = (void*)&msg.header;
|
||||
iov.iov_len = msg.header.nlmsg_len;
|
||||
|
||||
msghdr message;
|
||||
message.msg_name = nullptr;
|
||||
message.msg_namelen = 0;
|
||||
message.msg_iov = &iov;
|
||||
message.msg_iovlen = 1;
|
||||
message.msg_control = nullptr;
|
||||
message.msg_controllen = 0;
|
||||
message.msg_flags = 0;
|
||||
|
||||
ssize_t bytes_sent = sendmsg(m_nl_socket_fd, &message, flags);
|
||||
if (bytes_sent < 0) {
|
||||
throw std::runtime_error("Failed to send Netlink message: " + std::string(strerror(errno)));
|
||||
}
|
||||
if (static_cast<std::uint32_t>(bytes_sent) != msg.header.nlmsg_len) {
|
||||
throw std::runtime_error("Partial Netlink message sent. Expected " + std::to_string(msg.header.nlmsg_len) +
|
||||
" bytes, sent " + std::to_string(bytes_sent));
|
||||
}
|
||||
}
|
||||
|
||||
void vcan_netlink_manager::receive_message(NetlinkMessage& msg) {
|
||||
iovec iov;
|
||||
iov.iov_base = (void*)&msg.header;
|
||||
iov.iov_len = sizeof(msg);
|
||||
|
||||
msghdr message;
|
||||
message.msg_name = nullptr;
|
||||
message.msg_namelen = 0;
|
||||
message.msg_iov = &iov;
|
||||
message.msg_iovlen = 1;
|
||||
message.msg_control = nullptr;
|
||||
message.msg_controllen = 0;
|
||||
message.msg_flags = 0;
|
||||
|
||||
ssize_t bytes_received = recvmsg(m_nl_socket_fd, &message, 0); // Use shared FD
|
||||
if (bytes_received < 0) {
|
||||
throw std::runtime_error("Failed to receive Netlink message: " + std::string(strerror(errno)));
|
||||
}
|
||||
if (bytes_received == 0) {
|
||||
throw std::runtime_error("Netlink socket closed during receive.");
|
||||
}
|
||||
if (bytes_received < static_cast<int>(sizeof(struct nlmsghdr))) {
|
||||
throw std::runtime_error("Incomplete Netlink message header.");
|
||||
}
|
||||
}
|
||||
|
||||
void vcan_netlink_manager::send_netlink_request_impl(int msg_type, int flags, cb_type const& callback) {
|
||||
NetlinkMessage req;
|
||||
memset(&req, 0, sizeof(req));
|
||||
|
||||
req.header.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
|
||||
req.header.nlmsg_type = msg_type;
|
||||
req.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | flags;
|
||||
req.header.nlmsg_seq = ++s_netlink_seq_counter;
|
||||
req.header.nlmsg_pid = getpid();
|
||||
|
||||
struct ifinfomsg* ifi = (struct ifinfomsg*)NLMSG_DATA(&req.header);
|
||||
ifi->ifi_family = AF_UNSPEC;
|
||||
ifi->ifi_index = 0;
|
||||
ifi->ifi_flags = 0;
|
||||
ifi->ifi_change = 0xFFFFFFFF;
|
||||
|
||||
callback(&req.header, ifi, sizeof(req.buffer));
|
||||
send_message(req);
|
||||
|
||||
NetlinkMessage response;
|
||||
bool ack_received = false;
|
||||
int max_recv_attempts = 10;
|
||||
|
||||
for (int i = 0; i < max_recv_attempts; ++i) {
|
||||
ssize_t bytes_received = 0;
|
||||
try {
|
||||
memset(&response, 0, sizeof(response));
|
||||
receive_message(response);
|
||||
bytes_received = response.header.nlmsg_len;
|
||||
} catch (const std::runtime_error& e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (struct nlmsghdr* nlh = &response.header; NLMSG_OK(nlh, bytes_received);
|
||||
nlh = NLMSG_NEXT(nlh, bytes_received)) {
|
||||
if (static_cast<int>(nlh->nlmsg_pid) != getpid() || nlh->nlmsg_seq != req.header.nlmsg_seq) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nlh->nlmsg_type == NLMSG_ERROR) {
|
||||
struct nlmsgerr* err = (struct nlmsgerr*)NLMSG_DATA(nlh);
|
||||
if (err->error == 0) {
|
||||
ack_received = true;
|
||||
return;
|
||||
} else {
|
||||
throw std::runtime_error(std::string(strerror(-err->error)) +
|
||||
" (error code: " + std::to_string(err->error) + ")");
|
||||
}
|
||||
} else if (nlh->nlmsg_type == msg_type) {
|
||||
ack_received = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ack_received) {
|
||||
throw std::runtime_error("No valid ACK");
|
||||
}
|
||||
}
|
||||
|
||||
bool vcan_netlink_manager::send_netlink_request(int msg_type, int flags, cb_type const& callback,
|
||||
std::string const& interface_name, std::string const& caller) {
|
||||
try {
|
||||
send_netlink_request_impl(msg_type, flags, callback);
|
||||
return true;
|
||||
} catch (std::exception& e) {
|
||||
std::cerr << "[VCAN Netlink] (" << interface_name << ") '" << caller << "' -> " << e.what() << std::endl;
|
||||
|
||||
} catch (...) {
|
||||
std::cerr << "[VCAN Netlink] (" << interface_name << ") '" << caller << "' -> Unexpected exception"
|
||||
<< std::endl;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool vcan_netlink_manager::create(std::string const& interface_name) {
|
||||
return send_netlink_request(
|
||||
RTM_NEWLINK, NLM_F_CREATE | NLM_F_EXCL,
|
||||
[&](struct nlmsghdr* nlh, struct ifinfomsg* ifi, int max_buf_len) {
|
||||
ifi->ifi_type = AF_NETROM;
|
||||
|
||||
add_attribute(nlh, max_buf_len, IFLA_IFNAME, interface_name.c_str(), interface_name.length() + 1);
|
||||
|
||||
struct rtattr* linkinfo_attr = (struct rtattr*)(((char*)nlh) + NLMSG_ALIGN(nlh->nlmsg_len));
|
||||
linkinfo_attr->rta_type = IFLA_LINKINFO;
|
||||
linkinfo_attr->rta_len = RTA_LENGTH(0);
|
||||
|
||||
nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(RTA_LENGTH(0));
|
||||
|
||||
const char* vcan_kind = "vcan";
|
||||
add_attribute(nlh, max_buf_len, IFLA_INFO_KIND, vcan_kind, strlen(vcan_kind) + 1);
|
||||
|
||||
linkinfo_attr->rta_len = (char*)nlh + nlh->nlmsg_len - (char*)linkinfo_attr;
|
||||
},
|
||||
interface_name, "create");
|
||||
}
|
||||
|
||||
bool vcan_netlink_manager::bring_up(std::string const& interface_name) {
|
||||
return send_netlink_request(
|
||||
RTM_NEWLINK, 0,
|
||||
[&](struct nlmsghdr* nlh, struct ifinfomsg* ifi, int max_buf_len) {
|
||||
add_attribute(nlh, max_buf_len, IFLA_IFNAME, interface_name.c_str(), interface_name.length() + 1);
|
||||
ifi->ifi_flags = IFF_UP;
|
||||
ifi->ifi_change = IFF_UP;
|
||||
},
|
||||
interface_name, "bringUp");
|
||||
}
|
||||
|
||||
bool vcan_netlink_manager::bring_down(std::string const& interface_name) {
|
||||
return send_netlink_request(
|
||||
RTM_NEWLINK, 0,
|
||||
[&](struct nlmsghdr* nlh, struct ifinfomsg* ifi, int max_buf_len) {
|
||||
add_attribute(nlh, max_buf_len, IFLA_IFNAME, interface_name.c_str(), interface_name.length() + 1);
|
||||
ifi->ifi_flags = 0;
|
||||
ifi->ifi_change = IFF_UP;
|
||||
},
|
||||
interface_name, "bringDown");
|
||||
}
|
||||
|
||||
bool vcan_netlink_manager::destroy(std::string const& interface_name) {
|
||||
return send_netlink_request(
|
||||
RTM_DELLINK, 0,
|
||||
[&](struct nlmsghdr* nlh, struct ifinfomsg*, int max_buf_len) {
|
||||
add_attribute(nlh, max_buf_len, IFLA_IFNAME, interface_name.c_str(), interface_name.length() + 1);
|
||||
},
|
||||
interface_name, "destroy");
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::netlink
|
||||
70
tools/EVerest-main/lib/everest/io/src/raw/raw_socket.cpp
Normal file
70
tools/EVerest-main/lib/everest/io/src/raw/raw_socket.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#include <chrono>
|
||||
#include <everest/io/raw/raw_socket.hpp>
|
||||
#include <everest/io/socket/socket.hpp>
|
||||
#include <exception>
|
||||
#include <netdb.h>
|
||||
#include <thread>
|
||||
|
||||
namespace everest::lib::io::raw {
|
||||
|
||||
bool raw_socket::open(std::string const& if_name) {
|
||||
try {
|
||||
auto socket = socket::open_raw_promiscuous_socket(if_name);
|
||||
socket::set_non_blocking(socket);
|
||||
m_fd = std::move(socket);
|
||||
return socket::get_pending_error(m_fd) == 0;
|
||||
} catch (...) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool raw_socket::tx(PayloadT& payload) {
|
||||
if (not is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto status = ::send(m_fd, payload.data(), payload.size(), 0);
|
||||
if (status == -1) {
|
||||
return false;
|
||||
}
|
||||
if (status < static_cast<ssize_t>(payload.size())) {
|
||||
// We have a reference to the current data. Replace it with what is left to be written
|
||||
// and return false. This signals the current block cannot be removed from the buffer.
|
||||
payload = {payload.begin() + status, payload.end()};
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool raw_socket::rx(PayloadT& buffer) {
|
||||
if (not is_open()) {
|
||||
return false;
|
||||
}
|
||||
buffer.resize(default_buffer_size);
|
||||
auto status = ::recv(m_fd, buffer.data(), buffer.size(), 0);
|
||||
if (status <= 0) { // -1 is an error, 0 is a connection closed by the peer
|
||||
return false;
|
||||
}
|
||||
buffer.resize(status);
|
||||
return true;
|
||||
}
|
||||
|
||||
int raw_socket::get_fd() const {
|
||||
return m_fd;
|
||||
}
|
||||
|
||||
int raw_socket::get_error() const {
|
||||
return socket::get_pending_error(m_fd);
|
||||
}
|
||||
|
||||
bool raw_socket::is_open() const {
|
||||
return m_fd.is_fd();
|
||||
}
|
||||
|
||||
void raw_socket::close() {
|
||||
m_fd.close();
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::raw
|
||||
37
tools/EVerest-main/lib/everest/io/src/serial/event_pty.cpp
Normal file
37
tools/EVerest-main/lib/everest/io/src/serial/event_pty.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <cstring>
|
||||
#include <everest/io/serial/event_pty.hpp>
|
||||
#include <sys/types.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace everest::lib::io::serial {
|
||||
|
||||
void event_pty::set_status_handler(cb_status const& handler) {
|
||||
m_status = handler;
|
||||
}
|
||||
|
||||
void event_pty::set_data_handler(cb_rx const& handler) {
|
||||
m_data = handler;
|
||||
set_rx_handler([this, handler](auto const& pl, auto& obj) {
|
||||
if (pl.size() > 0) {
|
||||
if (pl[0] == 0) {
|
||||
if (m_data) {
|
||||
m_data({pl.begin() + 1, pl.end()}, obj);
|
||||
}
|
||||
} else {
|
||||
if (m_status) {
|
||||
m_status(get_raw_handler()->get_status());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::string event_pty::get_slave_path() {
|
||||
return get_raw_handler()->get_slave_path();
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::serial
|
||||
100
tools/EVerest-main/lib/everest/io/src/serial/pty_handler.cpp
Normal file
100
tools/EVerest-main/lib/everest/io/src/serial/pty_handler.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <cstring>
|
||||
#include <errno.h>
|
||||
#include <everest/io/serial/pty_handler.hpp>
|
||||
#include <iostream>
|
||||
#include <sys/types.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace everest::lib::io::serial {
|
||||
|
||||
bool pty_handler::tx(PayloadT& data) {
|
||||
auto status = ::write(m_dev.master_fd, data.data(), data.size());
|
||||
if (status == -1) {
|
||||
error_id = errno;
|
||||
std::cout << "ERROR: Failed to write to pty master." << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (status < static_cast<ssize_t>(data.size())) {
|
||||
// We have a reference to the current data. Replace it with what is left to be written
|
||||
// and return false. This signals the current block cannot be removed from the buffer.
|
||||
data = {data.begin() + status, data.end()};
|
||||
return false;
|
||||
}
|
||||
error_id = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pty_handler::rx(PayloadT& data) {
|
||||
// This should not be expensive, since capacity is only touched once, since
|
||||
// data is expected to stay the same object during the livetime of this instance
|
||||
data.resize(buffer_size_limit);
|
||||
auto n_bytes = ::read(m_dev.master_fd, data.data(), data.size());
|
||||
if (n_bytes == -1) {
|
||||
error_id = errno;
|
||||
return false;
|
||||
}
|
||||
data.resize(n_bytes);
|
||||
error_id = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pty_handler::open() {
|
||||
auto mypty = serial::openpty();
|
||||
if (not mypty.has_value()) {
|
||||
error_id = errno;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto aware = serial::make_pty_mode_aware(mypty.value());
|
||||
if (not aware) {
|
||||
std::cout << "ERROR: Preparing pty for packet mode and extproc" << std::endl;
|
||||
error_id = errno;
|
||||
return false;
|
||||
}
|
||||
auto binary = serial::set_binary_mode(mypty->slave_fd);
|
||||
if (not binary) {
|
||||
std::cout << "ERROR: Preparing pty for binary mode" << std::endl;
|
||||
error_id = errno;
|
||||
return false;
|
||||
}
|
||||
m_dev = std::move(mypty.value());
|
||||
error_id = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
int pty_handler::get_fd() const {
|
||||
return m_dev.master_fd;
|
||||
}
|
||||
|
||||
pty_status pty_handler::get_status() {
|
||||
pty_status result;
|
||||
struct termios status;
|
||||
auto attr_res = tcgetattr(m_dev.master_fd, &status);
|
||||
if (attr_res == -1) {
|
||||
error_id = errno;
|
||||
std::cout << "Failed to get attributes" << std::endl;
|
||||
return result;
|
||||
}
|
||||
|
||||
result.ixon = status.c_iflag & IXON; // enable xon/xoff flow control on output
|
||||
result.ixoff = status.c_iflag & IXOFF; // enable xon/xoff flow control on input
|
||||
result.cstopb = status.c_cflag & CSTOPB; // two stop bits instead of one
|
||||
result.cbaud = cfgetospeed(&status);
|
||||
|
||||
error_id = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
int pty_handler::get_error() const {
|
||||
return error_id;
|
||||
}
|
||||
|
||||
std::string pty_handler::get_slave_path() const {
|
||||
return m_dev.slave_path;
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::serial
|
||||
76
tools/EVerest-main/lib/everest/io/src/serial/serial.cpp
Normal file
76
tools/EVerest-main/lib/everest/io/src/serial/serial.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include <cstdlib>
|
||||
#include <everest/io/serial/serial.hpp>
|
||||
#include <fcntl.h>
|
||||
#include <optional>
|
||||
#include <stdlib.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace everest::lib::io::serial {
|
||||
|
||||
std::optional<pty> openpty() {
|
||||
pty result;
|
||||
|
||||
result.master_fd = event::unique_fd(::posix_openpt(O_RDWR | O_NOCTTY));
|
||||
if (not result.master_fd.is_fd()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (::grantpt(result.master_fd) == -1) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (::unlockpt(result.master_fd) == -1) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto mb_name = ::ptsname(result.master_fd);
|
||||
if (mb_name == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
result.slave_path = mb_name;
|
||||
result.slave_fd = event::unique_fd(::open(mb_name, 0));
|
||||
if (not result.slave_fd.is_fd()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool set_packet_mode(int fd) {
|
||||
int nonzero = 1;
|
||||
return ioctl(fd, TIOCPKT, &nonzero) == 0;
|
||||
}
|
||||
|
||||
bool set_extproc_flag(int fd) {
|
||||
struct termios tio;
|
||||
|
||||
if (tcgetattr(fd, &tio) == -1) {
|
||||
return false;
|
||||
}
|
||||
tio.c_lflag |= EXTPROC;
|
||||
|
||||
if (tcsetattr(fd, TCSANOW, &tio) == -1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool set_binary_mode(int fd) {
|
||||
struct termios tty;
|
||||
if (tcgetattr(fd, &tty) < 0) {
|
||||
return false;
|
||||
}
|
||||
cfmakeraw(&tty);
|
||||
|
||||
if (tcsetattr(fd, TCSAFLUSH, &tty) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool make_pty_mode_aware(pty const& item) {
|
||||
return set_packet_mode(item.master_fd) && set_extproc_flag(item.slave_fd);
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::serial
|
||||
925
tools/EVerest-main/lib/everest/io/src/socket/socket.cpp
Normal file
925
tools/EVerest-main/lib/everest/io/src/socket/socket.cpp
Normal file
@@ -0,0 +1,925 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <algorithm>
|
||||
#include <arpa/inet.h>
|
||||
#include <asm-generic/socket.h>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <iostream>
|
||||
#include <linux/if_packet.h>
|
||||
#include <linux/if_tun.h>
|
||||
#include <net/if.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <poll.h>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <everest/io/event/unique_fd.hpp>
|
||||
#include <everest/io/socket/socket.hpp>
|
||||
#include <everest/io/udp/endpoint.hpp>
|
||||
|
||||
namespace {
|
||||
// a simple raii wrapper, which will call the given c-like deleter
|
||||
// function on its supplied pointer at the end of its lifetime
|
||||
template <typename T, void (*deleter)(T*)> class handle_disposer {
|
||||
public:
|
||||
handle_disposer(T* pointer) : m_pointer(pointer){};
|
||||
|
||||
handle_disposer(const handle_disposer&) = delete;
|
||||
handle_disposer(handle_disposer&&) = delete;
|
||||
handle_disposer& operator=(const handle_disposer&) = delete;
|
||||
handle_disposer& operator=(handle_disposer&&) = delete;
|
||||
~handle_disposer() {
|
||||
deleter(m_pointer);
|
||||
}
|
||||
|
||||
private:
|
||||
T* m_pointer;
|
||||
};
|
||||
|
||||
long get_remaining_time_ms(std::chrono::steady_clock::time_point const& init_time, uint32_t timeout_ms) {
|
||||
auto time_delta = std::chrono::steady_clock::now() - init_time;
|
||||
auto time_delta_ms = std::chrono::duration_cast<std::chrono::milliseconds>(time_delta).count();
|
||||
auto remaining_ms = timeout_ms - time_delta_ms;
|
||||
return remaining_ms;
|
||||
}
|
||||
|
||||
void set_ifr_name(struct ifreq& ifr, std::string const& dev_name) {
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
strncpy(ifr.ifr_name, dev_name.c_str(),
|
||||
std::min(dev_name.size(), static_cast<std::string::size_type>(IFNAMSIZ - 1)));
|
||||
ifr.ifr_name[IFNAMSIZ - 1] = '\0';
|
||||
}
|
||||
|
||||
std::string build_errno_string(std::string const& msg) {
|
||||
std::stringstream str;
|
||||
str << msg << ": " << strerror(errno) << " (" << errno << ")";
|
||||
return str.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace everest::lib::io {
|
||||
namespace socket {
|
||||
|
||||
//
|
||||
// socket.hpp implementations
|
||||
//
|
||||
|
||||
namespace {
|
||||
// Returns true when SO_BINDTODEVICE succeeded. Returns false on EPERM/EACCES
|
||||
// (caller decides on a fallback). Throws on any other failure.
|
||||
bool apply_so_bindtodevice(int fd, std::string const& device) {
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, device.c_str(), device.length()) == 0) {
|
||||
return true;
|
||||
}
|
||||
if (errno == EPERM || errno == EACCES) {
|
||||
return false;
|
||||
}
|
||||
throw std::runtime_error(build_errno_string("Failed to bind socket to device " + device));
|
||||
}
|
||||
|
||||
// Returns the socket's address family via getsockname(). Returns AF_UNSPEC if unknown.
|
||||
sa_family_t get_socket_family(int fd) {
|
||||
sockaddr_storage ss{};
|
||||
socklen_t len = sizeof(ss);
|
||||
if (getsockname(fd, reinterpret_cast<struct sockaddr*>(&ss), &len) < 0) {
|
||||
return AF_UNSPEC;
|
||||
}
|
||||
return ss.ss_family;
|
||||
}
|
||||
|
||||
// Restrict the outgoing interface for unicast packets without requiring CAP_NET_RAW.
|
||||
// IPv4: IP_UNICAST_IF takes uint32_t (ifindex in network byte order via in_addr layout).
|
||||
// IPv6: IPV6_UNICAST_IF takes int (ifindex in host byte order).
|
||||
// Returns true on success; false if the socket family is neither AF_INET nor AF_INET6.
|
||||
// Throws if the interface lookup fails or if setsockopt fails with a non-permission error.
|
||||
bool apply_unicast_if(int fd, std::string const& device) {
|
||||
unsigned int ifindex = if_nametoindex(device.c_str());
|
||||
if (ifindex == 0) {
|
||||
throw std::runtime_error(build_errno_string("if_nametoindex(\"" + device + "\") failed"));
|
||||
}
|
||||
sa_family_t family = get_socket_family(fd);
|
||||
if (family == AF_INET) {
|
||||
std::uint32_t opt = htonl(ifindex);
|
||||
if (setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &opt, sizeof(opt)) == 0) {
|
||||
return true;
|
||||
}
|
||||
throw std::runtime_error(build_errno_string("setsockopt(IP_UNICAST_IF, " + device + ") failed"));
|
||||
}
|
||||
if (family == AF_INET6) {
|
||||
int opt = static_cast<int>(ifindex);
|
||||
if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &opt, sizeof(opt)) == 0) {
|
||||
return true;
|
||||
}
|
||||
throw std::runtime_error(build_errno_string("setsockopt(IPV6_UNICAST_IF, " + device + ") failed"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Steer multicast egress to @p device. SO_BINDTODEVICE and IP[V6]_UNICAST_IF do not
|
||||
// affect the multicast TX path; the kernel needs IP_MULTICAST_IF / IPV6_MULTICAST_IF
|
||||
// independently. For IPv6 we additionally populate sin6_scope_id on the destination
|
||||
// sockaddr so connect()/sendto() can resolve scope bound groups (ff02::/16). For IPv4
|
||||
// we use ip_mreqn so the interface is selected by index, avoiding the need for the
|
||||
// iface IPv4 address up front (works even before DHCP has assigned one). Returns
|
||||
// without action when not a multicast destination or device is empty.
|
||||
void set_multicast_if(int fd, std::string const& device, struct sockaddr* ai_addr) {
|
||||
if (device.empty() || ai_addr == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (ai_addr->sa_family == AF_INET6) {
|
||||
auto* sin6 = reinterpret_cast<sockaddr_in6*>(ai_addr);
|
||||
if (!IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) {
|
||||
return;
|
||||
}
|
||||
unsigned int ifindex = if_nametoindex(device.c_str());
|
||||
if (ifindex == 0) {
|
||||
throw std::runtime_error(build_errno_string("if_nametoindex(\"" + device + "\") failed"));
|
||||
}
|
||||
if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex, sizeof(ifindex)) != 0) {
|
||||
throw std::runtime_error(build_errno_string("setsockopt(IPV6_MULTICAST_IF, " + device + ") failed"));
|
||||
}
|
||||
sin6->sin6_scope_id = ifindex;
|
||||
return;
|
||||
}
|
||||
if (ai_addr->sa_family == AF_INET) {
|
||||
auto* sin = reinterpret_cast<sockaddr_in*>(ai_addr);
|
||||
if (!IN_MULTICAST(ntohl(sin->sin_addr.s_addr))) {
|
||||
return;
|
||||
}
|
||||
unsigned int ifindex = if_nametoindex(device.c_str());
|
||||
if (ifindex == 0) {
|
||||
throw std::runtime_error(build_errno_string("if_nametoindex(\"" + device + "\") failed"));
|
||||
}
|
||||
struct ip_mreqn mreq {};
|
||||
mreq.imr_ifindex = static_cast<int>(ifindex);
|
||||
if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &mreq, sizeof(mreq)) != 0) {
|
||||
throw std::runtime_error(build_errno_string("setsockopt(IP_MULTICAST_IF, " + device + ") failed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IPv4 source-IP bind to the address belonging to @p device. Used as a fallback
|
||||
// when SO_BINDTODEVICE is not permitted. @p port == 0 picks an ephemeral port.
|
||||
void bind_socket_to_interface_address(int fd, std::string const& device, std::uint16_t port) {
|
||||
std::string ip = get_interface_address(device);
|
||||
if (ip.empty()) {
|
||||
throw std::runtime_error("Cannot bind socket to device " + device +
|
||||
": no IPv4 address on interface (and SO_BINDTODEVICE not permitted)");
|
||||
}
|
||||
sockaddr_in local{};
|
||||
local.sin_family = AF_INET;
|
||||
local.sin_port = htons(port);
|
||||
if (inet_pton(AF_INET, ip.c_str(), &local.sin_addr) != 1) {
|
||||
throw std::runtime_error("Failed to parse interface address " + ip);
|
||||
}
|
||||
if (::bind(fd, reinterpret_cast<struct sockaddr*>(&local), sizeof(local)) < 0) {
|
||||
throw std::runtime_error(build_errno_string("Fallback bind to interface " + device + " (" + ip + ":" +
|
||||
std::to_string(port) + ") failed"));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void bind_socket_to_device(int fd, std::string const& device) {
|
||||
if (device.empty()) {
|
||||
return;
|
||||
}
|
||||
if (apply_so_bindtodevice(fd, device)) {
|
||||
return;
|
||||
}
|
||||
// Try IP[_V6]_UNICAST_IF for outgoing unicast routing without privilege. Works for both
|
||||
// IPv4 and IPv6 client sockets.
|
||||
if (apply_unicast_if(fd, device)) {
|
||||
return;
|
||||
}
|
||||
// Last resort for IPv4 sockets with an unusable family: bind a source IP belonging to the
|
||||
// interface (IPv4 only).
|
||||
bind_socket_to_interface_address(fd, device, 0);
|
||||
}
|
||||
|
||||
event::unique_fd open_udp_server_socket(std::uint16_t port, std::string const& device) {
|
||||
struct addrinfo hints {};
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
|
||||
addrinfo* servinfo;
|
||||
|
||||
const auto err = getaddrinfo(nullptr, std::to_string(port).c_str(), &hints, &servinfo);
|
||||
if (err) {
|
||||
throw std::runtime_error("Failed to resolve endpoint (getaddrinfo): " + std::string(gai_strerror(err)));
|
||||
};
|
||||
|
||||
handle_disposer<addrinfo, freeaddrinfo> addrinfo_disposer(servinfo);
|
||||
|
||||
// open the first possible socket
|
||||
for (auto* p = servinfo; p != NULL; p = p->ai_next) {
|
||||
auto socket_fd = event::unique_fd(::socket(p->ai_family, p->ai_socktype, p->ai_protocol));
|
||||
if (socket_fd == -1) {
|
||||
continue;
|
||||
}
|
||||
set_reuse_address(socket_fd);
|
||||
|
||||
if (!device.empty()) {
|
||||
if (!apply_so_bindtodevice(socket_fd, device)) {
|
||||
// SO_BINDTODEVICE not permitted: bind to (interface_ip:port) instead of wildcard,
|
||||
// so the server only receives traffic addressed to that interface's IPv4.
|
||||
bind_socket_to_interface_address(socket_fd, device, port);
|
||||
return socket_fd;
|
||||
}
|
||||
}
|
||||
|
||||
if (bind(socket_fd, p->ai_addr, p->ai_addrlen) == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return socket_fd;
|
||||
}
|
||||
throw std::runtime_error(std::string("Could not open a socket for localhost:") + std::to_string(port));
|
||||
}
|
||||
|
||||
event::unique_fd open_udp_client_socket(std::string const& host, std::uint16_t port, std::string const& device) {
|
||||
struct addrinfo hints {};
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
|
||||
addrinfo* servinfo;
|
||||
|
||||
const auto err = getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, &servinfo);
|
||||
if (err) {
|
||||
throw std::runtime_error("Failed to resolve endpoint (getaddrinfo): " + std::string(gai_strerror(err)));
|
||||
};
|
||||
|
||||
handle_disposer<addrinfo, freeaddrinfo> addrinfo_disposer(servinfo);
|
||||
|
||||
// open the first possible socket
|
||||
for (auto* p = servinfo; p != NULL; p = p->ai_next) {
|
||||
const auto socket_fd = ::socket(p->ai_family, p->ai_socktype, p->ai_protocol);
|
||||
|
||||
if (socket_fd == -1)
|
||||
continue;
|
||||
|
||||
try {
|
||||
bind_socket_to_device(socket_fd, device);
|
||||
set_multicast_if(socket_fd, device, p->ai_addr);
|
||||
} catch (...) {
|
||||
close(socket_fd);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (-1 == ::connect(socket_fd, p->ai_addr, p->ai_addrlen)) {
|
||||
close(socket_fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
return event::unique_fd{socket_fd};
|
||||
}
|
||||
|
||||
throw std::runtime_error(std::string("Could not open a socket for ") + host + ":" + std::to_string(port));
|
||||
}
|
||||
|
||||
event::unique_fd open_tcp_socket_with_timeout(const std::string& host, std::uint16_t port, unsigned int timeout_ms,
|
||||
const std::string& device) {
|
||||
struct addrinfo hints {};
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
addrinfo* servinfo;
|
||||
|
||||
const auto err = getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, &servinfo);
|
||||
if (err) {
|
||||
throw std::runtime_error("Failed to resolve endpoint (getaddrinfo): " + std::string(gai_strerror(err)));
|
||||
};
|
||||
|
||||
handle_disposer<addrinfo, freeaddrinfo> addrinfo_disposer(servinfo);
|
||||
|
||||
// open the first possible socket
|
||||
for (auto* p = servinfo; p != NULL; p = p->ai_next) {
|
||||
const auto socket_fd = ::socket(p->ai_family, p->ai_socktype, p->ai_protocol);
|
||||
|
||||
if (socket_fd == -1)
|
||||
continue;
|
||||
|
||||
try {
|
||||
bind_socket_to_device(socket_fd, device);
|
||||
} catch (...) {
|
||||
close(socket_fd);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (-1 == connect_with_timeout(socket_fd, p->ai_addr, p->ai_addrlen, timeout_ms)) {
|
||||
close(socket_fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
return event::unique_fd{socket_fd};
|
||||
}
|
||||
|
||||
throw std::runtime_error(std::string("Could not open a socket for ") + host + ":" + std::to_string(port));
|
||||
}
|
||||
|
||||
event::unique_fd open_tcp_socket(const std::string& host, std::uint16_t port, const std::string& device) {
|
||||
struct addrinfo hints {};
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
addrinfo* servinfo;
|
||||
|
||||
const auto err = getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, &servinfo);
|
||||
if (err) {
|
||||
throw std::runtime_error("Failed to resolve endpoint (getaddrinfo): " + std::string(gai_strerror(err)));
|
||||
};
|
||||
|
||||
handle_disposer<addrinfo, freeaddrinfo> addrinfo_disposer(servinfo);
|
||||
|
||||
// open the first possible socket
|
||||
for (auto* p = servinfo; p != NULL; p = p->ai_next) {
|
||||
const auto socket_fd = ::socket(p->ai_family, p->ai_socktype, p->ai_protocol);
|
||||
|
||||
if (socket_fd == -1)
|
||||
continue;
|
||||
|
||||
try {
|
||||
bind_socket_to_device(socket_fd, device);
|
||||
} catch (...) {
|
||||
close(socket_fd);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (-1 == connect(socket_fd, p->ai_addr, p->ai_addrlen)) {
|
||||
close(socket_fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
return event::unique_fd{socket_fd};
|
||||
}
|
||||
|
||||
throw std::runtime_error(std::string("Could not open a socket for ") + host + ":" + std::to_string(port));
|
||||
}
|
||||
|
||||
#ifndef EVEREST_NO_PACKET_IGNORE_OUTGOING
|
||||
event::unique_fd open_raw_promiscuous_socket(std::string const& if_name) {
|
||||
auto const socket_fd = ::socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
|
||||
if (socket_fd == -1) {
|
||||
auto msg = build_errno_string("Could not open raw socket");
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
int const if_index = if_nametoindex(if_name.c_str());
|
||||
if (if_index == 0) {
|
||||
close(socket_fd);
|
||||
throw std::runtime_error("Invalid interface name: " + if_name);
|
||||
}
|
||||
|
||||
int ignore_out = 1;
|
||||
if (setsockopt(socket_fd, SOL_PACKET, PACKET_IGNORE_OUTGOING, &ignore_out, sizeof(ignore_out)) == -1) {
|
||||
auto msg = build_errno_string("Could not set PACKET_IGNORE_OUTGOING for " + if_name);
|
||||
close(socket_fd);
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
struct sockaddr_ll sll;
|
||||
std::memset(&sll, 0, sizeof(sll));
|
||||
sll.sll_family = AF_PACKET;
|
||||
sll.sll_ifindex = if_index;
|
||||
sll.sll_protocol = htons(ETH_P_ALL);
|
||||
|
||||
if (::bind(socket_fd, (struct sockaddr*)&sll, sizeof(sll)) == -1) {
|
||||
auto msg = build_errno_string("Could not bind socket to " + if_name);
|
||||
close(socket_fd);
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
struct packet_mreq mreq;
|
||||
std::memset(&mreq, 0, sizeof(mreq));
|
||||
mreq.mr_ifindex = if_nametoindex(if_name.c_str());
|
||||
mreq.mr_type = PACKET_MR_PROMISC;
|
||||
auto error = setsockopt(socket_fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
|
||||
if (error) {
|
||||
auto msg = build_errno_string("Cannot set 'PROMISCUOUS MODE' for '" + if_name + "'");
|
||||
close(socket_fd);
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
return event::unique_fd(socket_fd);
|
||||
}
|
||||
#endif
|
||||
|
||||
void enable_tcp_no_delay(int fd) {
|
||||
socklen_t enable = 1;
|
||||
auto err = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable));
|
||||
|
||||
if (err) {
|
||||
throw std::runtime_error("Failed to setsockopt(TCP_NODELAY)");
|
||||
}
|
||||
}
|
||||
|
||||
void set_non_blocking(int fd) {
|
||||
const auto err = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
|
||||
|
||||
if (err) {
|
||||
throw std::runtime_error("Failed to fcntl(O_NONBLOCK)");
|
||||
}
|
||||
}
|
||||
|
||||
void set_keepalive(int fd, bool enable) {
|
||||
socklen_t value = static_cast<int>(enable);
|
||||
auto err = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &value, sizeof(value));
|
||||
|
||||
if (err) {
|
||||
throw std::runtime_error("Failed to setsockopt(SO_KEEPALIVE)");
|
||||
}
|
||||
}
|
||||
|
||||
void set_tcp_keepalive(int fd, uint32_t count, uint32_t idle_s, uint32_t intval_s) {
|
||||
set_keepalive(fd, true);
|
||||
auto err = setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count));
|
||||
if (err) {
|
||||
throw std::runtime_error("Failed to setsockopt(TCP_KEEPCNT)");
|
||||
}
|
||||
err = setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &idle_s, sizeof(idle_s));
|
||||
if (err) {
|
||||
throw std::runtime_error("Failed to setsockopt(TCP_KEEPIDLE)");
|
||||
}
|
||||
err = setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &intval_s, sizeof(intval_s));
|
||||
if (err) {
|
||||
throw std::runtime_error("Failed to setsockopt(TCP_INTVL)");
|
||||
}
|
||||
}
|
||||
|
||||
void set_tcp_user_timeout(int fd, uint32_t to_ms) {
|
||||
auto err = setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &to_ms, sizeof(to_ms));
|
||||
|
||||
if (err) {
|
||||
throw std::runtime_error("Failed to setsockopt(TCP_USER_TIMEOUT)");
|
||||
}
|
||||
}
|
||||
|
||||
void set_socket_send_buffer_to_min(int fd) {
|
||||
int sndbuf = 0;
|
||||
auto err = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
|
||||
|
||||
if (err) {
|
||||
throw std::runtime_error("Failed to setsockopt(SO_SNDBUF)");
|
||||
}
|
||||
}
|
||||
|
||||
int get_pending_error(int fd) {
|
||||
int error = 0;
|
||||
socklen_t errlen = sizeof(error);
|
||||
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*)&error, &errlen)) {
|
||||
return errno;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
bool is_tcp_socket_alive(int sock_fd) {
|
||||
char buffer[1];
|
||||
ssize_t bytes_peeked = recv(sock_fd, buffer, sizeof(buffer), MSG_PEEK | MSG_DONTWAIT);
|
||||
|
||||
if (bytes_peeked == -1) {
|
||||
return errno == EAGAIN || errno == EWOULDBLOCK;
|
||||
}
|
||||
return bytes_peeked not_eq 0;
|
||||
}
|
||||
|
||||
void bring_device_up(int sock_fd, std::string const& dev_name) {
|
||||
struct ifreq ifr;
|
||||
set_ifr_name(ifr, dev_name);
|
||||
|
||||
if (ioctl(sock_fd, SIOCGIFFLAGS, &ifr) == -1) {
|
||||
throw std::runtime_error(std::string("Failed to get device flags for ") + dev_name + ": " + strerror(errno));
|
||||
}
|
||||
|
||||
ifr.ifr_flags |= IFF_UP;
|
||||
|
||||
if (ioctl(sock_fd, SIOCSIFFLAGS, &ifr) == -1) {
|
||||
throw std::runtime_error(std::string("Failed to bring device UP: ") + dev_name + ": " + strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
void set_ip_address(int sock_fd, std::string const& dev_name, std::string const& ip_address_str) {
|
||||
struct ifreq ifr;
|
||||
set_ifr_name(ifr, dev_name);
|
||||
|
||||
struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(&ifr.ifr_addr);
|
||||
sin->sin_family = AF_INET;
|
||||
|
||||
if (inet_pton(AF_INET, ip_address_str.c_str(), &sin->sin_addr) <= 0) {
|
||||
throw std::runtime_error(std::string("Invalid IP address format: ") + ip_address_str +
|
||||
" (Error: " + strerror(errno) + ")");
|
||||
}
|
||||
|
||||
if (ioctl(sock_fd, SIOCSIFADDR, &ifr) == -1) {
|
||||
throw std::runtime_error(std::string("Failed to set IP address ") + ip_address_str + " on " + dev_name + ": " +
|
||||
strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
void set_netmask(int sock_fd, std::string const& dev_name, std::string const& netmask_str) {
|
||||
struct ifreq ifr;
|
||||
set_ifr_name(ifr, dev_name);
|
||||
|
||||
struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(&ifr.ifr_netmask);
|
||||
sin->sin_family = AF_INET;
|
||||
|
||||
if (inet_pton(AF_INET, netmask_str.c_str(), &sin->sin_addr) <= 0) {
|
||||
throw std::runtime_error(std::string("Invalid netmask format: ") + netmask_str + " (Error: " + strerror(errno) +
|
||||
")");
|
||||
}
|
||||
|
||||
if (ioctl(sock_fd, SIOCSIFNETMASK, &ifr) == -1) {
|
||||
throw std::runtime_error(std::string("Failed to set netmask ") + netmask_str + " on " + dev_name + ": " +
|
||||
strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
void set_mtu(int sock_fd, std::string const& dev_name, int mtu) {
|
||||
struct ifreq ifr;
|
||||
set_ifr_name(ifr, dev_name);
|
||||
ifr.ifr_mtu = mtu;
|
||||
|
||||
if (ioctl(sock_fd, SIOCSIFMTU, &ifr) == -1) {
|
||||
throw std::runtime_error(std::string("Failed to set MTU to ") + std::to_string(mtu) + " on " + dev_name + ": " +
|
||||
strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
event::unique_fd create_tap_device(std::string const& desired_device_name) {
|
||||
event::unique_fd tap_fd(::open("/dev/net/tun", O_RDWR));
|
||||
if (!tap_fd.is_fd()) {
|
||||
throw std::runtime_error(std::string("Unable to open /dev/net/tun: ") + strerror(errno));
|
||||
}
|
||||
if (desired_device_name.empty()) {
|
||||
throw std::runtime_error("create_tap_device: No device name provided. ");
|
||||
}
|
||||
|
||||
struct ifreq ifr;
|
||||
set_ifr_name(ifr, desired_device_name);
|
||||
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
|
||||
|
||||
if (ioctl(tap_fd, TUNSETIFF, &ifr) == -1) {
|
||||
if (errno == EEXIST) {
|
||||
throw std::runtime_error(std::string("TAP device name is not available: '") + desired_device_name +
|
||||
"': " + strerror(errno));
|
||||
|
||||
} else {
|
||||
throw std::runtime_error(std::string("Unable to create TAP device '") + desired_device_name +
|
||||
"': " + strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
return tap_fd;
|
||||
}
|
||||
|
||||
event::unique_fd open_control_socket() {
|
||||
event::unique_fd sock_fd(::socket(AF_INET, SOCK_DGRAM, 0));
|
||||
if (!sock_fd.is_fd()) {
|
||||
throw std::runtime_error(std::string("Unable to open control socket: ") + strerror(errno));
|
||||
}
|
||||
return sock_fd;
|
||||
}
|
||||
|
||||
bool configure_tap_device_properties(int tap_fd, std::string const& dev_name, std::string const& ip_addr_str,
|
||||
std::string const& netmask_str, int mtu) {
|
||||
event::unique_fd control_sock_fd = open_control_socket();
|
||||
|
||||
try {
|
||||
bring_device_up(control_sock_fd, dev_name);
|
||||
set_ip_address(control_sock_fd, dev_name, ip_addr_str);
|
||||
set_netmask(control_sock_fd, dev_name, netmask_str);
|
||||
set_mtu(control_sock_fd, dev_name, mtu);
|
||||
set_non_blocking(tap_fd);
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int get_socket_flags(int fd) {
|
||||
int result = fcntl(fd, F_GETFL, 0);
|
||||
if (result < 0) {
|
||||
throw std::runtime_error(std::string("Get flags for socket Error: ") + strerror(errno) + ")");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void set_socket_flags(int fd, int flags) {
|
||||
int result = fcntl(fd, F_SETFL, flags);
|
||||
if (result < 0) {
|
||||
throw std::runtime_error(std::string("Set flags for socket Error: ") + strerror(errno) + ")");
|
||||
}
|
||||
}
|
||||
|
||||
int poll_for_timeout_once(int fd, uint32_t timeout_ms) {
|
||||
struct pollfd pfd;
|
||||
pfd.fd = fd;
|
||||
pfd.events = POLLOUT;
|
||||
struct pollfd pfds[] = {pfd};
|
||||
return poll(pfds, 1, timeout_ms);
|
||||
}
|
||||
|
||||
int poll_until_timeout(int fd, uint32_t timeout_ms) {
|
||||
int result = 0;
|
||||
auto init_time = std::chrono::steady_clock::now();
|
||||
do {
|
||||
auto remaining_ms = get_remaining_time_ms(init_time, timeout_ms);
|
||||
if (remaining_ms < 0) {
|
||||
result = 0;
|
||||
break;
|
||||
}
|
||||
result = poll_for_timeout_once(fd, remaining_ms);
|
||||
if (result > 0) {
|
||||
errno = get_pending_error(fd);
|
||||
if (errno != 0) {
|
||||
result = -1;
|
||||
}
|
||||
}
|
||||
} while (result == -1 && errno == EINTR);
|
||||
if (result == 0) {
|
||||
errno = ETIMEDOUT;
|
||||
result = -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int connect_with_timeout_impl(int sockfd, const struct sockaddr* addr, socklen_t addrlen, unsigned int timeout_ms) {
|
||||
int result = 0;
|
||||
int original_flags = get_socket_flags(sockfd);
|
||||
set_non_blocking(sockfd);
|
||||
if (connect(sockfd, addr, addrlen) < 0) {
|
||||
if ((errno != EWOULDBLOCK) && (errno != EINPROGRESS)) {
|
||||
result = -1;
|
||||
} else {
|
||||
result = poll_until_timeout(sockfd, timeout_ms);
|
||||
}
|
||||
}
|
||||
set_socket_flags(sockfd, original_flags);
|
||||
return result;
|
||||
}
|
||||
|
||||
int connect_with_timeout(int fd, const struct sockaddr* addr, uint32_t addrlen, unsigned int timeout_ms) {
|
||||
auto result = 0;
|
||||
try {
|
||||
result = connect_with_timeout_impl(fd, addr, addrlen, timeout_ms);
|
||||
} catch (...) {
|
||||
result = -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string get_interface_address(std::string const& name) {
|
||||
if (name.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
struct ifaddrs* ifaddr{nullptr};
|
||||
struct ifaddrs* ifa{nullptr};
|
||||
if (getifaddrs(&ifaddr) == -1) {
|
||||
throw std::runtime_error("Cannot get interfaces: " + std::string(strerror(errno)));
|
||||
}
|
||||
|
||||
handle_disposer<ifaddrs, freeifaddrs> ifaddr_disposer(ifaddr);
|
||||
|
||||
for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
|
||||
if (ifa->ifa_addr == nullptr || ifa->ifa_addr->sa_family != AF_INET) {
|
||||
continue;
|
||||
}
|
||||
if (name == ifa->ifa_name) {
|
||||
char host[NI_MAXHOST];
|
||||
getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
|
||||
return host;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("Interface: " + name + " not found or has no IPv4");
|
||||
}
|
||||
|
||||
std::vector<if_info> get_all_interaces() {
|
||||
struct ifaddrs* ifaddr{nullptr};
|
||||
struct ifaddrs* ifa{nullptr};
|
||||
if (getifaddrs(&ifaddr) == -1) {
|
||||
throw std::runtime_error("Cannot get interfaces: " + std::string(strerror(errno)));
|
||||
}
|
||||
|
||||
handle_disposer<ifaddrs, freeifaddrs> ifaddr_disposer(ifaddr);
|
||||
|
||||
std::vector<if_info> interfaces;
|
||||
|
||||
for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
|
||||
if (ifa->ifa_addr == nullptr || ifa->ifa_addr->sa_family != AF_INET) {
|
||||
continue;
|
||||
}
|
||||
interfaces.push_back({ifa->ifa_name, get_interface_address(ifa->ifa_name)});
|
||||
}
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
event::unique_fd open_udp_unconnected_socket(udp::endpoint const& target, std::string const& iface) {
|
||||
const auto family = target.family();
|
||||
if (family != AF_INET && family != AF_INET6) {
|
||||
throw std::runtime_error("open_udp_unconnected_socket: target has no usable address family");
|
||||
}
|
||||
|
||||
event::unique_fd sock(::socket(family, SOCK_DGRAM, 0));
|
||||
if (not sock.is_fd()) {
|
||||
throw std::runtime_error(build_errno_string("socket(SOCK_DGRAM) failed"));
|
||||
}
|
||||
set_non_blocking(sock);
|
||||
|
||||
// Wildcard bind on the target's family, ephemeral port. RX is by destination
|
||||
// address and port; interface confinement (if any) comes from the options below.
|
||||
if (family == AF_INET6) {
|
||||
sockaddr_in6 local{};
|
||||
local.sin6_family = AF_INET6;
|
||||
local.sin6_addr = in6addr_any;
|
||||
if (::bind(sock, reinterpret_cast<sockaddr*>(&local), sizeof(local)) < 0) {
|
||||
throw std::runtime_error(build_errno_string("bind([::]:0) failed"));
|
||||
}
|
||||
} else {
|
||||
sockaddr_in local{};
|
||||
local.sin_family = AF_INET;
|
||||
local.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
if (::bind(sock, reinterpret_cast<sockaddr*>(&local), sizeof(local)) < 0) {
|
||||
throw std::runtime_error(build_errno_string("bind(0.0.0.0:0) failed"));
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& device = iface.empty() ? target.iface() : iface;
|
||||
|
||||
// Best-effort link confinement. Not required for the unicast-reply RX path
|
||||
// (delivery is by destination address and port on the wildcard bind).
|
||||
// Defense-in-depth only on multi-NIC hosts; unprivileged failure is tolerated.
|
||||
if (not device.empty()) {
|
||||
try {
|
||||
bind_socket_to_device(sock, device);
|
||||
} catch (const std::exception&) {
|
||||
// ignore: multicast TX interface is pinned by set_multicast_if below
|
||||
}
|
||||
}
|
||||
|
||||
// Pin multicast egress to the chosen interface (no-op for unicast targets).
|
||||
// A mutable copy is required: set_multicast_if writes sin6_scope_id, which
|
||||
// is irrelevant here since the socket option is what matters.
|
||||
sockaddr_storage ss{};
|
||||
std::memcpy(&ss, target.sa(), target.sa_len());
|
||||
set_multicast_if(sock, device, reinterpret_cast<sockaddr*>(&ss));
|
||||
|
||||
// No ::connect(), no group join: a connected datagram socket would drop a
|
||||
// unicast reply whose source differs from the configured target; a group
|
||||
// join is a receiver-side concern and is not needed to send to the group.
|
||||
return sock;
|
||||
}
|
||||
|
||||
event::unique_fd open_udp_dualstack_server_socket(std::uint16_t port, std::string const& device) {
|
||||
event::unique_fd sock(::socket(AF_INET6, SOCK_DGRAM, 0));
|
||||
if (not sock.is_fd() && errno == EAFNOSUPPORT) {
|
||||
// IPv6 unavailable on this host: v4-only fallback.
|
||||
event::unique_fd v4(::socket(AF_INET, SOCK_DGRAM, 0));
|
||||
if (not v4.is_fd()) {
|
||||
throw std::runtime_error(build_errno_string("socket(AF_INET, SOCK_DGRAM) failed"));
|
||||
}
|
||||
set_reuse_address(v4);
|
||||
set_non_blocking(v4);
|
||||
sockaddr_in local{};
|
||||
local.sin_family = AF_INET;
|
||||
local.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
local.sin_port = htons(port);
|
||||
if (::bind(v4, reinterpret_cast<sockaddr*>(&local), sizeof(local)) < 0) {
|
||||
throw std::runtime_error(build_errno_string("bind(0.0.0.0:" + std::to_string(port) + ") failed"));
|
||||
}
|
||||
if (not device.empty()) {
|
||||
try {
|
||||
bind_socket_to_device(v4, device);
|
||||
} catch (const std::exception&) {
|
||||
// best-effort; wildcard bind retained
|
||||
}
|
||||
}
|
||||
return v4;
|
||||
}
|
||||
|
||||
if (not sock.is_fd()) {
|
||||
throw std::runtime_error(build_errno_string("socket(AF_INET6, SOCK_DGRAM) failed"));
|
||||
}
|
||||
int v6only = 0;
|
||||
if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)) < 0) {
|
||||
throw std::runtime_error(build_errno_string("setsockopt(IPV6_V6ONLY=0) failed"));
|
||||
}
|
||||
set_reuse_address(sock);
|
||||
set_non_blocking(sock);
|
||||
sockaddr_in6 local{};
|
||||
local.sin6_family = AF_INET6;
|
||||
local.sin6_addr = in6addr_any;
|
||||
local.sin6_port = htons(port);
|
||||
if (::bind(sock, reinterpret_cast<sockaddr*>(&local), sizeof(local)) < 0) {
|
||||
throw std::runtime_error(build_errno_string("bind([::]:" + std::to_string(port) + ") failed"));
|
||||
}
|
||||
if (not device.empty()) {
|
||||
try {
|
||||
bind_socket_to_device(sock, device);
|
||||
} catch (const std::exception&) {
|
||||
// best-effort; wildcard bind retained
|
||||
}
|
||||
}
|
||||
return sock;
|
||||
}
|
||||
|
||||
event::unique_fd open_udp_multicast_socket(std::string const& multicast_group, std::uint16_t port,
|
||||
std::string interface_address, std::string listen_address,
|
||||
bool reuse_address, bool reuse_port) {
|
||||
auto sock = event::unique_fd(::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP));
|
||||
set_non_blocking(sock);
|
||||
if (reuse_address) {
|
||||
set_reuse_address(sock);
|
||||
}
|
||||
if (reuse_port) {
|
||||
set_reuse_port(sock);
|
||||
}
|
||||
|
||||
bind_socket_ip4(sock, listen_address, port);
|
||||
set_udp_multicast(sock, multicast_group, interface_address);
|
||||
return sock;
|
||||
}
|
||||
|
||||
event::unique_fd open_mdns_socket(std::string const& interface_name) {
|
||||
auto ip = get_interface_address(interface_name);
|
||||
auto sock = open_udp_multicast_socket("224.0.0.251", 5353, ip, "0.0.0.0", true, true);
|
||||
if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, interface_name.c_str(), interface_name.length()) < 0) {
|
||||
throw std::runtime_error("Failed to bind socket to device " + interface_name + " -> " + strerror(errno));
|
||||
}
|
||||
return sock;
|
||||
}
|
||||
|
||||
void set_reuse_address(int fd) {
|
||||
socklen_t enable = 1;
|
||||
auto err = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
|
||||
if (err) {
|
||||
throw std::runtime_error("Failed to setsockopt(SO_REUSEADDRE)");
|
||||
}
|
||||
}
|
||||
|
||||
void set_reuse_port(int fd) {
|
||||
#ifdef SO_REUSEPORT
|
||||
socklen_t enable = 1;
|
||||
auto err = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(enable));
|
||||
if (err) {
|
||||
throw std::runtime_error("Failed to setsockopt(SO_REUSEADDRE)");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void bind_socket_ip4(int fd, std::string const& ip, std::uint16_t port) {
|
||||
sockaddr_in local_bind_addr{};
|
||||
int result = inet_pton(AF_INET, ip.c_str(), &local_bind_addr.sin_addr.s_addr);
|
||||
if (result not_eq 1) {
|
||||
throw std::runtime_error("Invalid IP address: " + ip);
|
||||
}
|
||||
|
||||
local_bind_addr.sin_family = AF_INET;
|
||||
local_bind_addr.sin_port = htons(port);
|
||||
local_bind_addr.sin_addr.s_addr = ip_to_s_addr(ip);
|
||||
if (bind(fd, (struct sockaddr*)&local_bind_addr, sizeof(local_bind_addr)) < 0) {
|
||||
throw std::runtime_error("Cannot bind socket to " + ip + ":" + std::to_string(port) + " -> " + strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
void set_udp_multicast(int fd, std::string const& multicast_ip, std::string const& interface_ip) {
|
||||
struct in_addr out_addr;
|
||||
out_addr.s_addr = inet_addr(interface_ip.c_str());
|
||||
|
||||
auto err = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, (char*)&out_addr, sizeof(out_addr));
|
||||
if (err) {
|
||||
throw std::runtime_error("Failed to setsockopt(IP_MULTICAST_IF)");
|
||||
}
|
||||
|
||||
ip_mreq mreq;
|
||||
mreq.imr_multiaddr.s_addr = ip_to_s_addr(multicast_ip);
|
||||
mreq.imr_interface.s_addr = ip_to_s_addr(interface_ip);
|
||||
err = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
|
||||
if (err) {
|
||||
throw std::runtime_error("Failed to setsockopt(IP_ADD_MEMBERSHIP)");
|
||||
}
|
||||
}
|
||||
|
||||
std::uint32_t ip_to_s_addr(std::string const& ip) {
|
||||
std::uint32_t s_addr;
|
||||
int result = inet_pton(AF_INET, ip.c_str(), &s_addr);
|
||||
if (result not_eq 1) {
|
||||
throw std::runtime_error("Invalid IP address: " + ip);
|
||||
}
|
||||
return s_addr;
|
||||
}
|
||||
|
||||
} // namespace socket
|
||||
|
||||
} // namespace everest::lib::io
|
||||
126
tools/EVerest-main/lib/everest/io/src/tcp/tcp_socket.cpp
Normal file
126
tools/EVerest-main/lib/everest/io/src/tcp/tcp_socket.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#include <chrono>
|
||||
#include <everest/io/socket/socket.hpp>
|
||||
#include <everest/io/tcp/tcp_socket.hpp>
|
||||
#include <netdb.h>
|
||||
#include <thread>
|
||||
|
||||
namespace everest::lib::io::tcp {
|
||||
|
||||
bool tcp_socket::open(std::string const& remote, uint16_t port, std::string const& device) {
|
||||
m_remote = remote;
|
||||
m_port = port;
|
||||
m_timeout_ms = 1000;
|
||||
m_device = device;
|
||||
try {
|
||||
auto socket = socket::open_tcp_socket_with_timeout(remote, port, m_timeout_ms, m_device);
|
||||
socket::set_non_blocking(socket);
|
||||
m_fd = std::move(socket);
|
||||
return socket::get_pending_error(m_fd) == 0;
|
||||
} catch (...) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool tcp_socket::setup(std::string const& remote, uint16_t port, int timeout_ms, std::string const& device) {
|
||||
m_remote = remote;
|
||||
m_port = port;
|
||||
m_timeout_ms = timeout_ms;
|
||||
m_device = device;
|
||||
m_fd.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
void tcp_socket::connect(std::function<void(bool, int)> const& setup_cb) {
|
||||
try {
|
||||
auto socket = socket::open_tcp_socket_with_timeout(m_remote, m_port, m_timeout_ms, m_device);
|
||||
socket::set_non_blocking(socket);
|
||||
const auto fd = static_cast<int>(socket);
|
||||
m_fd = std::move(socket);
|
||||
setup_cb(true, fd);
|
||||
} catch (...) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(m_timeout_ms));
|
||||
setup_cb(false, -1);
|
||||
}
|
||||
}
|
||||
|
||||
bool tcp_socket::tx(PayloadT& payload) {
|
||||
if (not is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto status = ::send(m_fd, payload.data(), payload.size(), 0);
|
||||
if (status == -1) {
|
||||
return false;
|
||||
}
|
||||
if (status < static_cast<ssize_t>(payload.size())) {
|
||||
// We have a reference to the current data. Replace it with what is left to be written
|
||||
// and return false. This signals the current block cannot be removed from the buffer.
|
||||
payload = {payload.begin() + status, payload.end()};
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tcp_socket::rx(PayloadT& buffer) {
|
||||
if (not is_open()) {
|
||||
return false;
|
||||
}
|
||||
buffer.resize(default_buffer_size);
|
||||
auto status = ::recv(m_fd, buffer.data(), buffer.size(), 0);
|
||||
if (status <= 0) { // -1 is an error, 0 is a connection closed by the peer
|
||||
return false;
|
||||
}
|
||||
buffer.resize(status);
|
||||
return true;
|
||||
}
|
||||
|
||||
int tcp_socket::get_fd() const {
|
||||
return m_fd;
|
||||
}
|
||||
|
||||
int tcp_socket::get_error() const {
|
||||
if (socket::is_tcp_socket_alive(m_fd)) {
|
||||
return socket::get_pending_error(m_fd);
|
||||
} else if (is_open()) {
|
||||
return ECONNRESET;
|
||||
}
|
||||
return socket::get_pending_error(m_fd);
|
||||
}
|
||||
|
||||
bool tcp_socket::is_open() const {
|
||||
return m_fd.is_fd();
|
||||
}
|
||||
|
||||
void tcp_socket::close() {
|
||||
m_fd.close();
|
||||
}
|
||||
|
||||
bool tcp_socket::set_keep_alive(uint32_t count, uint32_t idle_s, uint32_t intval_s) {
|
||||
if (not is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
socket::set_tcp_keepalive(m_fd, count, idle_s, intval_s);
|
||||
return true;
|
||||
} catch (...) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool tcp_socket::set_user_timeout(uint32_t to_ms) {
|
||||
if (not is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
socket::set_tcp_user_timeout(m_fd, to_ms);
|
||||
return true;
|
||||
} catch (...) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::tcp
|
||||
@@ -0,0 +1,65 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#include <cstring>
|
||||
#include <everest/io/tun_tap/tap_handler.hpp>
|
||||
#include <fcntl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linux/if.h>
|
||||
#include <linux/if_tun.h>
|
||||
|
||||
#include <everest/io/socket/socket.hpp>
|
||||
|
||||
namespace everest::lib::io::tun_tap {
|
||||
|
||||
bool tap_handler::open(std::string const& device, std::string const& ip, std::string const& netmask, int mtu) {
|
||||
m_mtu = mtu;
|
||||
try {
|
||||
m_fd = socket::create_tap_device(device);
|
||||
} catch (...) {
|
||||
m_error = EPERM;
|
||||
return false;
|
||||
}
|
||||
if (not socket::configure_tap_device_properties(m_fd, device, ip, netmask, mtu)) {
|
||||
m_error = EPERM;
|
||||
m_fd.close();
|
||||
return false;
|
||||
}
|
||||
m_error = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tap_handler::tx(PayloadT const& data) {
|
||||
auto res = ::write(m_fd, data.data(), data.size());
|
||||
if (res != static_cast<ssize_t>(data.size())) {
|
||||
m_error = errno;
|
||||
return false;
|
||||
}
|
||||
m_error = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tap_handler::rx(PayloadT& data) {
|
||||
data.resize(m_mtu);
|
||||
auto res = ::read(m_fd, data.data(), data.size());
|
||||
if (res < 0) {
|
||||
m_error = errno;
|
||||
return false;
|
||||
}
|
||||
|
||||
data.resize(res);
|
||||
m_error = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
int tap_handler::get_fd() const {
|
||||
return m_fd;
|
||||
}
|
||||
|
||||
int tap_handler::get_error() const {
|
||||
return m_error;
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::tun_tap
|
||||
156
tools/EVerest-main/lib/everest/io/src/udp/endpoint.cpp
Normal file
156
tools/EVerest-main/lib/everest/io/src/udp/endpoint.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/io/udp/endpoint.hpp>
|
||||
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#include <netdb.h>
|
||||
|
||||
namespace everest::lib::io::udp {
|
||||
|
||||
namespace {
|
||||
|
||||
// Set sin6_scope_id from @p iface for IPv6 addresses that need a scope
|
||||
// (link-local fe80::/10 or multicast ff00::/8). Throws if the interface is
|
||||
// unknown. No-op for global addresses or an empty interface.
|
||||
void apply_v6_scope(sockaddr_in6& sin6, std::string const& iface) {
|
||||
if (iface.empty()) {
|
||||
return;
|
||||
}
|
||||
if (not IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr) && not IN6_IS_ADDR_MULTICAST(&sin6.sin6_addr)) {
|
||||
return;
|
||||
}
|
||||
unsigned int ifindex = if_nametoindex(iface.c_str());
|
||||
if (ifindex == 0) {
|
||||
throw std::runtime_error("if_nametoindex(\"" + iface + "\") failed");
|
||||
}
|
||||
sin6.sin6_scope_id = ifindex;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
endpoint::endpoint(std::string const& host, std::uint16_t port, std::string iface) : m_iface(std::move(iface)) {
|
||||
sockaddr_in v4{};
|
||||
if (inet_pton(AF_INET, host.c_str(), &v4.sin_addr) == 1) {
|
||||
v4.sin_family = AF_INET;
|
||||
v4.sin_port = htons(port);
|
||||
std::memcpy(&m_storage, &v4, sizeof(v4));
|
||||
m_len = sizeof(v4);
|
||||
return;
|
||||
}
|
||||
|
||||
sockaddr_in6 v6{};
|
||||
if (inet_pton(AF_INET6, host.c_str(), &v6.sin6_addr) == 1) {
|
||||
v6.sin6_family = AF_INET6;
|
||||
v6.sin6_port = htons(port);
|
||||
apply_v6_scope(v6, m_iface);
|
||||
std::memcpy(&m_storage, &v6, sizeof(v6));
|
||||
m_len = sizeof(v6);
|
||||
return;
|
||||
}
|
||||
|
||||
addrinfo hints{};
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
addrinfo* res = nullptr;
|
||||
const auto err = ::getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, &res);
|
||||
if (err != 0 || res == nullptr) {
|
||||
throw std::runtime_error("Failed to resolve endpoint \"" + host + "\": " + ::gai_strerror(err));
|
||||
}
|
||||
std::memcpy(&m_storage, res->ai_addr, res->ai_addrlen);
|
||||
m_len = res->ai_addrlen;
|
||||
::freeaddrinfo(res);
|
||||
|
||||
if (m_storage.ss_family == AF_INET6) {
|
||||
sockaddr_in6 scoped{};
|
||||
std::memcpy(&scoped, &m_storage, sizeof(scoped));
|
||||
scoped.sin6_port = htons(port);
|
||||
apply_v6_scope(scoped, m_iface);
|
||||
std::memcpy(&m_storage, &scoped, sizeof(scoped));
|
||||
}
|
||||
}
|
||||
|
||||
endpoint::endpoint(sockaddr_storage const& src, socklen_t len) : m_len(len) {
|
||||
std::memcpy(&m_storage, &src, sizeof(m_storage));
|
||||
}
|
||||
|
||||
sa_family_t endpoint::family() const {
|
||||
return m_len == 0 ? AF_UNSPEC : m_storage.ss_family;
|
||||
}
|
||||
|
||||
std::uint16_t endpoint::port() const {
|
||||
if (m_storage.ss_family == AF_INET) {
|
||||
return ntohs(reinterpret_cast<sockaddr_in const*>(&m_storage)->sin_port);
|
||||
}
|
||||
if (m_storage.ss_family == AF_INET6) {
|
||||
return ntohs(reinterpret_cast<sockaddr_in6 const*>(&m_storage)->sin6_port);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string endpoint::addr_str() const {
|
||||
char buf[INET6_ADDRSTRLEN]{};
|
||||
if (m_storage.ss_family == AF_INET) {
|
||||
inet_ntop(AF_INET, &reinterpret_cast<sockaddr_in const*>(&m_storage)->sin_addr, buf, sizeof(buf));
|
||||
} else if (m_storage.ss_family == AF_INET6) {
|
||||
inet_ntop(AF_INET6, &reinterpret_cast<sockaddr_in6 const*>(&m_storage)->sin6_addr, buf, sizeof(buf));
|
||||
}
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
sockaddr const* endpoint::sa() const {
|
||||
return reinterpret_cast<sockaddr const*>(&m_storage);
|
||||
}
|
||||
|
||||
socklen_t endpoint::sa_len() const {
|
||||
return m_len;
|
||||
}
|
||||
|
||||
std::string const& endpoint::iface() const {
|
||||
return m_iface;
|
||||
}
|
||||
|
||||
bool endpoint::is_v4_mapped() const {
|
||||
if (m_storage.ss_family != AF_INET6) {
|
||||
return false;
|
||||
}
|
||||
auto const* s6 = reinterpret_cast<sockaddr_in6 const*>(&m_storage);
|
||||
return IN6_IS_ADDR_V4MAPPED(&s6->sin6_addr);
|
||||
}
|
||||
|
||||
endpoint endpoint::as_v4() const {
|
||||
if (not is_v4_mapped()) {
|
||||
return endpoint{};
|
||||
}
|
||||
auto const* s6 = reinterpret_cast<sockaddr_in6 const*>(&m_storage);
|
||||
sockaddr_storage ss{};
|
||||
auto* s4 = reinterpret_cast<sockaddr_in*>(&ss);
|
||||
s4->sin_family = AF_INET;
|
||||
s4->sin_port = s6->sin6_port; // network order preserved
|
||||
std::memcpy(&s4->sin_addr.s_addr, reinterpret_cast<uint8_t const*>(&s6->sin6_addr) + 12, 4);
|
||||
return endpoint(ss, sizeof(sockaddr_in));
|
||||
}
|
||||
|
||||
bool endpoint::operator==(endpoint const& other) const {
|
||||
if (m_storage.ss_family != other.m_storage.ss_family) {
|
||||
return false;
|
||||
}
|
||||
if (m_storage.ss_family == AF_INET) {
|
||||
auto const* a = reinterpret_cast<sockaddr_in const*>(&m_storage);
|
||||
auto const* b = reinterpret_cast<sockaddr_in const*>(&other.m_storage);
|
||||
return a->sin_port == b->sin_port && a->sin_addr.s_addr == b->sin_addr.s_addr;
|
||||
}
|
||||
if (m_storage.ss_family == AF_INET6) {
|
||||
auto const* a = reinterpret_cast<sockaddr_in6 const*>(&m_storage);
|
||||
auto const* b = reinterpret_cast<sockaddr_in6 const*>(&other.m_storage);
|
||||
return a->sin6_port == b->sin6_port && a->sin6_scope_id == b->sin6_scope_id &&
|
||||
std::memcmp(&a->sin6_addr, &b->sin6_addr, sizeof(in6_addr)) == 0;
|
||||
}
|
||||
return m_len == other.m_len;
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::udp
|
||||
@@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/io/udp/udp_dualstack_server_socket.hpp>
|
||||
|
||||
#include <exception>
|
||||
#include <utility>
|
||||
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <everest/io/socket/socket.hpp>
|
||||
|
||||
namespace everest::lib::io::udp {
|
||||
|
||||
bool udp_dualstack_server_socket::open(std::uint16_t port, std::string device) {
|
||||
try {
|
||||
m_owned_udp_fd = socket::open_udp_dualstack_server_socket(port, device);
|
||||
return socket::get_pending_error(m_owned_udp_fd) == 0;
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool udp_dualstack_server_socket::tx(udp_payload const& payload) {
|
||||
if (not m_owned_udp_fd.is_fd() or not m_last_source) {
|
||||
return false;
|
||||
}
|
||||
const auto nbytes = ::sendto(m_owned_udp_fd, payload.buffer.data(), payload.size(), 0, m_last_source->sa(),
|
||||
m_last_source->sa_len());
|
||||
return nbytes == static_cast<ssize_t>(payload.size());
|
||||
}
|
||||
|
||||
bool udp_dualstack_server_socket::rx(udp_payload& payload) {
|
||||
if (not m_owned_udp_fd.is_fd()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sockaddr_storage src{};
|
||||
socklen_t src_len = sizeof(src);
|
||||
const auto nbytes = ::recvfrom(m_owned_udp_fd, m_rx_buffer.data(), m_rx_buffer.size(), 0,
|
||||
reinterpret_cast<sockaddr*>(&src), &src_len);
|
||||
if (nbytes < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
payload.set_message(m_rx_buffer.data(), static_cast<size_t>(nbytes));
|
||||
m_last_source = endpoint(src, src_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
int udp_dualstack_server_socket::get_fd() const {
|
||||
return m_owned_udp_fd;
|
||||
}
|
||||
|
||||
int udp_dualstack_server_socket::get_error() const {
|
||||
return socket::get_pending_error(m_owned_udp_fd);
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::udp
|
||||
42
tools/EVerest-main/lib/everest/io/src/udp/udp_payload.cpp
Normal file
42
tools/EVerest-main/lib/everest/io/src/udp/udp_payload.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/io/udp/udp_payload.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace everest::lib::io::udp {
|
||||
|
||||
udp_payload::udp_payload(std::string const& msg) {
|
||||
set_message(msg);
|
||||
}
|
||||
|
||||
udp_payload::udp_payload(const char* msg) {
|
||||
set_message(msg);
|
||||
}
|
||||
|
||||
bool udp_payload::operator==(udp_payload const& other) const {
|
||||
return buffer == other.buffer;
|
||||
}
|
||||
|
||||
size_t udp_payload::size() const {
|
||||
return buffer.size();
|
||||
}
|
||||
|
||||
bool udp_payload::set_message(std::string const& msg) {
|
||||
if (msg.size() > max_size) {
|
||||
return false;
|
||||
}
|
||||
buffer = std::vector<uint8_t>(msg.begin(), msg.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool udp_payload::set_message(void const* data, size_t size) {
|
||||
if (size > max_size) {
|
||||
return false;
|
||||
}
|
||||
auto ptr = reinterpret_cast<uint8_t const*>(data);
|
||||
buffer = std::vector<uint8_t>(ptr, ptr + size);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::udp
|
||||
155
tools/EVerest-main/lib/everest/io/src/udp/udp_socket.cpp
Normal file
155
tools/EVerest-main/lib/everest/io/src/udp/udp_socket.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "everest/io/udp/udp_payload.hpp"
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <everest/io/event/unique_fd.hpp>
|
||||
#include <everest/io/socket/socket.hpp>
|
||||
#include <everest/io/udp/udp_socket.hpp>
|
||||
#include <iostream>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <optional>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace everest::lib::io::udp {
|
||||
|
||||
bool udp_socket_base::open_as_client(std::string const& remote, uint16_t port, std::string const& device) {
|
||||
try {
|
||||
auto socket = socket::open_udp_client_socket(remote, port, device);
|
||||
socket::set_non_blocking(socket);
|
||||
m_owned_udp_fd = std::move(socket);
|
||||
return socket::get_pending_error(m_owned_udp_fd) == 0;
|
||||
} catch (...) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool udp_socket_base::open_as_server(uint16_t port, std::string const& device) {
|
||||
auto socket = socket::open_udp_server_socket(port, device);
|
||||
socket::set_non_blocking(socket);
|
||||
m_owned_udp_fd = std::move(socket);
|
||||
return socket::get_pending_error(m_owned_udp_fd) == 0;
|
||||
}
|
||||
|
||||
bool udp_socket_base::is_open() {
|
||||
return m_owned_udp_fd.is_fd();
|
||||
}
|
||||
|
||||
void udp_socket_base::close() {
|
||||
m_owned_udp_fd.close();
|
||||
}
|
||||
|
||||
int udp_socket_base::get_fd() const {
|
||||
return m_owned_udp_fd;
|
||||
}
|
||||
|
||||
int udp_socket_base::get_error() const {
|
||||
return socket::get_pending_error(m_owned_udp_fd);
|
||||
}
|
||||
|
||||
bool udp_socket_base::tx_impl(void const* payload, size_t size) {
|
||||
if (not is_open()) {
|
||||
return ENETDOWN;
|
||||
}
|
||||
size_t nbytes = ::send(m_owned_udp_fd, payload, size, 0);
|
||||
|
||||
return nbytes == size;
|
||||
}
|
||||
|
||||
bool udp_socket_base::tx_impl(void const* payload, size_t size, udp_info const& destination) {
|
||||
if (not is_open()) {
|
||||
return false;
|
||||
}
|
||||
socklen_t peer_addr_len = sizeof(struct sockaddr_in);
|
||||
struct sockaddr_in peer_addr;
|
||||
peer_addr.sin_port = destination.port;
|
||||
peer_addr.sin_addr.s_addr = destination.addr;
|
||||
peer_addr.sin_family = destination.family;
|
||||
size_t nbytes = ::sendto(m_owned_udp_fd, payload, size, 0, (struct sockaddr*)&peer_addr, peer_addr_len);
|
||||
|
||||
return nbytes == size;
|
||||
}
|
||||
|
||||
std::optional<udp_info> udp_socket_base::rx_impl(void* buffer, size_t buffer_size, ssize_t& payload_size) {
|
||||
socklen_t peer_addr_len = sizeof(struct sockaddr_in);
|
||||
struct sockaddr_in peer_addr;
|
||||
if (is_open()) {
|
||||
payload_size = ::recvfrom(m_owned_udp_fd, buffer, buffer_size, 0, (struct sockaddr*)&peer_addr, &peer_addr_len);
|
||||
if (payload_size >= 0) {
|
||||
return udp_info{peer_addr.sin_addr.s_addr, peer_addr.sin_port, peer_addr.sin_family};
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
bool udp_client_socket::setup(std::string const& remote, uint16_t port, int timeout_ms, std::string const& device) {
|
||||
m_remote = remote;
|
||||
m_port = port;
|
||||
m_timeout_ms = timeout_ms;
|
||||
m_device = device;
|
||||
m_owned_udp_fd.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
void udp_client_socket::connect(std::function<void(bool, int)> const& setup_cb) {
|
||||
try {
|
||||
auto socket = socket::open_udp_client_socket(m_remote, m_port, m_device);
|
||||
socket::set_non_blocking(socket);
|
||||
setup_cb(true, socket);
|
||||
m_owned_udp_fd = std::move(socket);
|
||||
} catch (...) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(m_timeout_ms));
|
||||
setup_cb(false, -1);
|
||||
}
|
||||
}
|
||||
|
||||
bool udp_client_socket::open(std::string const& remote, uint16_t port, std::string const& device) {
|
||||
return open_as_client(remote, port, device);
|
||||
}
|
||||
|
||||
bool udp_client_socket::tx(udp_payload const& payload) {
|
||||
return tx_impl(payload.buffer.data(), payload.size());
|
||||
}
|
||||
|
||||
bool udp_client_socket::rx(udp_payload& payload) {
|
||||
ssize_t msg_size = 0;
|
||||
auto result = rx_impl(rx_buffer.data(), rx_buffer.size(), msg_size);
|
||||
if (result) {
|
||||
payload.set_message(rx_buffer.data(), msg_size);
|
||||
}
|
||||
return result.has_value();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
bool udp_server_socket::open(uint16_t port, std::string const& device) {
|
||||
return open_as_server(port, device);
|
||||
}
|
||||
|
||||
bool udp_server_socket::tx(udp_payload const& payload) {
|
||||
if (not m_last_source) {
|
||||
return false;
|
||||
}
|
||||
return tx_impl(payload.buffer.data(), payload.size(), *m_last_source);
|
||||
}
|
||||
|
||||
bool udp_server_socket::rx(udp_payload& payload) {
|
||||
ssize_t msg_size = 0;
|
||||
auto result = rx_impl(rx_buffer.data(), rx_buffer.size(), msg_size);
|
||||
if (not result) {
|
||||
return false;
|
||||
}
|
||||
payload.set_message(rx_buffer.data(), msg_size);
|
||||
m_last_source = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::udp
|
||||
@@ -0,0 +1,61 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/io/udp/udp_unconnected_socket.hpp>
|
||||
|
||||
#include <exception>
|
||||
#include <utility>
|
||||
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <everest/io/socket/socket.hpp>
|
||||
|
||||
namespace everest::lib::io::udp {
|
||||
|
||||
bool udp_unconnected_socket::open(endpoint target, std::string iface) {
|
||||
try {
|
||||
m_owned_udp_fd = socket::open_udp_unconnected_socket(target, iface);
|
||||
m_target = std::move(target);
|
||||
return socket::get_pending_error(m_owned_udp_fd) == 0;
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool udp_unconnected_socket::tx(udp_payload const& payload) {
|
||||
if (not m_owned_udp_fd.is_fd()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto nbytes =
|
||||
::sendto(m_owned_udp_fd, payload.buffer.data(), payload.size(), 0, m_target.sa(), m_target.sa_len());
|
||||
return nbytes == static_cast<ssize_t>(payload.size());
|
||||
}
|
||||
|
||||
bool udp_unconnected_socket::rx(udp_payload& payload) {
|
||||
if (not m_owned_udp_fd.is_fd()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sockaddr_storage src{};
|
||||
socklen_t src_len = sizeof(src);
|
||||
const auto nbytes = ::recvfrom(m_owned_udp_fd, m_rx_buffer.data(), m_rx_buffer.size(), 0,
|
||||
reinterpret_cast<sockaddr*>(&src), &src_len);
|
||||
if (nbytes < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
payload.set_message(m_rx_buffer.data(), static_cast<size_t>(nbytes));
|
||||
m_last_source = endpoint(src, src_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
int udp_unconnected_socket::get_fd() const {
|
||||
return m_owned_udp_fd;
|
||||
}
|
||||
|
||||
int udp_unconnected_socket::get_error() const {
|
||||
return socket::get_pending_error(m_owned_udp_fd);
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::udp
|
||||
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include <everest/io/utilities/generic_error_state.hpp>
|
||||
#include <string.h>
|
||||
|
||||
namespace everest::lib::io::utilities {
|
||||
|
||||
bool generic_error_state::set_error_status(int error_code) {
|
||||
m_current_error = error_code;
|
||||
auto on_error = error_code != 0;
|
||||
m_clear_error_pending = (not on_error) and m_on_error;
|
||||
m_on_error = on_error;
|
||||
return not m_on_error;
|
||||
}
|
||||
|
||||
bool generic_error_state::clear_error_pending() const {
|
||||
return m_clear_error_pending;
|
||||
}
|
||||
|
||||
bool generic_error_state::on_error() const {
|
||||
return m_on_error;
|
||||
}
|
||||
|
||||
int generic_error_state::current_error() const {
|
||||
return m_current_error;
|
||||
}
|
||||
|
||||
void generic_error_state::call_error_handler(cb_error& handler) const {
|
||||
if (handler) {
|
||||
handler(m_current_error, strerror(m_current_error));
|
||||
}
|
||||
}
|
||||
|
||||
void generic_error_state::clear_error_handler(cb_error& handler) {
|
||||
if (handler) {
|
||||
handler(0, strerror(0));
|
||||
}
|
||||
set_error_cleared();
|
||||
}
|
||||
|
||||
void generic_error_state::set_error_cleared() {
|
||||
m_clear_error_pending = false;
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::utilities
|
||||
@@ -0,0 +1,38 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <chrono>
|
||||
#include <everest/io/utilities/stop_watch.hpp>
|
||||
#include <iostream>
|
||||
|
||||
namespace everest::lib::io::utilities {
|
||||
|
||||
stop_watch::stop_watch(std::string const& id) : m_id(id) {
|
||||
m_start_time = clock::now();
|
||||
}
|
||||
|
||||
stop_watch::tp stop_watch::reset() {
|
||||
m_start_time = clock::now();
|
||||
return m_start_time;
|
||||
}
|
||||
|
||||
stop_watch::us stop_watch::stop() {
|
||||
m_end_time = clock::now();
|
||||
auto dura = std::chrono::duration_cast<us>(m_end_time - m_start_time);
|
||||
return dura;
|
||||
}
|
||||
|
||||
stop_watch::us stop_watch::lap() const {
|
||||
auto current = clock::now();
|
||||
auto dura = std::chrono::duration_cast<us>(current - m_start_time);
|
||||
return dura;
|
||||
}
|
||||
|
||||
stop_watch::~stop_watch() {
|
||||
if (not m_id.empty()) {
|
||||
auto dura = stop();
|
||||
std::cout << "StopWatch ( " << m_id << " ) duration " << dura.count() << "us" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace everest::lib::io::utilities
|
||||
15
tools/EVerest-main/lib/everest/io/test/CMakeLists.txt
Normal file
15
tools/EVerest-main/lib/everest/io/test/CMakeLists.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
add_executable(everest_io_tests
|
||||
endpoint_test.cpp
|
||||
udp_client_v6_test.cpp
|
||||
udp_unconnected_socket_test.cpp
|
||||
udp_dualstack_server_socket_test.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(everest_io_tests
|
||||
PRIVATE
|
||||
GTest::gtest_main
|
||||
everest::io
|
||||
)
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(everest_io_tests TEST_PREFIX "io.")
|
||||
121
tools/EVerest-main/lib/everest/io/test/datagram_selftest.sh
Executable file
121
tools/EVerest-main/lib/everest/io/test/datagram_selftest.sh
Executable file
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env bash
|
||||
# datagram_selftest.sh - privileged real-multicast self-test for the generic
|
||||
# unconnected UDP client (udp_unconnected_client / udp_unconnected_socket).
|
||||
#
|
||||
# NOT a ctest case. The gtest suite (lib/everest/io/test/) covers parsing,
|
||||
# loopback round-trips and connect-drop-absence on the loopback interface.
|
||||
# This script exercises the one thing loopback cannot: a *real* multicast
|
||||
# send on a dedicated NIC followed by a *unicast* reply from a source whose
|
||||
# address differs from the multicast group. A connected socket would drop
|
||||
# that reply; the unconnected client must deliver it.
|
||||
#
|
||||
# It is fully rootless: it runs inside a user+network namespace via
|
||||
# unshare --user --map-root-user --net
|
||||
# so it needs no sudo and touches no host interface. It builds its own
|
||||
# `dummy` NIC, runs a group-joining responder that answers unicast, and
|
||||
# checks the example client receives the reply from the responder's unicast
|
||||
# address (!= group) for both an IPv4 and an IPv6 multicast group.
|
||||
#
|
||||
# Usage:
|
||||
# lib/everest/io/test/datagram_selftest.sh [path-to-test_udp_unconnected_client]
|
||||
#
|
||||
# Exit status: 0 = both v4 and v6 passed; non-zero = failure.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
|
||||
DEFAULT_BIN="${ROOT:-.}/build/lib/everest/io/examples/test_udp_unconnected_client"
|
||||
CLIENT_BIN="${1:-$DEFAULT_BIN}"
|
||||
|
||||
if [ ! -x "$CLIENT_BIN" ]; then
|
||||
echo "ERROR: client example not found/executable: $CLIENT_BIN" >&2
|
||||
echo "Build it with: cmake ... -DBUILD_EXAMPLES=ON && ninja -C build test_udp_unconnected_client" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if ! unshare --user --map-root-user --net true 2>/dev/null; then
|
||||
echo "ERROR: rootless 'unshare --user --map-root-user --net' unavailable on this host" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Everything below runs inside the fresh user+net namespace. The inner script
|
||||
# is single-quoted on purpose: it is interpreted by the namespaced bash, where
|
||||
# CLIENT_BIN arrives via the exported environment.
|
||||
export CLIENT_BIN
|
||||
# shellcheck disable=SC2016
|
||||
exec unshare --user --map-root-user --net -- bash -euo pipefail -c '
|
||||
IFACE=dt0
|
||||
V4_GROUP=239.55.0.1
|
||||
V4_IF_ADDR=10.55.0.1
|
||||
V6_GROUP=ff12::55
|
||||
V6_IF_ADDR=fd00:55::1
|
||||
PORT=49555
|
||||
|
||||
ip link set lo up
|
||||
ip link add "$IFACE" type dummy
|
||||
ip addr add "$V4_IF_ADDR"/24 dev "$IFACE"
|
||||
ip -6 addr add "$V6_IF_ADDR"/64 dev "$IFACE" nodad
|
||||
ip link set "$IFACE" up
|
||||
# Route the admin-scoped multicast ranges out the dummy NIC.
|
||||
ip route add 239.0.0.0/8 dev "$IFACE"
|
||||
ip -6 route add ff00::/8 dev "$IFACE"
|
||||
|
||||
RESPONDER=$(mktemp)
|
||||
trap "rm -f \"$RESPONDER\"" EXIT
|
||||
cat > "$RESPONDER" <<"PY"
|
||||
import socket, struct, sys
|
||||
fam = socket.AF_INET6 if sys.argv[1] == "6" else socket.AF_INET
|
||||
group, ifaddr, port = sys.argv[2], sys.argv[3], int(sys.argv[4])
|
||||
s = socket.socket(fam, socket.SOCK_DGRAM)
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
if fam == socket.AF_INET:
|
||||
s.bind(("", port))
|
||||
mreq = socket.inet_pton(socket.AF_INET, group) + socket.inet_aton(ifaddr)
|
||||
s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
|
||||
else:
|
||||
s.bind(("", port))
|
||||
ifidx = socket.if_nametoindex("dt0")
|
||||
mreq = socket.inet_pton(socket.AF_INET6, group) + struct.pack("I", ifidx)
|
||||
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
|
||||
s.settimeout(8)
|
||||
# Reply unicast from our interface address (source != multicast group).
|
||||
r = socket.socket(fam, socket.SOCK_DGRAM)
|
||||
r.bind((ifaddr, 0))
|
||||
try:
|
||||
while True:
|
||||
data, addr = s.recvfrom(2048)
|
||||
r.sendto(b"reply:" + data, addr)
|
||||
except socket.timeout:
|
||||
pass
|
||||
PY
|
||||
|
||||
run_case() {
|
||||
fam="$1"; group="$2"; ifaddr="$3"; label="$4"
|
||||
python3 "$RESPONDER" "$fam" "$group" "$ifaddr" "$PORT" &
|
||||
rpid=$!
|
||||
sleep 0.7
|
||||
out=$(stdbuf -oL timeout 4 stdbuf -oL "$CLIENT_BIN" "$group" "$PORT" "$IFACE" 2>&1 || true)
|
||||
wait "$rpid" 2>/dev/null || true
|
||||
echo "----- $label client output -----"
|
||||
echo "$out" | grep -aE "TX:|RX \(" | head -6 || true
|
||||
# Pass: an RX line whose source address is the responder unicast addr,
|
||||
# which is NOT the multicast group.
|
||||
if echo "$out" | grep -aq "RX (.*from \[${ifaddr}\]" && \
|
||||
! echo "$out" | grep -aq "RX (.*from \[${group}\]"; then
|
||||
echo "PASS ($label): unicast reply from $ifaddr delivered (group $group)"
|
||||
return 0
|
||||
fi
|
||||
echo "FAIL ($label): no unicast reply from $ifaddr (group $group)"
|
||||
return 1
|
||||
}
|
||||
|
||||
rc=0
|
||||
run_case 4 "$V4_GROUP" "$V4_IF_ADDR" "IPv4" || rc=1
|
||||
run_case 6 "$V6_GROUP" "$V6_IF_ADDR" "IPv6" || rc=1
|
||||
if [ "$rc" -eq 0 ]; then
|
||||
echo "datagram_selftest: ALL PASS (v4 + v6)"
|
||||
else
|
||||
echo "datagram_selftest: FAILURES" >&2
|
||||
fi
|
||||
exit "$rc"
|
||||
'
|
||||
149
tools/EVerest-main/lib/everest/io/test/endpoint_test.cpp
Normal file
149
tools/EVerest-main/lib/everest/io/test/endpoint_test.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/io/udp/endpoint.hpp>
|
||||
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using everest::lib::io::udp::endpoint;
|
||||
|
||||
TEST(endpoint_test, ipv4_literal) {
|
||||
endpoint ep("127.0.0.1", 8080);
|
||||
EXPECT_EQ(ep.family(), AF_INET);
|
||||
EXPECT_EQ(ep.port(), 8080);
|
||||
EXPECT_EQ(ep.addr_str(), "127.0.0.1");
|
||||
EXPECT_EQ(ep.sa_len(), sizeof(sockaddr_in));
|
||||
EXPECT_NE(ep.sa(), nullptr);
|
||||
}
|
||||
|
||||
TEST(endpoint_test, ipv6_loopback_literal) {
|
||||
endpoint ep("::1", 9000);
|
||||
EXPECT_EQ(ep.family(), AF_INET6);
|
||||
EXPECT_EQ(ep.port(), 9000);
|
||||
EXPECT_EQ(ep.addr_str(), "::1");
|
||||
EXPECT_EQ(ep.sa_len(), sizeof(sockaddr_in6));
|
||||
}
|
||||
|
||||
TEST(endpoint_test, ipv6_multicast_literal) {
|
||||
endpoint ep("ff02::1", 5353);
|
||||
EXPECT_EQ(ep.family(), AF_INET6);
|
||||
EXPECT_EQ(ep.port(), 5353);
|
||||
}
|
||||
|
||||
TEST(endpoint_test, port_is_host_order) {
|
||||
endpoint ep("127.0.0.1", 0x1234);
|
||||
// Raw sockaddr must carry network byte order.
|
||||
auto const* sin = reinterpret_cast<sockaddr_in const*>(ep.sa());
|
||||
EXPECT_EQ(sin->sin_port, htons(0x1234));
|
||||
EXPECT_EQ(ep.port(), 0x1234);
|
||||
}
|
||||
|
||||
TEST(endpoint_test, ipv6_link_local_scope_id_from_iface) {
|
||||
// Loopback always exists; it is not link-local, so no scope is applied.
|
||||
// Use a synthetic link-local address with the loopback interface name to
|
||||
// exercise the scope-id path deterministically.
|
||||
unsigned int lo = if_nametoindex("lo");
|
||||
ASSERT_NE(lo, 0u);
|
||||
endpoint ep("fe80::1", 1234, "lo");
|
||||
auto const* sin6 = reinterpret_cast<sockaddr_in6 const*>(ep.sa());
|
||||
EXPECT_EQ(sin6->sin6_scope_id, lo);
|
||||
EXPECT_EQ(ep.iface(), "lo");
|
||||
}
|
||||
|
||||
TEST(endpoint_test, hostname_resolves) {
|
||||
endpoint ep("localhost", 4711);
|
||||
EXPECT_TRUE(ep.family() == AF_INET || ep.family() == AF_INET6);
|
||||
EXPECT_EQ(ep.port(), 4711);
|
||||
}
|
||||
|
||||
TEST(endpoint_test, invalid_host_throws) {
|
||||
EXPECT_THROW(endpoint("definitely.not.a.host.invalid.", 80), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(endpoint_test, from_recvfrom_source_round_trips) {
|
||||
endpoint origin("127.0.0.1", 6543);
|
||||
sockaddr_storage ss{};
|
||||
std::memcpy(&ss, origin.sa(), origin.sa_len());
|
||||
endpoint restored(ss, origin.sa_len());
|
||||
EXPECT_EQ(restored.family(), AF_INET);
|
||||
EXPECT_EQ(restored.port(), 6543);
|
||||
EXPECT_EQ(restored.addr_str(), "127.0.0.1");
|
||||
EXPECT_TRUE(restored == origin);
|
||||
}
|
||||
|
||||
TEST(endpoint_test, equality_distinguishes_port) {
|
||||
endpoint a("127.0.0.1", 1000);
|
||||
endpoint b("127.0.0.1", 1001);
|
||||
EXPECT_FALSE(a == b);
|
||||
EXPECT_TRUE(a != b);
|
||||
}
|
||||
|
||||
TEST(endpoint_test, v4_mapped_detected_and_unmapped) {
|
||||
// Build a v4-mapped endpoint the way recvfrom on a dual-stack socket would.
|
||||
sockaddr_storage ss{};
|
||||
auto* s6 = reinterpret_cast<sockaddr_in6*>(&ss);
|
||||
s6->sin6_family = AF_INET6;
|
||||
s6->sin6_port = htons(40123);
|
||||
inet_pton(AF_INET6, "::ffff:127.0.0.1", &s6->sin6_addr);
|
||||
endpoint mapped(ss, sizeof(sockaddr_in6));
|
||||
|
||||
EXPECT_TRUE(mapped.is_v4_mapped());
|
||||
auto v4 = mapped.as_v4();
|
||||
EXPECT_EQ(v4.family(), AF_INET);
|
||||
EXPECT_EQ(v4.addr_str(), "127.0.0.1");
|
||||
EXPECT_EQ(v4.port(), 40123);
|
||||
}
|
||||
|
||||
TEST(endpoint_test, native_v6_is_not_v4_mapped_and_as_v4_is_empty) {
|
||||
endpoint v6("::1", 9000);
|
||||
EXPECT_FALSE(v6.is_v4_mapped());
|
||||
auto e = v6.as_v4();
|
||||
EXPECT_EQ(e.family(), AF_UNSPEC);
|
||||
EXPECT_EQ(e.sa_len(), 0);
|
||||
}
|
||||
|
||||
TEST(endpoint_test, already_v4_is_not_mapped_and_as_v4_is_empty) {
|
||||
endpoint v4("127.0.0.1", 1234);
|
||||
EXPECT_FALSE(v4.is_v4_mapped());
|
||||
EXPECT_EQ(v4.as_v4().family(), AF_UNSPEC);
|
||||
}
|
||||
|
||||
// The raw sockaddr/len pair must be directly usable by sendto(): send one
|
||||
// datagram to a bound loopback UDP socket and read it back.
|
||||
TEST(endpoint_test, sa_usable_by_sendto) {
|
||||
int rx = ::socket(AF_INET, SOCK_DGRAM, 0);
|
||||
ASSERT_GE(rx, 0);
|
||||
|
||||
sockaddr_in bind_addr{};
|
||||
bind_addr.sin_family = AF_INET;
|
||||
bind_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
bind_addr.sin_port = 0; // ephemeral
|
||||
ASSERT_EQ(::bind(rx, reinterpret_cast<sockaddr*>(&bind_addr), sizeof(bind_addr)), 0);
|
||||
|
||||
sockaddr_in bound{};
|
||||
socklen_t blen = sizeof(bound);
|
||||
ASSERT_EQ(::getsockname(rx, reinterpret_cast<sockaddr*>(&bound), &blen), 0);
|
||||
|
||||
endpoint target("127.0.0.1", ntohs(bound.sin_port));
|
||||
|
||||
int tx = ::socket(AF_INET, SOCK_DGRAM, 0);
|
||||
ASSERT_GE(tx, 0);
|
||||
const char msg[] = "hello";
|
||||
ASSERT_EQ(::sendto(tx, msg, sizeof(msg), 0, target.sa(), target.sa_len()), static_cast<ssize_t>(sizeof(msg)));
|
||||
|
||||
char buf[16]{};
|
||||
ASSERT_EQ(::recv(rx, buf, sizeof(buf), 0), static_cast<ssize_t>(sizeof(msg)));
|
||||
EXPECT_STREQ(buf, "hello");
|
||||
|
||||
::close(tx);
|
||||
::close(rx);
|
||||
}
|
||||
110
tools/EVerest-main/lib/everest/io/test/udp_client_v6_test.cpp
Normal file
110
tools/EVerest-main/lib/everest/io/test/udp_client_v6_test.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
//
|
||||
// Characterization test: the connected udp_client already works over IPv6.
|
||||
// This locks that guarantee so the unconnected-client refactor cannot regress
|
||||
// it. No udp_client / udp_socket production code is touched by this work.
|
||||
|
||||
#include <everest/io/udp/udp_client.hpp>
|
||||
#include <everest/io/udp/udp_payload.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using everest::lib::io::udp::udp_client;
|
||||
using everest::lib::io::udp::udp_payload;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace {
|
||||
|
||||
// A minimal blocking IPv6 echo socket on [::1]. Bound in the test body so the
|
||||
// ephemeral port is known before the client connects; serviced by a thread.
|
||||
class v6_echo {
|
||||
public:
|
||||
v6_echo() {
|
||||
m_fd = ::socket(AF_INET6, SOCK_DGRAM, 0);
|
||||
EXPECT_GE(m_fd, 0);
|
||||
sockaddr_in6 addr{};
|
||||
addr.sin6_family = AF_INET6;
|
||||
addr.sin6_addr = in6addr_loopback;
|
||||
addr.sin6_port = 0;
|
||||
EXPECT_EQ(::bind(m_fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)), 0);
|
||||
socklen_t len = sizeof(addr);
|
||||
EXPECT_EQ(::getsockname(m_fd, reinterpret_cast<sockaddr*>(&addr), &len), 0);
|
||||
m_port = ntohs(addr.sin6_port);
|
||||
|
||||
timeval tv{};
|
||||
tv.tv_usec = 200000; // 200ms so the loop can observe the stop flag
|
||||
::setsockopt(m_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
|
||||
m_thread = std::thread([this] { run(); });
|
||||
}
|
||||
|
||||
~v6_echo() {
|
||||
m_stop = true;
|
||||
if (m_thread.joinable()) {
|
||||
m_thread.join();
|
||||
}
|
||||
::close(m_fd);
|
||||
}
|
||||
|
||||
std::uint16_t port() const {
|
||||
return m_port;
|
||||
}
|
||||
|
||||
private:
|
||||
void run() {
|
||||
char buf[256];
|
||||
while (not m_stop) {
|
||||
sockaddr_in6 src{};
|
||||
socklen_t slen = sizeof(src);
|
||||
auto n = ::recvfrom(m_fd, buf, sizeof(buf), 0, reinterpret_cast<sockaddr*>(&src), &slen);
|
||||
if (n > 0) {
|
||||
::sendto(m_fd, buf, static_cast<size_t>(n), 0, reinterpret_cast<sockaddr*>(&src), slen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int m_fd{-1};
|
||||
std::uint16_t m_port{0};
|
||||
std::atomic<bool> m_stop{false};
|
||||
std::thread m_thread;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(udp_client_v6_test, connected_roundtrip_over_ipv6_loopback) {
|
||||
v6_echo echo;
|
||||
|
||||
udp_client client("::1", echo.port(), 1000);
|
||||
|
||||
std::atomic<bool> got{false};
|
||||
udp_payload received;
|
||||
client.set_rx_handler([&](udp_payload const& p, auto&) {
|
||||
received = p;
|
||||
got = true;
|
||||
});
|
||||
|
||||
udp_payload msg("v6-roundtrip");
|
||||
|
||||
auto deadline = std::chrono::steady_clock::now() + 5s;
|
||||
auto next_send = std::chrono::steady_clock::now();
|
||||
while (std::chrono::steady_clock::now() < deadline && not got) {
|
||||
if (std::chrono::steady_clock::now() >= next_send) {
|
||||
client.tx(msg);
|
||||
next_send = std::chrono::steady_clock::now() + 500ms;
|
||||
}
|
||||
client.sync(100ms);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(got) << "no IPv6 loopback echo received within timeout";
|
||||
EXPECT_EQ(received, msg);
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/io/socket/socket.hpp>
|
||||
#include <everest/io/udp/endpoint.hpp>
|
||||
#include <everest/io/udp/udp_dualstack_server.hpp>
|
||||
#include <everest/io/udp/udp_dualstack_server_socket.hpp>
|
||||
// event_client_async_policy_v arrives transitively via udp_dualstack_server.hpp
|
||||
// (fd_event_client.hpp); that header has no include guard, so do not include it
|
||||
// a second time directly here.
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <poll.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace everest::lib::io;
|
||||
using everest::lib::io::udp::endpoint;
|
||||
using everest::lib::io::udp::udp_dualstack_server_socket;
|
||||
using everest::lib::io::udp::udp_payload;
|
||||
using everest::lib::io::utilities::event_client_async_policy_v;
|
||||
|
||||
// Open only, no setup/connect: must be the synchronous client policy.
|
||||
static_assert(not event_client_async_policy_v<udp_dualstack_server_socket>,
|
||||
"udp_dualstack_server_socket must be a synchronous client policy");
|
||||
|
||||
namespace {
|
||||
|
||||
bool wait_readable(int fd, int timeout_ms) {
|
||||
pollfd pfd{fd, POLLIN, 0};
|
||||
return ::poll(&pfd, 1, timeout_ms) > 0 && (pfd.revents & POLLIN) != 0;
|
||||
}
|
||||
|
||||
bool rx_with_timeout(udp_dualstack_server_socket& s, udp_payload& out, int timeout_ms = 1000) {
|
||||
if (not wait_readable(s.get_fd(), timeout_ms)) {
|
||||
return false;
|
||||
}
|
||||
return s.rx(out);
|
||||
}
|
||||
|
||||
std::uint16_t bound_port(int fd) {
|
||||
sockaddr_storage ss{};
|
||||
socklen_t len = sizeof(ss);
|
||||
EXPECT_EQ(::getsockname(fd, reinterpret_cast<sockaddr*>(&ss), &len), 0);
|
||||
return ntohs(ss.ss_family == AF_INET6 ? reinterpret_cast<sockaddr_in6*>(&ss)->sin6_port
|
||||
: reinterpret_cast<sockaddr_in*>(&ss)->sin_port);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(udp_dualstack_factory_test, binds_dualstack_v6_any) {
|
||||
auto fd = socket::open_udp_dualstack_server_socket(0); // ephemeral
|
||||
ASSERT_GE(static_cast<int>(fd), 0);
|
||||
|
||||
int v6only = 1;
|
||||
socklen_t l = sizeof(v6only);
|
||||
ASSERT_EQ(::getsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, &l), 0);
|
||||
EXPECT_EQ(v6only, 0); // dual-stack
|
||||
|
||||
sockaddr_storage ss{};
|
||||
socklen_t sl = sizeof(ss);
|
||||
ASSERT_EQ(::getsockname(fd, reinterpret_cast<sockaddr*>(&ss), &sl), 0);
|
||||
EXPECT_EQ(ss.ss_family, AF_INET6);
|
||||
}
|
||||
|
||||
TEST(udp_dualstack_factory_test, device_empty_is_ok) {
|
||||
EXPECT_GE(static_cast<int>(socket::open_udp_dualstack_server_socket(0, {})), 0);
|
||||
}
|
||||
|
||||
TEST(udp_dualstack_server_socket_test, v6_roundtrip_loopback) {
|
||||
udp_dualstack_server_socket S;
|
||||
ASSERT_TRUE(S.open(0));
|
||||
const std::uint16_t srv_port = bound_port(S.get_fd());
|
||||
|
||||
int cl = ::socket(AF_INET6, SOCK_DGRAM, 0);
|
||||
ASSERT_GE(cl, 0);
|
||||
sockaddr_in6 cl_bind{};
|
||||
cl_bind.sin6_family = AF_INET6;
|
||||
cl_bind.sin6_addr = in6addr_loopback;
|
||||
ASSERT_EQ(::bind(cl, reinterpret_cast<sockaddr*>(&cl_bind), sizeof(cl_bind)), 0);
|
||||
const std::uint16_t cl_port = bound_port(cl);
|
||||
|
||||
sockaddr_in6 dst{};
|
||||
dst.sin6_family = AF_INET6;
|
||||
dst.sin6_addr = in6addr_loopback;
|
||||
dst.sin6_port = htons(srv_port);
|
||||
udp_payload ping("ping6");
|
||||
ASSERT_EQ(::sendto(cl, ping.buffer.data(), ping.size(), 0, reinterpret_cast<sockaddr*>(&dst), sizeof(dst)),
|
||||
static_cast<ssize_t>(ping.size()));
|
||||
|
||||
udp_payload got;
|
||||
ASSERT_TRUE(rx_with_timeout(S, got));
|
||||
EXPECT_EQ(got, udp_payload("ping6"));
|
||||
|
||||
auto src = S.last_source();
|
||||
ASSERT_TRUE(src.has_value());
|
||||
EXPECT_EQ(src->family(), AF_INET6);
|
||||
EXPECT_EQ(src->port(), cl_port);
|
||||
EXPECT_FALSE(src->is_v4_mapped());
|
||||
|
||||
ASSERT_TRUE(S.tx(udp_payload("pong6")));
|
||||
char buf[64]{};
|
||||
ASSERT_TRUE(wait_readable(cl, 1000));
|
||||
auto n = ::recv(cl, buf, sizeof(buf), 0);
|
||||
ASSERT_GT(n, 0);
|
||||
EXPECT_STREQ(buf, "pong6");
|
||||
::close(cl);
|
||||
}
|
||||
|
||||
TEST(udp_dualstack_server_socket_test, v4_mapped_roundtrip_loopback) {
|
||||
udp_dualstack_server_socket S;
|
||||
ASSERT_TRUE(S.open(0));
|
||||
const std::uint16_t srv_port = bound_port(S.get_fd());
|
||||
|
||||
int cl = ::socket(AF_INET, SOCK_DGRAM, 0);
|
||||
ASSERT_GE(cl, 0);
|
||||
sockaddr_in cl_bind{};
|
||||
cl_bind.sin_family = AF_INET;
|
||||
cl_bind.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
ASSERT_EQ(::bind(cl, reinterpret_cast<sockaddr*>(&cl_bind), sizeof(cl_bind)), 0);
|
||||
const std::uint16_t cl_port = bound_port(cl);
|
||||
|
||||
sockaddr_in dst{};
|
||||
dst.sin_family = AF_INET;
|
||||
dst.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
dst.sin_port = htons(srv_port);
|
||||
udp_payload ping("ping4");
|
||||
ASSERT_EQ(::sendto(cl, ping.buffer.data(), ping.size(), 0, reinterpret_cast<sockaddr*>(&dst), sizeof(dst)),
|
||||
static_cast<ssize_t>(ping.size()));
|
||||
|
||||
udp_payload got;
|
||||
ASSERT_TRUE(rx_with_timeout(S, got));
|
||||
EXPECT_EQ(got, udp_payload("ping4"));
|
||||
|
||||
auto src = S.last_source();
|
||||
ASSERT_TRUE(src.has_value());
|
||||
EXPECT_EQ(src->family(), AF_INET6); // v4-mapped is carried in a v6 sockaddr
|
||||
EXPECT_TRUE(src->is_v4_mapped());
|
||||
auto v4 = src->as_v4();
|
||||
EXPECT_EQ(v4.family(), AF_INET);
|
||||
EXPECT_EQ(v4.addr_str(), "127.0.0.1");
|
||||
EXPECT_EQ(v4.port(), cl_port);
|
||||
|
||||
// Reply must reach the v4 client via the verbatim mapped sockaddr.
|
||||
ASSERT_TRUE(S.tx(udp_payload("pong4")));
|
||||
char buf[64]{};
|
||||
ASSERT_TRUE(wait_readable(cl, 1000));
|
||||
auto n = ::recv(cl, buf, sizeof(buf), 0);
|
||||
ASSERT_GT(n, 0);
|
||||
EXPECT_STREQ(buf, "pong4");
|
||||
::close(cl);
|
||||
}
|
||||
|
||||
TEST(udp_dualstack_server_socket_test, tx_before_rx_is_false) {
|
||||
udp_dualstack_server_socket S;
|
||||
ASSERT_TRUE(S.open(0));
|
||||
EXPECT_FALSE(S.tx(udp_payload("nope")));
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/io/udp/udp_unconnected_client.hpp>
|
||||
#include <everest/io/udp/udp_unconnected_socket.hpp>
|
||||
// event_client_async_policy_v arrives transitively via udp_unconnected_client.hpp
|
||||
// (fd_event_client.hpp); that header has no include guard, so do not include it
|
||||
// a second time directly here.
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <poll.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using everest::lib::io::udp::endpoint;
|
||||
using everest::lib::io::udp::udp_payload;
|
||||
using everest::lib::io::udp::udp_unconnected_socket;
|
||||
using everest::lib::io::utilities::event_client_async_policy_v;
|
||||
|
||||
// Policy must be the synchronous variant (open only; no setup/connect), so
|
||||
// fd_event_client uses the in-thread open() path with no detached connect.
|
||||
static_assert(not event_client_async_policy_v<udp_unconnected_socket>,
|
||||
"udp_unconnected_socket must be a synchronous client policy");
|
||||
|
||||
namespace {
|
||||
|
||||
const char* loopback(int family) {
|
||||
return family == AF_INET6 ? "::1" : "127.0.0.1";
|
||||
}
|
||||
|
||||
// A raw datagram peer bound to loopback on an ephemeral port.
|
||||
struct peer {
|
||||
int fd{-1};
|
||||
std::uint16_t port{0};
|
||||
|
||||
explicit peer(int family) {
|
||||
fd = ::socket(family, SOCK_DGRAM, 0);
|
||||
EXPECT_GE(fd, 0);
|
||||
sockaddr_storage ss{};
|
||||
socklen_t len = 0;
|
||||
if (family == AF_INET6) {
|
||||
auto* a = reinterpret_cast<sockaddr_in6*>(&ss);
|
||||
a->sin6_family = AF_INET6;
|
||||
a->sin6_addr = in6addr_loopback;
|
||||
len = sizeof(*a);
|
||||
} else {
|
||||
auto* a = reinterpret_cast<sockaddr_in*>(&ss);
|
||||
a->sin_family = AF_INET;
|
||||
a->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
len = sizeof(*a);
|
||||
}
|
||||
EXPECT_EQ(::bind(fd, reinterpret_cast<sockaddr*>(&ss), len), 0);
|
||||
socklen_t blen = sizeof(ss);
|
||||
EXPECT_EQ(::getsockname(fd, reinterpret_cast<sockaddr*>(&ss), &blen), 0);
|
||||
port = ntohs(family == AF_INET6 ? reinterpret_cast<sockaddr_in6*>(&ss)->sin6_port
|
||||
: reinterpret_cast<sockaddr_in*>(&ss)->sin_port);
|
||||
}
|
||||
~peer() {
|
||||
if (fd >= 0) {
|
||||
::close(fd);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::uint16_t bound_port(int fd) {
|
||||
sockaddr_storage ss{};
|
||||
socklen_t len = sizeof(ss);
|
||||
EXPECT_EQ(::getsockname(fd, reinterpret_cast<sockaddr*>(&ss), &len), 0);
|
||||
return ntohs(ss.ss_family == AF_INET6 ? reinterpret_cast<sockaddr_in6*>(&ss)->sin6_port
|
||||
: reinterpret_cast<sockaddr_in*>(&ss)->sin_port);
|
||||
}
|
||||
|
||||
bool wait_readable(int fd, int timeout_ms) {
|
||||
pollfd pfd{fd, POLLIN, 0};
|
||||
return ::poll(&pfd, 1, timeout_ms) > 0 && (pfd.revents & POLLIN) != 0;
|
||||
}
|
||||
|
||||
bool rx_with_timeout(udp_unconnected_socket& sock, udp_payload& out, int timeout_ms = 1000) {
|
||||
if (not wait_readable(sock.get_fd(), timeout_ms)) {
|
||||
return false;
|
||||
}
|
||||
return sock.rx(out);
|
||||
}
|
||||
|
||||
void roundtrip_for_family(int family) {
|
||||
peer p(family);
|
||||
|
||||
udp_unconnected_socket u;
|
||||
ASSERT_TRUE(u.open(endpoint(loopback(family), p.port)));
|
||||
|
||||
udp_payload msg("ping");
|
||||
ASSERT_TRUE(u.tx(msg));
|
||||
|
||||
// Peer receives, learns u's source, echoes back.
|
||||
char buf[64];
|
||||
sockaddr_storage src{};
|
||||
socklen_t slen = sizeof(src);
|
||||
ASSERT_TRUE(wait_readable(p.fd, 1000));
|
||||
auto n = ::recvfrom(p.fd, buf, sizeof(buf), 0, reinterpret_cast<sockaddr*>(&src), &slen);
|
||||
ASSERT_GT(n, 0);
|
||||
ASSERT_EQ(::sendto(p.fd, buf, static_cast<size_t>(n), 0, reinterpret_cast<sockaddr*>(&src), slen),
|
||||
static_cast<ssize_t>(n));
|
||||
|
||||
udp_payload reply;
|
||||
ASSERT_TRUE(rx_with_timeout(u, reply));
|
||||
EXPECT_EQ(reply, msg);
|
||||
|
||||
auto last = u.last_source();
|
||||
ASSERT_TRUE(last.has_value());
|
||||
EXPECT_EQ(last->family(), family);
|
||||
EXPECT_EQ(last->port(), p.port);
|
||||
EXPECT_EQ(last->addr_str(), loopback(family));
|
||||
}
|
||||
|
||||
// connect-drop-absence: u targets an arbitrary endpoint, but a *different*
|
||||
// peer sends straight to u's ephemeral port. Because open() does no ::connect,
|
||||
// the unrelated source is still delivered, and last_source() reflects that
|
||||
// sender, not the configured target.
|
||||
void connect_drop_absence_for_family(int family) {
|
||||
peer target(family); // configured tx destination; never receives here
|
||||
udp_unconnected_socket u;
|
||||
ASSERT_TRUE(u.open(endpoint(loopback(family), target.port)));
|
||||
|
||||
std::uint16_t u_port = bound_port(u.get_fd());
|
||||
|
||||
peer other(family); // a sender unrelated to the configured target
|
||||
sockaddr_storage dst{};
|
||||
socklen_t dlen = 0;
|
||||
if (family == AF_INET6) {
|
||||
auto* a = reinterpret_cast<sockaddr_in6*>(&dst);
|
||||
a->sin6_family = AF_INET6;
|
||||
a->sin6_addr = in6addr_loopback;
|
||||
a->sin6_port = htons(u_port);
|
||||
dlen = sizeof(*a);
|
||||
} else {
|
||||
auto* a = reinterpret_cast<sockaddr_in*>(&dst);
|
||||
a->sin_family = AF_INET;
|
||||
a->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
a->sin_port = htons(u_port);
|
||||
dlen = sizeof(*a);
|
||||
}
|
||||
const char payload[] = "from-other";
|
||||
ASSERT_EQ(::sendto(other.fd, payload, sizeof(payload), 0, reinterpret_cast<sockaddr*>(&dst), dlen),
|
||||
static_cast<ssize_t>(sizeof(payload)));
|
||||
|
||||
udp_payload got;
|
||||
ASSERT_TRUE(rx_with_timeout(u, got)) << "datagram from a non-target source was dropped (socket is connected?)";
|
||||
|
||||
auto last = u.last_source();
|
||||
ASSERT_TRUE(last.has_value());
|
||||
EXPECT_EQ(last->port(), other.port);
|
||||
EXPECT_NE(last->port(), target.port);
|
||||
EXPECT_FALSE(*last == endpoint(loopback(family), target.port));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(udp_unconnected_socket_test, roundtrip_ipv4) {
|
||||
roundtrip_for_family(AF_INET);
|
||||
}
|
||||
|
||||
TEST(udp_unconnected_socket_test, roundtrip_ipv6) {
|
||||
roundtrip_for_family(AF_INET6);
|
||||
}
|
||||
|
||||
TEST(udp_unconnected_socket_test, connect_drop_absence_ipv4) {
|
||||
connect_drop_absence_for_family(AF_INET);
|
||||
}
|
||||
|
||||
TEST(udp_unconnected_socket_test, connect_drop_absence_ipv6) {
|
||||
connect_drop_absence_for_family(AF_INET6);
|
||||
}
|
||||
|
||||
TEST(udp_unconnected_socket_test, multicast_egress_iface_set_ipv6) {
|
||||
unsigned int lo = if_nametoindex("lo");
|
||||
ASSERT_NE(lo, 0u);
|
||||
udp_unconnected_socket u;
|
||||
ASSERT_TRUE(u.open(endpoint("ff02::1", 5353, "lo")));
|
||||
|
||||
int ifindex = 0;
|
||||
socklen_t len = sizeof(ifindex);
|
||||
ASSERT_EQ(::getsockopt(u.get_fd(), IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex, &len), 0);
|
||||
EXPECT_EQ(static_cast<unsigned int>(ifindex), lo);
|
||||
}
|
||||
|
||||
TEST(udp_unconnected_socket_test, multicast_egress_iface_set_ipv4) {
|
||||
unsigned int lo = if_nametoindex("lo");
|
||||
ASSERT_NE(lo, 0u);
|
||||
udp_unconnected_socket u;
|
||||
// set_multicast_if() throws if setsockopt(IP_MULTICAST_IF) fails, which
|
||||
// would make open() return false; a successful open for a multicast v4
|
||||
// target on a named interface proves the egress option was applied.
|
||||
// (Linux getsockopt(IP_MULTICAST_IF) returns only a zeroed in_addr when
|
||||
// set by interface index, so a value read-back is not possible here; the
|
||||
// v6 case below asserts the index strictly via getsockopt.)
|
||||
EXPECT_TRUE(u.open(endpoint("239.1.2.3", 5000, "lo")));
|
||||
}
|
||||
|
||||
TEST(udp_unconnected_socket_test, client_alias_constructs) {
|
||||
// The fd_event_client alias must instantiate against the sync policy and
|
||||
// open the underlying socket synchronously in its constructor.
|
||||
peer p(AF_INET);
|
||||
everest::lib::io::udp::udp_unconnected_client client(endpoint("127.0.0.1", p.port));
|
||||
EXPECT_GE(client.get_poll_fd(), 0);
|
||||
auto const& raw = client.get_raw_handler();
|
||||
ASSERT_NE(raw, nullptr);
|
||||
EXPECT_GE(raw->get_fd(), 0);
|
||||
}
|
||||
Reference in New Issue
Block a user