1 #include "MessageClient.hpp"
2 
3 #include <stdexcept>
4 #include <vector>
5 #include <algorithm>
6 #include <limits>
7 
8 #include <QUdpSocket>
9 #include <QNetworkInterface>
10 #include <QHostInfo>
11 #include <QTimer>
12 #include <QQueue>
13 #include <QByteArray>
14 #include <QColor>
15 #include <QDebug>
16 
17 #include "NetworkMessage.hpp"
18 #include "qt_helpers.hpp"
19 #include "pimpl_impl.hpp"
20 
21 #include "moc_MessageClient.cpp"
22 
23 // some trace macros
24 #if WSJT_TRACE_UDP
25 #define TRACE_UDP(MSG) qDebug () << QString {"MessageClient::%1:"}.arg (__func__) << MSG
26 #else
27 #define TRACE_UDP(MSG)
28 #endif
29 
30 class MessageClient::impl
31   : public QUdpSocket
32 {
33   Q_OBJECT;
34 
35 public:
impl(QString const & id,QString const & version,QString const & revision,port_type server_port,int TTL,MessageClient * self)36   impl (QString const& id, QString const& version, QString const& revision,
37         port_type server_port, int TTL, MessageClient * self)
38     : self_ {self}
39     , enabled_ {false}
40     , id_ {id}
41     , version_ {version}
42     , revision_ {revision}
43     , dns_lookup_id_ {-1}
44     , server_port_ {server_port}
45     , TTL_ {TTL}
46     , schema_ {2}  // use 2 prior to negotiation not 1 which is broken
47     , heartbeat_timer_ {new QTimer {this}}
48   {
49     connect (heartbeat_timer_, &QTimer::timeout, this, &impl::heartbeat);
50     connect (this, &QIODevice::readyRead, this, &impl::pending_datagrams);
51 
52     heartbeat_timer_->start (NetworkMessage::pulse * 1000);
53   }
54 
~impl()55   ~impl ()
56   {
57     closedown ();
58     if (dns_lookup_id_ != -1)
59       {
60         QHostInfo::abortHostLookup (dns_lookup_id_);
61       }
62   }
63 
64   enum StreamStatus {Fail, Short, OK};
65 
66   void set_server (QString const& server_name, QStringList const& network_interface_names);
67   Q_SLOT void host_info_results (QHostInfo);
68   void start ();
69   void parse_message (QByteArray const&);
70   void pending_datagrams ();
71   void heartbeat ();
72   void closedown ();
73   StreamStatus check_status (QDataStream const&) const;
74   void send_message (QByteArray const&, bool queue_if_pending = true, bool allow_duplicates = false);
send_message(QDataStream const & out,QByteArray const & message,bool queue_if_pending=true,bool allow_duplicates=false)75   void send_message (QDataStream const& out, QByteArray const& message, bool queue_if_pending = true, bool allow_duplicates = false)
76   {
77     if (OK == check_status (out))
78       {
79         send_message (message, queue_if_pending, allow_duplicates);
80       }
81     else
82       {
83         Q_EMIT self_->error ("Error creating UDP message");
84       }
85   }
86 
87   MessageClient * self_;
88   bool enabled_;
89   QString id_;
90   QString version_;
91   QString revision_;
92   int dns_lookup_id_;
93   QHostAddress server_;
94   port_type server_port_;
95   int TTL_;
96   std::vector<QNetworkInterface> network_interfaces_;
97   quint32 schema_;
98   QTimer * heartbeat_timer_;
99   std::vector<QHostAddress> blocked_addresses_;
100 
101   // hold messages sent before host lookup completes asynchronously
102   QQueue<QByteArray> pending_messages_;
103   QByteArray last_message_;
104 };
105 
106 #include "MessageClient.moc"
107 
set_server(QString const & server_name,QStringList const & network_interface_names)108 void MessageClient::impl::set_server (QString const& server_name, QStringList const& network_interface_names)
109 {
110   // qDebug () << "MessageClient server:" << server_name << "port:" << server_port_ << "interfaces:" << network_interface_names;
111   server_.setAddress (server_name);
112   network_interfaces_.clear ();
113   for (auto const& net_if_name : network_interface_names)
114     {
115       network_interfaces_.push_back (QNetworkInterface::interfaceFromName (net_if_name));
116     }
117 
118   if (server_.isNull () && server_name.size ()) // DNS lookup required
119     {
120       // queue a host address lookup
121 #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
122       dns_lookup_id_ = QHostInfo::lookupHost (server_name, this, &MessageClient::impl::host_info_results);
123 #else
124       dns_lookup_id_ = QHostInfo::lookupHost (server_name, this, SLOT (host_info_results (QHostInfo)));
125 #endif
126     }
127   else
128     {
129       start ();
130     }
131 }
132 
host_info_results(QHostInfo host_info)133 void MessageClient::impl::host_info_results (QHostInfo host_info)
134 {
135   if (host_info.lookupId () != dns_lookup_id_) return;
136   dns_lookup_id_ = -1;
137   if (QHostInfo::NoError != host_info.error ())
138     {
139       Q_EMIT self_->error ("UDP server DNS lookup failed: " + host_info.errorString ());
140     }
141   else
142     {
143       auto const& server_addresses = host_info.addresses ();
144       if (server_addresses.size ())
145         {
146           server_ = server_addresses[0];
147         }
148     }
149   start ();
150 }
151 
start()152 void MessageClient::impl::start ()
153 {
154   if (server_.isNull ())
155     {
156       Q_EMIT self_->close ();
157       pending_messages_.clear (); // discard
158       return;
159     }
160 
161   if (is_broadcast_address (server_))
162     {
163       Q_EMIT self_->error ("IPv4 broadcast not supported, please specify the loop-back address, a server host address, or multicast group address");
164       pending_messages_.clear (); // discard
165       return;
166     }
167 
168   if (blocked_addresses_.end () != std::find (blocked_addresses_.begin (), blocked_addresses_.end (), server_))
169     {
170       Q_EMIT self_->error ("UDP server blocked, please try another");
171       pending_messages_.clear (); // discard
172       return;
173     }
174 
175   TRACE_UDP ("Trying server:" << server_.toString ());
176   QHostAddress interface_addr {IPv6Protocol == server_.protocol () ? QHostAddress::AnyIPv6 : QHostAddress::AnyIPv4};
177 
178   if (localAddress () != interface_addr)
179     {
180       if (UnconnectedState != state () || state ())
181         {
182           close ();
183         }
184       // bind to an ephemeral port on the selected interface and set
185       // up for sending datagrams
186       bind (interface_addr);
187       // qDebug () << "Bound to UDP port:" << localPort () << "on:" << localAddress ();
188 
189       // set multicast TTL to limit scope when sending to multicast
190       // group addresses
191       setSocketOption (MulticastTtlOption, TTL_);
192     }
193 
194   // send initial heartbeat which allows schema negotiation
195   heartbeat ();
196 
197   // clear any backlog
198   while (pending_messages_.size ())
199     {
200       send_message (pending_messages_.dequeue (), true, false);
201     }
202 }
203 
pending_datagrams()204 void MessageClient::impl::pending_datagrams ()
205 {
206   while (hasPendingDatagrams ())
207     {
208       QByteArray datagram;
209       datagram.resize (pendingDatagramSize ());
210       QHostAddress sender_address;
211       port_type sender_port;
212       if (0 <= readDatagram (datagram.data (), datagram.size (), &sender_address, &sender_port))
213         {
214           TRACE_UDP ("message received from:" << sender_address << "port:" << sender_port);
215           parse_message (datagram);
216         }
217     }
218 }
219 
parse_message(QByteArray const & msg)220 void MessageClient::impl::parse_message (QByteArray const& msg)
221 {
222   try
223     {
224       //
225       // message format is described in NetworkMessage.hpp
226       //
227       NetworkMessage::Reader in {msg};
228       if (OK == check_status (in))
229         {
230           if (schema_ < in.schema ()) // one time record of server's
231                                       // negotiated schema
232             {
233               schema_ = in.schema ();
234             }
235 
236           if (!enabled_)
237             {
238               TRACE_UDP ("message processing disabled for id:" << in.id ());
239               return;
240             }
241 
242           //
243           // message format is described in NetworkMessage.hpp
244           //
245           switch (in.type ())
246             {
247             case NetworkMessage::Reply:
248               {
249                 // unpack message
250                 QTime time;
251                 qint32 snr;
252                 float delta_time;
253                 quint32 delta_frequency;
254                 QByteArray mode;
255                 QByteArray message;
256                 bool low_confidence {false};
257                 quint8 modifiers {0};
258                 in >> time >> snr >> delta_time >> delta_frequency >> mode >> message
259                    >> low_confidence >> modifiers;
260                 TRACE_UDP ("Reply: time:" << time << "snr:" << snr << "dt:" << delta_time << "df:" << delta_frequency << "mode:" << mode << "message:" << message << "low confidence:" << low_confidence << "modifiers: 0x"
261 #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
262                            << Qt::hex
263 #else
264                            << hex
265 #endif
266                            << modifiers);
267                 if (check_status (in) != Fail)
268                   {
269                     Q_EMIT self_->reply (time, snr, delta_time, delta_frequency
270                                          , QString::fromUtf8 (mode), QString::fromUtf8 (message)
271                                          , low_confidence, modifiers);
272                   }
273               }
274               break;
275 
276             case NetworkMessage::Clear:
277               {
278                 quint8 window {0};
279                 in >> window;
280                 TRACE_UDP ("Clear window:" << window);
281                 if (check_status (in) != Fail)
282                   {
283                     Q_EMIT self_->clear_decodes (window);
284                   }
285               }
286               break;
287 
288             case NetworkMessage::Close:
289               TRACE_UDP ("Close");
290               if (check_status (in) != Fail)
291                 {
292                   last_message_.clear ();
293                   Q_EMIT self_->close ();
294                 }
295               break;
296 
297             case NetworkMessage::Replay:
298               TRACE_UDP ("Replay");
299               if (check_status (in) != Fail)
300                 {
301                   last_message_.clear ();
302                   Q_EMIT self_->replay ();
303                 }
304               break;
305 
306             case NetworkMessage::HaltTx:
307               {
308                 bool auto_only {false};
309                 in >> auto_only;
310                 TRACE_UDP ("Halt Tx auto_only:" << auto_only);
311                 if (check_status (in) != Fail)
312                   {
313                     Q_EMIT self_->halt_tx (auto_only);
314                   }
315               }
316               break;
317 
318             case NetworkMessage::FreeText:
319               {
320                 QByteArray message;
321                 bool send {true};
322                 in >> message >> send;
323                 TRACE_UDP ("FreeText message:" << message << "send:" << send);
324                 if (check_status (in) != Fail)
325                   {
326                     Q_EMIT self_->free_text (QString::fromUtf8 (message), send);
327                   }
328               }
329               break;
330 
331             case NetworkMessage::Location:
332               {
333                 QByteArray location;
334                 in >> location;
335                 TRACE_UDP ("Location location:" << location);
336                 if (check_status (in) != Fail)
337                 {
338                     Q_EMIT self_->location (QString::fromUtf8 (location));
339                 }
340               }
341               break;
342 
343             case NetworkMessage::HighlightCallsign:
344               {
345                 QByteArray call;
346                 QColor bg;      // default invalid color
347                 QColor fg;      // default invalid color
348                 bool last_only {false};
349                 in >> call >> bg >> fg >> last_only;
350                 TRACE_UDP ("HighlightCallsign call:" << call << "bg:" << bg << "fg:" << fg << "last only:" << last_only);
351                 if (check_status (in) != Fail && call.size ())
352                   {
353                     Q_EMIT self_->highlight_callsign (QString::fromUtf8 (call), bg, fg, last_only);
354                   }
355               }
356               break;
357 
358             case NetworkMessage::SwitchConfiguration:
359               {
360                 QByteArray configuration_name;
361                 in >> configuration_name;
362                 TRACE_UDP ("Switch Configuration name:" << configuration_name);
363                 if (check_status (in) != Fail)
364                   {
365                     Q_EMIT self_->switch_configuration (QString::fromUtf8 (configuration_name));
366                   }
367               }
368               break;
369 
370             case NetworkMessage::Configure:
371               {
372                 QByteArray mode;
373                 quint32 frequency_tolerance;
374                 QByteArray submode;
375                 bool fast_mode {false};
376                 quint32 tr_period {std::numeric_limits<quint32>::max ()};
377                 quint32 rx_df {std::numeric_limits<quint32>::max ()};
378                 QByteArray dx_call;
379                 QByteArray dx_grid;
380                 bool generate_messages {false};
381                 in >> mode >> frequency_tolerance >> submode >> fast_mode >> tr_period >> rx_df
382                    >> dx_call >> dx_grid >> generate_messages;
383                 TRACE_UDP ("Configure mode:" << mode << "frequency tolerance:" << frequency_tolerance << "submode:" << submode << "fast mode:" << fast_mode << "T/R period:" << tr_period << "rx df:" << rx_df << "dx call:" << dx_call << "dx grid:" << dx_grid << "generate messages:" << generate_messages);
384                 if (check_status (in) != Fail)
385                   {
386                     Q_EMIT self_->configure (QString::fromUtf8 (mode), frequency_tolerance
387                                              , QString::fromUtf8 (submode), fast_mode, tr_period, rx_df
388                                              , QString::fromUtf8 (dx_call), QString::fromUtf8 (dx_grid)
389                                              , generate_messages);
390                   }
391               }
392               break;
393 
394             default:
395               // Ignore
396               //
397               // Note that although server  heartbeat messages are not
398               // parsed here  they are  still partially parsed  in the
399               // message reader class to  negotiate the maximum schema
400               // number being used on the network.
401               if (NetworkMessage::Heartbeat != in.type ())
402                 {
403                   TRACE_UDP ("ignoring message type:" << in.type ());
404                 }
405               break;
406             }
407         }
408       else
409         {
410           TRACE_UDP ("ignored message for id:" << in.id ());
411         }
412     }
413   catch (std::exception const& e)
414     {
415       Q_EMIT self_->error (QString {"MessageClient exception: %1"}.arg (e.what ()));
416     }
417   catch (...)
418     {
419       Q_EMIT self_->error ("Unexpected exception in MessageClient");
420     }
421 }
422 
heartbeat()423 void MessageClient::impl::heartbeat ()
424 {
425    if (server_port_ && !server_.isNull ())
426     {
427       QByteArray message;
428       NetworkMessage::Builder out {&message, NetworkMessage::Heartbeat, id_, schema_};
429       out << NetworkMessage::Builder::schema_number // maximum schema number accepted
430           << version_.toUtf8 () << revision_.toUtf8 ();
431       TRACE_UDP ("schema:" << schema_ << "max schema:" << NetworkMessage::Builder::schema_number << "version:" << version_ << "revision:" << revision_);
432       send_message (out, message, false, true);
433     }
434 }
435 
closedown()436 void MessageClient::impl::closedown ()
437 {
438    if (server_port_ && !server_.isNull ())
439     {
440       QByteArray message;
441       NetworkMessage::Builder out {&message, NetworkMessage::Close, id_, schema_};
442       TRACE_UDP ("");
443       send_message (out, message, false);
444     }
445 }
446 
send_message(QByteArray const & message,bool queue_if_pending,bool allow_duplicates)447 void MessageClient::impl::send_message (QByteArray const& message, bool queue_if_pending, bool allow_duplicates)
448 {
449   if (server_port_)
450     {
451       if (!server_.isNull ())
452         {
453           if (allow_duplicates || message != last_message_) // avoid duplicates
454             {
455               if (is_multicast_address (server_))
456                 {
457                   // send datagram on each selected network interface
458                   std::for_each (network_interfaces_.begin (), network_interfaces_.end ()
459                                  , [&] (QNetworkInterface const& net_if) {
460                                      setMulticastInterface (net_if);
461                                      // qDebug () << "Multicast UDP datagram sent to:" << server_ << "port:" << server_port_ << "on:" << multicastInterface ().humanReadableName ();
462                                      writeDatagram (message, server_, server_port_);
463                                    });
464                 }
465               else
466                 {
467                   // qDebug () << "Unicast UDP datagram sent to:" << server_ << "port:" << server_port_;
468                   writeDatagram (message, server_, server_port_);
469                 }
470               last_message_ = message;
471             }
472         }
473       else if (queue_if_pending)
474         {
475           pending_messages_.enqueue (message);
476         }
477     }
478 }
479 
check_status(QDataStream const & stream) const480 auto MessageClient::impl::check_status (QDataStream const& stream) const -> StreamStatus
481 {
482   auto stat = stream.status ();
483   StreamStatus result {Fail};
484   switch (stat)
485     {
486     case QDataStream::ReadPastEnd:
487       result = Short;
488       break;
489 
490     case QDataStream::ReadCorruptData:
491       Q_EMIT self_->error ("Message serialization error: read corrupt data");
492       break;
493 
494     case QDataStream::WriteFailed:
495       Q_EMIT self_->error ("Message serialization error: write error");
496       break;
497 
498     default:
499       result = OK;
500       break;
501     }
502   return result;
503 }
504 
MessageClient(QString const & id,QString const & version,QString const & revision,QString const & server_name,port_type server_port,QStringList const & network_interface_names,int TTL,QObject * self)505 MessageClient::MessageClient (QString const& id, QString const& version, QString const& revision,
506                               QString const& server_name, port_type server_port,
507                               QStringList const& network_interface_names,
508                               int TTL, QObject * self)
509   : QObject {self}
510   , m_ {id, version, revision, server_port, TTL, this}
511 {
512   connect (&*m_
513 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
514            , static_cast<void (impl::*) (impl::SocketError)> (&impl::error), [this] (impl::SocketError e)
515 #else
516            , &impl::errorOccurred, [this] (impl::SocketError e)
517 #endif
__anon54b2a9860202(impl::SocketError e) 518                                    {
519 #if defined (Q_OS_WIN)
520                                      if (e != impl::NetworkError // take this out when Qt 5.5 stops doing this spuriously
521                                          && e != impl::ConnectionRefusedError) // not interested in this with UDP socket
522                                        {
523 #else
524                                        {
525                                          Q_UNUSED (e);
526 #endif
527                                          Q_EMIT error (m_->errorString ());
528                                        }
529                                        });
530   m_->set_server (server_name, network_interface_names);
531 }
532 
server_address() const533 QHostAddress MessageClient::server_address () const
534 {
535   return m_->server_;
536 }
537 
server_port() const538 auto MessageClient::server_port () const -> port_type
539 {
540   return m_->server_port_;
541 }
542 
set_server(QString const & server_name,QStringList const & network_interface_names)543 void MessageClient::set_server (QString const& server_name, QStringList const& network_interface_names)
544 {
545   m_->set_server (server_name, network_interface_names);
546 }
547 
set_server_port(port_type server_port)548 void MessageClient::set_server_port (port_type server_port)
549 {
550   m_->server_port_ = server_port;
551 }
552 
set_TTL(int TTL)553 void MessageClient::set_TTL (int TTL)
554 {
555   m_->TTL_ = TTL;
556   m_->setSocketOption (QAbstractSocket::MulticastTtlOption, m_->TTL_);
557 }
558 
enable(bool flag)559 void MessageClient::enable (bool flag)
560 {
561   m_->enabled_ = flag;
562 }
563 
status_update(Frequency f,QString const & mode,QString const & dx_call,QString const & report,QString const & tx_mode,bool tx_enabled,bool transmitting,bool decoding,quint32 rx_df,quint32 tx_df,QString const & de_call,QString const & de_grid,QString const & dx_grid,bool watchdog_timeout,QString const & sub_mode,bool fast_mode,quint8 special_op_mode,quint32 frequency_tolerance,quint32 tr_period,QString const & configuration_name,QString const & tx_message)564 void MessageClient::status_update (Frequency f, QString const& mode, QString const& dx_call
565                                    , QString const& report, QString const& tx_mode
566                                    , bool tx_enabled, bool transmitting, bool decoding
567                                    , quint32 rx_df, quint32 tx_df, QString const& de_call
568                                    , QString const& de_grid, QString const& dx_grid
569                                    , bool watchdog_timeout, QString const& sub_mode
570                                    , bool fast_mode, quint8 special_op_mode
571                                    , quint32 frequency_tolerance, quint32 tr_period
572                                    , QString const& configuration_name
573                                    , QString const& tx_message)
574 {
575   if (m_->server_port_ && !m_->server_.isNull ())
576     {
577       QByteArray message;
578       NetworkMessage::Builder out {&message, NetworkMessage::Status, m_->id_, m_->schema_};
579       out << f << mode.toUtf8 () << dx_call.toUtf8 () << report.toUtf8 () << tx_mode.toUtf8 ()
580           << tx_enabled << transmitting << decoding << rx_df << tx_df << de_call.toUtf8 ()
581           << de_grid.toUtf8 () << dx_grid.toUtf8 () << watchdog_timeout << sub_mode.toUtf8 ()
582           << fast_mode << special_op_mode << frequency_tolerance << tr_period << configuration_name.toUtf8 ()
583           << tx_message.toUtf8 ();
584       TRACE_UDP ("frequency:" << f << "mode:" << mode << "DX:" << dx_call << "report:" << report << "Tx mode:" << tx_mode << "tx_enabled:" << tx_enabled << "Tx:" << transmitting << "decoding:" << decoding << "Rx df:" << rx_df << "Tx df:" << tx_df << "DE:" << de_call << "DE grid:" << de_grid << "DX grid:" << dx_grid << "w/d t/o:" << watchdog_timeout << "sub_mode:" << sub_mode << "fast mode:" << fast_mode << "spec op mode:" << special_op_mode << "frequency tolerance:" << frequency_tolerance << "T/R period:" << tr_period << "configuration name:" << configuration_name << "Tx message:" << tx_message);
585       m_->send_message (out, message);
586     }
587 }
588 
decode(bool is_new,QTime time,qint32 snr,float delta_time,quint32 delta_frequency,QString const & mode,QString const & message_text,bool low_confidence,bool off_air)589 void MessageClient::decode (bool is_new, QTime time, qint32 snr, float delta_time, quint32 delta_frequency
590                             , QString const& mode, QString const& message_text, bool low_confidence
591                             , bool off_air)
592 {
593    if (m_->server_port_ && !m_->server_.isNull ())
594     {
595       QByteArray message;
596       NetworkMessage::Builder out {&message, NetworkMessage::Decode, m_->id_, m_->schema_};
597       out << is_new << time << snr << delta_time << delta_frequency << mode.toUtf8 ()
598           << message_text.toUtf8 () << low_confidence << off_air;
599       TRACE_UDP ("new" << is_new << "time:" << time << "snr:" << snr << "dt:" << delta_time << "df:" << delta_frequency << "mode:" << mode << "text:" << message_text << "low conf:" << low_confidence << "off air:" << off_air);
600       m_->send_message (out, message);
601     }
602 }
603 
WSPR_decode(bool is_new,QTime time,qint32 snr,float delta_time,Frequency frequency,qint32 drift,QString const & callsign,QString const & grid,qint32 power,bool off_air)604 void MessageClient::WSPR_decode (bool is_new, QTime time, qint32 snr, float delta_time, Frequency frequency
605                                  , qint32 drift, QString const& callsign, QString const& grid, qint32 power
606                                  , bool off_air)
607 {
608    if (m_->server_port_ && !m_->server_.isNull ())
609     {
610       QByteArray message;
611       NetworkMessage::Builder out {&message, NetworkMessage::WSPRDecode, m_->id_, m_->schema_};
612       out << is_new << time << snr << delta_time << frequency << drift << callsign.toUtf8 ()
613           << grid.toUtf8 () << power << off_air;
614       TRACE_UDP ("new:" << is_new << "time:" << time << "snr:" << snr << "dt:" << delta_time << "frequency:" << frequency << "drift:" << drift << "call:" << callsign << "grid:" << grid << "pwr:" << power << "off air:" << off_air);
615       m_->send_message (out, message);
616     }
617 }
618 
decodes_cleared()619 void MessageClient::decodes_cleared ()
620 {
621    if (m_->server_port_ && !m_->server_.isNull ())
622     {
623       QByteArray message;
624       NetworkMessage::Builder out {&message, NetworkMessage::Clear, m_->id_, m_->schema_};
625       TRACE_UDP ("");
626       m_->send_message (out, message);
627     }
628 }
629 
qso_logged(QDateTime time_off,QString const & dx_call,QString const & dx_grid,Frequency dial_frequency,QString const & mode,QString const & report_sent,QString const & report_received,QString const & tx_power,QString const & comments,QString const & name,QDateTime time_on,QString const & operator_call,QString const & my_call,QString const & my_grid,QString const & exchange_sent,QString const & exchange_rcvd,QString const & propmode)630 void MessageClient::qso_logged (QDateTime time_off, QString const& dx_call, QString const& dx_grid
631                                 , Frequency dial_frequency, QString const& mode, QString const& report_sent
632                                 , QString const& report_received, QString const& tx_power
633                                 , QString const& comments, QString const& name, QDateTime time_on
634                                 , QString const& operator_call, QString const& my_call
635                                 , QString const& my_grid, QString const& exchange_sent
636                                 , QString const& exchange_rcvd, QString const& propmode)
637 {
638    if (m_->server_port_ && !m_->server_.isNull ())
639     {
640       QByteArray message;
641       NetworkMessage::Builder out {&message, NetworkMessage::QSOLogged, m_->id_, m_->schema_};
642       out << time_off << dx_call.toUtf8 () << dx_grid.toUtf8 () << dial_frequency << mode.toUtf8 ()
643           << report_sent.toUtf8 () << report_received.toUtf8 () << tx_power.toUtf8 () << comments.toUtf8 ()
644           << name.toUtf8 () << time_on << operator_call.toUtf8 () << my_call.toUtf8 () << my_grid.toUtf8 ()
645           << exchange_sent.toUtf8 () << exchange_rcvd.toUtf8 () << propmode.toUtf8 ();
646       TRACE_UDP ("time off:" << time_off << "DX:" << dx_call << "DX grid:" << dx_grid << "dial:" << dial_frequency << "mode:" << mode << "sent:" << report_sent << "rcvd:" << report_received << "pwr:" << tx_power << "comments:" << comments << "name:" << name << "time on:" << time_on << "op:" << operator_call << "DE:" << my_call << "DE grid:" << my_grid << "exch sent:" << exchange_sent << "exch rcvd:" << exchange_rcvd  << "prop_mode:" << propmode);
647       m_->send_message (out, message);
648     }
649 }
650 
logged_ADIF(QByteArray const & ADIF_record)651 void MessageClient::logged_ADIF (QByteArray const& ADIF_record)
652 {
653    if (m_->server_port_ && !m_->server_.isNull ())
654     {
655       QByteArray message;
656       NetworkMessage::Builder out {&message, NetworkMessage::LoggedADIF, m_->id_, m_->schema_};
657       QByteArray ADIF {"\n<adif_ver:5>3.1.0\n<programid:6>WSJT-X\n<EOH>\n" + ADIF_record + " <EOR>"};
658       out << ADIF;
659       TRACE_UDP ("ADIF:" << ADIF);
660       m_->send_message (out, message);
661     }
662 }
663