MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
UDPBackend.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "NetworkBackend.hpp"
4
5#include <asio/io_context.hpp>
6#include <asio/ip/udp.hpp>
7
8namespace MayaFlux::Core {
9
10/**
11 * @class UDPBackend
12 * @brief Connectionless datagram transport over UDP via standalone Asio
13 *
14 * Each endpoint maps to a (socket, optional default remote) pair.
15 * Endpoints that share a local port share the underlying socket.
16 *
17 * All I/O is async on the io_context provided by NetworkSubsystem.
18 * No dedicated threads, no mutexes on the data path.
19 *
20 * async_receive_from -> completion fires receive callback -> resubmit
21 * async_send_to -> fire-and-forget (UDP is unreliable anyway)
22 *
23 * OSC, Art-Net, or any other datagram protocol is a serialisation
24 * concern above this layer. This backend knows nothing about message
25 * formats: bytes in, bytes out.
26 *
27 * @code
28 * // Created and started by NetworkSubsystem. Direct use for testing:
29 * asio::io_context ctx;
30 * UDPBackend udp(config, ctx);
31 * udp.initialize();
32 * udp.set_receive_callback([](uint64_t id, const uint8_t* d, size_t s, std::string_view a) {
33 * // handle datagram
34 * });
35 * udp.start();
36 *
37 * EndpointInfo ep;
38 * ep.role = EndpointRole::BIDIRECTIONAL;
39 * ep.local_port = 8000;
40 * ep.remote_address = "127.0.0.1";
41 * ep.remote_port = 9000;
42 * auto id = udp.open_endpoint(ep);
43 *
44 * udp.send(id, payload.data(), payload.size());
45 *
46 * ctx.run(); // normally driven by NetworkSubsystem's jthread
47 * @endcode
48 */
49class MAYAFLUX_API UDPBackend : public INetworkBackend {
50public:
51 /**
52 * @brief Construct with config and a reference to the shared io_context
53 * @param config UDP-specific configuration.
54 * @param context The io_context owned by NetworkSubsystem. All async
55 * operations are posted here.
56 */
57 UDPBackend(const UDPBackendInfo& config, asio::io_context& context);
58 ~UDPBackend() override;
59
60 UDPBackend(const UDPBackend&) = delete;
61 UDPBackend& operator=(const UDPBackend&) = delete;
64
65 // ─── INetworkBackend lifecycle ──────────────────────────────────────────
66
67 bool initialize() override;
68 void start() override;
69 void stop() override;
70 void shutdown() override;
71
72 [[nodiscard]] bool is_initialized() const override { return m_initialized.load(); }
73 [[nodiscard]] bool is_running() const override { return m_running.load(); }
74
75 [[nodiscard]] NetworkTransport get_transport() const override { return NetworkTransport::UDP; }
76 [[nodiscard]] std::string get_name() const override { return "UDP (Asio)"; }
77 [[nodiscard]] std::string get_version() const override { return "1.0"; }
78
79 // ─── INetworkBackend endpoints ──────────────────────────────────────────
80
81 uint64_t open_endpoint(const EndpointInfo& info) override;
82 void close_endpoint(uint64_t endpoint_id) override;
83 [[nodiscard]] EndpointState get_endpoint_state(uint64_t endpoint_id) const override;
84 [[nodiscard]] std::vector<EndpointInfo> get_endpoints() const override;
85
86 // ─── INetworkBackend data ───────────────────────────────────────────────
87
88 bool send(uint64_t endpoint_id, const uint8_t* data, size_t size) override;
89 bool send_to(uint64_t endpoint_id, const uint8_t* data, size_t size,
90 const std::string& address, uint16_t port) override;
91
92 // ─── INetworkBackend callbacks ──────────────────────────────────────────
93
94 void set_receive_callback(NetworkReceiveCallback callback) override;
95 void set_state_callback(EndpointStateCallback callback) override;
96
97private:
98 /**
99 * @brief Per-bound-port socket state
100 *
101 * Multiple endpoints can share a socket (same local port, different
102 * remote targets). The socket owns the async_receive_from loop.
103 */
104 struct SocketState {
105 asio::ip::udp::socket socket;
106 std::array<uint8_t, 65536> recv_buffer {};
107 asio::ip::udp::endpoint sender_endpoint;
108 uint16_t local_port {};
109 uint32_t ref_count {};
110
111 explicit SocketState(asio::io_context& ctx)
112 : socket(ctx)
113 {
114 }
115 };
116
117 /**
118 * @brief Per-endpoint state
119 *
120 * Maps an endpoint id to its socket and optional default remote target.
121 */
124 SocketState* socket_state {};
125 asio::ip::udp::endpoint default_remote;
126 bool has_default_remote {};
127 };
128
130 asio::io_context& m_context;
131
132 std::atomic<bool> m_initialized { false };
133 std::atomic<bool> m_running { false };
134
135 mutable std::shared_mutex m_endpoints_mutex;
136 std::unordered_map<uint64_t, EndpointRecord> m_endpoints;
137
138 mutable std::shared_mutex m_sockets_mutex;
139 std::unordered_map<uint16_t, std::unique_ptr<SocketState>> m_sockets;
140
143
144 /**
145 * @brief Get or create a socket bound to local_port.
146 *
147 * If a socket for this port already exists, increments ref_count
148 * and returns it. Otherwise binds a new socket and starts the
149 * async receive loop.
150 */
151 SocketState* acquire_socket(uint16_t local_port);
152
153 /**
154 * @brief Decrement ref_count for a socket. Closes if zero.
155 */
156 void release_socket(uint16_t local_port);
157
158 /**
159 * @brief Post the first async_receive_from for a newly bound socket.
160 */
161 void start_receive_loop(SocketState& state);
162
163 /**
164 * @brief Completion handler for async_receive_from.
165 *
166 * Fires the receive callback with the endpoint id resolved from
167 * the sender address, then resubmits async_receive_from.
168 */
169 void on_receive(SocketState& state, const asio::error_code& ec, size_t bytes_received);
170
171 /**
172 * @brief Resolve which endpoint id a received datagram belongs to.
173 *
174 * For a given socket (local port), checks if any endpoint has a
175 * default_remote matching the sender. If none match, falls back to
176 * the first RECEIVE or BIDIRECTIONAL endpoint on that port.
177 */
178 uint64_t resolve_endpoint_for_sender(
179 uint16_t local_port, const asio::ip::udp::endpoint& sender) const;
180
181 void transition_state(EndpointRecord& record, EndpointState new_state);
182};
183
184} // namespace MayaFlux::Core
Range size
Abstract interface for network transport backends.
std::unordered_map< uint16_t, std::unique_ptr< SocketState > > m_sockets
NetworkReceiveCallback m_receive_callback
UDPBackend & operator=(const UDPBackend &)=delete
UDPBackend(UDPBackend &&)=delete
NetworkTransport get_transport() const override
bool is_initialized() const override
std::unordered_map< uint64_t, EndpointRecord > m_endpoints
std::string get_name() const override
UDPBackend(const UDPBackend &)=delete
EndpointStateCallback m_state_callback
bool is_running() const override
asio::io_context & m_context
UDPBackend & operator=(UDPBackend &&)=delete
std::shared_mutex m_sockets_mutex
std::string get_version() const override
std::shared_mutex m_endpoints_mutex
Connectionless datagram transport over UDP via standalone Asio.
void initialize()
Definition main.cpp:11
EndpointState
Observable connection state for an endpoint.
NetworkTransport
Identifies the transport protocol a backend implements.
std::function< void(uint64_t endpoint_id, const uint8_t *data, size_t size, std::string_view sender_addr)> NetworkReceiveCallback
Callback signature for inbound data on an endpoint.
std::function< void(const EndpointInfo &info, EndpointState previous, EndpointState current)> EndpointStateCallback
Callback signature for endpoint state changes.
Describes one logical send/receive endpoint managed by a backend.
Configuration for the UDP transport backend.
asio::ip::udp::endpoint sender_endpoint
Per-bound-port socket state.