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