1 #include "MessageServer.hpp"
2
3 #include <stdexcept>
4 #include <limits>
5
6 #include <QNetworkInterface>
7 #include <QUdpSocket>
8 #include <QTimer>
9 #include <QHash>
10
11 #include "Radio.hpp"
12 #include "Network/NetworkMessage.hpp"
13 #include "qt_helpers.hpp"
14
15 #include "pimpl_impl.hpp"
16
17 #include "moc_MessageServer.cpp"
18
19 namespace
20 {
21 auto quint32_max = std::numeric_limits<quint32>::max ();
22 }
23
24 class MessageServer::impl
25 : public QUdpSocket
26 {
27 Q_OBJECT;
28
29 public:
impl(MessageServer * self,QString const & version,QString const & revision)30 impl (MessageServer * self, QString const& version, QString const& revision)
31 : self_ {self}
32 , version_ {version}
33 , revision_ {revision}
34 , clock_ {new QTimer {this}}
35 {
36 // register the required types with Qt
37 Radio::register_types ();
38
39 connect (this, &QIODevice::readyRead, this, &MessageServer::impl::pending_datagrams);
40 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
41 connect (this, static_cast<void (impl::*) (SocketError)> (&impl::error)
42 , [this] (SocketError /* e */)
__anonc82af2fc0202(SocketError ) 43 {
44 Q_EMIT self_->error (errorString ());
45 });
46 #else
47 connect (this, &impl::errorOccurred, [this] (SocketError /* e */)
__anonc82af2fc0302(SocketError ) 48 {
49 Q_EMIT self_->error (errorString ());
50 });
51 #endif
52 connect (clock_, &QTimer::timeout, this, &impl::tick);
53 clock_->start (NetworkMessage::pulse * 1000);
54 }
55
56 enum StreamStatus {Fail, Short, OK};
57
58 void leave_multicast_group ();
59 void join_multicast_group ();
60 void parse_message (QHostAddress const& sender, port_type sender_port, QByteArray const& msg);
61 void tick ();
62 void pending_datagrams ();
63 StreamStatus check_status (QDataStream const&) const;
send_message(QDataStream const & out,QByteArray const & message,QHostAddress const & address,port_type port)64 void send_message (QDataStream const& out, QByteArray const& message, QHostAddress const& address, port_type port)
65 {
66 if (OK == check_status (out))
67 {
68 writeDatagram (message, address, port);
69 }
70 else
71 {
72 Q_EMIT self_->error ("Error creating UDP message");
73 }
74 }
75
76 MessageServer * self_;
77 QString version_;
78 QString revision_;
79 QHostAddress multicast_group_address_;
80 QSet<QString> network_interfaces_;
81 static BindMode constexpr bind_mode_ = ShareAddress | ReuseAddressHint;
82 struct Client
83 {
84 Client () = default;
ClientMessageServer::impl::Client85 Client (port_type const& sender_port)
86 : sender_port_ {sender_port}
87 , negotiated_schema_number_ {2} // not 1 because it's broken
88 , last_activity_ {QDateTime::currentDateTime ()}
89 {
90 }
91 Client (Client const&) = default;
92 Client& operator= (Client const&) = default;
93
94 port_type sender_port_;
95 quint32 negotiated_schema_number_;
96 QDateTime last_activity_;
97 };
98 QHash<ClientKey, Client> clients_; // maps id to Client
99 QTimer * clock_;
100 };
101
102 MessageServer::impl::BindMode constexpr MessageServer::impl::bind_mode_;
103
104 #include "MessageServer.moc"
105
leave_multicast_group()106 void MessageServer::impl::leave_multicast_group ()
107 {
108 if (BoundState == state () && is_multicast_address (multicast_group_address_))
109 {
110 for (auto const& if_name : network_interfaces_)
111 {
112 leaveMulticastGroup (multicast_group_address_, QNetworkInterface::interfaceFromName (if_name));
113 }
114 }
115 }
116
join_multicast_group()117 void MessageServer::impl::join_multicast_group ()
118 {
119 if (BoundState == state () && is_multicast_address (multicast_group_address_))
120 {
121 if (network_interfaces_.size ())
122 {
123 for (auto const& if_name : network_interfaces_)
124 {
125 joinMulticastGroup (multicast_group_address_, QNetworkInterface::interfaceFromName (if_name));
126 }
127 }
128 else
129 {
130 // find the loop-back interface and join on that
131 for (auto const& net_if : QNetworkInterface::allInterfaces ())
132 {
133 auto flags = QNetworkInterface::IsUp | QNetworkInterface::IsLoopBack | QNetworkInterface::CanMulticast;
134 if ((net_if.flags () & flags) == flags)
135 {
136 joinMulticastGroup (multicast_group_address_, net_if);
137 break;
138 }
139 }
140 }
141 }
142 }
143
pending_datagrams()144 void MessageServer::impl::pending_datagrams ()
145 {
146 while (hasPendingDatagrams ())
147 {
148 QByteArray datagram;
149 datagram.resize (pendingDatagramSize ());
150 QHostAddress sender_address;
151 port_type sender_port;
152 if (0 <= readDatagram (datagram.data (), datagram.size (), &sender_address, &sender_port))
153 {
154 parse_message (sender_address, sender_port, datagram);
155 }
156 }
157 }
158
parse_message(QHostAddress const & sender,port_type sender_port,QByteArray const & msg)159 void MessageServer::impl::parse_message (QHostAddress const& sender, port_type sender_port, QByteArray const& msg)
160 {
161 try
162 {
163 //
164 // message format is described in NetworkMessage.hpp
165 //
166 NetworkMessage::Reader in {msg};
167
168 auto id = in.id ();
169 if (OK == check_status (in))
170 {
171 auto client_key = ClientKey {sender, id};
172 if (!clients_.contains (client_key))
173 {
174 auto& client = (clients_[client_key] = {sender_port});
175 QByteArray client_version;
176 QByteArray client_revision;
177
178 if (NetworkMessage::Heartbeat == in.type ())
179 {
180 // negotiate a working schema number
181 in >> client.negotiated_schema_number_;
182 if (OK == check_status (in))
183 {
184 auto sn = NetworkMessage::Builder::schema_number;
185 client.negotiated_schema_number_ = std::min (sn, client.negotiated_schema_number_);
186
187 // reply to the new client informing it of the
188 // negotiated schema number
189 QByteArray message;
190 NetworkMessage::Builder hb {&message, NetworkMessage::Heartbeat, id, client.negotiated_schema_number_};
191 hb << NetworkMessage::Builder::schema_number // maximum schema number accepted
192 << version_.toUtf8 () << revision_.toUtf8 ();
193 if (impl::OK == check_status (hb))
194 {
195 writeDatagram (message, client_key.first, sender_port);
196 }
197 else
198 {
199 Q_EMIT self_->error ("Error creating UDP message");
200 }
201 }
202 // we don't care if this fails to read
203 in >> client_version >> client_revision;
204 }
205 Q_EMIT self_->client_opened (client_key, QString::fromUtf8 (client_version),
206 QString::fromUtf8 (client_revision));
207 }
208 clients_[client_key].last_activity_ = QDateTime::currentDateTime ();
209
210 //
211 // message format is described in NetworkMessage.hpp
212 //
213 switch (in.type ())
214 {
215 case NetworkMessage::Heartbeat:
216 //nothing to do here as time out handling deals with lifetime
217 break;
218
219 case NetworkMessage::Clear:
220 Q_EMIT self_->decodes_cleared (client_key);
221 break;
222
223 case NetworkMessage::Status:
224 {
225 // unpack message
226 Frequency f;
227 QByteArray mode;
228 QByteArray dx_call;
229 QByteArray report;
230 QByteArray tx_mode;
231 bool tx_enabled {false};
232 bool transmitting {false};
233 bool decoding {false};
234 quint32 rx_df {quint32_max};
235 quint32 tx_df {quint32_max};
236 QByteArray de_call;
237 QByteArray de_grid;
238 QByteArray dx_grid;
239 bool watchdog_timeout {false};
240 QByteArray sub_mode;
241 bool fast_mode {false};
242 quint8 special_op_mode {0};
243 quint32 frequency_tolerance {quint32_max};
244 quint32 tr_period {quint32_max};
245 QByteArray configuration_name;
246 QByteArray tx_message;
247 in >> f >> mode >> dx_call >> report >> tx_mode >> tx_enabled >> transmitting >> decoding
248 >> rx_df >> tx_df >> de_call >> de_grid >> dx_grid >> watchdog_timeout >> sub_mode
249 >> fast_mode >> special_op_mode >> frequency_tolerance >> tr_period >> configuration_name
250 >> tx_message;
251 if (check_status (in) != Fail)
252 {
253 Q_EMIT self_->status_update (client_key, f, QString::fromUtf8 (mode)
254 , QString::fromUtf8 (dx_call)
255 , QString::fromUtf8 (report), QString::fromUtf8 (tx_mode)
256 , tx_enabled, transmitting, decoding, rx_df, tx_df
257 , QString::fromUtf8 (de_call), QString::fromUtf8 (de_grid)
258 , QString::fromUtf8 (dx_grid), watchdog_timeout
259 , QString::fromUtf8 (sub_mode), fast_mode
260 , special_op_mode, frequency_tolerance, tr_period
261 , QString::fromUtf8 (configuration_name)
262 , QString::fromUtf8 (tx_message));
263 }
264 }
265 break;
266
267 case NetworkMessage::Decode:
268 {
269 // unpack message
270 bool is_new {true};
271 QTime time;
272 qint32 snr;
273 float delta_time;
274 quint32 delta_frequency;
275 QByteArray mode;
276 QByteArray message;
277 bool low_confidence {false};
278 bool off_air {false};
279 in >> is_new >> time >> snr >> delta_time >> delta_frequency >> mode
280 >> message >> low_confidence >> off_air;
281 if (check_status (in) != Fail)
282 {
283 Q_EMIT self_->decode (is_new, client_key, time, snr, delta_time, delta_frequency
284 , QString::fromUtf8 (mode), QString::fromUtf8 (message)
285 , low_confidence, off_air);
286 }
287 }
288 break;
289
290 case NetworkMessage::WSPRDecode:
291 {
292 // unpack message
293 bool is_new {true};
294 QTime time;
295 qint32 snr;
296 float delta_time;
297 Frequency frequency;
298 qint32 drift;
299 QByteArray callsign;
300 QByteArray grid;
301 qint32 power;
302 bool off_air {false};
303 in >> is_new >> time >> snr >> delta_time >> frequency >> drift >> callsign >> grid >> power
304 >> off_air;
305 if (check_status (in) != Fail)
306 {
307 Q_EMIT self_->WSPR_decode (is_new, client_key, time, snr, delta_time, frequency, drift
308 , QString::fromUtf8 (callsign), QString::fromUtf8 (grid)
309 , power, off_air);
310 }
311 }
312 break;
313
314 case NetworkMessage::QSOLogged:
315 {
316 QDateTime time_off;
317 QByteArray dx_call;
318 QByteArray dx_grid;
319 Frequency dial_frequency;
320 QByteArray mode;
321 QByteArray report_sent;
322 QByteArray report_received;
323 QByteArray tx_power;
324 QByteArray comments;
325 QByteArray name;
326 QDateTime time_on; // Note: LOTW uses TIME_ON for their +/- 30-minute time window
327 QByteArray operator_call;
328 QByteArray my_call;
329 QByteArray my_grid;
330 QByteArray exchange_sent;
331 QByteArray exchange_rcvd;
332 QByteArray prop_mode;
333 in >> time_off >> dx_call >> dx_grid >> dial_frequency >> mode >> report_sent >> report_received
334 >> tx_power >> comments >> name >> time_on >> operator_call >> my_call >> my_grid
335 >> exchange_sent >> exchange_rcvd >> prop_mode;
336 if (check_status (in) != Fail)
337 {
338 Q_EMIT self_->qso_logged (client_key, time_off, QString::fromUtf8 (dx_call)
339 , QString::fromUtf8 (dx_grid)
340 , dial_frequency, QString::fromUtf8 (mode)
341 , QString::fromUtf8 (report_sent)
342 , QString::fromUtf8 (report_received), QString::fromUtf8 (tx_power)
343 , QString::fromUtf8 (comments), QString::fromUtf8 (name), time_on
344 , QString::fromUtf8 (operator_call), QString::fromUtf8 (my_call)
345 , QString::fromUtf8 (my_grid), QString::fromUtf8 (exchange_sent)
346 , QString::fromUtf8 (exchange_rcvd), QString::fromUtf8 (prop_mode));
347 }
348 }
349 break;
350
351 case NetworkMessage::Close:
352 Q_EMIT self_->client_closed (client_key);
353 clients_.remove (client_key);
354 break;
355
356 case NetworkMessage::LoggedADIF:
357 {
358 QByteArray ADIF;
359 in >> ADIF;
360 if (check_status (in) != Fail)
361 {
362 Q_EMIT self_->logged_ADIF (client_key, ADIF);
363 }
364 }
365 break;
366
367 default:
368 // Ignore
369 break;
370 }
371 }
372 else
373 {
374 Q_EMIT self_->error ("MessageServer warning: invalid UDP message received");
375 }
376 }
377 catch (std::exception const& e)
378 {
379 Q_EMIT self_->error (QString {"MessageServer exception: %1"}.arg (e.what ()));
380 }
381 catch (...)
382 {
383 Q_EMIT self_->error ("Unexpected exception in MessageServer");
384 }
385 }
386
tick()387 void MessageServer::impl::tick ()
388 {
389 auto now = QDateTime::currentDateTime ();
390 auto iter = std::begin (clients_);
391 while (iter != std::end (clients_))
392 {
393 if (now > (*iter).last_activity_.addSecs (NetworkMessage::pulse))
394 {
395 Q_EMIT self_->decodes_cleared (iter.key ());
396 Q_EMIT self_->client_closed (iter.key ());
397 iter = clients_.erase (iter); // safe while iterating as doesn't rehash
398 }
399 else
400 {
401 ++iter;
402 }
403 }
404 }
405
check_status(QDataStream const & stream) const406 auto MessageServer::impl::check_status (QDataStream const& stream) const -> StreamStatus
407 {
408 auto stat = stream.status ();
409 StreamStatus result {Fail};
410 switch (stat)
411 {
412 case QDataStream::ReadPastEnd:
413 result = Short;
414 break;
415
416 case QDataStream::ReadCorruptData:
417 Q_EMIT self_->error ("Message serialization error: read corrupt data");
418 break;
419
420 case QDataStream::WriteFailed:
421 Q_EMIT self_->error ("Message serialization error: write error");
422 break;
423
424 default:
425 result = OK;
426 break;
427 }
428 return result;
429 }
430
MessageServer(QObject * parent,QString const & version,QString const & revision)431 MessageServer::MessageServer (QObject * parent, QString const& version, QString const& revision)
432 : QObject {parent}
433 , m_ {this, version, revision}
434 {
435 }
436
start(port_type port,QHostAddress const & multicast_group_address,QSet<QString> const & network_interface_names)437 void MessageServer::start (port_type port, QHostAddress const& multicast_group_address
438 , QSet<QString> const& network_interface_names)
439 {
440 // qDebug () << "MessageServer::start port:" << port << "multicast addr:" << multicast_group_address.toString () << "network interfaces:" << network_interface_names;
441 if (port != m_->localPort ()
442 || multicast_group_address != m_->multicast_group_address_
443 || network_interface_names != m_->network_interfaces_)
444 {
445 m_->leave_multicast_group ();
446 if (impl::UnconnectedState != m_->state ())
447 {
448 m_->close ();
449 }
450 if (!(multicast_group_address.isNull () || is_multicast_address (multicast_group_address)))
451 {
452 Q_EMIT error ("Invalid multicast group address");
453 }
454 else if (is_MAC_ambiguous_multicast_address (multicast_group_address))
455 {
456 Q_EMIT error ("MAC-ambiguous IPv4 multicast group address not supported");
457 }
458 else
459 {
460 m_->multicast_group_address_ = multicast_group_address;
461 m_->network_interfaces_ = network_interface_names;
462 QHostAddress local_addr {is_multicast_address (multicast_group_address)
463 && impl::IPv4Protocol == multicast_group_address.protocol () ? QHostAddress::AnyIPv4 : QHostAddress::Any};
464 if (port && m_->bind (local_addr, port, m_->bind_mode_))
465 {
466 m_->join_multicast_group ();
467 }
468 }
469 }
470 }
471
clear_decodes(ClientKey const & key,quint8 window)472 void MessageServer::clear_decodes (ClientKey const& key, quint8 window)
473 {
474 auto iter = m_->clients_.find (key);
475 if (iter != std::end (m_->clients_))
476 {
477 QByteArray message;
478 NetworkMessage::Builder out {&message, NetworkMessage::Clear, key.second, (*iter).negotiated_schema_number_};
479 out << window;
480 m_->send_message (out, message, key.first, (*iter).sender_port_);
481 }
482 }
483
reply(ClientKey const & key,QTime time,qint32 snr,float delta_time,quint32 delta_frequency,QString const & mode,QString const & message_text,bool low_confidence,quint8 modifiers)484 void MessageServer::reply (ClientKey const& key, QTime time, qint32 snr, float delta_time
485 , quint32 delta_frequency, QString const& mode
486 , QString const& message_text, bool low_confidence, quint8 modifiers)
487 {
488 auto iter = m_->clients_.find (key);
489 if (iter != std::end (m_->clients_))
490 {
491 QByteArray message;
492 NetworkMessage::Builder out {&message, NetworkMessage::Reply, key.second, (*iter).negotiated_schema_number_};
493 out << time << snr << delta_time << delta_frequency << mode.toUtf8 ()
494 << message_text.toUtf8 () << low_confidence << modifiers;
495 m_->send_message (out, message, key.first, (*iter).sender_port_);
496 }
497 }
498
replay(ClientKey const & key)499 void MessageServer::replay (ClientKey const& key)
500 {
501 auto iter = m_->clients_.find (key);
502 if (iter != std::end (m_->clients_))
503 {
504 QByteArray message;
505 NetworkMessage::Builder out {&message, NetworkMessage::Replay, key.second, (*iter).negotiated_schema_number_};
506 m_->send_message (out, message, key.first, (*iter).sender_port_);
507 }
508 }
509
close(ClientKey const & key)510 void MessageServer::close (ClientKey const& key)
511 {
512 auto iter = m_->clients_.find (key);
513 if (iter != std::end (m_->clients_))
514 {
515 QByteArray message;
516 NetworkMessage::Builder out {&message, NetworkMessage::Close, key.second, (*iter).negotiated_schema_number_};
517 m_->send_message (out, message, key.first, (*iter).sender_port_);
518 }
519 }
520
halt_tx(ClientKey const & key,bool auto_only)521 void MessageServer::halt_tx (ClientKey const& key, bool auto_only)
522 {
523 auto iter = m_->clients_.find (key);
524 if (iter != std::end (m_->clients_))
525 {
526 QByteArray message;
527 NetworkMessage::Builder out {&message, NetworkMessage::HaltTx, key.second, (*iter).negotiated_schema_number_};
528 out << auto_only;
529 m_->send_message (out, message, key.first, (*iter).sender_port_);
530 }
531 }
532
free_text(ClientKey const & key,QString const & text,bool send)533 void MessageServer::free_text (ClientKey const& key, QString const& text, bool send)
534 {
535 auto iter = m_->clients_.find (key);
536 if (iter != std::end (m_->clients_))
537 {
538 QByteArray message;
539 NetworkMessage::Builder out {&message, NetworkMessage::FreeText, key.second, (*iter).negotiated_schema_number_};
540 out << text.toUtf8 () << send;
541 m_->send_message (out, message, key.first, (*iter).sender_port_);
542 }
543 }
544
location(ClientKey const & key,QString const & loc)545 void MessageServer::location (ClientKey const& key, QString const& loc)
546 {
547 auto iter = m_->clients_.find (key);
548 if (iter != std::end (m_->clients_))
549 {
550 QByteArray message;
551 NetworkMessage::Builder out {&message, NetworkMessage::Location, key.second, (*iter).negotiated_schema_number_};
552 out << loc.toUtf8 ();
553 m_->send_message (out, message, key.first, (*iter).sender_port_);
554 }
555 }
556
highlight_callsign(ClientKey const & key,QString const & callsign,QColor const & bg,QColor const & fg,bool last_only)557 void MessageServer::highlight_callsign (ClientKey const& key, QString const& callsign
558 , QColor const& bg, QColor const& fg, bool last_only)
559 {
560 auto iter = m_->clients_.find (key);
561 if (iter != std::end (m_->clients_))
562 {
563 QByteArray message;
564 NetworkMessage::Builder out {&message, NetworkMessage::HighlightCallsign, key.second, (*iter).negotiated_schema_number_};
565 out << callsign.toUtf8 () << bg << fg << last_only;
566 m_->send_message (out, message, key.first, (*iter).sender_port_);
567 }
568 }
569
switch_configuration(ClientKey const & key,QString const & configuration_name)570 void MessageServer::switch_configuration (ClientKey const& key, QString const& configuration_name)
571 {
572 auto iter = m_->clients_.find (key);
573 if (iter != std::end (m_->clients_))
574 {
575 QByteArray message;
576 NetworkMessage::Builder out {&message, NetworkMessage::SwitchConfiguration, key.second, (*iter).negotiated_schema_number_};
577 out << configuration_name.toUtf8 ();
578 m_->send_message (out, message, key.first, (*iter).sender_port_);
579 }
580 }
581
configure(ClientKey const & key,QString const & mode,quint32 frequency_tolerance,QString const & submode,bool fast_mode,quint32 tr_period,quint32 rx_df,QString const & dx_call,QString const & dx_grid,bool generate_messages)582 void MessageServer::configure (ClientKey const& key, QString const& mode, quint32 frequency_tolerance
583 , QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df
584 , QString const& dx_call, QString const& dx_grid, bool generate_messages)
585 {
586 auto iter = m_->clients_.find (key);
587 if (iter != std::end (m_->clients_))
588 {
589 QByteArray message;
590 NetworkMessage::Builder out {&message, NetworkMessage::Configure, key.second, (*iter).negotiated_schema_number_};
591 out << mode.toUtf8 () << frequency_tolerance << submode.toUtf8 () << fast_mode << tr_period << rx_df
592 << dx_call.toUtf8 () << dx_grid.toUtf8 () << generate_messages;
593 m_->send_message (out, message, key.first, (*iter).sender_port_);
594 }
595 }
596