OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
qml_unix_inbound_socket.hpp
1#pragma once
2#include <ossia/network/context.hpp>
3#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
4#include <ossia/detail/variant.hpp>
5#include <ossia/network/sockets/configuration.hpp>
6#include <ossia/network/sockets/cobs_framing.hpp>
7#include <ossia/network/sockets/fixed_length_framing.hpp>
8#include <ossia/network/sockets/line_framing.hpp>
9#include <ossia/network/sockets/no_framing.hpp>
10#include <ossia/network/sockets/size_prefix_framing.hpp>
11#include <ossia/network/sockets/slip_framing.hpp>
12#include <ossia/network/sockets/stx_etx_framing.hpp>
13#include <ossia/network/sockets/unix_socket.hpp>
14#include <ossia/network/sockets/var_size_prefix_framing.hpp>
15
16#include <ossia-qt/protocols/utils.hpp>
17
18#include <QJSValue>
19#include <QObject>
20#include <QQmlEngine>
21
22#include <nano_observer.hpp>
23
24#include <verdigris>
25
26namespace ossia::qt
27{
28class qml_unix_datagram_inbound_socket
29 : public QObject
30 , public Nano::Observer
31{
32 W_OBJECT(qml_unix_datagram_inbound_socket)
33public:
34 struct state
35 {
36 ossia::net::unix_datagram_socket socket;
37 std::atomic_bool alive{true};
38 ossia::net::encoding enc{ossia::net::encoding::none};
39
40 state(
41 const ossia::net::fd_configuration& conf, boost::asio::io_context& ctx,
42 ossia::net::encoding e = ossia::net::encoding::none)
43 : socket{conf, ctx}
44 {
45 enc = e;
46 }
47 };
48
49 qml_unix_datagram_inbound_socket() { }
50
51 ~qml_unix_datagram_inbound_socket()
52 {
53 if(m_state)
54 {
55 m_state->alive = false;
56 close();
57 }
58 }
59
60 bool isOpen() const noexcept { return m_state != nullptr; }
61
62 void open(
63 const ossia::net::fd_configuration& conf, boost::asio::io_context& ctx,
64 ossia::net::encoding e = ossia::net::encoding::none)
65 {
66 m_state = std::make_shared<state>(conf, ctx, e);
67
68 if(onClose.isCallable())
69 m_state->socket.on_close.connect<&qml_unix_datagram_inbound_socket::on_close>(this);
70
71 m_state->socket.open();
72 if(onOpen.isCallable())
73 onOpen.call({qjsEngine(this)->newQObject(this)});
74
75 auto st = m_state;
76 auto self = QPointer{this};
77 st->socket.receive([st, self](const char* data, std::size_t sz) {
78 if(!st->alive)
79 return;
80 ossia::qt::run_async(
81 self.get(),
82 [self, arg = apply_decoding(st->enc, data, sz)] {
83 if(!self.get())
84 return;
85 if(self->onMessage.isCallable())
86 {
87 self->onMessage.call({qjsEngine(self.get())->toScriptValue(arg)});
88 }
89 },
90 Qt::AutoConnection);
91 });
92 }
93
94 void close()
95 {
96 if(!m_state)
97 return;
98 if(!m_state->socket.m_socket.is_open())
99 return;
100 auto st = m_state;
101 boost::asio::post(st->socket.m_context, [st] {
102 try
103 {
104 st->socket.m_socket.shutdown(boost::asio::ip::udp::socket::shutdown_both);
105 }
106 catch(...)
107 {
108 }
109 st->socket.m_socket.close();
110 st->socket.on_close();
111 });
112 }
113 W_SLOT(close)
114
115 void on_close()
116 {
117 if(!m_state || !m_state->alive)
118 return;
119 ossia::qt::run_async(this, [=, this] { onClose.call(); }, Qt::AutoConnection);
120 }
121
122 QJSValue onOpen;
123 QJSValue onClose;
124 QJSValue onError;
125 QJSValue onMessage;
126
127private:
128 std::shared_ptr<state> m_state;
129};
130
131class qml_unix_stream_connection
132 : public QObject
133 , public Nano::Observer
134{
135 W_OBJECT(qml_unix_stream_connection)
136public:
137 using socket_t = boost::asio::local::stream_protocol::socket;
138 using decoder_type = ossia::slow_variant<
139 ossia::net::no_framing::decoder<socket_t>,
140 ossia::net::slip_decoder<socket_t>,
141 ossia::net::size_prefix_decoder<socket_t>,
142 ossia::net::line_framing_decoder<socket_t>,
143 ossia::net::cobs_decoder<socket_t>,
144 ossia::net::stx_etx_framing::decoder<socket_t>,
145 ossia::net::size_prefix_1byte_framing::decoder<socket_t>,
146 ossia::net::size_prefix_2byte_be_framing::decoder<socket_t>,
147 ossia::net::size_prefix_2byte_le_framing::decoder<socket_t>,
148 ossia::net::size_prefix_4byte_le_framing::decoder<socket_t>,
149 ossia::net::fixed_length_decoder<socket_t>>;
150
151 struct state
152 {
153 boost::asio::io_context& context;
154 ossia::net::unix_stream_listener listener;
155 std::atomic_bool alive{true};
156 ossia::net::framing framing{ossia::net::framing::none};
157 ossia::net::encoding enc{ossia::net::encoding::none};
158 char line_delimiter[8] = {};
159 decoder_type decoder;
160
161 state(
162 ossia::net::unix_stream_listener l, boost::asio::io_context& ctx,
163 ossia::net::framing f = ossia::net::framing::none,
164 const std::string& delim = {},
165 ossia::net::encoding e = ossia::net::encoding::none)
166 : context{ctx}
167 , listener{std::move(l)}
168 , decoder{ossia::in_place_index<0>, listener.m_socket}
169 {
170 framing = f;
171 enc = e;
172 if(!delim.empty())
173 {
174 auto sz = std::min(delim.size(), (size_t)7);
175 std::copy_n(delim.begin(), sz, line_delimiter);
176 }
177 switch(f)
178 {
179 default:
180 case ossia::net::framing::none:
181 break;
182 case ossia::net::framing::slip:
183 decoder.template emplace<1>(listener.m_socket);
184 break;
185 case ossia::net::framing::size_prefix:
186 decoder.template emplace<2>(listener.m_socket);
187 break;
188 case ossia::net::framing::line_delimiter:
189 decoder.template emplace<3>(listener.m_socket);
190 {
191 auto& dec = ossia::get<3>(decoder);
192 std::copy_n(line_delimiter, 8, dec.delimiter);
193 }
194 break;
195 case ossia::net::framing::cobs:
196 decoder.template emplace<4>(listener.m_socket);
197 break;
198 case ossia::net::framing::stx_etx:
199 decoder.template emplace<5>(listener.m_socket);
200 break;
201 case ossia::net::framing::size_prefix_1byte:
202 decoder.template emplace<6>(listener.m_socket);
203 break;
204 case ossia::net::framing::size_prefix_2byte_be:
205 decoder.template emplace<7>(listener.m_socket);
206 break;
207 case ossia::net::framing::size_prefix_2byte_le:
208 decoder.template emplace<8>(listener.m_socket);
209 break;
210 case ossia::net::framing::size_prefix_4byte_le:
211 decoder.template emplace<9>(listener.m_socket);
212 break;
213 case ossia::net::framing::fixed_length:
214 decoder.template emplace<10>(listener.m_socket);
215 if(!delim.empty())
216 ossia::get<10>(decoder).frame_size = std::stoul(delim);
217 break;
218 }
219 }
220
221 void write_encoded(const char* data, std::size_t sz)
222 {
223 switch(framing)
224 {
225 default:
226 case ossia::net::framing::none:
227 listener.write(boost::asio::const_buffer(data, sz));
228 break;
229 case ossia::net::framing::slip:
230 ossia::net::slip_encoder<socket_t>{listener.m_socket}.write(data, sz);
231 break;
232 case ossia::net::framing::size_prefix:
233 ossia::net::size_prefix_encoder<socket_t>{listener.m_socket}.write(data, sz);
234 break;
235 case ossia::net::framing::line_delimiter: {
236 ossia::net::line_framing_encoder<socket_t> enc{listener.m_socket};
237 std::copy_n(line_delimiter, 8, enc.delimiter);
238 enc.write(data, sz);
239 break;
240 }
241 case ossia::net::framing::cobs:
242 ossia::net::cobs_encoder<socket_t>{listener.m_socket}.write(data, sz);
243 break;
244 case ossia::net::framing::stx_etx:
245 ossia::net::stx_etx_framing::encoder<socket_t>{listener.m_socket}.write(
246 data, sz);
247 break;
248 case ossia::net::framing::size_prefix_1byte:
249 ossia::net::size_prefix_1byte_framing::encoder<socket_t>{listener.m_socket}
250 .write(data, sz);
251 break;
252 case ossia::net::framing::size_prefix_2byte_be:
253 ossia::net::size_prefix_2byte_be_framing::encoder<socket_t>{listener.m_socket}
254 .write(data, sz);
255 break;
256 case ossia::net::framing::size_prefix_2byte_le:
257 ossia::net::size_prefix_2byte_le_framing::encoder<socket_t>{listener.m_socket}
258 .write(data, sz);
259 break;
260 case ossia::net::framing::size_prefix_4byte_le:
261 ossia::net::size_prefix_4byte_le_framing::encoder<socket_t>{listener.m_socket}
262 .write(data, sz);
263 break;
264 case ossia::net::framing::fixed_length:
265 ossia::net::fixed_length_encoder<socket_t>{listener.m_socket}.write(data, sz);
266 break;
267 }
268 }
269 };
270
271 struct receive_callback
272 {
273 std::shared_ptr<state> st;
274 QPointer<qml_unix_stream_connection> self;
275
276 void operator()(const unsigned char* data, std::size_t sz) const
277 {
278 if(!st->alive)
279 return;
280 auto buf = apply_decoding(st->enc, data, sz);
281 ossia::qt::run_async(
282 self.get(),
283 [self = self, buf] {
284 if(!self.get())
285 return;
286 if(self->onMessage.isCallable())
287 {
288 self->onMessage.call({qjsEngine(self.get())->toScriptValue(buf)});
289 }
290 },
291 Qt::AutoConnection);
292 }
293
294 bool validate_stream(boost::system::error_code ec) const
295 {
296 if(ec == boost::asio::error::operation_aborted)
297 return false;
298 if(ec == boost::asio::error::eof)
299 {
300 ossia::qt::run_async(
301 self.get(),
302 [self = self] {
303 if(self)
304 {
305 if(self->onClose.isCallable())
306 self->onClose.call();
307 self->deleteLater();
308 }
309 },
310 Qt::AutoConnection);
311 return false;
312 }
313 return true;
314 }
315 };
316
317 qml_unix_stream_connection(
318 ossia::net::unix_stream_listener listener, boost::asio::io_context& ctx,
319 ossia::net::framing f = ossia::net::framing::none,
320 const std::string& delim = {},
321 ossia::net::encoding e = ossia::net::encoding::none)
322 : m_state{std::make_shared<state>(std::move(listener), ctx, f, delim, e)}
323 {
324 }
325
326 ~qml_unix_stream_connection()
327 {
328 if(m_state)
329 {
330 m_state->alive = false;
331 close({});
332 }
333 }
334
335 bool isOpen() const noexcept { return m_state && m_state->alive; }
336
337 inline boost::asio::io_context& context() noexcept { return m_state->context; }
338
339 void write(QByteArray buffer)
340 {
341 if(!m_state)
342 return;
343 auto st = m_state;
344 if(st->enc != ossia::net::encoding::none)
345 buffer = apply_encoding(st->enc, buffer);
346 boost::asio::dispatch(st->context, [st, buffer = std::move(buffer)] {
347 if(st->alive)
348 st->write_encoded(buffer.data(), buffer.size());
349 });
350 }
351 W_SLOT(write)
352
353 void close(QByteArray)
354 {
355 if(!m_state)
356 return;
357 auto st = m_state;
358 boost::asio::dispatch(st->context, [st] { st->listener.close(); });
359 }
360 W_SLOT(close)
361
362 void startReceive()
363 {
364 auto st = m_state;
365 auto self = QPointer{this};
366 ossia::visit(
367 [cb = receive_callback{st, self}](auto& decoder) mutable {
368 decoder.receive(std::move(cb));
369 },
370 st->decoder);
371 }
372
373 QJSValue onMessage;
374 QJSValue onClose;
375 W_PROPERTY(QJSValue, onClose MEMBER onClose);
376
377private:
378 std::shared_ptr<state> m_state;
379};
380
381class qml_unix_stream_inbound_socket
382 : public QObject
383 , public Nano::Observer
384{
385 W_OBJECT(qml_unix_stream_inbound_socket)
386public:
387 struct state
388 {
389 ossia::net::unix_stream_server server;
390 std::atomic_bool alive{true};
391 ossia::net::framing framing{ossia::net::framing::none};
392 std::string framing_delimiter;
393 ossia::net::encoding enc{ossia::net::encoding::none};
394
395 state(
396 const ossia::net::fd_configuration& conf, boost::asio::io_context& ctx,
397 ossia::net::framing f = ossia::net::framing::none,
398 std::string delim = {},
399 ossia::net::encoding e = ossia::net::encoding::none)
400 : server{conf, ctx}
401 , framing{f}
402 , framing_delimiter{std::move(delim)}
403 , enc{e}
404 {
405 }
406 };
407
408 qml_unix_stream_inbound_socket() { }
409
410 ~qml_unix_stream_inbound_socket()
411 {
412 if(m_state)
413 {
414 m_state->alive = false;
415 close();
416 }
417 }
418
419 bool isOpen() const noexcept { return m_state != nullptr; }
420
421 void open(
422 const ossia::net::fd_configuration& conf, boost::asio::io_context& ctx,
423 ossia::net::framing f = ossia::net::framing::none,
424 const std::string& delim = {},
425 ossia::net::encoding e = ossia::net::encoding::none)
426 {
427 m_state = std::make_shared<state>(conf, ctx, f, delim, e);
428
429 accept_impl(m_state, QPointer{this});
430 if(onOpen.isCallable())
431 onOpen.call({qjsEngine(this)->newQObject(this)});
432 }
433
434 void close()
435 {
436 if(!m_state)
437 return;
438 m_state->server.m_acceptor.close();
439 if(onClose.isCallable())
440 onClose.call();
441 }
442 W_SLOT(close)
443
444 void on_close()
445 {
446 if(!m_state || !m_state->alive)
447 return;
448 ossia::qt::run_async(this, [=, this] { onClose.call(); }, Qt::AutoConnection);
449 }
450
451 QJSValue onOpen;
452 QJSValue onClose;
453 QJSValue onError;
454 QJSValue onConnection;
455
456private:
457 static void accept_impl(
458 std::shared_ptr<state> st, QPointer<qml_unix_stream_inbound_socket> self)
459 {
460 st->server.m_acceptor.async_accept(
461 [self, st](
462 boost::system::error_code ec,
463 ossia::net::unix_stream_server::proto::socket socket) {
464 if(!st->alive)
465 return;
466 if(!ec)
467 {
468 ossia::qt::run_async(
469 self.get(),
470 [self, st, socket = std::move(socket)]() mutable {
471 if(!self.get())
472 return;
473 auto conn = new qml_unix_stream_connection{
474 ossia::net::unix_stream_listener{std::move(socket)},
475 st->server.m_context, st->framing, st->framing_delimiter, st->enc};
476 conn->setParent(self.get());
477 conn->onMessage = self->onConnection;
478 conn->startReceive();
479
480 if(self->onConnection.isCallable())
481 {
482 self->onConnection.call(
483 {qjsEngine(self.get())->newQObject(static_cast<QObject*>(conn))});
484 }
485 },
486 Qt::AutoConnection);
487 accept_impl(st, self);
488 }
489 });
490 }
491
492 std::shared_ptr<state> m_state;
493};
494}
495#endif
Definition qml_device.cpp:43
Definition git_info.h:7