OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
unix_socket.hpp
1#pragma once
3#include <ossia/network/context.hpp>
4#include <ossia/network/sockets/configuration.hpp>
5#include <ossia/network/sockets/writers.hpp>
6
7#include <boost/asio/io_context.hpp>
8#include <boost/asio/ip/udp.hpp>
9#include <boost/asio/local/datagram_protocol.hpp>
10#include <boost/asio/local/stream_protocol.hpp>
11#include <boost/asio/placeholders.hpp>
12#include <boost/asio/strand.hpp>
13#include <boost/asio/write.hpp>
14
15#include <nano_signal_slot.hpp>
16
17namespace ossia::net
18{
19#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
20class unix_datagram_socket
21{
22 using proto = boost::asio::local::datagram_protocol;
23
24public:
25 unix_datagram_socket(const fd_configuration& conf, boost::asio::io_context& ctx)
26 : m_context{ctx}
27 , m_endpoint{conf.fd}
28 , m_socket{boost::asio::make_strand(ctx)}
29 {
30 }
31
32 void open()
33 {
34 ::unlink(m_endpoint.path().data());
35 m_socket.open();
36 m_socket.bind(m_endpoint);
37 }
38
39 void connect()
40 {
41 m_socket.open();
42 // m_socket.connect(m_endpoint);
43 }
44
45 void close()
46 {
47 if(m_socket.is_open())
48 {
49 boost::asio::post(m_context, [this] {
50 try
51 {
52 m_socket.shutdown(boost::asio::ip::udp::socket::shutdown_both);
53 }
54 catch(...)
55 {
56 }
57 m_socket.close();
58 on_close();
59 });
60 }
61 }
62
63 template <typename F>
64 void receive(F f)
65 {
66 m_socket.async_receive_from(
67 boost::asio::mutable_buffer(&m_data[0], std::size(m_data)), m_endpoint,
68 [this, f](boost::system::error_code ec, std::size_t sz) {
69 if(ec == boost::asio::error::operation_aborted)
70 return;
71
72 if(!ec && sz > 0)
73 try
74 {
75 f(m_data, sz);
76 }
77 catch(...)
78 {
79 }
80
81 this->receive(f);
82 });
83 }
84
85 void write(const char* data, std::size_t sz)
86 {
87 m_socket.send_to(boost::asio::buffer(data, sz), m_endpoint);
88 }
89
90 ~unix_datagram_socket()
91 {
92 // Cancel pending async_receive_from + drain so the recv_op's stored
93 // completion handler runs (with operation_aborted) and gets cleanly
94 // freed *before* `this` and m_data go out of scope. Otherwise the
95 // op outlives this object, and when its captured handler is later
96 // destroyed by the io_context the dangling layout corrupts libc++'s
97 // std::function vtable pointer (SIGBUS / EXC_ARM_DA_ALIGN @ 0x2 on
98 // macos-15 arm64 Debug shared).
99 if(m_socket.is_open())
100 {
101 boost::system::error_code ec;
102 m_socket.cancel(ec);
103 m_socket.close(ec);
104 }
105 while(m_context.poll() > 0) { }
106 }
107
108 Nano::Signal<void()> on_close;
109
110 boost::asio::io_context& m_context;
111 proto::endpoint m_endpoint;
112 proto::socket m_socket;
113 alignas(16) char m_data[65535];
114};
115
116class unix_stream_listener
117{
118public:
119 using proto = boost::asio::local::stream_protocol;
120 unix_stream_listener() = delete;
121 unix_stream_listener(const unix_stream_listener&) = delete;
122 unix_stream_listener& operator=(const unix_stream_listener&) = delete;
123 unix_stream_listener(unix_stream_listener&&) = default;
124 unix_stream_listener& operator=(unix_stream_listener&&) = default;
125 explicit unix_stream_listener(proto::socket sock)
126 : m_socket{std::move(sock)}
127 {
128 }
129
130 void close()
131 {
132 // FIXME async?
133 try
134 {
135 m_socket.shutdown(boost::asio::ip::udp::socket::shutdown_both);
136 }
137 catch(...)
138 {
139 }
140 m_socket.close();
141 }
142
143 void write(const boost::asio::const_buffer& buf) { boost::asio::write(m_socket, buf); }
144
145 void on_close() { }
146
147 void on_fail() { }
148
149 proto::socket m_socket;
150};
151
152class unix_stream_server
153{
154public:
155 using proto = boost::asio::local::stream_protocol;
156 using listener = unix_stream_listener;
157 [[no_unique_address]] struct ensure_reuse
158 {
159 explicit ensure_reuse(const proto::endpoint& endpoint)
160 {
161 ::unlink(endpoint.path().data());
162 }
163 } m_ensure_reuse;
164
165 unix_stream_server(const fd_configuration& conf, boost::asio::io_context& ctx)
166 : m_ensure_reuse{conf.fd}
167 , m_context{ctx}
168 , m_acceptor{boost::asio::make_strand(ctx), conf.fd}
169 {
170 }
171
172 unix_stream_server(const fd_configuration& conf, ossia::net::network_context_ptr ctx)
173 : unix_stream_server{conf, ctx->context}
174 {
175 }
176
177 boost::asio::io_context& m_context;
178 proto::acceptor m_acceptor;
179};
180
181class unix_stream_client
182{
183public:
184 using proto = boost::asio::local::stream_protocol;
185 using socket = typename proto::socket;
186
187 unix_stream_client(const fd_configuration& conf, boost::asio::io_context& ctx)
188 : m_context{ctx}
189 , m_endpoint{conf.fd}
190 , m_socket{boost::asio::make_strand(ctx)}
191 {
192 }
193
194 void connect()
195 {
196 m_socket.connect(m_endpoint);
197 on_open();
198 }
199
200 bool connected() const { return m_connected; }
201
202 void close()
203 {
204 boost::asio::post(m_context, [this, alive = m_lifetime.watch()] {
205 if(alive.expired())
206 return;
207
208 try
209 {
210 m_socket.shutdown(boost::asio::ip::udp::socket::shutdown_both);
211 }
212 catch(...)
213 {
214 }
215 m_socket.close();
216 on_close();
217 });
218 }
219
220 void write(const char* data, std::size_t sz)
221 {
222 boost::asio::write(m_socket, boost::asio::buffer(data, sz));
223 }
224
225 Nano::Signal<void()> on_open;
226 Nano::Signal<void()> on_close;
227 Nano::Signal<void()> on_fail;
228
229 boost::asio::io_context& m_context;
230 proto::endpoint m_endpoint;
231 proto::socket m_socket;
232 bool m_connected{false};
233
234 // Guards the handler posted by close(); the pending reads of a framed client
235 // are guarded by the decoder's own token. See lifetime_token.
236 lifetime_token m_lifetime;
237};
238#endif
239}