mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2026-06-05 07:24:38 -04:00
Merge branch 'develop' into feat-fst280
This commit is contained in:
+17
-36
@@ -36,6 +36,7 @@ public:
|
||||
impl (QString const& id, QString const& version, QString const& revision,
|
||||
port_type server_port, MessageClient * self)
|
||||
: self_ {self}
|
||||
, dns_lookup_id_ {0}
|
||||
, enabled_ {false}
|
||||
, id_ {id}
|
||||
, version_ {version}
|
||||
@@ -81,6 +82,7 @@ public:
|
||||
Q_SLOT void host_info_results (QHostInfo);
|
||||
|
||||
MessageClient * self_;
|
||||
int dns_lookup_id_;
|
||||
bool enabled_;
|
||||
QString id_;
|
||||
QString version_;
|
||||
@@ -101,6 +103,7 @@ public:
|
||||
|
||||
void MessageClient::impl::host_info_results (QHostInfo host_info)
|
||||
{
|
||||
if (host_info.lookupId () != dns_lookup_id_) return;
|
||||
if (QHostInfo::NoError != host_info.error ())
|
||||
{
|
||||
Q_EMIT self_->error ("UDP server lookup failed:\n" + host_info.errorString ());
|
||||
@@ -423,25 +426,24 @@ MessageClient::MessageClient (QString const& id, QString const& version, QString
|
||||
: QObject {self}
|
||||
, m_ {id, version, revision, server_port, this}
|
||||
{
|
||||
connect (&*m_
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
||||
connect (&*m_, static_cast<void (impl::*) (impl::SocketError)> (&impl::error)
|
||||
, [this] (impl::SocketError e) {
|
||||
, static_cast<void (impl::*) (impl::SocketError)> (&impl::error), [this] (impl::SocketError e)
|
||||
#else
|
||||
connect (&*m_, &impl::errorOccurred, [this] (impl::SocketError e) {
|
||||
, &impl::errorOccurred, [this] (impl::SocketError e)
|
||||
#endif
|
||||
{
|
||||
#if defined (Q_OS_WIN)
|
||||
// take this out when Qt 5.5 stops doing this spuriously
|
||||
if (e != impl::NetworkError
|
||||
// not interested in this with UDP socket
|
||||
&& e != impl::ConnectionRefusedError)
|
||||
if (e != impl::NetworkError // take this out when Qt 5.5 stops doing this spuriously
|
||||
&& e != impl::ConnectionRefusedError) // not interested in this with UDP socket
|
||||
{
|
||||
#else
|
||||
Q_UNUSED (e);
|
||||
{
|
||||
Q_UNUSED (e);
|
||||
#endif
|
||||
{
|
||||
Q_EMIT error (m_->errorString ());
|
||||
}
|
||||
});
|
||||
|
||||
Q_EMIT error (m_->errorString ());
|
||||
}
|
||||
});
|
||||
set_server (server);
|
||||
}
|
||||
|
||||
@@ -459,11 +461,11 @@ void MessageClient::set_server (QString const& server)
|
||||
{
|
||||
m_->server_.clear ();
|
||||
m_->server_string_ = server;
|
||||
if (!server.isEmpty ())
|
||||
if (server.size ())
|
||||
{
|
||||
// queue a host address lookup
|
||||
TRACE_UDP ("server host DNS lookup:" << server);
|
||||
QHostInfo::lookupHost (server, &*m_, SLOT (host_info_results (QHostInfo)));
|
||||
m_->dns_lookup_id_ = QHostInfo::lookupHost (server, &*m_, &MessageClient::impl::host_info_results);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,27 +474,6 @@ void MessageClient::set_server_port (port_type server_port)
|
||||
m_->server_port_ = server_port;
|
||||
}
|
||||
|
||||
qint64 MessageClient::send_raw_datagram (QByteArray const& message, QHostAddress const& dest_address
|
||||
, port_type dest_port)
|
||||
{
|
||||
if (dest_port && !dest_address.isNull ())
|
||||
{
|
||||
return m_->writeDatagram (message, dest_address, dest_port);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MessageClient::add_blocked_destination (QHostAddress const& a)
|
||||
{
|
||||
m_->blocked_addresses_.push_back (a);
|
||||
if (a == m_->server_)
|
||||
{
|
||||
m_->server_.clear ();
|
||||
Q_EMIT error ("UDP server blocked, please try another");
|
||||
m_->pending_messages_.clear (); // discard
|
||||
}
|
||||
}
|
||||
|
||||
void MessageClient::enable (bool flag)
|
||||
{
|
||||
m_->enabled_ = flag;
|
||||
|
||||
@@ -76,15 +76,6 @@ public:
|
||||
// of record marker
|
||||
Q_SLOT void logged_ADIF (QByteArray const& ADIF_record);
|
||||
|
||||
// this may be used to send arbitrary UDP datagrams to and
|
||||
// destination allowing the underlying socket to be used for general
|
||||
// UDP messaging if desired
|
||||
qint64 send_raw_datagram (QByteArray const&, QHostAddress const& dest_address, port_type dest_port);
|
||||
|
||||
// disallowed message destination (does not block datagrams sent
|
||||
// with send_raw_datagram() above)
|
||||
Q_SLOT void add_blocked_destination (QHostAddress const&);
|
||||
|
||||
// this signal is emitted if the server has requested a decode
|
||||
// window clear action
|
||||
Q_SIGNAL void clear_decodes (quint8 window);
|
||||
|
||||
@@ -0,0 +1,498 @@
|
||||
#include "PSKReporter.hpp"
|
||||
|
||||
// Interface for posting spots to PSK Reporter web site
|
||||
// Implemented by Edson Pereira PY2SDR
|
||||
// Updated by Bill Somerville, G4WJS
|
||||
//
|
||||
// Reports will be sent in batch mode every 5 minutes.
|
||||
|
||||
#include <cmath>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QDateTime>
|
||||
#include <QSharedPointer>
|
||||
#include <QUdpSocket>
|
||||
#include <QTcpSocket>
|
||||
#include <QHostInfo>
|
||||
#include <QUDPSocket>
|
||||
#include <QQueue>
|
||||
#include <QByteArray>
|
||||
#include <QDataStream>
|
||||
#include <QTimer>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
#include <QRandomGenerator>
|
||||
#endif
|
||||
#include <QDebug>
|
||||
|
||||
#include "Configuration.hpp"
|
||||
#include "pimpl_impl.hpp"
|
||||
|
||||
|
||||
#include "moc_PSKReporter.cpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr QLatin1String HOST {"report.pskreporter.info"};
|
||||
// constexpr QLatin1String HOST {"127.0.0.1"};
|
||||
constexpr quint16 SERVICE_PORT {4739};
|
||||
// constexpr quint16 SERVICE_PORT {14739};
|
||||
constexpr int MIN_SEND_INTERVAL {15}; // in seconds
|
||||
constexpr int FLUSH_INTERVAL {4 * 5}; // in send intervals
|
||||
constexpr bool ALIGNMENT_PADDING {true};
|
||||
constexpr int MIN_PAYLOAD_LENGTH {508};
|
||||
constexpr int MAX_PAYLOAD_LENGTH {1400};
|
||||
}
|
||||
|
||||
class PSKReporter::impl final
|
||||
: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
impl (PSKReporter * self, Configuration const * config, QString const& program_info)
|
||||
: self_ {self}
|
||||
, config_ {config}
|
||||
, sequence_number_ {0u}
|
||||
, send_descriptors_ {0}
|
||||
, send_receiver_data_ {0}
|
||||
, flush_counter_ {0u}
|
||||
, prog_id_ {program_info}
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
||||
observation_id_ = qrand();
|
||||
#else
|
||||
observation_id_ = QRandomGenerator::global ()->generate ();
|
||||
#endif
|
||||
|
||||
// This timer sets the interval to check for spots to send.
|
||||
connect (&report_timer_, &QTimer::timeout, [this] () {send_report ();});
|
||||
report_timer_.start (MIN_SEND_INTERVAL * 1000);
|
||||
|
||||
// This timer repeats the sending of IPFIX templates and receiver
|
||||
// information if we are using UDP, in case server has been
|
||||
// restarted ans lost cached information.
|
||||
connect (&descriptor_timer_, &QTimer::timeout, [this] () {
|
||||
if (socket_
|
||||
&& QAbstractSocket::UdpSocket == socket_->socketType ())
|
||||
{
|
||||
// send templates again
|
||||
send_descriptors_ = 3; // three times
|
||||
// send receiver data set again
|
||||
send_receiver_data_ = 3; // three times
|
||||
}
|
||||
});
|
||||
descriptor_timer_.start (1 * 60 * 60 * 1000); // hourly
|
||||
}
|
||||
|
||||
void check_connection ()
|
||||
{
|
||||
if (!socket_
|
||||
|| QAbstractSocket::UnconnectedState == socket_->state ()
|
||||
|| (socket_->socketType () != config_->psk_reporter_tcpip () ? QAbstractSocket::TcpSocket : QAbstractSocket::UdpSocket))
|
||||
{
|
||||
// we need to create the appropriate socket
|
||||
if (socket_
|
||||
&& QAbstractSocket::UnconnectedState != socket_->state ()
|
||||
&& QAbstractSocket::ClosingState != socket_->state ())
|
||||
{
|
||||
// handle re-opening asynchronously
|
||||
auto connection = QSharedPointer<QMetaObject::Connection>::create ();
|
||||
*connection = connect (socket_.get (), &QAbstractSocket::disconnected, [this, connection] () {
|
||||
qDebug () << "PSKReporter::impl::check_connection: disconnected, socket state:" << socket_->state ();
|
||||
disconnect (*connection);
|
||||
check_connection ();
|
||||
});
|
||||
// close gracefully
|
||||
socket_->close ();
|
||||
}
|
||||
else
|
||||
{
|
||||
reconnect ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handle_socket_error (QAbstractSocket::SocketError e)
|
||||
{
|
||||
switch (e)
|
||||
{
|
||||
case QAbstractSocket::RemoteHostClosedError:
|
||||
socket_->disconnectFromHost ();
|
||||
break;
|
||||
|
||||
case QAbstractSocket::TemporaryError:
|
||||
break;
|
||||
|
||||
default:
|
||||
spots_.clear ();
|
||||
qDebug () << "PSKReporter::impl::handle_socket_error:" << socket_->errorString ();
|
||||
Q_EMIT self_->errorOccurred (socket_->errorString ());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void reconnect ()
|
||||
{
|
||||
// Using deleteLater for the deleter as we may eventually
|
||||
// be called from the disconnected handler above.
|
||||
if (config_->psk_reporter_tcpip ())
|
||||
{
|
||||
socket_.reset (new QTcpSocket, &QObject::deleteLater);
|
||||
send_descriptors_ = 1;
|
||||
send_receiver_data_ = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
socket_.reset (new QUdpSocket, &QObject::deleteLater);
|
||||
send_descriptors_ = 3;
|
||||
send_receiver_data_ = 3;
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
connect (socket_.get (), &QAbstractSocket::errorOccurred, this, &PSKReporter::impl::handle_socket_error);
|
||||
#else
|
||||
connect (socket_.get (), QOverload<QAbstractSocket::SocketError>::of (&QAbstractSocket::error), this, &PSKReporter::impl::handle_socket_error);
|
||||
#endif
|
||||
|
||||
// use this for pseudo connection with UDP, allows us to use
|
||||
// QIODevice::write() instead of QUDPSocket::writeDatagram()
|
||||
socket_->connectToHost (HOST, SERVICE_PORT, QAbstractSocket::WriteOnly);
|
||||
}
|
||||
|
||||
void send_report (bool send_residue = false);
|
||||
void build_preamble (QDataStream&);
|
||||
|
||||
bool flushing ()
|
||||
{
|
||||
return FLUSH_INTERVAL && !(++flush_counter_ % FLUSH_INTERVAL);
|
||||
}
|
||||
|
||||
PSKReporter * self_;
|
||||
Configuration const * config_;
|
||||
QSharedPointer<QAbstractSocket> socket_;
|
||||
int dns_lookup_id_;
|
||||
QByteArray payload_;
|
||||
quint32 sequence_number_;
|
||||
int send_descriptors_;
|
||||
|
||||
// Currently PSK Reporter requires that a receiver data set is sent
|
||||
// in every data flow. This memeber variable can be used to only
|
||||
// send that information at session start (3 times for UDP), when it
|
||||
// changes (3 times for UDP), or once per hour (3 times) if using
|
||||
// UDP. Uncomment the relevant code to enable that fuctionality.
|
||||
int send_receiver_data_;
|
||||
|
||||
unsigned flush_counter_;
|
||||
quint32 observation_id_;
|
||||
QString rx_call_;
|
||||
QString rx_grid_;
|
||||
QString rx_ant_;
|
||||
QString prog_id_;
|
||||
QByteArray tx_data_;
|
||||
QByteArray tx_residue_;
|
||||
struct Spot
|
||||
{
|
||||
bool operator == (Spot const& rhs)
|
||||
{
|
||||
return
|
||||
call_ == rhs.call_
|
||||
&& grid_ == rhs.grid_
|
||||
&& mode_ == rhs.mode_
|
||||
&& std::abs (Radio::FrequencyDelta (freq_ - rhs.freq_)) < 50;
|
||||
}
|
||||
|
||||
QString call_;
|
||||
QString grid_;
|
||||
int snr_;
|
||||
Radio::Frequency freq_;
|
||||
QString mode_;
|
||||
QDateTime time_;
|
||||
};
|
||||
QQueue<Spot> spots_;
|
||||
QTimer report_timer_;
|
||||
QTimer descriptor_timer_;
|
||||
};
|
||||
|
||||
#include "PSKReporter.moc"
|
||||
|
||||
namespace
|
||||
{
|
||||
void writeUtfString (QDataStream& out, QString const& s)
|
||||
{
|
||||
auto const& utf = s.toUtf8 ().left (254);
|
||||
out << quint8 (utf.size ());
|
||||
out.writeRawData (utf, utf.size ());
|
||||
}
|
||||
|
||||
constexpr
|
||||
int num_pad_bytes (int len)
|
||||
{
|
||||
return ALIGNMENT_PADDING ? (4 - len % 4) % 4 : 0;
|
||||
}
|
||||
|
||||
void set_length (QDataStream& out, QByteArray& b)
|
||||
{
|
||||
// pad with nulls modulo 4
|
||||
auto pad_len = num_pad_bytes (b.size ());
|
||||
out.writeRawData (QByteArray {pad_len, '\0'}.constData (), pad_len);
|
||||
auto pos = out.device ()->pos ();
|
||||
out.device ()->seek (sizeof (quint16));
|
||||
// insert length
|
||||
out << static_cast<quint16> (b.size ());
|
||||
out.device ()->seek (pos);
|
||||
}
|
||||
}
|
||||
|
||||
void PSKReporter::impl::build_preamble (QDataStream& message)
|
||||
{
|
||||
// Message Header
|
||||
message
|
||||
<< quint16 (10u) // Version Number
|
||||
<< quint16 (0u) // Length (place-holder filled in later)
|
||||
<< quint32 (0u) // Export Time (place-holder filled in later)
|
||||
<< ++sequence_number_ // Sequence Number
|
||||
<< observation_id_; // Observation Domain ID
|
||||
|
||||
// qDebug () << "PSKReporter::impl::build_preamble: send_descriptors_:" << send_descriptors_;
|
||||
if (send_descriptors_)
|
||||
{
|
||||
--send_descriptors_;
|
||||
{
|
||||
// Sender Information descriptor
|
||||
QByteArray descriptor;
|
||||
QDataStream out {&descriptor, QIODevice::WriteOnly};
|
||||
out
|
||||
<< quint16 (2u) // Template Set ID
|
||||
<< quint16 (0u) // Length (place-holder)
|
||||
<< quint16 (0x50e3) // Link ID
|
||||
<< quint16 (7u) // Field Count
|
||||
<< quint16 (0x8000 + 1u) // Option 1 Information Element ID (senderCallsign)
|
||||
<< quint16 (0xffff) // Option 1 Field Length (variable)
|
||||
<< quint32 (30351u) // Option 1 Enterprise Number
|
||||
<< quint16 (0x8000 + 5u) // Option 2 Information Element ID (frequency)
|
||||
<< quint16 (4u) // Option 2 Field Length
|
||||
<< quint32 (30351u) // Option 2 Enterprise Number
|
||||
<< quint16 (0x8000 + 6u) // Option 3 Information Element ID (sNR)
|
||||
<< quint16 (1u) // Option 3 Field Length
|
||||
<< quint32 (30351u) // Option 3 Enterprise Number
|
||||
<< quint16 (0x8000 + 10u) // Option 4 Information Element ID (mode)
|
||||
<< quint16 (0xffff) // Option 4 Field Length (variable)
|
||||
<< quint32 (30351u) // Option 4 Enterprise Number
|
||||
<< quint16 (0x8000 + 3u) // Option 5 Information Element ID (senderLocator)
|
||||
<< quint16 (0xffff) // Option 5 Field Length (variable)
|
||||
<< quint32 (30351u) // Option 5 Enterprise Number
|
||||
<< quint16 (0x8000 + 11u) // Option 6 Information Element ID (informationSource)
|
||||
<< quint16 (1u) // Option 6 Field Length
|
||||
<< quint32 (30351u) // Option 6 Enterprise Number
|
||||
<< quint16 (150u) // Option 7 Information Element ID (dateTimeSeconds)
|
||||
<< quint16 (4u); // Option 7 Field Length
|
||||
// insert Length and move to payload
|
||||
set_length (out, descriptor);
|
||||
message.writeRawData (descriptor.constData (), descriptor.size ());
|
||||
}
|
||||
{
|
||||
// Receiver Information descriptor
|
||||
QByteArray descriptor;
|
||||
QDataStream out {&descriptor, QIODevice::WriteOnly};
|
||||
out
|
||||
<< quint16 (3u) // Options Template Set ID
|
||||
<< quint16 (0u) // Length (place-holder)
|
||||
<< quint16 (0x50e2) // Link ID
|
||||
<< quint16 (4u) // Field Count
|
||||
<< quint16 (0u) // Scope Field Count
|
||||
<< quint16 (0x8000 + 2u) // Option 1 Information Element ID (receiverCallsign)
|
||||
<< quint16 (0xffff) // Option 1 Field Length (variable)
|
||||
<< quint32 (30351u) // Option 1 Enterprise Number
|
||||
<< quint16 (0x8000 + 4u) // Option 2 Information Element ID (receiverLocator)
|
||||
<< quint16 (0xffff) // Option 2 Field Length (variable)
|
||||
<< quint32 (30351u) // Option 2 Enterprise Number
|
||||
<< quint16 (0x8000 + 8u) // Option 3 Information Element ID (decodingSoftware)
|
||||
<< quint16 (0xffff) // Option 3 Field Length (variable)
|
||||
<< quint32 (30351u) // Option 3 Enterprise Number
|
||||
<< quint16 (0x8000 + 9u) // Option 4 Information Element ID (antennaInformation)
|
||||
<< quint16 (0xffff) // Option 4 Field Length (variable)
|
||||
<< quint32 (30351u); // Option 4 Enterprise Number
|
||||
// insert Length
|
||||
set_length (out, descriptor);
|
||||
message.writeRawData (descriptor.constData (), descriptor.size ());
|
||||
}
|
||||
}
|
||||
|
||||
// qDebug () << "PSKReporter::impl::build_preamble: send_receiver_data_:" << send_receiver_data_;
|
||||
// if (send_receiver_data_)
|
||||
{
|
||||
// --send_receiver_data_;
|
||||
|
||||
// Receiver information
|
||||
QByteArray data;
|
||||
QDataStream out {&data, QIODevice::WriteOnly};
|
||||
|
||||
// Set Header
|
||||
out
|
||||
<< quint16 (0x50e2) // Template ID
|
||||
<< quint16 (0u); // Length (place-holder)
|
||||
|
||||
// Set data
|
||||
writeUtfString (out, rx_call_);
|
||||
writeUtfString (out, rx_grid_);
|
||||
writeUtfString (out, prog_id_);
|
||||
writeUtfString (out, rx_ant_);
|
||||
|
||||
// insert Length and move to payload
|
||||
set_length (out, data);
|
||||
message.writeRawData (data.constData (), data.size ());
|
||||
}
|
||||
}
|
||||
|
||||
void PSKReporter::impl::send_report (bool send_residue)
|
||||
{
|
||||
check_connection ();
|
||||
// qDebug () << "PSKReporter::impl::send_report: send_residue:" << send_residue;
|
||||
if (QAbstractSocket::ConnectedState != socket_->state ()) return;
|
||||
|
||||
QDataStream message {&payload_, QIODevice::WriteOnly | QIODevice::Append};
|
||||
QDataStream tx_out {&tx_data_, QIODevice::WriteOnly | QIODevice::Append};
|
||||
|
||||
if (!payload_.size ())
|
||||
{
|
||||
// qDebug () << "PSKReporter::impl::send_report: building header";
|
||||
// Build header, optional descriptors, and receiver information
|
||||
build_preamble (message);
|
||||
}
|
||||
|
||||
auto flush = flushing () || send_residue;
|
||||
// qDebug () << "PSKReporter::impl::send_report: flush:" << flush;
|
||||
while (spots_.size () || flush)
|
||||
{
|
||||
if (!payload_.size ())
|
||||
{
|
||||
// Build header, optional descriptors, and receiver information
|
||||
build_preamble (message);
|
||||
}
|
||||
|
||||
if (!tx_data_.size () && (spots_.size () || tx_residue_.size ()))
|
||||
{
|
||||
// Set Header
|
||||
tx_out
|
||||
<< quint16 (0x50e3) // Template ID
|
||||
<< quint16 (0u); // Length (place-holder)
|
||||
// qDebug () << "PSKReporter::impl::send_report: set data set header";
|
||||
}
|
||||
|
||||
// insert any residue
|
||||
if (tx_residue_.size ())
|
||||
{
|
||||
tx_out.writeRawData (tx_residue_.constData (), tx_residue_.size ());
|
||||
tx_residue_.clear ();
|
||||
// qDebug () << "PSKReporter::impl::send_report: inserted data residue";
|
||||
}
|
||||
|
||||
while (spots_.size () || flush)
|
||||
{
|
||||
auto tx_data_size = tx_data_.size ();
|
||||
if (spots_.size ())
|
||||
{
|
||||
auto const& spot = spots_.dequeue ();
|
||||
// qDebug () << "PSKReporter::impl::send_report: processing spotted call:" << spot.call_;
|
||||
|
||||
// Sender information
|
||||
writeUtfString (tx_out, spot.call_);
|
||||
tx_out
|
||||
<< static_cast<quint32> (spot.freq_)
|
||||
<< static_cast<qint8> (spot.snr_);
|
||||
writeUtfString (tx_out, spot.mode_);
|
||||
writeUtfString (tx_out, spot.grid_);
|
||||
tx_out
|
||||
<< quint8 (1u) // REPORTER_SOURCE_AUTOMATIC
|
||||
<< static_cast<quint32> (spot.time_.toSecsSinceEpoch ());
|
||||
}
|
||||
|
||||
auto len = payload_.size () + tx_data_.size ();
|
||||
len += num_pad_bytes (tx_data_.size ());
|
||||
len += num_pad_bytes (len);
|
||||
if (len > MAX_PAYLOAD_LENGTH // our upper datagram size limit
|
||||
|| (!spots_.size () && len > MIN_PAYLOAD_LENGTH) // spots drained and above lower datagram size limit
|
||||
|| (flush && !spots_.size ())) // send what we have, possibly no spots
|
||||
{
|
||||
if (tx_data_.size ())
|
||||
{
|
||||
if (len <= MAX_PAYLOAD_LENGTH)
|
||||
{
|
||||
tx_data_size = tx_data_.size ();
|
||||
// qDebug () << "PSKReporter::impl::send_report: sending short payload:" << tx_data_size;
|
||||
}
|
||||
QByteArray tx {tx_data_.left (tx_data_size)};
|
||||
QDataStream out {&tx, QIODevice::WriteOnly | QIODevice::Append};
|
||||
// insert Length
|
||||
set_length (out, tx);
|
||||
message.writeRawData (tx.constData (), tx.size ());
|
||||
}
|
||||
|
||||
// insert Length and Export Time
|
||||
set_length (message, payload_);
|
||||
message.device ()->seek (2 * sizeof (quint16));
|
||||
message << static_cast<quint32> (QDateTime::currentDateTime ().toSecsSinceEpoch ());
|
||||
|
||||
// Send data to PSK Reporter site
|
||||
socket_->write (payload_); // TODO: handle errors
|
||||
// qDebug () << "PSKReporter::impl::send_report: sent payload:" << payload_.size () << "observation id:" << observation_id_;
|
||||
flush = false; // break loop
|
||||
message.device ()->seek (0u);
|
||||
payload_.clear (); // Fresh message
|
||||
// Save unsent spots
|
||||
tx_residue_ = tx_data_.right (tx_data_.size () - tx_data_size);
|
||||
tx_out.device ()->seek (0u);
|
||||
tx_data_.clear ();
|
||||
// qDebug () << "PSKReporter::impl::send_report: payload sent residue length:" << tx_residue_.size ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PSKReporter::PSKReporter (Configuration const * config, QString const& program_info)
|
||||
: m_ {this, config, program_info}
|
||||
{
|
||||
}
|
||||
|
||||
PSKReporter::~PSKReporter ()
|
||||
{
|
||||
// m_->send_report (true); // send any pending spots
|
||||
}
|
||||
|
||||
void PSKReporter::reconnect ()
|
||||
{
|
||||
m_->reconnect ();
|
||||
}
|
||||
|
||||
void PSKReporter::setLocalStation (QString const& call, QString const& gridSquare, QString const& antenna)
|
||||
{
|
||||
if (call != m_->rx_call_ || gridSquare != m_->rx_grid_ || antenna != m_->rx_ant_)
|
||||
{
|
||||
m_->send_receiver_data_ = m_->socket_
|
||||
&& QAbstractSocket::UdpSocket == m_->socket_->socketType () ? 3 : 1;
|
||||
m_->rx_call_ = call;
|
||||
m_->rx_grid_ = gridSquare;
|
||||
m_->rx_ant_ = antenna;
|
||||
}
|
||||
}
|
||||
|
||||
bool PSKReporter::addRemoteStation (QString const& call, QString const& grid, Radio::Frequency freq
|
||||
, QString const& mode, int snr)
|
||||
{
|
||||
if (m_->socket_ && m_->socket_->isValid ())
|
||||
{
|
||||
if (QAbstractSocket::UnconnectedState == m_->socket_->state ())
|
||||
{
|
||||
reconnect ();
|
||||
}
|
||||
m_->spots_.enqueue ({call, grid, snr, freq, mode, QDateTime::currentDateTimeUtc ()});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PSKReporter::sendReport ()
|
||||
{
|
||||
m_->send_report (true);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef PSK_REPORTER_HPP_
|
||||
#define PSK_REPORTER_HPP_
|
||||
|
||||
#include <QObject>
|
||||
#include "Radio.hpp"
|
||||
#include "pimpl_h.hpp"
|
||||
|
||||
class QString;
|
||||
class Configuration;
|
||||
|
||||
class PSKReporter final
|
||||
: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PSKReporter (Configuration const *, QString const& program_info);
|
||||
~PSKReporter ();
|
||||
|
||||
void reconnect ();
|
||||
|
||||
void setLocalStation (QString const& call, QString const& grid, QString const& antenna);
|
||||
|
||||
//
|
||||
// Returns false if PSK Reporter connection is not available
|
||||
//
|
||||
bool addRemoteStation (QString const& call, QString const& grid, Radio::Frequency freq, QString const& mode, int snr);
|
||||
|
||||
//
|
||||
// Flush any pending spots to PSK Reporter
|
||||
//
|
||||
void sendReport ();
|
||||
|
||||
Q_SIGNAL void errorOccurred (QString const& reason);
|
||||
|
||||
private:
|
||||
class impl;
|
||||
pimpl<impl> m_;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,140 +0,0 @@
|
||||
// KISS Interface for posting spots to PSK Reporter web site
|
||||
// Implemented by Edson Pereira PY2SDR
|
||||
//
|
||||
// Reports will be sent in batch mode every 5 minutes.
|
||||
|
||||
#include "psk_reporter.h"
|
||||
|
||||
#include <QHostInfo>
|
||||
#include <QTimer>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
#include <QRandomGenerator>
|
||||
#endif
|
||||
|
||||
#include "Network/MessageClient.hpp"
|
||||
|
||||
#include "moc_psk_reporter.cpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
int constexpr MAX_PAYLOAD_LENGTH {1400};
|
||||
}
|
||||
|
||||
PSK_Reporter::PSK_Reporter(MessageClient * message_client, QObject *parent) :
|
||||
QObject {parent},
|
||||
m_messageClient {message_client},
|
||||
reportTimer {new QTimer {this}},
|
||||
m_sequenceNumber {0}
|
||||
{
|
||||
m_header_h = "000Allllttttttttssssssssiiiiiiii";
|
||||
|
||||
// We use 50E2 and 50E3 for link Id
|
||||
m_rxInfoDescriptor_h = "0003002C50E200040000"
|
||||
"8002FFFF0000768F" // 2. Rx Call
|
||||
"8004FFFF0000768F" // 4. Rx Grid
|
||||
"8008FFFF0000768F" // 8. Rx Soft
|
||||
"8009FFFF0000768F" // 9. Rx Antenna
|
||||
"0000";
|
||||
|
||||
m_txInfoDescriptor_h = "0002003C50E30007"
|
||||
"8001FFFF0000768F" // 1. Tx Call
|
||||
"800500040000768F" // 5. Tx Freq
|
||||
"800600010000768F" // 6. Tx snr
|
||||
"800AFFFF0000768F" // 10. Tx Mode
|
||||
"8003FFFF0000768F" // 3. Tx Grid
|
||||
"800B00010000768F" // 11. Tx info src
|
||||
"00960004"; // Report time
|
||||
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
||||
m_randomId_h = QString("%1").arg(qrand(),8,16,QChar('0'));
|
||||
#else
|
||||
m_randomId_h = QString("%1").arg(QRandomGenerator::global ()->generate (), 8, 16, QChar('0'));
|
||||
#endif
|
||||
|
||||
QHostInfo::lookupHost("report.pskreporter.info", this, SLOT(dnsLookupResult(QHostInfo)));
|
||||
|
||||
connect(reportTimer, SIGNAL(timeout()), this, SLOT(sendReport()));
|
||||
reportTimer->start(5*60*1000); // 5 minutes;
|
||||
}
|
||||
|
||||
void PSK_Reporter::setLocalStation(QString call, QString gridSquare, QString antenna, QString programInfo)
|
||||
{
|
||||
m_rxCall = call;
|
||||
m_rxGrid = gridSquare;
|
||||
m_rxAnt = antenna;
|
||||
m_progId = programInfo;
|
||||
}
|
||||
|
||||
void PSK_Reporter::addRemoteStation(QString call, QString grid, QString freq, QString mode, QString snr, QString time )
|
||||
{
|
||||
QHash<QString,QString> spot;
|
||||
spot["call"] = call;
|
||||
spot["grid"] = grid;
|
||||
spot["snr"] = snr;
|
||||
spot["freq"] = freq;
|
||||
spot["mode"] = mode;
|
||||
spot["time"] = time;
|
||||
m_spotQueue.enqueue(spot);
|
||||
}
|
||||
|
||||
void PSK_Reporter::sendReport()
|
||||
{
|
||||
while (!m_spotQueue.isEmpty()) {
|
||||
QString report_h;
|
||||
|
||||
// Header
|
||||
QString header_h = m_header_h;
|
||||
header_h.replace("tttttttt", QString("%1").arg(QDateTime::currentDateTime().toTime_t(),8,16,QChar('0')));
|
||||
header_h.replace("ssssssss", QString("%1").arg(++m_sequenceNumber,8,16,QChar('0')));
|
||||
header_h.replace("iiiiiiii", m_randomId_h);
|
||||
|
||||
// Receiver information
|
||||
QString rxInfoData_h = "50E2llll";
|
||||
rxInfoData_h += QString("%1").arg(m_rxCall.length(),2,16,QChar('0')) + m_rxCall.toUtf8().toHex();
|
||||
rxInfoData_h += QString("%1").arg(m_rxGrid.length(),2,16,QChar('0')) + m_rxGrid.toUtf8().toHex();
|
||||
rxInfoData_h += QString("%1").arg(m_progId.length(),2,16,QChar('0')) + m_progId.toUtf8().toHex();
|
||||
rxInfoData_h += QString("%1").arg(m_rxAnt.length(),2,16,QChar('0')) + m_rxAnt.toUtf8().toHex();
|
||||
rxInfoData_h += "0000";
|
||||
rxInfoData_h.replace("50E2llll", "50E2" + QString("%1").arg(rxInfoData_h.length()/2,4,16,QChar('0')));
|
||||
|
||||
// Sender information
|
||||
QString txInfoData_h = "50E3llll";
|
||||
while (!m_spotQueue.isEmpty()
|
||||
&& (header_h.size () + m_rxInfoDescriptor_h.size () + m_txInfoDescriptor_h.size () + rxInfoData_h.size () + txInfoData_h.size ()) / 2 < MAX_PAYLOAD_LENGTH) {
|
||||
QHash<QString,QString> spot = m_spotQueue.dequeue();
|
||||
txInfoData_h += QString("%1").arg(spot["call"].length(),2,16,QChar('0')) + spot["call"].toUtf8().toHex();
|
||||
txInfoData_h += QString("%1").arg(spot["freq"].toLongLong(),8,16,QChar('0'));
|
||||
txInfoData_h += QString("%1").arg(spot["snr"].toInt(),8,16,QChar('0')).right(2);
|
||||
txInfoData_h += QString("%1").arg(spot["mode"].length(),2,16,QChar('0')) + spot["mode"].toUtf8().toHex();
|
||||
txInfoData_h += QString("%1").arg(spot["grid"].length(),2,16,QChar('0')) + spot["grid"].toUtf8().toHex();
|
||||
txInfoData_h += QString("%1").arg(1,2,16,QChar('0')); // REPORTER_SOURCE_AUTOMATIC
|
||||
txInfoData_h += QString("%1").arg(spot["time"].toInt(),8,16,QChar('0'));
|
||||
}
|
||||
txInfoData_h += "0000";
|
||||
txInfoData_h.replace("50E3llll", "50E3" + QString("%1").arg(txInfoData_h.length()/2,4,16,QChar('0')));
|
||||
report_h = header_h + m_rxInfoDescriptor_h + m_txInfoDescriptor_h + rxInfoData_h + txInfoData_h;
|
||||
//qDebug() << "Sending Report TX: ";
|
||||
|
||||
report_h.replace("000Allll", "000A" + QString("%1").arg(report_h.length()/2,4,16,QChar('0')));
|
||||
QByteArray report = QByteArray::fromHex(report_h.toUtf8());
|
||||
|
||||
// Send data to PSK Reporter site
|
||||
if (!m_pskReporterAddress.isNull()) {
|
||||
m_messageClient->send_raw_datagram (report, m_pskReporterAddress, 4739);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PSK_Reporter::dnsLookupResult(QHostInfo info)
|
||||
{
|
||||
if (!info.addresses().isEmpty()) {
|
||||
m_pskReporterAddress = info.addresses().at(0);
|
||||
// qDebug() << "PSK Reporter IP: " << m_pskReporterAddress;
|
||||
|
||||
// deal with miss-configured settings that attempt to set a
|
||||
// Pskreporter Internet address for the WSJT-X UDP protocol
|
||||
// server address
|
||||
m_messageClient->add_blocked_destination (m_pskReporterAddress);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// -*- Mode: C++ -*-
|
||||
#ifndef PSK_REPORTER_H
|
||||
#define PSK_REPORTER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QHostAddress>
|
||||
#include <QQueue>
|
||||
#include <QHash>
|
||||
|
||||
class MessageClient;
|
||||
class QTimer;
|
||||
class QHostInfo;
|
||||
|
||||
class PSK_Reporter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PSK_Reporter(MessageClient *, QObject *parent = nullptr);
|
||||
void setLocalStation(QString call, QString grid, QString antenna, QString programInfo);
|
||||
void addRemoteStation(QString call, QString grid, QString freq, QString mode, QString snr, QString time);
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
void sendReport();
|
||||
|
||||
private slots:
|
||||
void dnsLookupResult(QHostInfo info);
|
||||
|
||||
private:
|
||||
QString m_header_h;
|
||||
QString m_rxInfoDescriptor_h;
|
||||
QString m_txInfoDescriptor_h;
|
||||
QString m_randomId_h;
|
||||
QString m_linkId_h;
|
||||
|
||||
QString m_rxCall;
|
||||
QString m_rxGrid;
|
||||
QString m_rxAnt;
|
||||
QString m_progId;
|
||||
|
||||
QHostAddress m_pskReporterAddress;
|
||||
|
||||
QQueue< QHash<QString,QString> > m_spotQueue;
|
||||
|
||||
MessageClient * m_messageClient;
|
||||
|
||||
QTimer *reportTimer;
|
||||
|
||||
int m_sequenceNumber;
|
||||
};
|
||||
|
||||
#endif // PSK_REPORTER_H
|
||||
@@ -0,0 +1,12 @@
|
||||
senderCallsign(30351/1)<string>[65535]
|
||||
receiverCallsign(30351/2)<string>[65535]
|
||||
senderLocator(30351/3)<string>[65535]
|
||||
receiverLocator(30351/4)<string>[65535]
|
||||
frequency(30351/5)<unsigned32>[4]
|
||||
sNR(30351/6)<signed8>[1]
|
||||
iMD(30351/7)<signed8>[1]
|
||||
decoderSoftware(30351/8)<string>[65535]
|
||||
antennaInformation(30351/9)<string>[65535]
|
||||
mode(30351/10)<string>[65535]
|
||||
informationSource(30351/11)<signed8>[1]
|
||||
persistentIdentifier(30351/12)<string>[65535]
|
||||
@@ -0,0 +1,94 @@
|
||||
from __future__ import unicode_literals
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
import time
|
||||
import threading
|
||||
import ipfix.ie
|
||||
import ipfix.reader
|
||||
import ipfix.message
|
||||
import socketserver
|
||||
import socket
|
||||
|
||||
class IPFixDatagramHandler (socketserver.DatagramRequestHandler):
|
||||
|
||||
def handle (self):
|
||||
logging.info (f'Connection from {self.client_address}')
|
||||
try:
|
||||
self.server.msg_buffer.from_bytes (self.packet)
|
||||
for rec in self.server.msg_buffer.namedict_iterator ():
|
||||
logging.info (f't: {self.server.msg_buffer.get_export_time()}: {rec}')
|
||||
except:
|
||||
logging.error ('Unexpected exception:', sys.exc_info ()[0])
|
||||
|
||||
|
||||
class IPFixUdpServer (socketserver.UDPServer):
|
||||
|
||||
def __init__ (self, *args, **kwargs):
|
||||
|
||||
self.msg_buffer = ipfix.message.MessageBuffer ()
|
||||
super ().__init__ (*args, **kwargs)
|
||||
|
||||
class IPFixStreamHandler (socketserver.StreamRequestHandler):
|
||||
|
||||
def handle (self):
|
||||
logging.info (f'Connection from {self.client_address}')
|
||||
try:
|
||||
msg_reader = ipfix.reader.from_stream (self.rfile)
|
||||
for rec in msg_reader.namedict_iterator ():
|
||||
logging.info (f't: {msg_reader.msg.get_export_time()}: {rec}')
|
||||
logging.info (f'{self.client_address} closed their connection')
|
||||
except ConnectionResetError:
|
||||
logging.info (f'{self.client_address} connection reset')
|
||||
except TimeoutError:
|
||||
logging.info (f'{self.client_address} connection timed out')
|
||||
except:
|
||||
logging.error ('Unexpected exception:', sys.exc_info ()[0])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
INTERFACE, PORT = '', 4739
|
||||
|
||||
ap = argparse.ArgumentParser (description='Dump IPFIX data collectedover UDP')
|
||||
ap.add_argument ('-l', '--log', metavar='loglevel', default='WARNING', help='logging level')
|
||||
ap.add_argument ('-s', '--spec', metavar='specfile', help='iespec file to read')
|
||||
args = ap.parse_args ()
|
||||
|
||||
log_level = getattr (logging, args.log.upper (), None)
|
||||
if not isinstance (log_level, int):
|
||||
raise ValueError (f'Invalif log level: {log_level}')
|
||||
logging.basicConfig (
|
||||
level=log_level
|
||||
, format='[%(levelname)s] (%(threadName)-10s) %(message)s'
|
||||
,
|
||||
)
|
||||
|
||||
logging.info (f'Starting IPFix servers on service port: {PORT}')
|
||||
|
||||
ipfix.ie.use_iana_default ()
|
||||
ipfix.ie.use_5103_default ()
|
||||
if args.spec:
|
||||
ipfix.ie.use_specfile (args.spec)
|
||||
|
||||
udp_server = IPFixUdpServer ((INTERFACE, PORT), IPFixDatagramHandler)
|
||||
udp_thread = threading.Thread (name='UDP Server', target=udp_server.serve_forever)
|
||||
|
||||
tcp_server = socketserver.TCPServer ((INTERFACE, PORT), IPFixStreamHandler)
|
||||
tcp_server.allow_reuse_address = True
|
||||
tcp_server.socket.setsockopt (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
tcp_thread = threading.Thread (name='TCP/IP Server', target=tcp_server.serve_forever)
|
||||
|
||||
udp_thread.start ()
|
||||
tcp_thread.start ()
|
||||
|
||||
try:
|
||||
while True:
|
||||
time.sleep (1000)
|
||||
except KeyboardInterrupt:
|
||||
logging.warning ('Closing down servers')
|
||||
udp_server.shutdown ()
|
||||
tcp_server.shutdown ()
|
||||
|
||||
udp_thread.join ()
|
||||
tcp_thread.join ()
|
||||
logging.info ('Servers closed')
|
||||
@@ -0,0 +1,64 @@
|
||||
The following changes since commit e487dfbead9965c1a251d110dc55039a7ba4afef:
|
||||
|
||||
sphinx doc update (2017-09-20 16:07:22 +0200)
|
||||
|
||||
are available in the Git repository at:
|
||||
|
||||
git@github.com:g4wjs/python-ipfix.git varlen-and-padding
|
||||
|
||||
for you to fetch changes up to 6dcc106f22d25ac21b27f8ccb9b82be12e7eb18e:
|
||||
|
||||
Parse and emit data elements correctly when variable length items included (2020-06-19 19:53:33 +0100)
|
||||
|
||||
----------------------------------------------------------------
|
||||
Bill Somerville (2):
|
||||
Allow for alignment padding when parsing sets
|
||||
Parse and emit data elements correctly when variable length items included
|
||||
|
||||
ipfix/message.py | 3 ++-
|
||||
ipfix/template.py | 4 ++--
|
||||
2 files changed, 4 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/ipfix/message.py b/ipfix/message.py
|
||||
index 0af2fad..71c1328 100644
|
||||
--- a/ipfix/message.py
|
||||
+++ b/ipfix/message.py
|
||||
@@ -398,7 +398,7 @@ class MessageBuffer(object):
|
||||
offset += _sethdr_st.size # skip set header in decode
|
||||
if setid == template.TEMPLATE_SET_ID or\
|
||||
setid == template.OPTIONS_SET_ID:
|
||||
- while offset < setend:
|
||||
+ while offset + 4 < setend: # allow for padding up to 4 byte alignment
|
||||
(tmpl, offset) = template.decode_template_from(
|
||||
self.mbuf, offset, setid)
|
||||
# FIXME handle withdrawal
|
||||
@@ -431,6 +431,7 @@ class MessageBuffer(object):
|
||||
# KeyError on template lookup - unknown data set
|
||||
self.unknown_data_set_hook(self,
|
||||
self.mbuf[offset-_sethdr_st.size:setend])
|
||||
+ offset = setend # real end may be greater that accumulated offset
|
||||
|
||||
def namedict_iterator(self):
|
||||
"""
|
||||
diff --git a/ipfix/template.py b/ipfix/template.py
|
||||
index 1203e55..26cd8c1 100644
|
||||
--- a/ipfix/template.py
|
||||
+++ b/ipfix/template.py
|
||||
@@ -187,7 +187,7 @@ class Template(object):
|
||||
offset += packplan.st.size
|
||||
|
||||
# short circuit on no varlen
|
||||
- if not self.varlenslice:
|
||||
+ if self.varlenslice is None:
|
||||
return (vals, offset)
|
||||
|
||||
# direct iteration over remaining IEs
|
||||
@@ -239,7 +239,7 @@ class Template(object):
|
||||
offset += packplan.st.size
|
||||
|
||||
# shortcircuit no varlen
|
||||
- if not self.varlenslice:
|
||||
+ if self.varlenslice is None:
|
||||
return offset
|
||||
|
||||
# direct iteration over remaining IEs
|
||||
Reference in New Issue
Block a user