OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
qml_tcp_inbound_socket.hpp
1#pragma once
2#include <ossia/detail/variant.hpp>
3#include <ossia/network/context.hpp>
4#include <ossia/network/sockets/configuration.hpp>
5#include <ossia/network/sockets/encoding.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/tcp_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_tcp_connection
29 : public QObject
30 , public Nano::Observer
31{
32 W_OBJECT(qml_tcp_connection)
33public:
34 using socket_t = boost::asio::ip::tcp::socket;
35 using decoder_type = ossia::slow_variant<
36 ossia::net::no_framing::decoder<socket_t>,
37 ossia::net::slip_decoder<socket_t>,
38 ossia::net::size_prefix_decoder<socket_t>,
39 ossia::net::line_framing_decoder<socket_t>,
40 ossia::net::cobs_decoder<socket_t>,
41 ossia::net::stx_etx_framing::decoder<socket_t>,
42 ossia::net::size_prefix_1byte_framing::decoder<socket_t>,
43 ossia::net::size_prefix_2byte_be_framing::decoder<socket_t>,
44 ossia::net::size_prefix_2byte_le_framing::decoder<socket_t>,
45 ossia::net::size_prefix_4byte_le_framing::decoder<socket_t>,
46 ossia::net::fixed_length_decoder<socket_t>>;
47
48 struct state
49 {
50 boost::asio::io_context& context;
51 ossia::net::tcp_listener listener;
52 std::atomic_bool alive{true};
53 ossia::net::framing framing{ossia::net::framing::none};
54 ossia::net::encoding enc{ossia::net::encoding::none};
55 char line_delimiter[8] = {};
56 decoder_type decoder;
57
58 state(
59 ossia::net::tcp_listener l, boost::asio::io_context& ctx,
60 ossia::net::framing f = ossia::net::framing::none,
61 const std::string& delim = {},
62 ossia::net::encoding e = ossia::net::encoding::none)
63 : context{ctx}
64 , listener{std::move(l)}
65 , decoder{ossia::in_place_index<0>, listener.m_socket}
66 {
67 framing = f;
68 enc = e;
69 if(!delim.empty())
70 {
71 auto sz = std::min(delim.size(), (size_t)7);
72 std::copy_n(delim.begin(), sz, line_delimiter);
73 }
74
75 switch(f)
76 {
77 default:
78 case ossia::net::framing::none:
79 break;
80 case ossia::net::framing::slip:
81 decoder.template emplace<1>(listener.m_socket);
82 break;
83 case ossia::net::framing::size_prefix:
84 decoder.template emplace<2>(listener.m_socket);
85 break;
86 case ossia::net::framing::line_delimiter:
87 decoder.template emplace<3>(listener.m_socket);
88 {
89 auto& dec = ossia::get<3>(decoder);
90 std::copy_n(line_delimiter, 8, dec.delimiter);
91 }
92 break;
93 case ossia::net::framing::cobs:
94 decoder.template emplace<4>(listener.m_socket);
95 break;
96 case ossia::net::framing::stx_etx:
97 decoder.template emplace<5>(listener.m_socket);
98 break;
99 case ossia::net::framing::size_prefix_1byte:
100 decoder.template emplace<6>(listener.m_socket);
101 break;
102 case ossia::net::framing::size_prefix_2byte_be:
103 decoder.template emplace<7>(listener.m_socket);
104 break;
105 case ossia::net::framing::size_prefix_2byte_le:
106 decoder.template emplace<8>(listener.m_socket);
107 break;
108 case ossia::net::framing::size_prefix_4byte_le:
109 decoder.template emplace<9>(listener.m_socket);
110 break;
111 case ossia::net::framing::fixed_length:
112 decoder.template emplace<10>(listener.m_socket);
113 if(!delim.empty())
114 ossia::get<10>(decoder).frame_size = std::stoul(delim);
115 break;
116 }
117 }
118
119 void write_encoded(const char* data, std::size_t sz)
120 {
121 switch(framing)
122 {
123 default:
124 case ossia::net::framing::none:
125 listener.write(boost::asio::const_buffer(data, sz));
126 break;
127 case ossia::net::framing::slip:
128 ossia::net::slip_encoder<socket_t>{listener.m_socket}.write(data, sz);
129 break;
130 case ossia::net::framing::size_prefix:
131 ossia::net::size_prefix_encoder<socket_t>{listener.m_socket}.write(data, sz);
132 break;
133 case ossia::net::framing::line_delimiter: {
134 ossia::net::line_framing_encoder<socket_t> enc{listener.m_socket};
135 std::copy_n(line_delimiter, 8, enc.delimiter);
136 enc.write(data, sz);
137 break;
138 }
139 case ossia::net::framing::cobs:
140 ossia::net::cobs_encoder<socket_t>{listener.m_socket}.write(data, sz);
141 break;
142 case ossia::net::framing::stx_etx:
143 ossia::net::stx_etx_framing::encoder<socket_t>{listener.m_socket}.write(
144 data, sz);
145 break;
146 case ossia::net::framing::size_prefix_1byte:
147 ossia::net::size_prefix_1byte_framing::encoder<socket_t>{listener.m_socket}
148 .write(data, sz);
149 break;
150 case ossia::net::framing::size_prefix_2byte_be:
151 ossia::net::size_prefix_2byte_be_framing::encoder<socket_t>{listener.m_socket}
152 .write(data, sz);
153 break;
154 case ossia::net::framing::size_prefix_2byte_le:
155 ossia::net::size_prefix_2byte_le_framing::encoder<socket_t>{listener.m_socket}
156 .write(data, sz);
157 break;
158 case ossia::net::framing::size_prefix_4byte_le:
159 ossia::net::size_prefix_4byte_le_framing::encoder<socket_t>{listener.m_socket}
160 .write(data, sz);
161 break;
162 case ossia::net::framing::fixed_length:
163 ossia::net::fixed_length_encoder<socket_t>{listener.m_socket}.write(data, sz);
164 break;
165 }
166 }
167 };
168
169 struct receive_callback
170 {
171 std::shared_ptr<state> st;
172 QPointer<qml_tcp_connection> self;
173
174 void operator()(const unsigned char* data, std::size_t sz) const
175 {
176 if(!st->alive)
177 return;
178 auto buf = apply_decoding(st->enc, data, sz);
179 ossia::qt::run_async(
180 self.get(),
181 [self = self, buf] {
182 if(!self.get())
183 return;
184 if(self->onBytes.isCallable())
185 {
186 auto engine = qjsEngine(self.get());
187 if(engine)
188 self->onBytes.call({engine->toScriptValue(buf)});
189 }
190 },
191 Qt::AutoConnection);
192 }
193
194 bool validate_stream(boost::system::error_code ec) const
195 {
196 if(ec == boost::asio::error::operation_aborted)
197 return false;
198 if(ec == boost::asio::error::eof)
199 {
200 ossia::qt::run_async(
201 self.get(),
202 [self = self] {
203 if(self)
204 {
205 if(self->onClose.isCallable())
206 self->onClose.call();
207 // Connection is dead — schedule cleanup.
208 // Parent (server) child list is updated automatically by Qt.
209 self->deleteLater();
210 }
211 },
212 Qt::AutoConnection);
213 return false;
214 }
215 return true;
216 }
217 };
218
219 explicit qml_tcp_connection(
220 ossia::net::tcp_listener listener, boost::asio::io_context& ctx,
221 ossia::net::framing f = ossia::net::framing::none,
222 const std::string& delim = {},
223 ossia::net::encoding e = ossia::net::encoding::none)
224 : m_state{std::make_shared<state>(std::move(listener), ctx, f, delim, e)}
225 {
226 }
227
228 ~qml_tcp_connection()
229 {
230 if(m_state)
231 {
232 m_state->alive = false;
233 close({});
234 }
235 }
236
237 bool isOpen() const noexcept { return m_state && m_state->alive; }
238
239 inline boost::asio::io_context& context() noexcept { return m_state->context; }
240
241 void write(QByteArray buffer)
242 {
243 if(!m_state)
244 return;
245 auto st = m_state;
246 if(st->enc != ossia::net::encoding::none)
247 buffer = apply_encoding(st->enc, buffer);
248 boost::asio::dispatch(st->context, [st, buffer = std::move(buffer)] {
249 if(st->alive)
250 st->write_encoded(buffer.data(), buffer.size());
251 });
252 }
253 W_SLOT(write)
254
255 void close(QByteArray)
256 {
257 if(!m_state)
258 return;
259 auto st = m_state;
260 boost::asio::dispatch(st->context, [st] { st->listener.close(); });
261 }
262 W_SLOT(close)
263
264 void receive(QJSValue v)
265 {
266 onBytes = v;
267 auto st = m_state;
268 auto self = QPointer{this};
269 ossia::visit(
270 [cb = receive_callback{st, self}](auto& decoder) mutable {
271 decoder.receive(std::move(cb));
272 },
273 st->decoder);
274 }
275 W_SLOT(receive)
276
277 QJSValue onBytes;
278 QJSValue onClose;
279 W_PROPERTY(QJSValue, onClose W_MEMBER onClose);
280
281private:
282 std::shared_ptr<state> m_state;
283};
284
285class qml_tcp_inbound_socket
286 : public QObject
287 , public Nano::Observer
288{
289 W_OBJECT(qml_tcp_inbound_socket)
290public:
291 struct state
292 {
293 ossia::net::tcp_server server;
294 std::atomic_bool alive{true};
295 std::atomic_bool open{false};
296 ossia::net::framing framing{ossia::net::framing::none};
297 std::string framing_delimiter;
298 ossia::net::encoding enc{ossia::net::encoding::none};
299
300 state(
301 const ossia::net::inbound_socket_configuration& conf,
302 boost::asio::io_context& ctx,
303 ossia::net::framing f = ossia::net::framing::none,
304 std::string delim = {},
305 ossia::net::encoding e = ossia::net::encoding::none)
306 : server{conf, ctx}
307 , framing{f}
308 , framing_delimiter{std::move(delim)}
309 , enc{e}
310 {
311 }
312 };
313
314 qml_tcp_inbound_socket() { }
315
316 ~qml_tcp_inbound_socket()
317 {
318 if(m_state)
319 {
320 m_state->alive = false;
321 close();
322 }
323 }
324
325 bool isOpen() const noexcept { return m_state && m_state->open; }
326
327 void open(
328 const ossia::net::inbound_socket_configuration& conf,
329 boost::asio::io_context& ctx,
330 ossia::net::framing f = ossia::net::framing::none,
331 const std::string& delim = {},
332 ossia::net::encoding e = ossia::net::encoding::none)
333 {
334 m_state = std::make_shared<state>(conf, ctx, f, delim, e);
335 m_state->open = true;
336 accept_impl(m_state, QPointer{this});
337 if(onOpen.isCallable())
338 onOpen.call({qjsEngine(this)->newQObject(this)});
339 }
340
341 void close()
342 {
343 if(!m_state)
344 return;
345 m_state->open = false;
346 m_state->server.m_acceptor.close();
347 if(onClose.isCallable())
348 onClose.call();
349 }
350 W_SLOT(close)
351
352 void on_close()
353 {
354 if(!m_state)
355 return;
356 m_state->open = false;
357 ossia::qt::run_async(this, [=, this] { onClose.call(); }, Qt::AutoConnection);
358 }
359
360 QJSValue onOpen;
361 QJSValue onClose;
362 QJSValue onError;
363 QJSValue onConnection;
364
365private:
366 static void accept_impl(
367 std::shared_ptr<state> st, QPointer<qml_tcp_inbound_socket> self)
368 {
369 st->server.m_acceptor.async_accept(
370 [self, st](
371 boost::system::error_code ec, ossia::net::tcp_server::proto::socket socket) {
372 if(!st->alive || !st->open)
373 return;
374 if(!ec)
375 {
376 ossia::qt::run_async(
377 self.get(),
378 [self, st, socket = std::move(socket)]() mutable {
379 if(!self.get())
380 return;
381 auto conn = new qml_tcp_connection{
382 ossia::net::tcp_listener{std::move(socket)}, st->server.m_context,
383 st->framing, st->framing_delimiter, st->enc};
384
385 // Parent to the server so Qt uses CppOwnership (prevents QML GC)
386 // and automatically deletes all connections when the server is destroyed.
387 conn->setParent(self.get());
388
389 if(self->onConnection.isCallable())
390 {
391 self->onConnection.call({qjsEngine(self.get())->newQObject(conn)});
392 }
393 },
394 Qt::AutoConnection);
395 accept_impl(st, self);
396 }
397 });
398 }
399
400 std::shared_ptr<state> m_state;
401};
402
403}
Definition qml_device.cpp:43
Definition git_info.h:7