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:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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