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