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