llmx-rtaco 0.0.1
RTNL-only netlink control-plane library for Linux (C++23).
Loading...
Searching...
No Matches
nl_request_task.hxx
Go to the documentation of this file.
1#pragma once
2
3#include <cstddef>
4#include <cstdint>
5#include <concepts>
6#include <optional>
7#include <span>
8#include <system_error>
9#include <expected>
10#include <vector>
11
12#include <linux/netlink.h>
13
14#include <boost/asio/async_result.hpp>
15#include <boost/asio/awaitable.hpp>
16#include <boost/asio/buffer.hpp>
17#include <boost/asio/redirect_error.hpp>
18#include <boost/asio/use_awaitable.hpp>
19#include <boost/system/error_code.hpp>
20
22
23namespace llmx {
24namespace rtaco {
25
26namespace detail {
27
28inline auto validate_received_size(size_t bytes, size_t capacity)
29 -> std::expected<void, std::error_code> {
30 if (bytes >= capacity) {
31 return std::unexpected(std::make_error_code(std::errc::message_size));
32 }
33
34 return {};
35}
36
37} // namespace detail
38
39template<typename Derived, typename Result>
41 requires(Derived& derived, const Derived& const_derived, const nlmsghdr& header) {
42 { derived.prepare_request() } -> std::same_as<void>;
43 { const_derived.request_payload() } -> std::same_as<std::span<const uint8_t>>;
44 {
45 derived.process_message(header)
46 } -> std::same_as<std::optional<std::expected<Result, std::error_code>>>;
47 };
48
58template<typename Derived, typename Result>
60 static constexpr size_t MAX_RESPONSE_BYTES = 64U * 1024U;
61 std::array<uint8_t, MAX_RESPONSE_BYTES> receive_buffer_;
62
64 uint32_t ifindex_;
65 uint32_t sequence_;
66
67public:
74 RequestTask(SocketGuard& socket_guard, uint32_t ifindex, uint32_t sequence) noexcept
75 : socket_guard_{socket_guard}
78
80 virtual ~RequestTask() = default;
81
87 auto async_run() -> boost::asio::awaitable<std::expected<Result, std::error_code>> {
88 impl().prepare_request();
89
90 if (auto send_result = co_await send_request(); !send_result) {
91 co_return std::unexpected(send_result.error());
92 }
93
94 co_return co_await read_loop();
95 }
96
97protected:
98 auto socket() noexcept -> Socket& {
99 return socket_guard_.socket();
100 }
101
102 auto sequence() const noexcept -> uint32_t {
103 return sequence_;
104 }
105
106 auto ifindex() const noexcept -> uint32_t {
107 return ifindex_;
108 }
109
110private:
111 auto impl() noexcept -> Derived& {
112 return static_cast<Derived&>(*this);
113 }
114
115 auto impl() const noexcept -> const Derived& {
116 return static_cast<const Derived&>(*this);
117 }
118
119 auto send_request() -> boost::asio::awaitable<std::expected<void, std::error_code>> {
120 const auto payload = impl().request_payload();
121 size_t offset = 0;
122
123 while (offset < payload.size()) {
124 boost::system::error_code ec{};
125 const auto sent = co_await socket().async_send(
126 boost::asio::buffer(payload.data() + offset, payload.size() - offset),
127 boost::asio::redirect_error(boost::asio::use_awaitable, ec));
128
129 if (ec) {
130 co_return std::unexpected(
131 std::error_code{ec.value(), std::generic_category()});
132 }
133
134 offset += sent;
135 }
136
137 co_return std::expected<void, std::error_code>{};
138 }
139
140 auto read_loop() -> boost::asio::awaitable<std::expected<Result, std::error_code>> {
141 while (true) {
142 boost::system::error_code ec{};
143 const auto bytes = co_await socket().async_receive(
144 boost::asio::buffer(receive_buffer_),
145 boost::asio::redirect_error(boost::asio::use_awaitable, ec));
146
147 if (ec) {
148 co_return std::unexpected(
149 std::error_code{ec.value(), std::generic_category()});
150 }
151
152 if (auto size_ok = detail::validate_received_size(
153 bytes, receive_buffer_.size());
154 !size_ok) {
155 co_return std::unexpected(size_ok.error());
156 }
157
158 auto remaining = static_cast<unsigned int>(bytes);
159 const auto header_size = static_cast<unsigned int>(sizeof(nlmsghdr));
160 const auto* header = reinterpret_cast<const nlmsghdr*>(receive_buffer_
161 .data());
162
163 while (remaining >= header_size && NLMSG_OK(header, remaining)) {
164 if (auto result = impl().process_message(*header)) {
165 co_return std::move(*result);
166 }
167
168 header = NLMSG_NEXT(header, remaining);
169 }
170 }
171 }
172};
173
174} // namespace rtaco
175} // namespace llmx
auto impl() const noexcept -> const Derived &
Definition nl_request_task.hxx:115
uint32_t sequence_
Definition nl_request_task.hxx:65
auto socket() noexcept -> Socket &
Definition nl_request_task.hxx:98
virtual ~RequestTask()=default
Virtual destructor.
SocketGuard & socket_guard_
Definition nl_request_task.hxx:63
auto read_loop() -> boost::asio::awaitable< std::expected< Result, std::error_code > >
Definition nl_request_task.hxx:140
static constexpr size_t MAX_RESPONSE_BYTES
Definition nl_request_task.hxx:60
auto impl() noexcept -> Derived &
Definition nl_request_task.hxx:111
RequestTask(SocketGuard &socket_guard, uint32_t ifindex, uint32_t sequence) noexcept
Construct a RequestTask.
Definition nl_request_task.hxx:74
auto async_run() -> boost::asio::awaitable< std::expected< Result, std::error_code > >
Run the request asynchronously and return the result.
Definition nl_request_task.hxx:87
auto send_request() -> boost::asio::awaitable< std::expected< void, std::error_code > >
Definition nl_request_task.hxx:119
uint32_t ifindex_
Definition nl_request_task.hxx:64
std::array< uint8_t, MAX_RESPONSE_BYTES > receive_buffer_
Definition nl_request_task.hxx:61
auto sequence() const noexcept -> uint32_t
Definition nl_request_task.hxx:102
auto ifindex() const noexcept -> uint32_t
Definition nl_request_task.hxx:106
Thread-safe guard owning a labeled Socket.
Definition nl_socket_guard.hxx:25
RAII wrapper for a Boost.Asio netlink socket.
Definition nl_socket.hxx:46
Definition nl_request_task.hxx:40
Definition nl_signal.hxx:28
auto validate_received_size(size_t bytes, size_t capacity) -> std::expected< void, std::error_code >
Definition nl_request_task.hxx:28
Definition nl_common.hxx:22
Definition nl_common.hxx:21