1 #include "PSKReporter.hpp"
2
3 // Interface for posting spots to PSK Reporter web site
4 // Implemented by Edson Pereira PY2SDR
5 // Updated by Bill Somerville, G4WJS
6 //
7 // Reports will be sent in batch mode every 5 minutes.
8
9 #include <cmath>
10 #include <QObject>
11 #include <QString>
12 #include <QDateTime>
13 #include <QSharedPointer>
14 #include <QUdpSocket>
15 #include <QTcpSocket>
16 #include <QHostInfo>
17 #include <QQueue>
18 #include <QByteArray>
19 #include <QDataStream>
20 #include <QTimer>
21 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
22 #include <QRandomGenerator>
23 #endif
24
25 #include "Logger.hpp"
26 #include "Configuration.hpp"
27 #include "pimpl_impl.hpp"
28
29
30 #include "moc_PSKReporter.cpp"
31
32 namespace
33 {
34 QLatin1String HOST {"report.pskreporter.info"};
35 // QLatin1String HOST {"127.0.0.1"};
36 quint16 SERVICE_PORT {4739};
37 // quint16 SERVICE_PORT {14739};
38 int MIN_SEND_INTERVAL {15}; // in seconds
39 int FLUSH_INTERVAL {4 * 5}; // in send intervals
40 bool ALIGNMENT_PADDING {true};
41 int MIN_PAYLOAD_LENGTH {508};
42 int MAX_PAYLOAD_LENGTH {1400};
43 }
44
45 class PSKReporter::impl final
46 : public QObject
47 {
48 Q_OBJECT
49
50 using logger_type = boost::log::sources::severity_channel_logger_mt<boost::log::trivial::severity_level>;
51
52 public:
impl(PSKReporter * self,Configuration const * config,QString const & program_info)53 impl (PSKReporter * self, Configuration const * config, QString const& program_info)
54 : logger_ {boost::log::keywords::channel = "PSKRPRT"}
55 , self_ {self}
56 , config_ {config}
57 , sequence_number_ {0u}
58 , send_descriptors_ {0}
59 , send_receiver_data_ {0}
60 , flush_counter_ {0u}
61 , prog_id_ {program_info}
62 {
63 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
64 observation_id_ = qrand();
65 #else
66 observation_id_ = QRandomGenerator::global ()->generate ();
67 #endif
68
69 // This timer sets the interval to check for spots to send.
__anoned0308030202() 70 connect (&report_timer_, &QTimer::timeout, [this] () {send_report ();});
71
72 // This timer repeats the sending of IPFIX templates and receiver
73 // information if we are using UDP, in case server has been
74 // restarted ans lost cached information.
__anoned0308030302() 75 connect (&descriptor_timer_, &QTimer::timeout, [this] () {
76 if (socket_
77 && QAbstractSocket::UdpSocket == socket_->socketType ())
78 {
79 LOG_LOG_LOCATION (logger_, trace, "enable descriptor resend");
80 // send templates again
81 send_descriptors_ = 3; // three times
82 // send receiver data set again
83 send_receiver_data_ = 3; // three times
84 }
85 });
86 }
87
check_connection()88 void check_connection ()
89 {
90 if (!socket_
91 || QAbstractSocket::UnconnectedState == socket_->state ()
92 || (socket_->socketType () != (config_->psk_reporter_tcpip () ? QAbstractSocket::TcpSocket : QAbstractSocket::UdpSocket)))
93 {
94 // we need to create the appropriate socket
95 if (socket_
96 && QAbstractSocket::UnconnectedState != socket_->state ()
97 && QAbstractSocket::ClosingState != socket_->state ())
98 {
99 LOG_LOG_LOCATION (logger_, trace, "create/recreate socket");
100 // handle re-opening asynchronously
101 auto connection = QSharedPointer<QMetaObject::Connection>::create ();
102 *connection = connect (socket_.data (), &QAbstractSocket::disconnected, [this, connection] () {
103 disconnect (*connection);
104 check_connection ();
105 });
106 // close gracefully
107 send_report (true);
108 socket_->close ();
109 }
110 else
111 {
112 reconnect ();
113 }
114 }
115 }
116
handle_socket_error(QAbstractSocket::SocketError e)117 void handle_socket_error (QAbstractSocket::SocketError e)
118 {
119 LOG_LOG_LOCATION (logger_, warning, "socket error: " << socket_->errorString ());
120 switch (e)
121 {
122 case QAbstractSocket::RemoteHostClosedError:
123 socket_->disconnectFromHost ();
124 break;
125
126 case QAbstractSocket::TemporaryError:
127 break;
128
129 default:
130 spots_.clear ();
131 Q_EMIT self_->errorOccurred (socket_->errorString ());
132 break;
133 }
134 }
135
reconnect()136 void reconnect ()
137 {
138 // Using deleteLater for the deleter as we may eventually
139 // be called from the disconnected handler above.
140 if (config_->psk_reporter_tcpip ())
141 {
142 LOG_LOG_LOCATION (logger_, trace, "create TCP/IP socket");
143 socket_.reset (new QTcpSocket, &QObject::deleteLater);
144 send_descriptors_ = 1;
145 send_receiver_data_ = 1;
146 }
147 else
148 {
149 LOG_LOG_LOCATION (logger_, trace, "create UDP/IP socket");
150 socket_.reset (new QUdpSocket, &QObject::deleteLater);
151 send_descriptors_ = 3;
152 send_receiver_data_ = 3;
153 }
154
155 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
156 connect (socket_.get (), &QAbstractSocket::errorOccurred, this, &PSKReporter::impl::handle_socket_error);
157 #elif QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
158 connect (socket_.data (), QOverload<QAbstractSocket::SocketError>::of (&QAbstractSocket::error), this, &PSKReporter::impl::handle_socket_error);
159 #else
160 connect (socket_.data (), static_cast<void (QAbstractSocket::*) (QAbstractSocket::SocketError)> (&QAbstractSocket::error), this, &PSKReporter::impl::handle_socket_error);
161 #endif
162
163 // use this for pseudo connection with UDP, allows us to use
164 // QIODevice::write() instead of QUDPSocket::writeDatagram()
165 socket_->connectToHost (HOST, SERVICE_PORT, QAbstractSocket::WriteOnly);
166 LOG_LOG_LOCATION (logger_, debug, "remote host: " << HOST << " port: " << SERVICE_PORT);
167
168 if (!report_timer_.isActive ())
169 {
170 report_timer_.start (MIN_SEND_INTERVAL * 1000);
171 }
172 if (!descriptor_timer_.isActive ())
173 {
174 descriptor_timer_.start (1 * 60 * 60 * 1000); // hourly
175 }
176 }
177
stop()178 void stop ()
179 {
180 if (socket_)
181 {
182 LOG_LOG_LOCATION (logger_, trace, "disconnecting");
183 socket_->disconnectFromHost ();
184 }
185 descriptor_timer_.stop ();
186 report_timer_.stop ();
187 }
188
189 void send_report (bool send_residue = false);
190 void build_preamble (QDataStream&);
191
flushing()192 bool flushing ()
193 {
194 bool flush = FLUSH_INTERVAL && !(++flush_counter_ % FLUSH_INTERVAL);
195 LOG_LOG_LOCATION (logger_, trace, "flush: " << flush);
196 return flush;
197 }
198
199 logger_type mutable logger_;
200 PSKReporter * self_;
201 Configuration const * config_;
202 QSharedPointer<QAbstractSocket> socket_;
203 int dns_lookup_id_;
204 QByteArray payload_;
205 quint32 sequence_number_;
206 int send_descriptors_;
207
208 // Currently PSK Reporter requires that a receiver data set is sent
209 // in every data flow. This memeber variable can be used to only
210 // send that information at session start (3 times for UDP), when it
211 // changes (3 times for UDP), or once per hour (3 times) if using
212 // UDP. Uncomment the relevant code to enable that fuctionality.
213 int send_receiver_data_;
214
215 unsigned flush_counter_;
216 quint32 observation_id_;
217 QString rx_call_;
218 QString rx_grid_;
219 QString rx_ant_;
220 QString prog_id_;
221 QByteArray tx_data_;
222 QByteArray tx_residue_;
223 struct Spot
224 {
operator ==PSKReporter::impl::Spot225 bool operator == (Spot const& rhs)
226 {
227 return
228 call_ == rhs.call_
229 && grid_ == rhs.grid_
230 && mode_ == rhs.mode_
231 && std::abs (Radio::FrequencyDelta (freq_ - rhs.freq_)) < 50;
232 }
233
234 QString call_;
235 QString grid_;
236 int snr_;
237 Radio::Frequency freq_;
238 QString mode_;
239 QDateTime time_;
240 };
241 QQueue<Spot> spots_;
242 QTimer report_timer_;
243 QTimer descriptor_timer_;
244 };
245
246 #include "PSKReporter.moc"
247
248 namespace
249 {
writeUtfString(QDataStream & out,QString const & s)250 void writeUtfString (QDataStream& out, QString const& s)
251 {
252 auto const& utf = s.toUtf8 ().left (254);
253 out << quint8 (utf.size ());
254 out.writeRawData (utf, utf.size ());
255 }
256
num_pad_bytes(int len)257 int num_pad_bytes (int len)
258 {
259 return ALIGNMENT_PADDING ? (4 - len % 4) % 4 : 0;
260 }
261
set_length(QDataStream & out,QByteArray & b)262 void set_length (QDataStream& out, QByteArray& b)
263 {
264 // pad with nulls modulo 4
265 auto pad_len = num_pad_bytes (b.size ());
266 out.writeRawData (QByteArray {pad_len, '\0'}.constData (), pad_len);
267 auto pos = out.device ()->pos ();
268 out.device ()->seek (sizeof (quint16));
269 // insert length
270 out << static_cast<quint16> (b.size ());
271 out.device ()->seek (pos);
272 }
273 }
274
build_preamble(QDataStream & message)275 void PSKReporter::impl::build_preamble (QDataStream& message)
276 {
277 // Message Header
278 message
279 << quint16 (10u) // Version Number
280 << quint16 (0u) // Length (place-holder filled in later)
281 << quint32 (0u) // Export Time (place-holder filled in later)
282 << ++sequence_number_ // Sequence Number
283 << observation_id_; // Observation Domain ID
284 LOG_LOG_LOCATION (logger_, trace, "#: " << sequence_number_);
285
286 if (send_descriptors_)
287 {
288 --send_descriptors_;
289 {
290 // Sender Information descriptor
291 QByteArray descriptor;
292 QDataStream out {&descriptor, QIODevice::WriteOnly};
293 out
294 << quint16 (2u) // Template Set ID
295 << quint16 (0u) // Length (place-holder)
296 << quint16 (0x50e3) // Link ID
297 << quint16 (7u) // Field Count
298 << quint16 (0x8000 + 1u) // Option 1 Information Element ID (senderCallsign)
299 << quint16 (0xffff) // Option 1 Field Length (variable)
300 << quint32 (30351u) // Option 1 Enterprise Number
301 << quint16 (0x8000 + 5u) // Option 2 Information Element ID (frequency)
302 << quint16 (4u) // Option 2 Field Length
303 << quint32 (30351u) // Option 2 Enterprise Number
304 << quint16 (0x8000 + 6u) // Option 3 Information Element ID (sNR)
305 << quint16 (1u) // Option 3 Field Length
306 << quint32 (30351u) // Option 3 Enterprise Number
307 << quint16 (0x8000 + 10u) // Option 4 Information Element ID (mode)
308 << quint16 (0xffff) // Option 4 Field Length (variable)
309 << quint32 (30351u) // Option 4 Enterprise Number
310 << quint16 (0x8000 + 3u) // Option 5 Information Element ID (senderLocator)
311 << quint16 (0xffff) // Option 5 Field Length (variable)
312 << quint32 (30351u) // Option 5 Enterprise Number
313 << quint16 (0x8000 + 11u) // Option 6 Information Element ID (informationSource)
314 << quint16 (1u) // Option 6 Field Length
315 << quint32 (30351u) // Option 6 Enterprise Number
316 << quint16 (150u) // Option 7 Information Element ID (dateTimeSeconds)
317 << quint16 (4u); // Option 7 Field Length
318 // insert Length and move to payload
319 set_length (out, descriptor);
320 message.writeRawData (descriptor.constData (), descriptor.size ());
321 }
322 {
323 // Receiver Information descriptor
324 QByteArray descriptor;
325 QDataStream out {&descriptor, QIODevice::WriteOnly};
326 out
327 << quint16 (3u) // Options Template Set ID
328 << quint16 (0u) // Length (place-holder)
329 << quint16 (0x50e2) // Link ID
330 << quint16 (4u) // Field Count
331 << quint16 (0u) // Scope Field Count
332 << quint16 (0x8000 + 2u) // Option 1 Information Element ID (receiverCallsign)
333 << quint16 (0xffff) // Option 1 Field Length (variable)
334 << quint32 (30351u) // Option 1 Enterprise Number
335 << quint16 (0x8000 + 4u) // Option 2 Information Element ID (receiverLocator)
336 << quint16 (0xffff) // Option 2 Field Length (variable)
337 << quint32 (30351u) // Option 2 Enterprise Number
338 << quint16 (0x8000 + 8u) // Option 3 Information Element ID (decodingSoftware)
339 << quint16 (0xffff) // Option 3 Field Length (variable)
340 << quint32 (30351u) // Option 3 Enterprise Number
341 << quint16 (0x8000 + 9u) // Option 4 Information Element ID (antennaInformation)
342 << quint16 (0xffff) // Option 4 Field Length (variable)
343 << quint32 (30351u); // Option 4 Enterprise Number
344 // insert Length
345 set_length (out, descriptor);
346 message.writeRawData (descriptor.constData (), descriptor.size ());
347 LOG_LOG_LOCATION (logger_, debug, "sent descriptors");
348 }
349 }
350
351 // if (send_receiver_data_)
352 {
353 // --send_receiver_data_;
354
355 // Receiver information
356 QByteArray data;
357 QDataStream out {&data, QIODevice::WriteOnly};
358
359 // Set Header
360 out
361 << quint16 (0x50e2) // Template ID
362 << quint16 (0u); // Length (place-holder)
363
364 // Set data
365 writeUtfString (out, rx_call_);
366 writeUtfString (out, rx_grid_);
367 writeUtfString (out, prog_id_);
368 writeUtfString (out, rx_ant_);
369
370 // insert Length and move to payload
371 set_length (out, data);
372 message.writeRawData (data.constData (), data.size ());
373 LOG_LOG_LOCATION (logger_, debug, "sent local information");
374 }
375 }
376
send_report(bool send_residue)377 void PSKReporter::impl::send_report (bool send_residue)
378 {
379 LOG_LOG_LOCATION (logger_, trace, "sending residue: " << send_residue);
380 if (QAbstractSocket::ConnectedState != socket_->state ()) return;
381
382 QDataStream message {&payload_, QIODevice::WriteOnly | QIODevice::Append};
383 QDataStream tx_out {&tx_data_, QIODevice::WriteOnly | QIODevice::Append};
384
385 if (!payload_.size ())
386 {
387 // Build header, optional descriptors, and receiver information
388 build_preamble (message);
389 }
390
391 auto flush = flushing () || send_residue;
392 while (spots_.size () || flush)
393 {
394 if (!payload_.size ())
395 {
396 // Build header, optional descriptors, and receiver information
397 build_preamble (message);
398 }
399
400 if (!tx_data_.size () && (spots_.size () || tx_residue_.size ()))
401 {
402 // Set Header
403 tx_out
404 << quint16 (0x50e3) // Template ID
405 << quint16 (0u); // Length (place-holder)
406 }
407
408 // insert any residue
409 if (tx_residue_.size ())
410 {
411 tx_out.writeRawData (tx_residue_.constData (), tx_residue_.size ());
412 LOG_LOG_LOCATION (logger_, debug, "sent residue");
413 tx_residue_.clear ();
414 }
415
416 LOG_LOG_LOCATION (logger_, debug, "pending spots: " << spots_.size ());
417 while (spots_.size () || flush)
418 {
419 auto tx_data_size = tx_data_.size ();
420 if (spots_.size ())
421 {
422 auto const& spot = spots_.dequeue ();
423
424 // Sender information
425 writeUtfString (tx_out, spot.call_);
426 tx_out
427 << static_cast<quint32> (spot.freq_)
428 << static_cast<qint8> (spot.snr_);
429 writeUtfString (tx_out, spot.mode_);
430 writeUtfString (tx_out, spot.grid_);
431 tx_out
432 << quint8 (1u) // REPORTER_SOURCE_AUTOMATIC
433 << static_cast<quint32> (
434 #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
435 spot.time_.toSecsSinceEpoch ()
436 #else
437 spot.time_.toMSecsSinceEpoch () / 1000
438 #endif
439 );
440 }
441
442 auto len = payload_.size () + tx_data_.size ();
443 len += num_pad_bytes (tx_data_.size ());
444 len += num_pad_bytes (len);
445 if (len > MAX_PAYLOAD_LENGTH // our upper datagram size limit
446 || (!spots_.size () && len > MIN_PAYLOAD_LENGTH) // spots drained and above lower datagram size limit
447 || (flush && !spots_.size ())) // send what we have, possibly no spots
448 {
449 if (tx_data_.size ())
450 {
451 if (len <= MAX_PAYLOAD_LENGTH)
452 {
453 tx_data_size = tx_data_.size ();
454 }
455 QByteArray tx {tx_data_.left (tx_data_size)};
456 QDataStream out {&tx, QIODevice::WriteOnly | QIODevice::Append};
457 // insert Length
458 set_length (out, tx);
459 message.writeRawData (tx.constData (), tx.size ());
460 }
461
462 // insert Length and Export Time
463 set_length (message, payload_);
464 message.device ()->seek (2 * sizeof (quint16));
465 message << static_cast<quint32> (
466 #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
467 QDateTime::currentDateTime ().toSecsSinceEpoch ()
468 #else
469 QDateTime::currentDateTime ().toMSecsSinceEpoch () / 1000
470 #endif
471 );
472
473 // Send data to PSK Reporter site
474 socket_->write (payload_); // TODO: handle errors
475 LOG_LOG_LOCATION (logger_, debug, "sent spots");
476 flush = false; // break loop
477 message.device ()->seek (0u);
478 payload_.clear (); // Fresh message
479 // Save unsent spots
480 tx_residue_ = tx_data_.right (tx_data_.size () - tx_data_size);
481 tx_out.device ()->seek (0u);
482 tx_data_.clear ();
483 break;
484 }
485 }
486 LOG_LOG_LOCATION (logger_, debug, "remaining spots: " << spots_.size ());
487 }
488 }
489
PSKReporter(Configuration const * config,QString const & program_info)490 PSKReporter::PSKReporter (Configuration const * config, QString const& program_info)
491 : m_ {this, config, program_info}
492 {
493 LOG_LOG_LOCATION (m_->logger_, trace, "Started for: " << program_info);
494 }
495
~PSKReporter()496 PSKReporter::~PSKReporter ()
497 {
498 // m_->send_report (true); // send any pending spots
499 LOG_LOG_LOCATION (m_->logger_, trace, "Ended");
500 }
501
reconnect()502 void PSKReporter::reconnect ()
503 {
504 LOG_LOG_LOCATION (m_->logger_, trace, "");
505 m_->reconnect ();
506 }
507
setLocalStation(QString const & call,QString const & gridSquare,QString const & antenna)508 void PSKReporter::setLocalStation (QString const& call, QString const& gridSquare, QString const& antenna)
509 {
510 LOG_LOG_LOCATION (m_->logger_, trace, "call: " << call << " grid: " << gridSquare << " ant: " << antenna);
511 m_->check_connection ();
512 if (call != m_->rx_call_ || gridSquare != m_->rx_grid_ || antenna != m_->rx_ant_)
513 {
514 LOG_LOG_LOCATION (m_->logger_, trace, "updating information");
515 m_->send_receiver_data_ = m_->socket_
516 && QAbstractSocket::UdpSocket == m_->socket_->socketType () ? 3 : 1;
517 m_->rx_call_ = call;
518 m_->rx_grid_ = gridSquare;
519 m_->rx_ant_ = antenna;
520 }
521 }
522
addRemoteStation(QString const & call,QString const & grid,Radio::Frequency freq,QString const & mode,int snr)523 bool PSKReporter::addRemoteStation (QString const& call, QString const& grid, Radio::Frequency freq
524 , QString const& mode, int snr)
525 {
526 LOG_LOG_LOCATION (m_->logger_, trace, "call: " << call << " grid: " << grid << " freq: " << freq << " mode: " << mode << " snr: " << snr);
527 m_->check_connection ();
528 if (m_->socket_ && m_->socket_->isValid ())
529 {
530 if (QAbstractSocket::UnconnectedState == m_->socket_->state ())
531 {
532 reconnect ();
533 }
534 m_->spots_.enqueue ({call, grid, snr, freq, mode, QDateTime::currentDateTimeUtc ()});
535 return true;
536 }
537 return false;
538 }
539
sendReport(bool last)540 void PSKReporter::sendReport (bool last)
541 {
542 LOG_LOG_LOCATION (m_->logger_, trace, "last: " << last);
543 m_->check_connection ();
544 if (m_->socket_ && QAbstractSocket::ConnectedState == m_->socket_->state ())
545 {
546 m_->send_report (true);
547 }
548 if (last)
549 {
550 m_->stop ();
551 }
552 }
553