From 662ed0fa7a0feeda2e91e2c5e1d6bd843ad65cca Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Mon, 2 Nov 2020 15:33:44 +0000 Subject: [PATCH 01/16] Network interface selection for outgoing UDP multicast datagrams Default selection is the loop-back interface. Users who require interoperation between WSJT-X instances cooperating applications running on different hosts should select a suitable network interface and carefully choose a multicast group address, and TTL, that has minimal scope covering the necessary network(s). Using 224.0.0.1 is a reasonable strategy if all hosts are on the same subnet. Administratively scoped multicast group addresses like those within 239.255.0.0/16 can cover larger boundaries, but care must be taken if the local subnet has access to a multicast enabled router. The IPv4 broadcast address (255.255.255.255) may be used as an alternative to multicast UDP, but note that WSJT-X will only send broadcast UDP datagrams on the loop-back interface, so all recipient applications must be running on the same host system. The reference UDP Message protocol applications are being extended to be configurable with a list of interfaces to join a multicast group address on. By default they will only join on the loop-back interface, which is also recommended for any applications designed to take part in the WSJT-X UDP Message Protocol. This allows full user control of the scope of multicast group membership with a very conservative default mode that will work with all interoperating applications running on the same host system. --- Configuration.cpp | 159 +++++++++++++++++++++++++++- Configuration.hpp | 6 +- Configuration.ui | 61 ++++++++--- Network/MessageClient.cpp | 193 ++++++++++++++++++++++++---------- Network/MessageClient.hpp | 15 ++- UDPExamples/MessageServer.cpp | 85 +++++++-------- UDPExamples/MessageServer.hpp | 7 +- UDPExamples/UDPDaemon.cpp | 19 +++- qt_helpers.hpp | 26 +++++ tests/test_qt_helpers.cpp | 74 +++++++++++++ widgets/mainwindow.cpp | 4 +- 11 files changed, 510 insertions(+), 139 deletions(-) diff --git a/Configuration.cpp b/Configuration.cpp index f244ecd9f..fac5564ab 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -163,6 +163,9 @@ #include #include #include +#include +#include +#include #include #include "pimpl_impl.hpp" @@ -439,6 +442,10 @@ private: void load_audio_devices (QAudio::Mode, QComboBox *, QAudioDeviceInfo *); void update_audio_channels (QComboBox const *, int, QComboBox *, bool); + void load_network_interfaces (QComboBox *, QString const& current); + Q_SLOT void host_info_results (QHostInfo); + void check_multicast (QHostAddress const&); + void find_tab (QWidget *); void initialize_models (); @@ -492,6 +499,8 @@ private: Q_SLOT void on_add_macro_line_edit_editingFinished (); Q_SLOT void delete_macro (); void delete_selected_macros (QModelIndexList); + Q_SLOT void on_udp_server_line_edit_textChanged (QString const&); + Q_SLOT void on_udp_server_line_edit_editingFinished (); Q_SLOT void on_save_path_select_push_button_clicked (bool); Q_SLOT void on_azel_path_select_push_button_clicked (bool); Q_SLOT void on_calibration_intercept_spin_box_valueChanged (double); @@ -641,7 +650,11 @@ private: bool use_dynamic_grid_; QString opCall_; QString udp_server_name_; + bool udp_server_name_edited_; + int dns_lookup_id_; port_type udp_server_port_; + QString udp_interface_name_; + int udp_TTL_; QString n1mm_server_name_; port_type n1mm_server_port_; bool broadcast_to_n1mm_; @@ -741,6 +754,8 @@ QString Configuration::opCall() const {return m_->opCall_;} void Configuration::opCall (QString const& call) {m_->opCall_ = call;} QString Configuration::udp_server_name () const {return m_->udp_server_name_;} auto Configuration::udp_server_port () const -> port_type {return m_->udp_server_port_;} +QString Configuration::udp_interface_name () const {return m_->udp_interface_name_;} +int Configuration::udp_TTL () const {return m_->udp_TTL_;} bool Configuration::accept_udp_requests () const {return m_->accept_udp_requests_;} QString Configuration::n1mm_server_name () const {return m_->n1mm_server_name_;} auto Configuration::n1mm_server_port () const -> port_type {return m_->n1mm_server_port_;} @@ -995,6 +1010,8 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network , transceiver_command_number_ {0} , degrade_ {0.} // initialize to zero each run, not // saved in settings + , udp_server_name_edited_ {false} + , dns_lookup_id_ {-1} { ui_->setupUi (this); @@ -1044,6 +1061,7 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network // this must be done after the default paths above are set read_settings (); + // set up dynamic loading of audio devices connect (ui_->sound_input_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () { QGuiApplication::setOverrideCursor (QCursor {Qt::WaitCursor}); load_audio_devices (QAudio::AudioInput, ui_->sound_input_combo_box, &next_audio_input_device_); @@ -1059,6 +1077,13 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network QGuiApplication::restoreOverrideCursor (); }); + // set up dynamic loading of network interfaces + connect (ui_->udp_interface_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () { + QGuiApplication::setOverrideCursor (QCursor {Qt::WaitCursor}); + load_network_interfaces (ui_->udp_interface_combo_box, udp_interface_name_); + QGuiApplication::restoreOverrideCursor (); + }); + // set up LoTW users CSV file fetching connect (&lotw_users_, &LotWUsers::load_finished, [this] () { ui_->LotW_CSV_fetch_push_button->setEnabled (true); @@ -1335,7 +1360,14 @@ void Configuration::impl::initialize_models () ui_->CAT_poll_interval_spin_box->setValue (rig_params_.poll_interval); ui_->opCallEntry->setText (opCall_); ui_->udp_server_line_edit->setText (udp_server_name_); + on_udp_server_line_edit_editingFinished (); ui_->udp_server_port_spin_box->setValue (udp_server_port_); + load_network_interfaces (ui_->udp_interface_combo_box, udp_interface_name_); + if (!udp_interface_name_.size ()) + { + udp_interface_name_ = ui_->udp_interface_combo_box->currentData ().toString (); + } + ui_->udp_TTL_spin_box->setValue (udp_TTL_); ui_->accept_udp_requests_check_box->setChecked (accept_udp_requests_); ui_->n1mm_server_name_line_edit->setText (n1mm_server_name_); ui_->n1mm_server_port_spin_box->setValue (n1mm_server_port_); @@ -1513,6 +1545,8 @@ void Configuration::impl::read_settings () rig_params_.split_mode = settings_->value ("SplitMode", QVariant::fromValue (TransceiverFactory::split_mode_none)).value (); opCall_ = settings_->value ("OpCall", "").toString (); udp_server_name_ = settings_->value ("UDPServer", "127.0.0.1").toString (); + udp_interface_name_ = settings_->value ("UDPInterface").toString (); + udp_TTL_ = settings_->value ("UDPTTL").toInt (); udp_server_port_ = settings_->value ("UDPServerPort", 2237).toUInt (); n1mm_server_name_ = settings_->value ("N1MMServer", "127.0.0.1").toString (); n1mm_server_port_ = settings_->value ("N1MMServerPort", 2333).toUInt (); @@ -1641,6 +1675,8 @@ void Configuration::impl::write_settings () settings_->setValue ("OpCall", opCall_); settings_->setValue ("UDPServer", udp_server_name_); settings_->setValue ("UDPServerPort", udp_server_port_); + settings_->setValue ("UDPInterface", udp_interface_name_); + settings_->setValue ("UDPTTL", udp_TTL_); settings_->setValue ("N1MMServer", n1mm_server_name_); settings_->setValue ("N1MMServerPort", n1mm_server_port_); settings_->setValue ("BroadcastToN1MM", broadcast_to_n1mm_); @@ -1843,6 +1879,12 @@ bool Configuration::impl::validate () return false; } + if (dns_lookup_id_ > -1) + { + MessageBox::information_message (this, tr ("Pending DNS lookup, please try again later")); + return false; + } + return true; } @@ -2061,20 +2103,30 @@ void Configuration::impl::accept () pwrBandTxMemory_ = ui_->checkBoxPwrBandTxMemory->isChecked (); pwrBandTuneMemory_ = ui_->checkBoxPwrBandTuneMemory->isChecked (); opCall_=ui_->opCallEntry->text(); - auto new_server = ui_->udp_server_line_edit->text (); - if (new_server != udp_server_name_) + + auto new_server = ui_->udp_server_line_edit->text ().trimmed (); + auto new_interface = ui_->udp_interface_combo_box->currentData ().toString (); + if (new_server != udp_server_name_ || new_interface != udp_interface_name_) { udp_server_name_ = new_server; - Q_EMIT self_->udp_server_changed (new_server); + udp_interface_name_ = new_interface; + Q_EMIT self_->udp_server_changed (udp_server_name_, udp_interface_name_); } auto new_port = ui_->udp_server_port_spin_box->value (); if (new_port != udp_server_port_) { udp_server_port_ = new_port; - Q_EMIT self_->udp_server_port_changed (new_port); + Q_EMIT self_->udp_server_port_changed (udp_server_port_); } - + + auto new_TTL = ui_->udp_TTL_spin_box->value (); + if (new_TTL != udp_TTL_) + { + udp_TTL_ = new_TTL; + Q_EMIT self_->udp_TTL_changed (udp_TTL_); + } + if (ui_->accept_udp_requests_check_box->isChecked () != accept_udp_requests_) { accept_udp_requests_ = ui_->accept_udp_requests_check_box->isChecked (); @@ -2130,6 +2182,12 @@ void Configuration::impl::accept () void Configuration::impl::reject () { + if (dns_lookup_id_ > -1) + { + QHostInfo::abortHostLookup (dns_lookup_id_); + dns_lookup_id_ = -1; + } + initialize_models (); // reverts to settings as at exec () // check if the Transceiver instance changed, in which case we need @@ -2344,6 +2402,72 @@ void Configuration::impl::on_add_macro_push_button_clicked (bool /* checked */) } } +void Configuration::impl::on_udp_server_line_edit_textChanged (QString const&) +{ + udp_server_name_edited_ = true; +} + +void Configuration::impl::on_udp_server_line_edit_editingFinished () +{ + if (udp_server_name_edited_) + { + auto const& server = ui_->udp_server_line_edit->text ().trimmed (); + QHostAddress ha {server}; + if (server.size () && ha.isNull ()) + { + // queue a host address lookup + qDebug () << "server host DNS lookup:" << server; +#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) + dns_lookup_id_ = QHostInfo::lookupHost (server, this, &Configuration::impl::host_info_results); +#else + dns_lookup_id_ = QHostInfo::lookupHost (server, this, SLOT (host_info_results (QHostInfo))); +#endif + } + else + { + check_multicast (ha); + } + } +} + +void Configuration::impl::host_info_results (QHostInfo host_info) +{ + if (host_info.lookupId () != dns_lookup_id_) return; + dns_lookup_id_ = -1; + if (QHostInfo::NoError != host_info.error ()) + { + MessageBox::critical_message (this, tr ("UDP server DNS lookup failed"), host_info.errorString ()); + } + else + { + auto const& server_addresses = host_info.addresses (); + qDebug () << "message server addresses:" << server_addresses; + if (server_addresses.size ()) + { + check_multicast (server_addresses[0]); + } + } +} + +void Configuration::impl::check_multicast (QHostAddress const& ha) +{ + auto is_multicast = is_multicast_address (ha); + ui_->udp_interface_label->setVisible (is_multicast); + ui_->udp_interface_combo_box->setVisible (is_multicast); + ui_->udp_TTL_label->setVisible (is_multicast); + ui_->udp_TTL_spin_box->setVisible (is_multicast); + if (isVisible ()) + { + if (is_MAC_ambiguous_multicast_address (ha)) + { + MessageBox::warning_message (this, tr ("MAC-ambiguous multicast groups addresses not supported")); + find_tab (ui_->udp_server_line_edit); + ui_->udp_server_line_edit->clear (); + } + } + udp_server_name_edited_ = false; +} + void Configuration::impl::delete_frequencies () { auto selection_model = ui_->frequencies_table_view->selectionModel (); @@ -2868,6 +2992,31 @@ void Configuration::impl::load_audio_devices (QAudio::Mode mode, QComboBox * com combo_box->setCurrentIndex (current_index); } +// load the available network interfaces into the selection combo box +void Configuration::impl::load_network_interfaces (QComboBox * combo_box, QString const& current) +{ + combo_box->clear (); + int current_index = -1; + for (auto const& interface : QNetworkInterface::allInterfaces ()) + { + if (interface.flags () & QNetworkInterface::IsUp) + { + auto const& name = interface.name (); + combo_box->addItem (interface.humanReadableName (), name); + // select the first loopback interface as a default to + // discourage spamming the network (possibly the Internet), + // particularly important with administratively scoped + // multicast UDP + if (name == current + || (!current.size () && (interface.flags () & QNetworkInterface::IsLoopBack))) + { + current_index = combo_box->count () - 1; + } + } + } + combo_box->setCurrentIndex (current_index); +} + // enable only the channels that are supported by the selected audio device void Configuration::impl::update_audio_channels (QComboBox const * source_combo_box, int index, QComboBox * combo_box, bool allow_both) { diff --git a/Configuration.hpp b/Configuration.hpp index 8594a4a6f..403673663 100644 --- a/Configuration.hpp +++ b/Configuration.hpp @@ -21,7 +21,6 @@ class Bands; class FrequencyList_v2; class StationList; class QStringListModel; -class QHostAddress; class LotWUsers; class DecodeHighlightingModel; class LogBook; @@ -152,6 +151,8 @@ public: void opCall (QString const&); QString udp_server_name () const; port_type udp_server_port () const; + QString udp_interface_name () const; + int udp_TTL () const; QString n1mm_server_name () const; port_type n1mm_server_port () const; bool valid_n1mm_info () const; @@ -273,8 +274,9 @@ public: // // This signal is emitted when the UDP server changes // - Q_SIGNAL void udp_server_changed (QString const& udp_server) const; + Q_SIGNAL void udp_server_changed (QString& udp_server_name, QString const& network_interface) const; Q_SIGNAL void udp_server_port_changed (port_type server_port) const; + Q_SIGNAL void udp_TTL_changed (int TTL) const; Q_SIGNAL void accept_udp_requests_changed (bool checked) const; // signal updates to decode highlighting diff --git a/Configuration.ui b/Configuration.ui index fbd53c422..fd8fb8955 100644 --- a/Configuration.ui +++ b/Configuration.ui @@ -7,7 +7,7 @@ 0 0 554 - 556 + 560 @@ -1864,12 +1864,6 @@ and DX Grid fields when a 73 or free text message is sent. - - - 0 - 0 - - <html><head/><body><p>Optional hostname of network service to receive decodes.</p><p>Formats:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">hostname</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv4 address</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv6 address</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv4 multicast group address</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv6 multicast group address</li></ul><p>Clearing this field will disable the broadcasting of UDP status updates.</p></body></html> @@ -1898,6 +1892,39 @@ and DX Grid fields when a 73 or free text message is sent. + + + + Outgoing interface: + + + udp_interface_combo_box + + + + + + + + + + 255 + + + 1 + + + + + + + Multicast TTL: + + + udp_TTL_spin_box + + + @@ -3084,6 +3111,8 @@ Right click for insert and delete options. psk_reporter_tcpip_check_box udp_server_line_edit udp_server_port_spin_box + udp_interface_combo_box + udp_TTL_spin_box accept_udp_requests_check_box udpWindowToFront udpWindowRestore @@ -3101,8 +3130,8 @@ Right click for insert and delete options. include_WAE_check_box rescan_log_push_button LotW_CSV_URL_line_edit - LotW_days_since_upload_spin_box LotW_CSV_fetch_push_button + LotW_days_since_upload_spin_box sbNtrials sbAggressive cbTwoPass @@ -3118,11 +3147,11 @@ Right click for insert and delete options. rbHound rbNA_VHF_Contest rbField_Day - Field_Day_Exchange rbEU_VHF_Contest rbRTTY_Roundup - RTTY_Exchange rbWW_DIGI + Field_Day_Exchange + RTTY_Exchange @@ -3192,13 +3221,13 @@ Right click for insert and delete options. - - - - - - + + + + + + diff --git a/Network/MessageClient.cpp b/Network/MessageClient.cpp index 3c208fd3f..57ffb2806 100644 --- a/Network/MessageClient.cpp +++ b/Network/MessageClient.cpp @@ -6,16 +6,16 @@ #include #include +#include #include #include #include #include -#include #include #include #include "NetworkMessage.hpp" - +#include "qt_helpers.hpp" #include "pimpl_impl.hpp" #include "moc_MessageClient.cpp" @@ -34,14 +34,15 @@ class MessageClient::impl public: impl (QString const& id, QString const& version, QString const& revision, - port_type server_port, MessageClient * self) + port_type server_port, int TTL, MessageClient * self) : self_ {self} - , dns_lookup_id_ {0} , enabled_ {false} , id_ {id} , version_ {version} , revision_ {revision} + , dns_lookup_id_ {-1} , server_port_ {server_port} + , TTL_ {TTL} , schema_ {2} // use 2 prior to negotiation not 1 which is broken , heartbeat_timer_ {new QTimer {this}} { @@ -49,9 +50,6 @@ public: connect (this, &QIODevice::readyRead, this, &impl::pending_datagrams); heartbeat_timer_->start (NetworkMessage::pulse * 1000); - - // bind to an ephemeral port - bind (); } ~impl () @@ -61,7 +59,10 @@ public: enum StreamStatus {Fail, Short, OK}; - void parse_message (QByteArray const& msg); + void set_server (QString const& server_name, QString const& network_interface_name); + Q_SLOT void host_info_results (QHostInfo); + void start (); + void parse_message (QByteArray const&); void pending_datagrams (); void heartbeat (); void closedown (); @@ -69,27 +70,26 @@ public: void send_message (QByteArray const&); void send_message (QDataStream const& out, QByteArray const& message) { - if (OK == check_status (out)) - { - send_message (message); - } - else - { - Q_EMIT self_->error ("Error creating UDP message"); - } + if (OK == check_status (out)) + { + send_message (message); + } + else + { + Q_EMIT self_->error ("Error creating UDP message"); + } } - Q_SLOT void host_info_results (QHostInfo); - MessageClient * self_; - int dns_lookup_id_; bool enabled_; QString id_; QString version_; QString revision_; - QString server_string_; - port_type server_port_; + int dns_lookup_id_; QHostAddress server_; + port_type server_port_; + int TTL_; + QNetworkInterface network_interface_; quint32 schema_; QTimer * heartbeat_timer_; std::vector blocked_addresses_; @@ -101,37 +101,117 @@ public: #include "MessageClient.moc" +void MessageClient::impl::set_server (QString const& server_name, QString const& network_interface_name) +{ + server_.setAddress (server_name); + network_interface_ = QNetworkInterface::interfaceFromName (network_interface_name); + if (server_.isNull () && server_name.size ()) // DNS lookup required + { + // queue a host address lookup +#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) + dns_lookup_id_ = QHostInfo::lookupHost (server_name, this, &MessageClient::impl::host_info_results); +#else + dns_lookup_id_ = QHostInfo::lookupHost (server_name, this, SLOT (host_info_results (QHostInfo))); +#endif + } + else + { + start (); + } +} + void MessageClient::impl::host_info_results (QHostInfo host_info) { if (host_info.lookupId () != dns_lookup_id_) return; + dns_lookup_id_ = -1; if (QHostInfo::NoError != host_info.error ()) { - Q_EMIT self_->error ("UDP server lookup failed:\n" + host_info.errorString ()); - pending_messages_.clear (); // discard + Q_EMIT self_->error ("UDP server DNS lookup failed: " + host_info.errorString ()); } - else if (host_info.addresses ().size ()) + else { - auto server = host_info.addresses ()[0]; - if (blocked_addresses_.end () == std::find (blocked_addresses_.begin (), blocked_addresses_.end (), server)) + auto const& server_addresses = host_info.addresses (); + if (server_addresses.size ()) { - server_ = server; - TRACE_UDP ("resulting server:" << server); + server_ = server_addresses[0]; + } + } + start (); +} - // send initial heartbeat which allows schema negotiation - heartbeat (); +void MessageClient::impl::start () +{ + if (server_.isNull ()) + { + Q_EMIT self_->close (); + pending_messages_.clear (); // discard + return; + } - // clear any backlog - while (pending_messages_.size ()) + if (blocked_addresses_.end () != std::find (blocked_addresses_.begin (), blocked_addresses_.end (), server_)) + { + Q_EMIT self_->error ("UDP server blocked, please try another"); + pending_messages_.clear (); // discard + return; + } + + TRACE_UDP ("Trying server:" << server_.toString () << "on interface:" << network_interface_.humanReadableName ()); + QHostAddress interface_ip {QHostAddress::Any}; + if (network_interface_.isValid ()) + { + if (is_multicast_address (server_) && !(network_interface_.flags () & QNetworkInterface::CanMulticast)) + { + Q_EMIT self_->error ("Network interface is not multicast capable, please try another"); + return; + } + for (auto const& ae : network_interface_.addressEntries ()) + { + auto const& ip = ae.ip (); + if (server_.protocol () == ip.protocol ()) { - send_message (pending_messages_.dequeue ()); + interface_ip = ip; + break; } } - else + if (QHostAddress {QHostAddress::Any} == interface_ip) { - Q_EMIT self_->error ("UDP server blocked, please try another"); + Q_EMIT self_->error ("Network interface has no suitable address for server IP protocol, please try another"); pending_messages_.clear (); // discard + return; } } + + if (server_.isBroadcast ()) + { + // only allow broadcast on the loopback interface to avoid + // flooding the local subnet which may be large with some ISPs + //interface_ip.setAddress ("127.0.0.1"); + } + + if (localAddress () != interface_ip) + { + if (UnconnectedState != state () || state ()) + { + close (); + } + // bind to an ephemeral port on the selected interface and set + // up for sending datagrams + bind (interface_ip); + setMulticastInterface (network_interface_); + + // set multicast TTL to limit scope when sending to multicast + // group addresses + setSocketOption (MulticastTtlOption, TTL_); + } + + // send initial heartbeat which allows schema negotiation + heartbeat (); + + // clear any backlog + while (pending_messages_.size ()) + { + send_message (pending_messages_.dequeue ()); + } } void MessageClient::impl::pending_datagrams () @@ -428,9 +508,11 @@ auto MessageClient::impl::check_status (QDataStream const& stream) const -> Stre } MessageClient::MessageClient (QString const& id, QString const& version, QString const& revision, - QString const& server, port_type server_port, QObject * self) + QString const& server_name, port_type server_port, + QString const& network_interface_name, + int TTL, QObject * self) : QObject {self} - , m_ {id, version, revision, server_port, this} + , m_ {id, version, revision, server_port, TTL, this} { connect (&*m_ #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) @@ -449,8 +531,8 @@ MessageClient::MessageClient (QString const& id, QString const& version, QString #endif Q_EMIT error (m_->errorString ()); } - }); - set_server (server); + }); + m_->set_server (server_name, network_interface_name); } QHostAddress MessageClient::server_address () const @@ -463,20 +545,9 @@ auto MessageClient::server_port () const -> port_type return m_->server_port_; } -void MessageClient::set_server (QString const& server) +void MessageClient::set_server (QString const& server_name, QString const& network_interface_name) { - m_->server_.clear (); - m_->server_string_ = server; - if (server.size ()) - { - // queue a host address lookup - TRACE_UDP ("server host DNS lookup:" << server); -#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) - m_->dns_lookup_id_ = QHostInfo::lookupHost (server, &*m_, &MessageClient::impl::host_info_results); -#else - m_->dns_lookup_id_ = QHostInfo::lookupHost (server, &*m_, SLOT (host_info_results (QHostInfo))); -#endif - } + m_->set_server (server_name, network_interface_name); } void MessageClient::set_server_port (port_type server_port) @@ -484,6 +555,12 @@ void MessageClient::set_server_port (port_type server_port) m_->server_port_ = server_port; } +void MessageClient::set_TTL (int TTL) +{ + m_->TTL_ = TTL; + m_->setSocketOption (QAbstractSocket::MulticastTtlOption, m_->TTL_); +} + void MessageClient::enable (bool flag) { m_->enabled_ = flag; @@ -499,7 +576,7 @@ void MessageClient::status_update (Frequency f, QString const& mode, QString con , quint32 frequency_tolerance, quint32 tr_period , QString const& configuration_name) { - if (m_->server_port_ && !m_->server_string_.isEmpty ()) + if (m_->server_port_ && !m_->server_.isNull ()) { QByteArray message; NetworkMessage::Builder out {&message, NetworkMessage::Status, m_->id_, m_->schema_}; @@ -516,7 +593,7 @@ void MessageClient::decode (bool is_new, QTime time, qint32 snr, float delta_tim , QString const& mode, QString const& message_text, bool low_confidence , bool off_air) { - if (m_->server_port_ && !m_->server_string_.isEmpty ()) + if (m_->server_port_ && !m_->server_.isNull ()) { QByteArray message; NetworkMessage::Builder out {&message, NetworkMessage::Decode, m_->id_, m_->schema_}; @@ -531,7 +608,7 @@ void MessageClient::WSPR_decode (bool is_new, QTime time, qint32 snr, float delt , qint32 drift, QString const& callsign, QString const& grid, qint32 power , bool off_air) { - if (m_->server_port_ && !m_->server_string_.isEmpty ()) + if (m_->server_port_ && !m_->server_.isNull ()) { QByteArray message; NetworkMessage::Builder out {&message, NetworkMessage::WSPRDecode, m_->id_, m_->schema_}; @@ -544,7 +621,7 @@ void MessageClient::WSPR_decode (bool is_new, QTime time, qint32 snr, float delt void MessageClient::decodes_cleared () { - if (m_->server_port_ && !m_->server_string_.isEmpty ()) + if (m_->server_port_ && !m_->server_.isNull ()) { QByteArray message; NetworkMessage::Builder out {&message, NetworkMessage::Clear, m_->id_, m_->schema_}; @@ -561,7 +638,7 @@ void MessageClient::qso_logged (QDateTime time_off, QString const& dx_call, QStr , QString const& my_grid, QString const& exchange_sent , QString const& exchange_rcvd, QString const& propmode) { - if (m_->server_port_ && !m_->server_string_.isEmpty ()) + if (m_->server_port_ && !m_->server_.isNull ()) { QByteArray message; NetworkMessage::Builder out {&message, NetworkMessage::QSOLogged, m_->id_, m_->schema_}; @@ -576,7 +653,7 @@ void MessageClient::qso_logged (QDateTime time_off, QString const& dx_call, QStr void MessageClient::logged_ADIF (QByteArray const& ADIF_record) { - if (m_->server_port_ && !m_->server_string_.isEmpty ()) + if (m_->server_port_ && !m_->server_.isNull ()) { QByteArray message; NetworkMessage::Builder out {&message, NetworkMessage::LoggedADIF, m_->id_, m_->schema_}; diff --git a/Network/MessageClient.hpp b/Network/MessageClient.hpp index afe361fab..d0066dbe4 100644 --- a/Network/MessageClient.hpp +++ b/Network/MessageClient.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "Radio.hpp" #include "pimpl_h.hpp" @@ -34,19 +35,25 @@ public: // // messages will be silently dropped until a server host lookup is complete MessageClient (QString const& id, QString const& version, QString const& revision, - QString const& server, port_type server_port, QObject * parent = nullptr); + QString const& server_name, port_type server_port, + QString const& network_interface_name, + int TTL, QObject * parent = nullptr); // query server details QHostAddress server_address () const; port_type server_port () const; - // initiate a new server host lookup or is the server name is empty - // the sending of messages is disabled - Q_SLOT void set_server (QString const& server = QString {}); + // initiate a new server host lookup or if the server name is empty + // the sending of messages is disabled, if an interface is specified + // then that interface is used for outgoing datagrams + Q_SLOT void set_server (QString const& server_name, QString const& network_interface_name); // change the server port messages are sent to Q_SLOT void set_server_port (port_type server_port = 0u); + // change the server port messages are sent to + Q_SLOT void set_TTL (int TTL); + // enable incoming messages Q_SLOT void enable (bool); diff --git a/UDPExamples/MessageServer.cpp b/UDPExamples/MessageServer.cpp index 42a9689ef..fc36aad9d 100644 --- a/UDPExamples/MessageServer.cpp +++ b/UDPExamples/MessageServer.cpp @@ -32,7 +32,6 @@ public: : self_ {self} , version_ {version} , revision_ {revision} - , port_ {0u} , clock_ {new QTimer {this}} { // register the required types with Qt @@ -78,8 +77,8 @@ public: MessageServer * self_; QString version_; QString revision_; - port_type port_; QHostAddress multicast_group_address_; + QStringList network_interfaces_; static BindMode constexpr bind_mode_ = ShareAddress | ReuseAddressHint; struct Client { @@ -109,56 +108,39 @@ MessageServer::impl::BindMode constexpr MessageServer::impl::bind_mode_; void MessageServer::impl::leave_multicast_group () { - if (!multicast_group_address_.isNull () && BoundState == state () -#if QT_VERSION >= 0x050600 - && multicast_group_address_.isMulticast () -#endif - ) + if (BoundState == state () && is_multicast_address (multicast_group_address_)) { - for (auto const& interface : QNetworkInterface::allInterfaces ()) + for (auto const& if_name : network_interfaces_) { - if (QNetworkInterface::CanMulticast & interface.flags ()) - { - leaveMulticastGroup (multicast_group_address_, interface); - } + leaveMulticastGroup (multicast_group_address_, QNetworkInterface::interfaceFromName (if_name)); } } } void MessageServer::impl::join_multicast_group () { - if (BoundState == state () - && !multicast_group_address_.isNull () -#if QT_VERSION >= 0x050600 - && multicast_group_address_.isMulticast () -#endif - ) + if (BoundState == state () && is_multicast_address (multicast_group_address_)) { - auto mcast_iface = multicastInterface (); - if (IPv4Protocol == multicast_group_address_.protocol () - && IPv4Protocol != localAddress ().protocol ()) + if (network_interfaces_.size ()) { - close (); - bind (QHostAddress::AnyIPv4, port_, bind_mode_); - } - bool joined {false}; - for (auto const& interface : QNetworkInterface::allInterfaces ()) - { - if (QNetworkInterface::CanMulticast & interface.flags ()) + for (auto const& if_name : network_interfaces_) { - // Windows requires outgoing interface to match - // interface to be joined while joining, at least for - // IPv4 it seems to - setMulticastInterface (interface); - - joined |= joinMulticastGroup (multicast_group_address_, interface); + joinMulticastGroup (multicast_group_address_, QNetworkInterface::interfaceFromName (if_name)); } } - if (!joined) + else { - multicast_group_address_.clear (); + // find the loop-back interface and join on that + for (auto const& net_if : QNetworkInterface::allInterfaces ()) + { + auto flags = QNetworkInterface::IsUp | QNetworkInterface::IsLoopBack | QNetworkInterface::CanMulticast; + if ((net_if.flags () & flags) == flags) + { + joinMulticastGroup (multicast_group_address_, net_if); + break; + } + } } - setMulticastInterface (mcast_iface); } } @@ -448,27 +430,34 @@ MessageServer::MessageServer (QObject * parent, QString const& version, QString { } -void MessageServer::start (port_type port, QHostAddress const& multicast_group_address) +void MessageServer::start (port_type port, QHostAddress const& multicast_group_address + , QStringList const& network_interface_names) { - if (port != m_->port_ - || multicast_group_address != m_->multicast_group_address_) + if (port != m_->localPort () || multicast_group_address != m_->multicast_group_address_) { m_->leave_multicast_group (); - if (impl::BoundState == m_->state ()) + if (impl::UnconnectedState != m_->state ()) { m_->close (); } - m_->multicast_group_address_ = multicast_group_address; - auto address = m_->multicast_group_address_.isNull () - || impl::IPv4Protocol != m_->multicast_group_address_.protocol () ? QHostAddress::Any : QHostAddress::AnyIPv4; - if (port && m_->bind (address, port, m_->bind_mode_)) + if (!(multicast_group_address.isNull () || is_multicast_address (multicast_group_address))) { - m_->port_ = port; - m_->join_multicast_group (); + Q_EMIT error ("Invalid multicast group address"); + } + else if (is_MAC_ambiguous_multicast_address (multicast_group_address)) + { + Q_EMIT error ("MAC-ambiguous IPv4 multicast group address not supported"); } else { - m_->port_ = 0; + m_->multicast_group_address_ = multicast_group_address; + m_->network_interfaces_ = network_interface_names; + QHostAddress local_addr {is_multicast_address (multicast_group_address) + && impl::IPv4Protocol == multicast_group_address.protocol () ? QHostAddress::AnyIPv4 : QHostAddress::Any}; + if (port && m_->bind (local_addr, port, m_->bind_mode_)) + { + m_->join_multicast_group (); + } } } } diff --git a/UDPExamples/MessageServer.hpp b/UDPExamples/MessageServer.hpp index 184410117..449c70f4b 100644 --- a/UDPExamples/MessageServer.hpp +++ b/UDPExamples/MessageServer.hpp @@ -2,6 +2,8 @@ #define MESSAGE_SERVER_HPP__ #include +#include +#include #include #include #include @@ -38,8 +40,9 @@ public: // start or restart the server, if the multicast_group_address // argument is given it is assumed to be a multicast group address // which the server will join - Q_SLOT void start (port_type port, - QHostAddress const& multicast_group_address = QHostAddress {}); + Q_SLOT void start (port_type port + , QHostAddress const& multicast_group_address = QHostAddress {} + , QStringList const& network_interface_names = QStringList {}); // ask the client to clear one or both of the decode windows Q_SLOT void clear_decodes (QString const& id, quint8 window = 0); diff --git a/UDPExamples/UDPDaemon.cpp b/UDPExamples/UDPDaemon.cpp index be1b5edeb..2668a6021 100644 --- a/UDPExamples/UDPDaemon.cpp +++ b/UDPExamples/UDPDaemon.cpp @@ -20,6 +20,9 @@ #include #include +#include +#include +#include #include #include #include @@ -144,7 +147,7 @@ class Server Q_OBJECT public: - Server (port_type port, QHostAddress const& multicast_group) + Server (port_type port, QHostAddress const& multicast_group, QStringList const& network_interface_names) : server_ {new MessageServer {this}} { // connect up server @@ -154,7 +157,7 @@ public: connect (server_, &MessageServer::client_opened, this, &Server::add_client); connect (server_, &MessageServer::client_closed, this, &Server::remove_client); - server_->start (port, multicast_group); + server_->start (port, multicast_group, network_interface_names); } private: @@ -232,9 +235,19 @@ int main (int argc, char * argv[]) app.translate ("UDPDaemon", "GROUP")); parser.addOption (multicast_addr_option); + QCommandLineOption network_interface_option (QStringList {"i", "network-interface"}, + app.translate ("UDPDaemon", + "Where is the network interface name to join on.\n" + "This option can be passed more than once to specify multiple network interfaces\n" + "The default is use just the loop back interface."), + app.translate ("UDPDaemon", "INTERFACE")); + parser.addOption (network_interface_option); + parser.process (app); - Server server {static_cast (parser.value (port_option).toUInt ()), QHostAddress {parser.value (multicast_addr_option)}}; + Server server {static_cast (parser.value (port_option).toUInt ()) + , QHostAddress {parser.value (multicast_addr_option).trimmed ()} + , parser.values (network_interface_option)}; return app.exec (); } diff --git a/qt_helpers.hpp b/qt_helpers.hpp index aae8c5f07..7170bda0b 100644 --- a/qt_helpers.hpp +++ b/qt_helpers.hpp @@ -117,6 +117,32 @@ namespace std } #endif + +inline +bool is_multicast_address (QHostAddress const& host_addr) +{ +#if QT_VERSION >= 0x050600 + return host_addr.isMulticast (); +#else + bool ok; + return (((host_addr.toIPv4Address (&ok) & 0xf0000000u) == 0xe0000000u) && ok) + || host_addr.toIPv6Address ()[0] == 0xff; +#endif +} + +inline +bool is_MAC_ambiguous_multicast_address (QHostAddress const& host_addr) +{ + // sub-ranges 224.128.0.0/24, 225.0.0.0/24, 225.128.0.0/24, + // 226.0.0.0/24, 226.128.0.0/24, ..., 239.0.0.0/24, 239.128.0.0/24 + // are not supported as they are inefficient due to ambiguous + // mappings to Ethernet MAC addresses. 224.0.0.0/24 alone is allowed + // from these ranges + bool ok; + auto ipv4 = host_addr.toIPv4Address (&ok); + return ok && !((ipv4 & 0xffffff00u) == 0xe0000000) && (ipv4 & 0xf07fff00) == 0xe0000000; +} + // Register some useful Qt types with QMetaType Q_DECLARE_METATYPE (QHostAddress); diff --git a/tests/test_qt_helpers.cpp b/tests/test_qt_helpers.cpp index cb6744df2..195c16e0d 100644 --- a/tests/test_qt_helpers.cpp +++ b/tests/test_qt_helpers.cpp @@ -131,6 +131,80 @@ private: QDateTime dt {QDate {2020, 8, 6}, QTime {14, 15, 22, 501}}; QCOMPARE (qt_truncate_date_time_to (dt, 7500), QDateTime (QDate (2020, 8, 6), QTime (14, 15, 22, 500))); } + + Q_SLOT void is_multicast_address_data () + { + QTest::addColumn ("addr"); + QTest::addColumn ("result"); + + QTest::newRow ("loopback") << "127.0.0.1" << false; + QTest::newRow ("looback IPv6") << "::1" << false; + QTest::newRow ("lowest-") << "223.255.255.255" << false; + QTest::newRow ("lowest") << "224.0.0.0" << true; + QTest::newRow ("lowest- IPv6") << "feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << false; + QTest::newRow ("lowest IPv6") << "ff00::" << true; + QTest::newRow ("highest") << "239.255.255.255" << true; + QTest::newRow ("highest+") << "240.0.0.0" << false; + QTest::newRow ("highest IPv6") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << true; + } + + Q_SLOT void is_multicast_address () + { + QFETCH (QString, addr); + QFETCH (bool, result); + + QCOMPARE (::is_multicast_address (QHostAddress {addr}), result); + } + + Q_SLOT void is_MAC_ambiguous_multicast_address_data () + { + QTest::addColumn ("addr"); + QTest::addColumn ("result"); + + QTest::newRow ("loopback") << "127.0.0.1" << false; + QTest::newRow ("looback IPv6") << "::1" << false; + + QTest::newRow ("lowest- R1") << "223.255.255.255" << false; + QTest::newRow ("lowest R1") << "224.0.0.0" << false; + QTest::newRow ("highest R1") << "224.0.0.255" << false; + QTest::newRow ("highest+ R1") << "224.0.1.0" << false; + QTest::newRow ("lowest- R1A") << "224.127.255.255" << false; + QTest::newRow ("lowest R1A") << "224.128.0.0" << true; + QTest::newRow ("highest R1A") << "224.128.0.255" << true; + QTest::newRow ("highest+ R1A") << "224.128.1.0" << false; + + QTest::newRow ("lowest- R2") << "224.255.255.255" << false; + QTest::newRow ("lowest R2") << "225.0.0.0" << true; + QTest::newRow ("highest R2") << "225.0.0.255" << true; + QTest::newRow ("highest+ R2") << "225.0.1.0" << false; + QTest::newRow ("lowest- R2A") << "225.127.255.255" << false; + QTest::newRow ("lowest R2A") << "225.128.0.0" << true; + QTest::newRow ("highest R2A") << "225.128.0.255" << true; + QTest::newRow ("highest+ R2A") << "225.128.1.0" << false; + + QTest::newRow ("lowest- R3") << "238.255.255.255" << false; + QTest::newRow ("lowest R3") << "239.0.0.0" << true; + QTest::newRow ("highest R3") << "239.0.0.255" << true; + QTest::newRow ("highest+ R3") << "239.0.1.0" << false; + QTest::newRow ("lowest- R3A") << "239.127.255.255" << false; + QTest::newRow ("lowest R3A") << "239.128.0.0" << true; + QTest::newRow ("highest R3A") << "239.128.0.255" << true; + QTest::newRow ("highest+ R3A") << "239.128.1.0" << false; + + QTest::newRow ("lowest- IPv6") << "feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << false; + QTest::newRow ("lowest IPv6") << "ff00::" << false; + QTest::newRow ("highest") << "239.255.255.255" << false; + QTest::newRow ("highest+") << "240.0.0.0" << false; + QTest::newRow ("highest IPv6") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << false; + } + + Q_SLOT void is_MAC_ambiguous_multicast_address () + { + QFETCH (QString, addr); + QFETCH (bool, result); + + QCOMPARE (::is_MAC_ambiguous_multicast_address (QHostAddress {addr}), result); + } }; QTEST_MAIN (TestQtHelpers); diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index be5062fa0..e40009cb4 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -417,6 +417,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, m_messageClient {new MessageClient {QApplication::applicationName (), version (), revision (), m_config.udp_server_name (), m_config.udp_server_port (), + m_config.udp_interface_name (), m_config.udp_TTL (), this}}, m_psk_Reporter {&m_config, QString {"WSJT-X v" + version () + " " + m_revision}.simplified ()}, m_manual {&m_network_manager}, @@ -785,6 +786,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, connect (&m_config, &Configuration::transceiver_failure, this, &MainWindow::handle_transceiver_failure); connect (&m_config, &Configuration::udp_server_changed, m_messageClient, &MessageClient::set_server); connect (&m_config, &Configuration::udp_server_port_changed, m_messageClient, &MessageClient::set_server_port); + connect (&m_config, &Configuration::udp_TTL_changed, m_messageClient, &MessageClient::set_TTL); connect (&m_config, &Configuration::accept_udp_requests_changed, m_messageClient, &MessageClient::enable); connect (&m_config, &Configuration::enumerating_audio_devices, [this] () { showStatusMessage (tr ("Enumerating audio devices")); @@ -7835,7 +7837,7 @@ void MainWindow::networkError (QString const& e) , MessageBox::Cancel)) { // retry server lookup - m_messageClient->set_server (m_config.udp_server_name ()); + m_messageClient->set_server (m_config.udp_server_name (), m_config.udp_interface_name ()); } } From 7b54428a60c9956ab9c7a77d3ff22ea693296a47 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Mon, 2 Nov 2020 21:35:48 +0000 Subject: [PATCH 02/16] Extend UDP MessageServer client mapping key with the host address The UDP Example reference applications now work correctly with WSJT-X instances with duplicate --rig-name= command line arguments so long as duplicate instances are run on unique hosts. --- UDPExamples/BeaconsModel.cpp | 19 +-- UDPExamples/BeaconsModel.hpp | 6 +- UDPExamples/ClientWidget.cpp | 78 ++++++------- UDPExamples/ClientWidget.hpp | 34 +++--- UDPExamples/DecodesModel.cpp | 26 +++-- UDPExamples/DecodesModel.hpp | 10 +- UDPExamples/MessageAggregatorMainWindow.cpp | 28 +++-- UDPExamples/MessageAggregatorMainWindow.hpp | 10 +- UDPExamples/MessageServer.cpp | 122 ++++++++++---------- UDPExamples/MessageServer.hpp | 63 +++++----- UDPExamples/UDPDaemon.cpp | 72 +++++++----- 11 files changed, 251 insertions(+), 217 deletions(-) diff --git a/UDPExamples/BeaconsModel.cpp b/UDPExamples/BeaconsModel.cpp index 7ac1323a1..8a6cdd279 100644 --- a/UDPExamples/BeaconsModel.cpp +++ b/UDPExamples/BeaconsModel.cpp @@ -25,10 +25,13 @@ namespace QFont text_font {"Courier", 10}; - QList make_row (QString const& client_id, QTime time, qint32 snr, float delta_time + QList make_row (MessageServer::ClientKey const& key, QTime time, qint32 snr, float delta_time , Frequency frequency, qint32 drift, QString const& callsign , QString const& grid, qint32 power, bool off_air) { + auto client_item = new QStandardItem {QString {"%1(%2)"}.arg (key.second).arg (key.first.toString ())}; + client_item->setData (QVariant::fromValue (key)); + auto time_item = new QStandardItem {time.toString ("hh:mm")}; time_item->setData (time); time_item->setTextAlignment (Qt::AlignRight); @@ -60,7 +63,7 @@ namespace live->setTextAlignment (Qt::AlignHCenter); QList row { - new QStandardItem {client_id}, time_item, snr_item, dt, freq, dri, gd, pwr, live, new QStandardItem {callsign}}; + client_item, time_item, snr_item, dt, freq, dri, gd, pwr, live, new QStandardItem {callsign}}; Q_FOREACH (auto& item, row) { item->setEditable (false); @@ -81,7 +84,7 @@ BeaconsModel::BeaconsModel (QObject * parent) } } -void BeaconsModel::add_beacon_spot (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time +void BeaconsModel::add_beacon_spot (bool is_new, ClientKey const& key, QTime time, qint32 snr, float delta_time , Frequency frequency, qint32 drift, QString const& callsign , QString const& grid, qint32 power, bool off_air) { @@ -90,7 +93,7 @@ void BeaconsModel::add_beacon_spot (bool is_new, QString const& client_id, QTime int target_row {-1}; for (auto row = 0; row < rowCount (); ++row) { - if (data (index (row, 0)).toString () == client_id) + if (item (row, 0)->data ().value () == key) { auto row_time = item (row, 1)->data ().toTime (); if (row_time == time @@ -113,19 +116,19 @@ void BeaconsModel::add_beacon_spot (bool is_new, QString const& client_id, QTime } if (target_row >= 0) { - insertRow (target_row + 1, make_row (client_id, time, snr, delta_time, frequency, drift, callsign, grid, power, off_air)); + insertRow (target_row + 1, make_row (key, time, snr, delta_time, frequency, drift, callsign, grid, power, off_air)); return; } } - appendRow (make_row (client_id, time, snr, delta_time, frequency, drift, callsign, grid, power, off_air)); + appendRow (make_row (key, time, snr, delta_time, frequency, drift, callsign, grid, power, off_air)); } -void BeaconsModel::decodes_cleared (QString const& client_id) +void BeaconsModel::decodes_cleared (ClientKey const& key) { for (auto row = rowCount () - 1; row >= 0; --row) { - if (data (index (row, 0)).toString () == client_id) + if (item (row, 0)->data ().value () == key) { removeRow (row); } diff --git a/UDPExamples/BeaconsModel.hpp b/UDPExamples/BeaconsModel.hpp index b089349cc..2baa99ec3 100644 --- a/UDPExamples/BeaconsModel.hpp +++ b/UDPExamples/BeaconsModel.hpp @@ -26,13 +26,15 @@ class BeaconsModel { Q_OBJECT; + using ClientKey = MessageServer::ClientKey; + public: explicit BeaconsModel (QObject * parent = nullptr); - Q_SLOT void add_beacon_spot (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time + Q_SLOT void add_beacon_spot (bool is_new, ClientKey const&, QTime time, qint32 snr, float delta_time , Frequency frequency, qint32 drift, QString const& callsign, QString const& grid , qint32 power, bool off_air); - Q_SLOT void decodes_cleared (QString const& client_id); + Q_SLOT void decodes_cleared (ClientKey const&); }; #endif diff --git a/UDPExamples/ClientWidget.cpp b/UDPExamples/ClientWidget.cpp index b2ee03609..3be8c788c 100644 --- a/UDPExamples/ClientWidget.cpp +++ b/UDPExamples/ClientWidget.cpp @@ -27,9 +27,9 @@ namespace } } -ClientWidget::IdFilterModel::IdFilterModel (QString const& client_id, QObject * parent) +ClientWidget::IdFilterModel::IdFilterModel (ClientKey const& key, QObject * parent) : QSortFilterProxyModel {parent} - , client_id_ {client_id} + , key_ {key} , rx_df_ (quint32_max) { } @@ -73,7 +73,7 @@ bool ClientWidget::IdFilterModel::filterAcceptsRow (int source_row , QModelIndex const& source_parent) const { auto source_index_col0 = sourceModel ()->index (source_row, 0, source_parent); - return sourceModel ()->data (source_index_col0).toString () == client_id_; + return sourceModel ()->data (source_index_col0).value () == key_; } void ClientWidget::IdFilterModel::de_call (QString const& call) @@ -106,9 +106,9 @@ void ClientWidget::IdFilterModel::rx_df (quint32 df) namespace { - QString make_title (QString const& id, QString const& version, QString const& revision) + QString make_title (MessageServer::ClientKey const& key, QString const& version, QString const& revision) { - QString title {id}; + QString title {QString {"%1(%2)"}.arg (key.second).arg (key.first.toString ())}; if (version.size ()) { title += QString {" v%1"}.arg (version); @@ -122,14 +122,14 @@ namespace } ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model - , QString const& id, QString const& version, QString const& revision + , ClientKey const& key, QString const& version, QString const& revision , QListWidget const * calls_of_interest, QWidget * parent) - : QDockWidget {make_title (id, version, revision), parent} - , id_ {id} + : QDockWidget {make_title (key, version, revision), parent} + , key_ {key} , done_ {false} , calls_of_interest_ {calls_of_interest} - , decodes_proxy_model_ {id} - , beacons_proxy_model_ {id} + , decodes_proxy_model_ {key} + , beacons_proxy_model_ {key} , erase_action_ {new QAction {tr ("&Erase Band Activity"), this}} , erase_rx_frequency_action_ {new QAction {tr ("Erase &Rx Frequency"), this}} , erase_both_action_ {new QAction {tr ("Erase &Both"), this}} @@ -209,56 +209,56 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod horizontal_layout_->addLayout (subform3_layout_); connect (message_line_edit_, &QLineEdit::textEdited, [this] (QString const& text) { - Q_EMIT do_free_text (id_, text, false); + Q_EMIT do_free_text (key_, text, false); }); connect (message_line_edit_, &QLineEdit::editingFinished, [this] () { - Q_EMIT do_free_text (id_, message_line_edit_->text (), true); + Q_EMIT do_free_text (key_, message_line_edit_->text (), true); }); connect (grid_line_edit_, &QLineEdit::editingFinished, [this] () { - Q_EMIT location (id_, grid_line_edit_->text ()); + Q_EMIT location (key_, grid_line_edit_->text ()); }); connect (configuration_line_edit_, &QLineEdit::editingFinished, [this] () { - Q_EMIT switch_configuration (id_, configuration_line_edit_->text ()); + Q_EMIT switch_configuration (key_, configuration_line_edit_->text ()); }); connect (mode_line_edit_, &QLineEdit::editingFinished, [this] () { QString empty; - Q_EMIT configure (id_, mode_line_edit_->text (), quint32_max, empty, fast_mode () + Q_EMIT configure (key_, mode_line_edit_->text (), quint32_max, empty, fast_mode () , quint32_max, quint32_max, empty, empty, false); }); connect (frequency_tolerance_spin_box_, static_cast (&QSpinBox::valueChanged), [this] (int i) { QString empty; auto f = frequency_tolerance_spin_box_->specialValueText ().size () ? quint32_max : i; - Q_EMIT configure (id_, empty, f, empty, fast_mode () + Q_EMIT configure (key_, empty, f, empty, fast_mode () , quint32_max, quint32_max, empty, empty, false); }); connect (submode_line_edit_, &QLineEdit::editingFinished, [this] () { QString empty; - Q_EMIT configure (id_, empty, quint32_max, submode_line_edit_->text (), fast_mode () + Q_EMIT configure (key_, empty, quint32_max, submode_line_edit_->text (), fast_mode () , quint32_max, quint32_max, empty, empty, false); }); connect (fast_mode_check_box_, &QCheckBox::stateChanged, [this] (int state) { QString empty; - Q_EMIT configure (id_, empty, quint32_max, empty, Qt::Checked == state + Q_EMIT configure (key_, empty, quint32_max, empty, Qt::Checked == state , quint32_max, quint32_max, empty, empty, false); }); connect (tr_period_spin_box_, static_cast (&QSpinBox::valueChanged), [this] (int i) { QString empty; - Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode () + Q_EMIT configure (key_, empty, quint32_max, empty, fast_mode () , i, quint32_max, empty, empty, false); }); connect (rx_df_spin_box_, static_cast (&QSpinBox::valueChanged), [this] (int i) { QString empty; - Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode () + Q_EMIT configure (key_, empty, quint32_max, empty, fast_mode () , quint32_max, i, empty, empty, false); }); connect (dx_call_line_edit_, &QLineEdit::editingFinished, [this] () { QString empty; - Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode () + Q_EMIT configure (key_, empty, quint32_max, empty, fast_mode () , quint32_max, quint32_max, dx_call_line_edit_->text (), empty, false); }); connect (dx_grid_line_edit_, &QLineEdit::editingFinished, [this] () { QString empty; - Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode () + Q_EMIT configure (key_, empty, quint32_max, empty, fast_mode () , quint32_max, quint32_max, empty, dx_grid_line_edit_->text (), false); }); @@ -289,14 +289,14 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod halt_tx_button_ = control_button_box_->addButton (tr ("&Halt Tx"), QDialogButtonBox::ActionRole); connect (generate_messages_push_button_, &QAbstractButton::clicked, [this] (bool /*checked*/) { QString empty; - Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode () + Q_EMIT configure (key_, empty, quint32_max, empty, fast_mode () , quint32_max, quint32_max, empty, empty, true); }); connect (auto_off_button_, &QAbstractButton::clicked, [this] (bool /* checked */) { - Q_EMIT do_halt_tx (id_, true); + Q_EMIT do_halt_tx (key_, true); }); connect (halt_tx_button_, &QAbstractButton::clicked, [this] (bool /* checked */) { - Q_EMIT do_halt_tx (id_, false); + Q_EMIT do_halt_tx (key_, false); }); content_layout_->addWidget (control_button_box_); @@ -318,13 +318,13 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod // connect context menu actions connect (erase_action_, &QAction::triggered, [this] (bool /*checked*/) { - Q_EMIT do_clear_decodes (id_); + Q_EMIT do_clear_decodes (key_); }); connect (erase_rx_frequency_action_, &QAction::triggered, [this] (bool /*checked*/) { - Q_EMIT do_clear_decodes (id_, 1); + Q_EMIT do_clear_decodes (key_, 1); }); connect (erase_both_action_, &QAction::triggered, [this] (bool /*checked*/) { - Q_EMIT do_clear_decodes (id_, 2); + Q_EMIT do_clear_decodes (key_, 2); }); // connect up table view signals @@ -335,7 +335,7 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod // tell new client about calls of interest for (int row = 0; row < calls_of_interest_->count (); ++row) { - Q_EMIT highlight_callsign (id_, calls_of_interest_->item (row)->text (), QColor {Qt::blue}, QColor {Qt::yellow}); + Q_EMIT highlight_callsign (key_, calls_of_interest_->item (row)->text (), QColor {Qt::blue}, QColor {Qt::yellow}); } } @@ -349,7 +349,7 @@ void ClientWidget::closeEvent (QCloseEvent *e) { if (!done_) { - Q_EMIT do_close (id_); + Q_EMIT do_close (key_); e->ignore (); // defer closure until client actually closes } else @@ -363,7 +363,7 @@ ClientWidget::~ClientWidget () for (int row = 0; row < calls_of_interest_->count (); ++row) { // tell client to forget calls of interest - Q_EMIT highlight_callsign (id_, calls_of_interest_->item (row)->text ()); + Q_EMIT highlight_callsign (key_, calls_of_interest_->item (row)->text ()); } } @@ -395,7 +395,7 @@ namespace } } -void ClientWidget::update_status (QString const& id, Frequency f, QString const& mode, QString const& dx_call +void ClientWidget::update_status (ClientKey const& key, 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 @@ -403,7 +403,7 @@ void ClientWidget::update_status (QString const& id, Frequency f, QString const& , quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period , QString const& configuration_name) { - if (id == id_) + if (key == key_) { fast_mode_check_box_->setChecked (fast_mode); decodes_proxy_model_.de_call (de_call); @@ -447,11 +447,11 @@ void ClientWidget::update_status (QString const& id, Frequency f, QString const& } } -void ClientWidget::decode_added (bool /*is_new*/, QString const& client_id, QTime /*time*/, qint32 /*snr*/ +void ClientWidget::decode_added (bool /*is_new*/, ClientKey const& key, QTime /*time*/, qint32 /*snr*/ , float /*delta_time*/, quint32 /*delta_frequency*/, QString const& /*mode*/ , QString const& /*message*/, bool /*low_confidence*/, bool /*off_air*/) { - if (client_id == id_ && !columns_resized_) + if (key == key_ && !columns_resized_) { decodes_stack_->setCurrentIndex (0); decodes_table_view_->resizeColumnsToContents (); @@ -460,12 +460,12 @@ void ClientWidget::decode_added (bool /*is_new*/, QString const& client_id, QTim decodes_table_view_->scrollToBottom (); } -void ClientWidget::beacon_spot_added (bool /*is_new*/, QString const& client_id, QTime /*time*/, qint32 /*snr*/ +void ClientWidget::beacon_spot_added (bool /*is_new*/, ClientKey const& key, QTime /*time*/, qint32 /*snr*/ , float /*delta_time*/, Frequency /*delta_frequency*/, qint32 /*drift*/ , QString const& /*callsign*/, QString const& /*grid*/, qint32 /*power*/ , bool /*off_air*/) { - if (client_id == id_ && !columns_resized_) + if (key == key_ && !columns_resized_) { decodes_stack_->setCurrentIndex (1); beacons_table_view_->resizeColumnsToContents (); @@ -474,9 +474,9 @@ void ClientWidget::beacon_spot_added (bool /*is_new*/, QString const& client_id, beacons_table_view_->scrollToBottom (); } -void ClientWidget::decodes_cleared (QString const& client_id) +void ClientWidget::decodes_cleared (ClientKey const& key) { - if (client_id == id_) + if (key == key_) { columns_resized_ = false; } diff --git a/UDPExamples/ClientWidget.hpp b/UDPExamples/ClientWidget.hpp index 983ddd874..ee91d084a 100644 --- a/UDPExamples/ClientWidget.hpp +++ b/UDPExamples/ClientWidget.hpp @@ -35,42 +35,44 @@ class ClientWidget { Q_OBJECT; + using ClientKey = MessageServer::ClientKey; + public: explicit ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model - , QString const& id, QString const& version, QString const& revision + , ClientKey const& key, QString const& version, QString const& revision , QListWidget const * calls_of_interest, QWidget * parent = nullptr); void dispose (); ~ClientWidget (); bool fast_mode () const; - Q_SLOT void update_status (QString const& id, Frequency f, QString const& mode, QString const& dx_call + Q_SLOT void update_status (ClientKey const& key, 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); - Q_SLOT void decode_added (bool is_new, QString const& client_id, QTime, qint32 snr + Q_SLOT void decode_added (bool is_new, ClientKey const& key, QTime, qint32 snr , float delta_time, quint32 delta_frequency, QString const& mode , QString const& message, bool low_confidence, bool off_air); - Q_SLOT void beacon_spot_added (bool is_new, QString const& client_id, QTime, qint32 snr + Q_SLOT void beacon_spot_added (bool is_new, ClientKey const& key, QTime, qint32 snr , float delta_time, Frequency delta_frequency, qint32 drift , QString const& callsign, QString const& grid, qint32 power , bool off_air); - Q_SLOT void decodes_cleared (QString const& client_id); + Q_SLOT void decodes_cleared (ClientKey const& key); - Q_SIGNAL void do_clear_decodes (QString const& id, quint8 window = 0); - Q_SIGNAL void do_close (QString const& id); + Q_SIGNAL void do_clear_decodes (ClientKey const& key, quint8 window = 0); + Q_SIGNAL void do_close (ClientKey const& key); Q_SIGNAL void do_reply (QModelIndex const&, quint8 modifier); - Q_SIGNAL void do_halt_tx (QString const& id, bool auto_only); - Q_SIGNAL void do_free_text (QString const& id, QString const& text, bool); - Q_SIGNAL void location (QString const& id, QString const& text); - Q_SIGNAL void highlight_callsign (QString const& id, QString const& call + Q_SIGNAL void do_halt_tx (ClientKey const& key, bool auto_only); + Q_SIGNAL void do_free_text (ClientKey const& key, QString const& text, bool); + Q_SIGNAL void location (ClientKey const& key, QString const& text); + Q_SIGNAL void highlight_callsign (ClientKey const& key, QString const& call , QColor const& bg = QColor {}, QColor const& fg = QColor {} , bool last_only = false); - Q_SIGNAL void switch_configuration (QString const& id, QString const& configuration_name); - Q_SIGNAL void configure (QString const& id, QString const& mode, quint32 frequency_tolerance + Q_SIGNAL void switch_configuration (ClientKey const& key, QString const& configuration_name); + Q_SIGNAL void configure (ClientKey const& key, QString const& mode, quint32 frequency_tolerance , QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df , QString const& dx_call, QString const& dx_grid, bool generate_messages); @@ -79,7 +81,7 @@ private: : public QSortFilterProxyModel { public: - IdFilterModel (QString const& client_id, QObject * = nullptr); + IdFilterModel (ClientKey const& key, QObject * = nullptr); void de_call (QString const&); void rx_df (quint32); @@ -88,7 +90,7 @@ private: private: bool filterAcceptsRow (int source_row, QModelIndex const& source_parent) const override; - QString client_id_; + ClientKey key_; QString call_; QRegularExpression base_call_re_; quint32 rx_df_; @@ -96,7 +98,7 @@ private: void closeEvent (QCloseEvent *) override; - QString id_; + ClientKey key_; bool done_; QListWidget const * calls_of_interest_; IdFilterModel decodes_proxy_model_; diff --git a/UDPExamples/DecodesModel.cpp b/UDPExamples/DecodesModel.cpp index 6121ff31a..7b8d87c52 100644 --- a/UDPExamples/DecodesModel.cpp +++ b/UDPExamples/DecodesModel.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -33,10 +34,13 @@ namespace QFont text_font {"Courier", 10}; - QList make_row (QString const& client_id, QTime time, qint32 snr, float delta_time - , quint32 delta_frequency, QString const& mode, QString const& message - , bool low_confidence, bool off_air, bool is_fast) + QList make_row (MessageServer::ClientKey const& key, QTime time, qint32 snr + , float delta_time, quint32 delta_frequency, QString const& mode + , QString const& message, bool low_confidence, bool off_air, bool is_fast) { + auto client_item = new QStandardItem {QString {"%1(%2)"}.arg (key.second).arg (key.first.toString ())}; + client_item->setData (QVariant::fromValue (key)); + auto time_item = new QStandardItem {time.toString (is_fast || "~" == mode ? "hh:mm:ss" : "hh:mm")}; time_item->setData (time); time_item->setTextAlignment (Qt::AlignRight); @@ -63,7 +67,7 @@ namespace live->setTextAlignment (Qt::AlignHCenter); QList row { - new QStandardItem {client_id}, time_item, snr_item, dt, df, md, confidence, live, new QStandardItem {message}}; + client_item, time_item, snr_item, dt, df, md, confidence, live, new QStandardItem {message}}; Q_FOREACH (auto& item, row) { item->setEditable (false); @@ -84,7 +88,7 @@ DecodesModel::DecodesModel (QObject * parent) } } -void DecodesModel::add_decode (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time +void DecodesModel::add_decode (bool is_new, ClientKey const& key, QTime time, qint32 snr, float delta_time , quint32 delta_frequency, QString const& mode, QString const& message , bool low_confidence, bool off_air, bool is_fast) { @@ -93,7 +97,7 @@ void DecodesModel::add_decode (bool is_new, QString const& client_id, QTime time int target_row {-1}; for (auto row = 0; row < rowCount (); ++row) { - if (data (index (row, 0)).toString () == client_id) + if (item (row, 0)->data ().value () == key) { auto row_time = item (row, 1)->data ().toTime (); if (row_time == time @@ -115,21 +119,21 @@ void DecodesModel::add_decode (bool is_new, QString const& client_id, QTime time } if (target_row >= 0) { - insertRow (target_row + 1, make_row (client_id, time, snr, delta_time, delta_frequency, mode + insertRow (target_row + 1, make_row (key, time, snr, delta_time, delta_frequency, mode , message, low_confidence, off_air, is_fast)); return; } } - appendRow (make_row (client_id, time, snr, delta_time, delta_frequency, mode, message, low_confidence + appendRow (make_row (key, time, snr, delta_time, delta_frequency, mode, message, low_confidence , off_air, is_fast)); } -void DecodesModel::decodes_cleared (QString const& client_id) +void DecodesModel::decodes_cleared (ClientKey const& key) { for (auto row = rowCount () - 1; row >= 0; --row) { - if (data (index (row, 0)).toString () == client_id) + if (item (row, 0)->data ().value () == key) { removeRow (row); } @@ -139,7 +143,7 @@ void DecodesModel::decodes_cleared (QString const& client_id) void DecodesModel::do_reply (QModelIndex const& source, quint8 modifiers) { auto row = source.row (); - Q_EMIT reply (data (index (row, 0)).toString () + Q_EMIT reply (item (row, 0)->data ().value () , item (row, 1)->data ().toTime () , item (row, 2)->data ().toInt () , item (row, 3)->data ().toFloat () diff --git a/UDPExamples/DecodesModel.hpp b/UDPExamples/DecodesModel.hpp index 17c9ae125..27a168c87 100644 --- a/UDPExamples/DecodesModel.hpp +++ b/UDPExamples/DecodesModel.hpp @@ -5,8 +5,6 @@ #include "MessageServer.hpp" -using Frequency = MessageServer::Frequency; - class QTime; class QString; class QModelIndex; @@ -28,16 +26,18 @@ class DecodesModel { Q_OBJECT; + using ClientKey = MessageServer::ClientKey; + public: explicit DecodesModel (QObject * parent = nullptr); - Q_SLOT void add_decode (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time + Q_SLOT void add_decode (bool is_new, ClientKey const&, QTime, qint32 snr, float delta_time , quint32 delta_frequency, QString const& mode, QString const& message , bool low_confidence, bool off_air, bool is_fast); - Q_SLOT void decodes_cleared (QString const& client_id); + Q_SLOT void decodes_cleared (ClientKey const&); Q_SLOT void do_reply (QModelIndex const& source, quint8 modifiers); - Q_SIGNAL void reply (QString const& id, QTime time, qint32 snr, float delta_time, quint32 delta_frequency + Q_SIGNAL void reply (ClientKey const&, QTime, qint32 snr, float delta_time, quint32 delta_frequency , QString const& mode, QString const& message, bool low_confidence, quint8 modifiers); }; diff --git a/UDPExamples/MessageAggregatorMainWindow.cpp b/UDPExamples/MessageAggregatorMainWindow.cpp index ba034b0e2..7e74342c0 100644 --- a/UDPExamples/MessageAggregatorMainWindow.cpp +++ b/UDPExamples/MessageAggregatorMainWindow.cpp @@ -184,13 +184,16 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow () connect (server_, &MessageServer::client_closed, this, &MessageAggregatorMainWindow::remove_client); connect (server_, &MessageServer::client_closed, decodes_model_, &DecodesModel::decodes_cleared); connect (server_, &MessageServer::client_closed, beacons_model_, &BeaconsModel::decodes_cleared); - connect (server_, &MessageServer::decode, [this] (bool is_new, QString const& id, QTime time + connect (server_, &MessageServer::decode, [this] (bool is_new, ClientKey const& key, QTime time , qint32 snr, float delta_time , quint32 delta_frequency, QString const& mode , QString const& message, bool low_confidence , bool off_air) { - decodes_model_->add_decode (is_new, id, time, snr, delta_time, delta_frequency, mode, message - , low_confidence, off_air, dock_widgets_[id]->fast_mode ());}); + decodes_model_->add_decode (is_new, key, time, snr, delta_time + , delta_frequency, mode, message + , low_confidence, off_air + , dock_widgets_[key]->fast_mode ()); + }); connect (server_, &MessageServer::WSPR_decode, beacons_model_, &BeaconsModel::add_beacon_spot); connect (server_, &MessageServer::decodes_cleared, decodes_model_, &DecodesModel::decodes_cleared); connect (server_, &MessageServer::decodes_cleared, beacons_model_, &BeaconsModel::decodes_cleared); @@ -207,7 +210,8 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow () show (); } -void MessageAggregatorMainWindow::log_qso (QString const& /*id*/, QDateTime time_off, QString const& dx_call +void MessageAggregatorMainWindow::log_qso (ClientKey const& /*key*/, 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 @@ -240,9 +244,9 @@ void MessageAggregatorMainWindow::log_qso (QString const& /*id*/, QDateTime time log_table_view_->scrollToBottom (); } -void MessageAggregatorMainWindow::add_client (QString const& id, QString const& version, QString const& revision) +void MessageAggregatorMainWindow::add_client (ClientKey const& key, QString const& version, QString const& revision) { - auto dock = new ClientWidget {decodes_model_, beacons_model_, id, version, revision, calls_of_interest_, this}; + auto dock = new ClientWidget {decodes_model_, beacons_model_, key, version, revision, calls_of_interest_, this}; dock->setAttribute (Qt::WA_DeleteOnClose); auto view_action = dock->toggleViewAction (); view_action->setEnabled (true); @@ -262,13 +266,13 @@ void MessageAggregatorMainWindow::add_client (QString const& id, QString const& connect (dock, &ClientWidget::highlight_callsign, server_, &MessageServer::highlight_callsign); connect (dock, &ClientWidget::switch_configuration, server_, &MessageServer::switch_configuration); connect (dock, &ClientWidget::configure, server_, &MessageServer::configure); - dock_widgets_[id] = dock; - server_->replay (id); // request decodes and status + dock_widgets_[key] = dock; + server_->replay (key); // request decodes and status } -void MessageAggregatorMainWindow::remove_client (QString const& id) +void MessageAggregatorMainWindow::remove_client (ClientKey const& key) { - auto iter = dock_widgets_.find (id); + auto iter = dock_widgets_.find (key); if (iter != std::end (dock_widgets_)) { (*iter)->dispose (); @@ -287,9 +291,9 @@ MessageAggregatorMainWindow::~MessageAggregatorMainWindow () void MessageAggregatorMainWindow::change_highlighting (QString const& call, QColor const& bg, QColor const& fg , bool last_only) { - for (auto id : dock_widgets_.keys ()) + for (auto key : dock_widgets_.keys ()) { - server_->highlight_callsign (id, call, bg, fg, last_only); + server_->highlight_callsign (key, call, bg, fg, last_only); } } diff --git a/UDPExamples/MessageAggregatorMainWindow.hpp b/UDPExamples/MessageAggregatorMainWindow.hpp index 045f4e945..445a5908e 100644 --- a/UDPExamples/MessageAggregatorMainWindow.hpp +++ b/UDPExamples/MessageAggregatorMainWindow.hpp @@ -24,11 +24,13 @@ class MessageAggregatorMainWindow { Q_OBJECT; + using ClientKey = MessageServer::ClientKey; + public: MessageAggregatorMainWindow (); ~MessageAggregatorMainWindow (); - Q_SLOT void log_qso (QString const& /*id*/, QDateTime time_off, QString const& dx_call, QString const& dx_grid + Q_SLOT void log_qso (ClientKey const&, 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 @@ -36,13 +38,13 @@ public: , QString const& exchange_sent, QString const& exchange_rcvd, QString const& prop_mode); private: - void add_client (QString const& id, QString const& version, QString const& revision); - void remove_client (QString const& id); + void add_client (ClientKey const&, QString const& version, QString const& revision); + void remove_client (ClientKey const&); void change_highlighting (QString const& call, QColor const& bg = QColor {}, QColor const& fg = QColor {}, bool last_only = false); // maps client id to widgets - using ClientsDictionary = QHash; + using ClientsDictionary = QHash; ClientsDictionary dock_widgets_; QStandardItemModel * log_; diff --git a/UDPExamples/MessageServer.cpp b/UDPExamples/MessageServer.cpp index fc36aad9d..8869c4b89 100644 --- a/UDPExamples/MessageServer.cpp +++ b/UDPExamples/MessageServer.cpp @@ -83,9 +83,8 @@ public: struct Client { Client () = default; - Client (QHostAddress const& sender_address, port_type const& sender_port) - : sender_address_ {sender_address} - , sender_port_ {sender_port} + Client (port_type const& sender_port) + : sender_port_ {sender_port} , negotiated_schema_number_ {2} // not 1 because it's broken , last_activity_ {QDateTime::currentDateTime ()} { @@ -93,12 +92,11 @@ public: Client (Client const&) = default; Client& operator= (Client const&) = default; - QHostAddress sender_address_; port_type sender_port_; quint32 negotiated_schema_number_; QDateTime last_activity_; }; - QHash clients_; // maps id to Client + QHash clients_; // maps id to Client QTimer * clock_; }; @@ -171,9 +169,10 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s auto id = in.id (); if (OK == check_status (in)) { - if (!clients_.contains (id)) + auto client_key = ClientKey {sender, id}; + if (!clients_.contains (client_key)) { - auto& client = (clients_[id] = {sender, sender_port}); + auto& client = (clients_[client_key] = {sender_port}); QByteArray client_version; QByteArray client_revision; @@ -194,7 +193,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s << version_.toUtf8 () << revision_.toUtf8 (); if (impl::OK == check_status (hb)) { - writeDatagram (message, client.sender_address_, client.sender_port_); + writeDatagram (message, client_key.first, sender_port); } else { @@ -204,10 +203,10 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s // we don't care if this fails to read in >> client_version >> client_revision; } - Q_EMIT self_->client_opened (id, QString::fromUtf8 (client_version), + Q_EMIT self_->client_opened (client_key, QString::fromUtf8 (client_version), QString::fromUtf8 (client_revision)); } - clients_[id].last_activity_ = QDateTime::currentDateTime (); + clients_[client_key].last_activity_ = QDateTime::currentDateTime (); // // message format is described in NetworkMessage.hpp @@ -219,7 +218,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s break; case NetworkMessage::Clear: - Q_EMIT self_->decodes_cleared (id); + Q_EMIT self_->decodes_cleared (client_key); break; case NetworkMessage::Status: @@ -250,7 +249,8 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s >> fast_mode >> special_op_mode >> frequency_tolerance >> tr_period >> configuration_name; if (check_status (in) != Fail) { - Q_EMIT self_->status_update (id, f, QString::fromUtf8 (mode), QString::fromUtf8 (dx_call) + Q_EMIT self_->status_update (client_key, f, QString::fromUtf8 (mode) + , QString::fromUtf8 (dx_call) , QString::fromUtf8 (report), QString::fromUtf8 (tx_mode) , tx_enabled, transmitting, decoding, rx_df, tx_df , QString::fromUtf8 (de_call), QString::fromUtf8 (de_grid) @@ -278,7 +278,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s >> message >> low_confidence >> off_air; if (check_status (in) != Fail) { - Q_EMIT self_->decode (is_new, id, time, snr, delta_time, delta_frequency + Q_EMIT self_->decode (is_new, client_key, time, snr, delta_time, delta_frequency , QString::fromUtf8 (mode), QString::fromUtf8 (message) , low_confidence, off_air); } @@ -302,7 +302,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s >> off_air; if (check_status (in) != Fail) { - Q_EMIT self_->WSPR_decode (is_new, id, time, snr, delta_time, frequency, drift + Q_EMIT self_->WSPR_decode (is_new, client_key, time, snr, delta_time, frequency, drift , QString::fromUtf8 (callsign), QString::fromUtf8 (grid) , power, off_air); } @@ -333,8 +333,10 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s >> exchange_sent >> exchange_rcvd >> prop_mode; if (check_status (in) != Fail) { - Q_EMIT self_->qso_logged (id, time_off, QString::fromUtf8 (dx_call), QString::fromUtf8 (dx_grid) - , dial_frequency, QString::fromUtf8 (mode), QString::fromUtf8 (report_sent) + Q_EMIT self_->qso_logged (client_key, time_off, QString::fromUtf8 (dx_call) + , QString::fromUtf8 (dx_grid) + , dial_frequency, QString::fromUtf8 (mode) + , QString::fromUtf8 (report_sent) , QString::fromUtf8 (report_received), QString::fromUtf8 (tx_power) , QString::fromUtf8 (comments), QString::fromUtf8 (name), time_on , QString::fromUtf8 (operator_call), QString::fromUtf8 (my_call) @@ -345,8 +347,8 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s break; case NetworkMessage::Close: - Q_EMIT self_->client_closed (id); - clients_.remove (id); + Q_EMIT self_->client_closed (client_key); + clients_.remove (client_key); break; case NetworkMessage::LoggedADIF: @@ -355,7 +357,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s in >> ADIF; if (check_status (in) != Fail) { - Q_EMIT self_->logged_ADIF (id, ADIF); + Q_EMIT self_->logged_ADIF (client_key, ADIF); } } break; @@ -388,7 +390,7 @@ void MessageServer::impl::tick () { if (now > (*iter).last_activity_.addSecs (NetworkMessage::pulse)) { - Q_EMIT self_->clear_decodes (iter.key ()); + Q_EMIT self_->decodes_cleared (iter.key ()); Q_EMIT self_->client_closed (iter.key ()); iter = clients_.erase (iter); // safe while iterating as doesn't rehash } @@ -462,127 +464,127 @@ void MessageServer::start (port_type port, QHostAddress const& multicast_group_a } } -void MessageServer::clear_decodes (QString const& id, quint8 window) +void MessageServer::clear_decodes (ClientKey const& key, quint8 window) { - auto iter = m_->clients_.find (id); + auto iter = m_->clients_.find (key); if (iter != std::end (m_->clients_)) { QByteArray message; - NetworkMessage::Builder out {&message, NetworkMessage::Clear, id, (*iter).negotiated_schema_number_}; + NetworkMessage::Builder out {&message, NetworkMessage::Clear, key.second, (*iter).negotiated_schema_number_}; out << window; - m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); + m_->send_message (out, message, key.first, (*iter).sender_port_); } } -void MessageServer::reply (QString const& id, QTime time, qint32 snr, float delta_time +void MessageServer::reply (ClientKey const& key, QTime time, qint32 snr, float delta_time , quint32 delta_frequency, QString const& mode , QString const& message_text, bool low_confidence, quint8 modifiers) { - auto iter = m_->clients_.find (id); + auto iter = m_->clients_.find (key); if (iter != std::end (m_->clients_)) { QByteArray message; - NetworkMessage::Builder out {&message, NetworkMessage::Reply, id, (*iter).negotiated_schema_number_}; + NetworkMessage::Builder out {&message, NetworkMessage::Reply, key.second, (*iter).negotiated_schema_number_}; out << time << snr << delta_time << delta_frequency << mode.toUtf8 () << message_text.toUtf8 () << low_confidence << modifiers; - m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); + m_->send_message (out, message, key.first, (*iter).sender_port_); } } -void MessageServer::replay (QString const& id) +void MessageServer::replay (ClientKey const& key) { - auto iter = m_->clients_.find (id); + auto iter = m_->clients_.find (key); if (iter != std::end (m_->clients_)) { QByteArray message; - NetworkMessage::Builder out {&message, NetworkMessage::Replay, id, (*iter).negotiated_schema_number_}; - m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); + NetworkMessage::Builder out {&message, NetworkMessage::Replay, key.second, (*iter).negotiated_schema_number_}; + m_->send_message (out, message, key.first, (*iter).sender_port_); } } -void MessageServer::close (QString const& id) +void MessageServer::close (ClientKey const& key) { - auto iter = m_->clients_.find (id); + auto iter = m_->clients_.find (key); if (iter != std::end (m_->clients_)) { QByteArray message; - NetworkMessage::Builder out {&message, NetworkMessage::Close, id, (*iter).negotiated_schema_number_}; - m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); + NetworkMessage::Builder out {&message, NetworkMessage::Close, key.second, (*iter).negotiated_schema_number_}; + m_->send_message (out, message, key.first, (*iter).sender_port_); } } -void MessageServer::halt_tx (QString const& id, bool auto_only) +void MessageServer::halt_tx (ClientKey const& key, bool auto_only) { - auto iter = m_->clients_.find (id); + auto iter = m_->clients_.find (key); if (iter != std::end (m_->clients_)) { QByteArray message; - NetworkMessage::Builder out {&message, NetworkMessage::HaltTx, id, (*iter).negotiated_schema_number_}; + NetworkMessage::Builder out {&message, NetworkMessage::HaltTx, key.second, (*iter).negotiated_schema_number_}; out << auto_only; - m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); + m_->send_message (out, message, key.first, (*iter).sender_port_); } } -void MessageServer::free_text (QString const& id, QString const& text, bool send) +void MessageServer::free_text (ClientKey const& key, QString const& text, bool send) { - auto iter = m_->clients_.find (id); + auto iter = m_->clients_.find (key); if (iter != std::end (m_->clients_)) { QByteArray message; - NetworkMessage::Builder out {&message, NetworkMessage::FreeText, id, (*iter).negotiated_schema_number_}; + NetworkMessage::Builder out {&message, NetworkMessage::FreeText, key.second, (*iter).negotiated_schema_number_}; out << text.toUtf8 () << send; - m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); + m_->send_message (out, message, key.first, (*iter).sender_port_); } } -void MessageServer::location (QString const& id, QString const& loc) +void MessageServer::location (ClientKey const& key, QString const& loc) { - auto iter = m_->clients_.find (id); + auto iter = m_->clients_.find (key); if (iter != std::end (m_->clients_)) { QByteArray message; - NetworkMessage::Builder out {&message, NetworkMessage::Location, id, (*iter).negotiated_schema_number_}; + NetworkMessage::Builder out {&message, NetworkMessage::Location, key.second, (*iter).negotiated_schema_number_}; out << loc.toUtf8 (); - m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); + m_->send_message (out, message, key.first, (*iter).sender_port_); } } -void MessageServer::highlight_callsign (QString const& id, QString const& callsign +void MessageServer::highlight_callsign (ClientKey const& key, QString const& callsign , QColor const& bg, QColor const& fg, bool last_only) { - auto iter = m_->clients_.find (id); + auto iter = m_->clients_.find (key); if (iter != std::end (m_->clients_)) { QByteArray message; - NetworkMessage::Builder out {&message, NetworkMessage::HighlightCallsign, id, (*iter).negotiated_schema_number_}; + NetworkMessage::Builder out {&message, NetworkMessage::HighlightCallsign, key.second, (*iter).negotiated_schema_number_}; out << callsign.toUtf8 () << bg << fg << last_only; - m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); + m_->send_message (out, message, key.first, (*iter).sender_port_); } } -void MessageServer::switch_configuration (QString const& id, QString const& configuration_name) +void MessageServer::switch_configuration (ClientKey const& key, QString const& configuration_name) { - auto iter = m_->clients_.find (id); + auto iter = m_->clients_.find (key); if (iter != std::end (m_->clients_)) { QByteArray message; - NetworkMessage::Builder out {&message, NetworkMessage::SwitchConfiguration, id, (*iter).negotiated_schema_number_}; + NetworkMessage::Builder out {&message, NetworkMessage::SwitchConfiguration, key.second, (*iter).negotiated_schema_number_}; out << configuration_name.toUtf8 (); - m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); + m_->send_message (out, message, key.first, (*iter).sender_port_); } } -void MessageServer::configure (QString const& id, QString const& mode, quint32 frequency_tolerance +void MessageServer::configure (ClientKey const& key, QString const& mode, quint32 frequency_tolerance , QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df , QString const& dx_call, QString const& dx_grid, bool generate_messages) { - auto iter = m_->clients_.find (id); + auto iter = m_->clients_.find (key); if (iter != std::end (m_->clients_)) { QByteArray message; - NetworkMessage::Builder out {&message, NetworkMessage::Configure, id, (*iter).negotiated_schema_number_}; + NetworkMessage::Builder out {&message, NetworkMessage::Configure, key.second, (*iter).negotiated_schema_number_}; out << mode.toUtf8 () << frequency_tolerance << submode.toUtf8 () << fast_mode << tr_period << rx_df << dx_call.toUtf8 () << dx_grid.toUtf8 () << generate_messages; - m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); + m_->send_message (out, message, key.first, (*iter).sender_port_); } } diff --git a/UDPExamples/MessageServer.hpp b/UDPExamples/MessageServer.hpp index 449c70f4b..8a31e7bd5 100644 --- a/UDPExamples/MessageServer.hpp +++ b/UDPExamples/MessageServer.hpp @@ -2,6 +2,7 @@ #define MESSAGE_SERVER_HPP__ #include +#include #include #include #include @@ -33,6 +34,7 @@ class UDP_EXPORT MessageServer public: using port_type = quint16; using Frequency = Radio::Frequency; + using ClientKey = QPair; MessageServer (QObject * parent = nullptr, QString const& version = QString {}, QString const& revision = QString {}); @@ -45,73 +47,72 @@ public: , QStringList const& network_interface_names = QStringList {}); // ask the client to clear one or both of the decode windows - Q_SLOT void clear_decodes (QString const& id, quint8 window = 0); + Q_SLOT void clear_decodes (ClientKey const&, quint8 window = 0); // ask the client with identification 'id' to make the same action // as a double click on the decode would // // note that the client is not obliged to take any action and only // takes any action if the decode is present and is a CQ or QRZ message - Q_SLOT void reply (QString const& id, QTime time, qint32 snr, float delta_time, quint32 delta_frequency + Q_SLOT void reply (ClientKey const&, QTime time, qint32 snr, float delta_time, quint32 delta_frequency , QString const& mode, QString const& message, bool low_confidence, quint8 modifiers); - // ask the client with identification 'id' to close down gracefully - Q_SLOT void close (QString const& id); + // ask the client to close down gracefully + Q_SLOT void close (ClientKey const&); - // ask the client with identification 'id' to replay all decodes - Q_SLOT void replay (QString const& id); + // ask the client to replay all decodes + Q_SLOT void replay (ClientKey const&); - // ask the client with identification 'id' to halt transmitting - // auto_only just disables auto Tx, otherwise halt is immediate - Q_SLOT void halt_tx (QString const& id, bool auto_only); + // ask the client to halt transmitting auto_only just disables auto + // Tx, otherwise halt is immediate + Q_SLOT void halt_tx (ClientKey const&, bool auto_only); - // ask the client with identification 'id' to set the free text - // message and optionally send it ASAP - Q_SLOT void free_text (QString const& id, QString const& text, bool send); + // ask the client to set the free text message and optionally send + // it ASAP + Q_SLOT void free_text (ClientKey const&, QString const& text, bool send); - // ask the client with identification 'id' to set the location provided - Q_SLOT void location (QString const& id, QString const& location); + // ask the client to set the location provided + Q_SLOT void location (ClientKey const&, QString const& location); - // ask the client with identification 'id' to highlight the callsign - // specified with the given colors - Q_SLOT void highlight_callsign (QString const& id, QString const& callsign + // ask the client to highlight the callsign specified with the given + // colors + Q_SLOT void highlight_callsign (ClientKey const&, QString const& callsign , QColor const& bg = QColor {}, QColor const& fg = QColor {} , bool last_only = false); - // ask the client with identification 'id' to switch to - // configuration 'configuration_name' - Q_SLOT void switch_configuration (QString const& id, QString const& configuration_name); + // ask the client to switch to configuration 'configuration_name' + Q_SLOT void switch_configuration (ClientKey const&, QString const& configuration_name); - // ask the client with identification 'id' to change configuration - Q_SLOT void configure (QString const& id, QString const& mode, quint32 frequency_tolerance + // ask the client to change configuration + Q_SLOT void configure (ClientKey const&, QString const& mode, quint32 frequency_tolerance , QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df , QString const& dx_call, QString const& dx_grid, bool generate_messages); // the following signals are emitted when a client broadcasts the // matching message - Q_SIGNAL void client_opened (QString const& id, QString const& version, QString const& revision); - Q_SIGNAL void status_update (QString const& id, Frequency, QString const& mode, QString const& dx_call + Q_SIGNAL void client_opened (ClientKey const&, QString const& version, QString const& revision); + Q_SIGNAL void status_update (ClientKey const&, Frequency, 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); - Q_SIGNAL void client_closed (QString const& id); - Q_SIGNAL void decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time + Q_SIGNAL void client_closed (ClientKey const&); + Q_SIGNAL void decode (bool is_new, ClientKey const&, QTime time, qint32 snr, float delta_time , quint32 delta_frequency, QString const& mode, QString const& message , bool low_confidence, bool off_air); - Q_SIGNAL void WSPR_decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time, Frequency + Q_SIGNAL void WSPR_decode (bool is_new, ClientKey const&, QTime time, qint32 snr, float delta_time, Frequency , qint32 drift, QString const& callsign, QString const& grid, qint32 power , bool off_air); - Q_SIGNAL void qso_logged (QString const& id, QDateTime time_off, QString const& dx_call, QString const& dx_grid + Q_SIGNAL void qso_logged (ClientKey const&, 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& prop_mode); - Q_SIGNAL void decodes_cleared (QString const& id); - Q_SIGNAL void logged_ADIF (QString const& id, QByteArray const& ADIF); + Q_SIGNAL void decodes_cleared (ClientKey const&); + Q_SIGNAL void logged_ADIF (ClientKey const&, QByteArray const& ADIF); // this signal is emitted when a network error occurs Q_SIGNAL void error (QString const&) const; @@ -121,4 +122,6 @@ private: pimpl m_; }; +Q_DECLARE_METATYPE (MessageServer::ClientKey); + #endif diff --git a/UDPExamples/UDPDaemon.cpp b/UDPExamples/UDPDaemon.cpp index 2668a6021..b7620ec90 100644 --- a/UDPExamples/UDPDaemon.cpp +++ b/UDPExamples/UDPDaemon.cpp @@ -41,15 +41,17 @@ class Client { Q_OBJECT + using ClientKey = MessageServer::ClientKey; + public: - explicit Client (QString const& id, QObject * parent = nullptr) + explicit Client (ClientKey const& key, QObject * parent = nullptr) : QObject {parent} - , id_ {id} + , key_ {key} , dial_frequency_ {0u} { } - Q_SLOT void update_status (QString const& id, Frequency f, QString const& mode, QString const& /*dx_call*/ + Q_SLOT void update_status (ClientKey const& key, Frequency f, QString const& mode, QString const& /*dx_call*/ , QString const& /*report*/, QString const& /*tx_mode*/, bool /*tx_enabled*/ , bool /*transmitting*/, bool /*decoding*/, qint32 /*rx_df*/, qint32 /*tx_df*/ , QString const& /*de_call*/, QString const& /*de_grid*/, QString const& /*dx_grid*/ @@ -57,67 +59,73 @@ public: , quint8 /*special_op_mode*/, quint32 /*frequency_tolerance*/, quint32 /*tr_period*/ , QString const& /*configuration_name*/) { - if (id == id_) + if (key == key_) { if (f != dial_frequency_) { - std::cout << tr ("%1: Dial frequency changed to %2").arg (id_).arg (f).toStdString () << std::endl; + std::cout << tr ("%1(%2): Dial frequency changed to %3") + .arg (key_.second).arg (key_.first.toString ()).arg (f).toStdString () << std::endl; dial_frequency_ = f; } if (mode + sub_mode != mode_) { - std::cout << tr ("%1: Mode changed to %2").arg (id_).arg (mode + sub_mode).toStdString () << std::endl; + std::cout << tr ("%1(%2): Mode changed to %3") + .arg (key_.second).arg (key_.first.toString ()).arg (mode + sub_mode).toStdString () << std::endl; mode_ = mode + sub_mode; } } } - Q_SLOT void decode_added (bool is_new, QString const& client_id, QTime time, qint32 snr + Q_SLOT void decode_added (bool is_new, ClientKey const& key, QTime time, qint32 snr , float delta_time, quint32 delta_frequency, QString const& mode , QString const& message, bool low_confidence, bool off_air) { - if (client_id == id_) + if (key == key_) { qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr << "Dt:" << delta_time << "Df:" << delta_frequency << "mode:" << mode << "Confidence:" << (low_confidence ? "low" : "high") << "On air:" << !off_air; - std::cout << tr ("%1: Decoded %2").arg (id_).arg (message).toStdString () << std::endl; + std::cout << tr ("%1(%2): Decoded %3") + .arg (key_.second).arg (key_.first.toString ()).arg (message).toStdString () << std::endl; } } - Q_SLOT void beacon_spot_added (bool is_new, QString const& client_id, QTime time, qint32 snr + Q_SLOT void beacon_spot_added (bool is_new, ClientKey const& key, QTime time, qint32 snr , float delta_time, Frequency delta_frequency, qint32 drift, QString const& callsign , QString const& grid, qint32 power, bool off_air) { - if (client_id == id_) + if (key == key_) { qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr << "Dt:" << delta_time << "Df:" << delta_frequency << "drift:" << drift; - std::cout << tr ("%1: WSPR decode %2 grid %3 power: %4").arg (id_).arg (callsign).arg (grid).arg (power).toStdString () + std::cout << tr ("%1(%2): WSPR decode %3 grid %4 power: %5") + .arg (key_.second).arg (key_.first.toString ()).arg (callsign).arg (grid).arg (power).toStdString () << "On air:" << !off_air << std::endl; } } - Q_SLOT void qso_logged (QString const&client_id, QDateTime time_off, QString const& dx_call, QString const& dx_grid + Q_SLOT void qso_logged (ClientKey const& key, 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& prop_mode) { - if (client_id == id_) + if (key == key_) { - qDebug () << "time_on:" << time_on << "time_off:" << time_off << "dx_call:" << dx_call << "grid:" << dx_grid + qDebug () << "time_on:" << time_on << "time_off:" << time_off << "dx_call:" + << dx_call << "grid:" << dx_grid << "freq:" << dial_frequency << "mode:" << mode << "rpt_sent:" << report_sent << "rpt_rcvd:" << report_received << "Tx_pwr:" << tx_power << "comments:" << comments << "name:" << name << "operator_call:" << operator_call << "my_call:" << my_call << "my_grid:" << my_grid << "exchange_sent:" << exchange_sent << "exchange_rcvd:" << exchange_rcvd << "prop_mode:" << prop_mode; std::cout << QByteArray {80, '-'}.data () << '\n'; - std::cout << tr ("%1: Logged %2 grid: %3 power: %4 sent: %5 recd: %6 freq: %7 time_off: %8 op: %9 my_call: %10 my_grid: %11 exchange_sent: %12 exchange_rcvd: %13 comments: %14 prop_mode: %15") - .arg (id_).arg (dx_call).arg (dx_grid).arg (tx_power).arg (report_sent).arg (report_received) + std::cout << tr ("%1(%2): Logged %3 grid: %4 power: %5 sent: %6 recd: %7 freq: %8 time_off: %9 op: %10 my_call: %11 my_grid: %12 exchange_sent: %13 exchange_rcvd: %14 comments: %15 prop_mode: %16") + .arg (key_.second).arg (key.first.toString ()).arg (dx_call).arg (dx_grid).arg (tx_power) + .arg (report_sent).arg (report_received) .arg (dial_frequency).arg (time_off.toString("yyyy-MM-dd hh:mm:ss.z")).arg (operator_call) .arg (my_call).arg (my_grid).arg (exchange_sent).arg (exchange_rcvd) .arg (comments).arg (prop_mode).toStdString () @@ -125,9 +133,9 @@ public: } } - Q_SLOT void logged_ADIF (QString const&client_id, QByteArray const& ADIF) + Q_SLOT void logged_ADIF (ClientKey const& key, QByteArray const& ADIF) { - if (client_id == id_) + if (key == key_) { qDebug () << "ADIF:" << ADIF; std::cout << QByteArray {80, '-'}.data () << '\n'; @@ -136,7 +144,7 @@ public: } private: - QString id_; + ClientKey key_; Frequency dial_frequency_; QString mode_; }; @@ -146,6 +154,8 @@ class Server { Q_OBJECT + using ClientKey = MessageServer::ClientKey; + public: Server (port_type port, QHostAddress const& multicast_group, QStringList const& network_interface_names) : server_ {new MessageServer {this}} @@ -161,17 +171,18 @@ public: } private: - void add_client (QString const& id, QString const& version, QString const& revision) + void add_client (ClientKey const& key, QString const& version, QString const& revision) { - auto client = new Client {id}; + auto client = new Client {key}; connect (server_, &MessageServer::status_update, client, &Client::update_status); connect (server_, &MessageServer::decode, client, &Client::decode_added); connect (server_, &MessageServer::WSPR_decode, client, &Client::beacon_spot_added); connect (server_, &MessageServer::qso_logged, client, &Client::qso_logged); connect (server_, &MessageServer::logged_ADIF, client, &Client::logged_ADIF); - clients_[id] = client; - server_->replay (id); - std::cout << "Discovered WSJT-X instance: " << id.toStdString (); + clients_[key] = client; + server_->replay (key); + std::cout << "Discovered WSJT-X instance: " << key.second.toStdString () + << '(' << key.first.toString ().toStdString () << ')'; if (version.size ()) { std::cout << " v" << version.toStdString (); @@ -183,21 +194,22 @@ private: std::cout << std::endl; } - void remove_client (QString const& id) + void remove_client (ClientKey const& key) { - auto iter = clients_.find (id); + auto iter = clients_.find (key); if (iter != std::end (clients_)) { clients_.erase (iter); (*iter)->deleteLater (); } - std::cout << "Removed WSJT-X instance: " << id.toStdString () << std::endl; + std::cout << "Removed WSJT-X instance: " << key.second.toStdString () + << '(' << key.first.toString ().toStdString () << ')' << std::endl; } MessageServer * server_; - // maps client id to clients - QHash clients_; + // maps client key to clients + QHash clients_; }; #include "UDPDaemon.moc" From f9aa10f3a5795e795aef2cbfbd12311126d20eb6 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Mon, 2 Nov 2020 22:08:37 +0000 Subject: [PATCH 03/16] UDPDaemon command line flag to list network interfaces --- UDPExamples/UDPDaemon.cpp | 49 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/UDPExamples/UDPDaemon.cpp b/UDPExamples/UDPDaemon.cpp index b7620ec90..2044bfb00 100644 --- a/UDPExamples/UDPDaemon.cpp +++ b/UDPExamples/UDPDaemon.cpp @@ -17,12 +17,14 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -212,6 +214,42 @@ private: QHash clients_; }; +void list_interfaces () +{ + for (auto const& net_if : QNetworkInterface::allInterfaces ()) + { + if (net_if.flags () & QNetworkInterface::IsUp) + { + std::cout << net_if.humanReadableName ().toStdString () << ":\n" + " id: " << net_if.name ().toStdString () << " (" << net_if.index () << ")\n" + " addr: " << net_if.hardwareAddress ().toStdString () << "\n" + " flags: "; + if (net_if.flags () & QNetworkInterface::IsRunning) + { + std::cout << "Running "; + } + if (net_if.flags () & QNetworkInterface::CanBroadcast) + { + std::cout << "Broadcast "; + } + if (net_if.flags () & QNetworkInterface::CanMulticast) + { + std::cout << "Multicast "; + } + if (net_if.flags () & QNetworkInterface::IsLoopBack) + { + std::cout << "Loop-back "; + } + std::cout << "\n addresses:\n"; + for (auto const& ae : net_if.addressEntries ()) + { + std::cout << " " << ae.ip ().toString ().toStdString () << '\n'; + } + std::cout << '\n'; + } + } +} + #include "UDPDaemon.moc" int main (int argc, char * argv[]) @@ -232,6 +270,11 @@ int main (int argc, char * argv[]) auto help_option = parser.addHelpOption (); auto version_option = parser.addVersionOption (); + QCommandLineOption list_option (QStringList {"l", "list-interfaces"}, + app.translate ("UDPDaemon", + "Print the available network interfaces.")); + parser.addOption (list_option); + QCommandLineOption port_option (QStringList {"p", "port"}, app.translate ("UDPDaemon", "Where is the UDP service port number to listen on.\n" @@ -257,6 +300,12 @@ int main (int argc, char * argv[]) parser.process (app); + if (parser.isSet (list_option)) + { + list_interfaces (); + return EXIT_SUCCESS; + } + Server server {static_cast (parser.value (port_option).toUInt ()) , QHostAddress {parser.value (multicast_addr_option).trimmed ()} , parser.values (network_interface_option)}; From 4e0e23c301145f13c400c3fb91dcc593c5b77fdb Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Tue, 3 Nov 2020 20:28:29 +0000 Subject: [PATCH 04/16] New combo box with a list of checkable items --- CMakeLists.txt | 1 + widgets/CheckableItemComboBox.cpp | 93 +++++++++++++++++++++++++++++++ widgets/CheckableItemComboBox.hpp | 36 ++++++++++++ widgets/widgets.pri | 7 ++- 4 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 widgets/CheckableItemComboBox.cpp create mode 100644 widgets/CheckableItemComboBox.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 331194711..5b07684e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -241,6 +241,7 @@ set (wsjt_qt_CXXSRCS logbook/Multiplier.cpp Network/NetworkAccessManager.cpp widgets/LazyFillComboBox.cpp + widgets/CheckableItemComboBox.cpp ) set (wsjt_qtmm_CXXSRCS diff --git a/widgets/CheckableItemComboBox.cpp b/widgets/CheckableItemComboBox.cpp new file mode 100644 index 000000000..3ef926243 --- /dev/null +++ b/widgets/CheckableItemComboBox.cpp @@ -0,0 +1,93 @@ +#include "CheckableItemComboBox.hpp" + +#include +#include +#include +#include +#include +#include + +class CheckableItemComboBoxStyledItemDelegate + : public QStyledItemDelegate +{ +public: + explicit CheckableItemComboBoxStyledItemDelegate (QObject * parent = nullptr) + : QStyledItemDelegate {parent} + { + } + + void paint (QPainter * painter, QStyleOptionViewItem const& option, QModelIndex const& index) const override + { + QStyleOptionViewItem& mutable_option = const_cast (option); + mutable_option.showDecorationSelected = false; + QStyledItemDelegate::paint (painter, mutable_option, index); + } +}; + +CheckableItemComboBox::CheckableItemComboBox (QWidget * parent) + : QComboBox {parent} + , model_ {new QStandardItemModel()} +{ + setModel (model_.data ()); + + setEditable (true); + lineEdit ()->setReadOnly (true); + lineEdit ()->installEventFilter (this); + setItemDelegate (new CheckableItemComboBoxStyledItemDelegate {this}); + + connect (lineEdit(), &QLineEdit::selectionChanged, lineEdit(), &QLineEdit::deselect); + connect (static_cast (view ()), &QListView::pressed, this, &CheckableItemComboBox::item_pressed); + connect (model_.data (), &QStandardItemModel::dataChanged, this, &CheckableItemComboBox::model_data_changed); +} + +QStandardItem * CheckableItemComboBox::addCheckItem (QString const& label, QVariant const& data + , Qt::CheckState checkState) +{ + auto * item = new QStandardItem {label}; + item->setCheckState (checkState); + item->setData (data); + item->setFlags (Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); + model_->appendRow (item); + update_text (); + return item; +} + +bool CheckableItemComboBox::eventFilter (QObject * object, QEvent * event) +{ + if (object == lineEdit() && event->type () == QEvent::MouseButtonPress) + { + showPopup(); + return true; + } + return false; +} + +void CheckableItemComboBox::update_text() +{ + QString text; + for (int i = 0; i < model_->rowCount (); ++i) + { + if (model_->item (i)->checkState () == Qt::Checked) + { + if (text.size ()) + { + text+= ", "; + } + text += model_->item (i)->data ().toString (); + } + } + lineEdit ()->setText (text); +} + +void CheckableItemComboBox::model_data_changed () +{ + update_text (); +} + +void CheckableItemComboBox::item_pressed (QModelIndex const& index) +{ + QStandardItem * item = model_->itemFromIndex (index); + item->setCheckState (item->checkState () == Qt::Checked ? Qt::Unchecked : Qt::Checked); +} + +#include "widgets/moc_CheckableItemComboBox.cpp" diff --git a/widgets/CheckableItemComboBox.hpp b/widgets/CheckableItemComboBox.hpp new file mode 100644 index 000000000..2aa3e9831 --- /dev/null +++ b/widgets/CheckableItemComboBox.hpp @@ -0,0 +1,36 @@ +#ifndef CHECKABLE_ITEM_COMBO_BOX_HPP__ +#define CHECKABLE_ITEM_COMBO_BOX_HPP__ + +#include +#include + +class QStandardItemModel; +class QStandardItem; + +/** + * @brief QComboBox with support of checkboxes + * http://stackoverflow.com/questions/8422760/combobox-of-checkboxes + */ +class CheckableItemComboBox + : public QComboBox +{ + Q_OBJECT + +public: + explicit CheckableItemComboBox (QWidget * parent = nullptr); + QStandardItem * addCheckItem (QString const& label, QVariant const& data, Qt::CheckState checkState); + +protected: + bool eventFilter (QObject *, QEvent *) override; + +private: + void update_text(); + + Q_SLOT void model_data_changed (); + Q_SLOT void item_pressed (QModelIndex const&); + +private: + QScopedPointer model_; +}; + +#endif diff --git a/widgets/widgets.pri b/widgets/widgets.pri index 7a364c4ee..6a623d825 100644 --- a/widgets/widgets.pri +++ b/widgets/widgets.pri @@ -10,7 +10,9 @@ SOURCES += \ widgets/AbstractLogWindow.cpp \ widgets/FrequencyLineEdit.cpp widgets/FrequencyDeltaLineEdit.cpp \ widgets/FoxLogWindow.cpp widgets/CabrilloLogWindow.cpp \ - widgets/HelpTextWindow.cpp widgets/RestrictedSpinBox.cpp + widgets/HelpTextWindow.cpp widgets/RestrictedSpinBox.cpp \ + widgets/LazyFillComboBox.cpp widgets/CheckableItemComboBox.cpp + HEADERS += \ widgets/mainwindow.h widgets/plotter.h \ widgets/about.h widgets/widegraph.h \ @@ -22,7 +24,8 @@ HEADERS += \ widgets/ExportCabrillo.h widgets/AbstractLogWindow.hpp \ widgets/FoxLogWindow.hpp widgets/CabrilloLogWindow.hpp \ widgets/DateTimeEdit.hpp widgets/HelpTextWindow.hpp \ - widgets/RestrictedSpinBox.hpp + widgets/RestrictedSpinBox.hpp \ + widgets/LazyFillComboBox.hpp widgets/CheckableItemComboBox.hpp FORMS += \ widgets/mainwindow.ui widgets/about.ui \ From 1d9dd7df24a6b452de757257211ac26ac3ebf38e Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Tue, 3 Nov 2020 20:29:25 +0000 Subject: [PATCH 05/16] Remove unwanted file and directory --- lib/fsk4hf/.DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 lib/fsk4hf/.DS_Store diff --git a/lib/fsk4hf/.DS_Store b/lib/fsk4hf/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 Date: Tue, 3 Nov 2020 20:31:11 +0000 Subject: [PATCH 06/16] Add network interface selection combo box widget to message_aggregator --- CMakeLists.txt | 2 +- UDPExamples/MessageAggregatorMainWindow.cpp | 96 ++++++++++++++++++--- UDPExamples/MessageAggregatorMainWindow.hpp | 7 ++ UDPExamples/MessageServer.cpp | 10 ++- UDPExamples/MessageServer.hpp | 4 +- UDPExamples/UDPDaemon.cpp | 2 +- 6 files changed, 101 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b07684e9..d9ff2be75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1491,7 +1491,7 @@ add_executable (message_aggregator ${message_aggregator_RESOURCES_RCC} ${message_aggregator_VERSION_RESOURCES} ) -target_link_libraries (message_aggregator Qt5::Widgets wsjtx_udp-static) +target_link_libraries (message_aggregator wsjt_qt Qt5::Widgets wsjtx_udp-static) if (WSJT_CREATE_WINMAIN) set_target_properties (message_aggregator PROPERTIES WIN32_EXECUTABLE ON) diff --git a/UDPExamples/MessageAggregatorMainWindow.cpp b/UDPExamples/MessageAggregatorMainWindow.cpp index 7e74342c0..d110bfc44 100644 --- a/UDPExamples/MessageAggregatorMainWindow.cpp +++ b/UDPExamples/MessageAggregatorMainWindow.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include "DecodesModel.hpp" #include "BeaconsModel.hpp" @@ -37,8 +39,10 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow () , decodes_model_ {new DecodesModel {this}} , beacons_model_ {new BeaconsModel {this}} , server_ {new MessageServer {this}} - , multicast_group_line_edit_ {new QLineEdit} - , log_table_view_ {new QTableView} + , port_spin_box_ {new QSpinBox {this}} + , multicast_group_line_edit_ {new QLineEdit {this}} + , network_interfaces_combo_box_ {new CheckableItemComboBox {this}} + , log_table_view_ {new QTableView {this}} , add_call_of_interest_action_ {new QAction {tr ("&Add callsign"), this}} , delete_call_of_interest_action_ {new QAction {tr ("&Delete callsign"), this}} , last_call_of_interest_action_ {new QAction {tr ("&Highlight last only"), this}} @@ -68,16 +72,66 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow () auto central_layout = new QVBoxLayout; // server details - auto port_spin_box = new QSpinBox; - port_spin_box->setMinimum (1); - port_spin_box->setMaximum (std::numeric_limits::max ()); + port_spin_box_->setMinimum (1); + port_spin_box_->setMaximum (std::numeric_limits::max ()); auto group_box_layout = new QFormLayout; - group_box_layout->addRow (tr ("Port number:"), port_spin_box); + group_box_layout->addRow (tr ("Port number:"), port_spin_box_); group_box_layout->addRow (tr ("Multicast Group (blank for unicast server):"), multicast_group_line_edit_); + group_box_layout->addRow (tr ("Network interfaces:"), network_interfaces_combo_box_); + int row; + QFormLayout::ItemRole role; + group_box_layout->getWidgetPosition (network_interfaces_combo_box_, &row, &role); + Q_ASSERT (row >= 0); + network_interfaces_form_label_widget_ = static_cast (group_box_layout->itemAt (row, QFormLayout::LabelRole)->widget ()); + network_interfaces_form_label_widget_->hide (); + network_interfaces_form_label_widget_->buddy ()->hide (); + connect (multicast_group_line_edit_, &QLineEdit::editingFinished, [this] { + if (multicast_group_line_edit_->text ().size ()) + { + network_interfaces_form_label_widget_->show (); + network_interfaces_form_label_widget_->buddy ()->show (); + } + else + { + network_interfaces_form_label_widget_->hide (); + network_interfaces_form_label_widget_->buddy ()->hide (); + } + }); auto group_box = new QGroupBox {tr ("Server Details")}; group_box->setLayout (group_box_layout); central_layout->addWidget (group_box); + // populate network interface list + for (auto const& net_if : QNetworkInterface::allInterfaces ()) + { + auto flags = QNetworkInterface::IsRunning | QNetworkInterface::CanMulticast; + if ((net_if.flags () & flags) == flags) + { + auto is_loopback = net_if.flags () & QNetworkInterface::IsLoopBack; + auto item = network_interfaces_combo_box_->addCheckItem (net_if.humanReadableName () + , net_if.name () + , is_loopback ? Qt::Checked : Qt::Unchecked); + item->setEnabled (!is_loopback); + auto tip = QString {"name(index): %1(%2) - %3"}.arg (net_if.name ()).arg (net_if.index ()) + .arg (net_if.flags () & QNetworkInterface::IsUp ? "Up" : "Down"); + auto hw_addr = net_if.hardwareAddress (); + if (hw_addr.size ()) + { + tip += QString {"\nhw: %1"}.arg (net_if.hardwareAddress ()); + } + auto aes = net_if.addressEntries (); + if (aes.size ()) + { + tip += "\naddresses:"; + for (auto const& ae : aes) + { + tip += QString {"\n ip: %1/%2"}.arg (ae.ip ().toString ()).arg (ae.prefixLength ()); + } + } + item->setToolTip (tip); + } + } + log_table_view_->setModel (log_); log_table_view_->verticalHeader ()->hide (); central_layout->addWidget (log_table_view_); @@ -200,16 +254,34 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow () connect (decodes_model_, &DecodesModel::reply, server_, &MessageServer::reply); // UI behaviour - connect (port_spin_box, static_cast (&QSpinBox::valueChanged) - , [this] (port_type port) {server_->start (port);}); - connect (multicast_group_line_edit_, &QLineEdit::editingFinished, [this, port_spin_box] () { - server_->start (port_spin_box->value (), QHostAddress {multicast_group_line_edit_->text ()}); - }); + connect (port_spin_box_, static_cast (&QSpinBox::valueChanged) + , [this] (int /*port*/) {restart_server ();}); + connect (multicast_group_line_edit_, &QLineEdit::editingFinished, [this] () {restart_server ();}); + connect (network_interfaces_combo_box_, &QComboBox::currentTextChanged, [this] () {restart_server ();}); - port_spin_box->setValue (2237); // start up in unicast mode + port_spin_box_->setValue (2237); // start up in unicast mode show (); } +void MessageAggregatorMainWindow::restart_server () +{ + QSet net_ifs; + if (network_interfaces_combo_box_->isVisible ()) + { + auto model = static_cast (network_interfaces_combo_box_->model ()); + for (int row = 0; row < model->rowCount (); ++row) + { + if (Qt::Checked == model->item (row)->checkState ()) + { + net_ifs << model->item (row)->data ().toString (); + } + } + } + server_->start (port_spin_box_->value () + , QHostAddress {multicast_group_line_edit_->text ()} + , net_ifs); +} + void MessageAggregatorMainWindow::log_qso (ClientKey const& /*key*/, QDateTime time_off , QString const& dx_call , QString const& dx_grid, Frequency dial_frequency, QString const& mode diff --git a/UDPExamples/MessageAggregatorMainWindow.hpp b/UDPExamples/MessageAggregatorMainWindow.hpp index 445a5908e..1bff0d692 100644 --- a/UDPExamples/MessageAggregatorMainWindow.hpp +++ b/UDPExamples/MessageAggregatorMainWindow.hpp @@ -6,6 +6,7 @@ #include #include "MessageServer.hpp" +#include "widgets/CheckableItemComboBox.hpp" class QDateTime; class QStandardItemModel; @@ -16,6 +17,8 @@ class QLineEdit; class QTableView; class ClientWidget; class QListWidget; +class QLabel; +class QSpinBox; using Frequency = MessageServer::Frequency; @@ -38,6 +41,7 @@ public: , QString const& exchange_sent, QString const& exchange_rcvd, QString const& prop_mode); private: + void restart_server (); void add_client (ClientKey const&, QString const& version, QString const& revision); void remove_client (ClientKey const&); void change_highlighting (QString const& call, QColor const& bg = QColor {}, QColor const& fg = QColor {}, @@ -52,7 +56,10 @@ private: DecodesModel * decodes_model_; BeaconsModel * beacons_model_; MessageServer * server_; + QSpinBox * port_spin_box_; QLineEdit * multicast_group_line_edit_; + CheckableItemComboBox * network_interfaces_combo_box_; + QLabel * network_interfaces_form_label_widget_; QTableView * log_table_view_; QListWidget * calls_of_interest_; QAction * add_call_of_interest_action_; diff --git a/UDPExamples/MessageServer.cpp b/UDPExamples/MessageServer.cpp index 8869c4b89..6745fe0a7 100644 --- a/UDPExamples/MessageServer.cpp +++ b/UDPExamples/MessageServer.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include @@ -78,7 +77,7 @@ public: QString version_; QString revision_; QHostAddress multicast_group_address_; - QStringList network_interfaces_; + QSet network_interfaces_; static BindMode constexpr bind_mode_ = ShareAddress | ReuseAddressHint; struct Client { @@ -433,9 +432,12 @@ MessageServer::MessageServer (QObject * parent, QString const& version, QString } void MessageServer::start (port_type port, QHostAddress const& multicast_group_address - , QStringList const& network_interface_names) + , QSet const& network_interface_names) { - if (port != m_->localPort () || multicast_group_address != m_->multicast_group_address_) + qDebug () << "MessageServer::start port:" << port << "multicast addr:" << multicast_group_address.toString () << "network interfaces:" << network_interface_names; + if (port != m_->localPort () + || multicast_group_address != m_->multicast_group_address_ + || network_interface_names != m_->network_interfaces_) { m_->leave_multicast_group (); if (impl::UnconnectedState != m_->state ()) diff --git a/UDPExamples/MessageServer.hpp b/UDPExamples/MessageServer.hpp index 8a31e7bd5..7cf5653a0 100644 --- a/UDPExamples/MessageServer.hpp +++ b/UDPExamples/MessageServer.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include #include @@ -44,7 +44,7 @@ public: // which the server will join Q_SLOT void start (port_type port , QHostAddress const& multicast_group_address = QHostAddress {} - , QStringList const& network_interface_names = QStringList {}); + , QSet const& network_interface_names = QSet {}); // ask the client to clear one or both of the decode windows Q_SLOT void clear_decodes (ClientKey const&, quint8 window = 0); diff --git a/UDPExamples/UDPDaemon.cpp b/UDPExamples/UDPDaemon.cpp index 2044bfb00..0f6793028 100644 --- a/UDPExamples/UDPDaemon.cpp +++ b/UDPExamples/UDPDaemon.cpp @@ -169,7 +169,7 @@ public: connect (server_, &MessageServer::client_opened, this, &Server::add_client); connect (server_, &MessageServer::client_closed, this, &Server::remove_client); - server_->start (port, multicast_group, network_interface_names); + server_->start (port, multicast_group, QSet {network_interface_names.begin (), network_interface_names.end ()}); } private: From ff6f01c6b5126be90d56f368de9e7b3d77820f8b Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Tue, 3 Nov 2020 23:07:38 +0000 Subject: [PATCH 07/16] Pre Qt v5.11 compatibility --- qt_helpers.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/qt_helpers.hpp b/qt_helpers.hpp index 7170bda0b..034a8852f 100644 --- a/qt_helpers.hpp +++ b/qt_helpers.hpp @@ -117,6 +117,16 @@ namespace std } #endif +inline +bool is_broadcast_address (QHostAddress const& host_addr) +{ +#if QT_VERSION >= 0x051100 + return host_addr.isBroadcast (); +#else + bool ok; + return host_addr.toIPv4Address (&ok) == 0xffffffffu && ok; +#endif +} inline bool is_multicast_address (QHostAddress const& host_addr) From e538ce9294d57508655136b2c09632025f7e247c Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Tue, 3 Nov 2020 23:08:12 +0000 Subject: [PATCH 08/16] Disallow sending UDP Message Protocol traffic to broadcast address --- Network/MessageClient.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Network/MessageClient.cpp b/Network/MessageClient.cpp index 57ffb2806..3474f19bb 100644 --- a/Network/MessageClient.cpp +++ b/Network/MessageClient.cpp @@ -148,6 +148,13 @@ void MessageClient::impl::start () return; } + if (is_broadcast_address (server_)) + { + Q_EMIT self_->error ("IPv4 broadcast not supported, please specify the loop-back address, a server host address, or multicast group address"); + pending_messages_.clear (); // discard + return; + } + if (blocked_addresses_.end () != std::find (blocked_addresses_.begin (), blocked_addresses_.end (), server_)) { Q_EMIT self_->error ("UDP server blocked, please try another"); @@ -181,13 +188,6 @@ void MessageClient::impl::start () } } - if (server_.isBroadcast ()) - { - // only allow broadcast on the loopback interface to avoid - // flooding the local subnet which may be large with some ISPs - //interface_ip.setAddress ("127.0.0.1"); - } - if (localAddress () != interface_ip) { if (UnconnectedState != state () || state ()) From 50cce43e927d47fb8e318dff0006c2a7a38100c3 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Tue, 3 Nov 2020 23:14:26 +0000 Subject: [PATCH 09/16] Pre Qt 5.14 compatibility --- UDPExamples/UDPDaemon.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/UDPExamples/UDPDaemon.cpp b/UDPExamples/UDPDaemon.cpp index 0f6793028..6d98b3a6b 100644 --- a/UDPExamples/UDPDaemon.cpp +++ b/UDPExamples/UDPDaemon.cpp @@ -169,7 +169,11 @@ public: connect (server_, &MessageServer::client_opened, this, &Server::add_client); connect (server_, &MessageServer::client_closed, this, &Server::remove_client); +#if QT_VERSION >= 0x051400 server_->start (port, multicast_group, QSet {network_interface_names.begin (), network_interface_names.end ()}); +#else + server_->start (port, multicast_group, network_interface_names.toSet ()); +#endif } private: From f4db2904a3191a53e2f1342df2a7440d2b7cbb4d Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Tue, 3 Nov 2020 23:44:07 +0000 Subject: [PATCH 10/16] Correct Qt version checks --- Network/NetworkMessage.cpp | 8 ++++---- Network/NetworkMessage.hpp | 4 ++-- UDPExamples/UDPDaemon.cpp | 2 +- qt_helpers.hpp | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Network/NetworkMessage.cpp b/Network/NetworkMessage.cpp index 5af133c7f..a1e7050f2 100644 --- a/Network/NetworkMessage.cpp +++ b/Network/NetworkMessage.cpp @@ -28,13 +28,13 @@ namespace NetworkMessage { setVersion (QDataStream::Qt_5_0); // Qt schema version } -#if QT_VERSION >= 0x050200 +#if QT_VERSION >= QT_VERSION_CHECK (5, 2, 0) else if (schema <= 2) { setVersion (QDataStream::Qt_5_2); // Qt schema version } #endif -#if QT_VERSION >= 0x050400 +#if QT_VERSION >= QT_VERSION_CHECK (5, 4, 0) else if (schema <= 3) { setVersion (QDataStream::Qt_5_4); // Qt schema version @@ -73,13 +73,13 @@ namespace NetworkMessage { parent->setVersion (QDataStream::Qt_5_0); } -#if QT_VERSION >= 0x050200 +#if QT_VERSION >= QT_VERSION_CHECK (5, 2, 0) else if (schema_ <= 2) { parent->setVersion (QDataStream::Qt_5_2); } #endif -#if QT_VERSION >= 0x050400 +#if QT_VERSION >= QT_VERSION_CHECK (5, 4, 0) else if (schema_ <= 3) { parent->setVersion (QDataStream::Qt_5_4); diff --git a/Network/NetworkMessage.hpp b/Network/NetworkMessage.hpp index c484efb23..f892eda06 100644 --- a/Network/NetworkMessage.hpp +++ b/Network/NetworkMessage.hpp @@ -540,9 +540,9 @@ namespace NetworkMessage // increment this if a newer Qt schema is required and add decode // logic to the Builder and Reader class implementations -#if QT_VERSION >= 0x050400 +#if QT_VERSION >= QT_VERSION_CHECK (5, 4, 0) static quint32 constexpr schema_number {3}; -#elif QT_VERSION >= 0x050200 +#elif QT_VERSION >= QT_VERSION_CHECK (5, 2, 0) static quint32 constexpr schema_number {2}; #else // Schema 1 (Qt_5_0) is broken diff --git a/UDPExamples/UDPDaemon.cpp b/UDPExamples/UDPDaemon.cpp index 6d98b3a6b..78d1a0c52 100644 --- a/UDPExamples/UDPDaemon.cpp +++ b/UDPExamples/UDPDaemon.cpp @@ -169,7 +169,7 @@ public: connect (server_, &MessageServer::client_opened, this, &Server::add_client); connect (server_, &MessageServer::client_closed, this, &Server::remove_client); -#if QT_VERSION >= 0x051400 +#if QT_VERSION >= QT_VERSION_CHECK (5, 14, 0) server_->start (port, multicast_group, QSet {network_interface_names.begin (), network_interface_names.end ()}); #else server_->start (port, multicast_group, network_interface_names.toSet ()); diff --git a/qt_helpers.hpp b/qt_helpers.hpp index 034a8852f..416b6421b 100644 --- a/qt_helpers.hpp +++ b/qt_helpers.hpp @@ -120,7 +120,7 @@ namespace std inline bool is_broadcast_address (QHostAddress const& host_addr) { -#if QT_VERSION >= 0x051100 +#if QT_VERSION >= QT_VERSION_CHECK (5, 11, 0) return host_addr.isBroadcast (); #else bool ok; @@ -131,7 +131,7 @@ bool is_broadcast_address (QHostAddress const& host_addr) inline bool is_multicast_address (QHostAddress const& host_addr) { -#if QT_VERSION >= 0x050600 +#if QT_VERSION >= QT_VERSION_CHECK (5, 6, 0) return host_addr.isMulticast (); #else bool ok; From e61a1f969c8b0e21d611412cdf2f9e388333a323 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Wed, 4 Nov 2020 16:02:04 +0000 Subject: [PATCH 11/16] UDP multicast TTL default as one Restricts scope to local subnet. --- Configuration.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration.cpp b/Configuration.cpp index fac5564ab..78f3dafa6 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -1546,7 +1546,7 @@ void Configuration::impl::read_settings () opCall_ = settings_->value ("OpCall", "").toString (); udp_server_name_ = settings_->value ("UDPServer", "127.0.0.1").toString (); udp_interface_name_ = settings_->value ("UDPInterface").toString (); - udp_TTL_ = settings_->value ("UDPTTL").toInt (); + udp_TTL_ = settings_->value ("UDPTTL", 1).toInt (); udp_server_port_ = settings_->value ("UDPServerPort", 2237).toUInt (); n1mm_server_name_ = settings_->value ("N1MMServer", "127.0.0.1").toString (); n1mm_server_port_ = settings_->value ("N1MMServerPort", 2333).toUInt (); From 780b1f74ee0251392d242ee5d1fcb4ef92d58fd7 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Thu, 5 Nov 2020 03:37:01 +0000 Subject: [PATCH 12/16] Message Client allows sending multicast UDP on multiple interfaces --- Configuration.cpp | 87 ++++++++++++------- Configuration.hpp | 6 +- Configuration.ui | 21 +++-- Network/MessageClient.cpp | 92 ++++++++------------- Network/MessageClient.hpp | 4 +- UDPExamples/MessageAggregatorMainWindow.cpp | 22 ++--- UDPExamples/UDPDaemon.cpp | 30 +++---- widgets/CheckableItemComboBox.cpp | 2 +- widgets/CheckableItemComboBox.hpp | 5 +- widgets/LazyFillComboBox.hpp | 2 +- widgets/mainwindow.cpp | 4 +- 11 files changed, 145 insertions(+), 130 deletions(-) diff --git a/Configuration.cpp b/Configuration.cpp index 78f3dafa6..7f68bb417 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -166,6 +166,7 @@ #include #include #include +#include #include #include "pimpl_impl.hpp" @@ -442,7 +443,8 @@ private: void load_audio_devices (QAudio::Mode, QComboBox *, QAudioDeviceInfo *); void update_audio_channels (QComboBox const *, int, QComboBox *, bool); - void load_network_interfaces (QComboBox *, QString const& current); + void load_network_interfaces (CheckableItemComboBox *, QStringList const& current); + QStringList get_selected_network_interfaces (CheckableItemComboBox *); Q_SLOT void host_info_results (QHostInfo); void check_multicast (QHostAddress const&); @@ -653,7 +655,7 @@ private: bool udp_server_name_edited_; int dns_lookup_id_; port_type udp_server_port_; - QString udp_interface_name_; + QStringList udp_interface_names_; int udp_TTL_; QString n1mm_server_name_; port_type n1mm_server_port_; @@ -754,7 +756,7 @@ QString Configuration::opCall() const {return m_->opCall_;} void Configuration::opCall (QString const& call) {m_->opCall_ = call;} QString Configuration::udp_server_name () const {return m_->udp_server_name_;} auto Configuration::udp_server_port () const -> port_type {return m_->udp_server_port_;} -QString Configuration::udp_interface_name () const {return m_->udp_interface_name_;} +QStringList Configuration::udp_interface_names () const {return m_->udp_interface_names_;} int Configuration::udp_TTL () const {return m_->udp_TTL_;} bool Configuration::accept_udp_requests () const {return m_->accept_udp_requests_;} QString Configuration::n1mm_server_name () const {return m_->n1mm_server_name_;} @@ -1078,9 +1080,9 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network }); // set up dynamic loading of network interfaces - connect (ui_->udp_interface_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () { + connect (ui_->udp_interfaces_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () { QGuiApplication::setOverrideCursor (QCursor {Qt::WaitCursor}); - load_network_interfaces (ui_->udp_interface_combo_box, udp_interface_name_); + load_network_interfaces (ui_->udp_interfaces_combo_box, udp_interface_names_); QGuiApplication::restoreOverrideCursor (); }); @@ -1362,10 +1364,10 @@ void Configuration::impl::initialize_models () ui_->udp_server_line_edit->setText (udp_server_name_); on_udp_server_line_edit_editingFinished (); ui_->udp_server_port_spin_box->setValue (udp_server_port_); - load_network_interfaces (ui_->udp_interface_combo_box, udp_interface_name_); - if (!udp_interface_name_.size ()) + load_network_interfaces (ui_->udp_interfaces_combo_box, udp_interface_names_); + if (!udp_interface_names_.size ()) { - udp_interface_name_ = ui_->udp_interface_combo_box->currentData ().toString (); + udp_interface_names_ = get_selected_network_interfaces (ui_->udp_interfaces_combo_box); } ui_->udp_TTL_spin_box->setValue (udp_TTL_); ui_->accept_udp_requests_check_box->setChecked (accept_udp_requests_); @@ -1545,7 +1547,7 @@ void Configuration::impl::read_settings () rig_params_.split_mode = settings_->value ("SplitMode", QVariant::fromValue (TransceiverFactory::split_mode_none)).value (); opCall_ = settings_->value ("OpCall", "").toString (); udp_server_name_ = settings_->value ("UDPServer", "127.0.0.1").toString (); - udp_interface_name_ = settings_->value ("UDPInterface").toString (); + udp_interface_names_ = settings_->value ("UDPInterface").toStringList (); udp_TTL_ = settings_->value ("UDPTTL", 1).toInt (); udp_server_port_ = settings_->value ("UDPServerPort", 2237).toUInt (); n1mm_server_name_ = settings_->value ("N1MMServer", "127.0.0.1").toString (); @@ -1675,7 +1677,7 @@ void Configuration::impl::write_settings () settings_->setValue ("OpCall", opCall_); settings_->setValue ("UDPServer", udp_server_name_); settings_->setValue ("UDPServerPort", udp_server_port_); - settings_->setValue ("UDPInterface", udp_interface_name_); + settings_->setValue ("UDPInterface", QVariant::fromValue (udp_interface_names_)); settings_->setValue ("UDPTTL", udp_TTL_); settings_->setValue ("N1MMServer", n1mm_server_name_); settings_->setValue ("N1MMServerPort", n1mm_server_port_); @@ -2105,12 +2107,12 @@ void Configuration::impl::accept () opCall_=ui_->opCallEntry->text(); auto new_server = ui_->udp_server_line_edit->text ().trimmed (); - auto new_interface = ui_->udp_interface_combo_box->currentData ().toString (); - if (new_server != udp_server_name_ || new_interface != udp_interface_name_) + auto new_interfaces = get_selected_network_interfaces (ui_->udp_interfaces_combo_box); + if (new_server != udp_server_name_ || new_interfaces != udp_interface_names_) { udp_server_name_ = new_server; - udp_interface_name_ = new_interface; - Q_EMIT self_->udp_server_changed (udp_server_name_, udp_interface_name_); + udp_interface_names_ = new_interfaces; + Q_EMIT self_->udp_server_changed (udp_server_name_, udp_interface_names_); } auto new_port = ui_->udp_server_port_spin_box->value (); @@ -2452,8 +2454,8 @@ void Configuration::impl::host_info_results (QHostInfo host_info) void Configuration::impl::check_multicast (QHostAddress const& ha) { auto is_multicast = is_multicast_address (ha); - ui_->udp_interface_label->setVisible (is_multicast); - ui_->udp_interface_combo_box->setVisible (is_multicast); + ui_->udp_interfaces_label->setVisible (is_multicast); + ui_->udp_interfaces_combo_box->setVisible (is_multicast); ui_->udp_TTL_label->setVisible (is_multicast); ui_->udp_TTL_spin_box->setVisible (is_multicast); if (isVisible ()) @@ -2993,28 +2995,53 @@ void Configuration::impl::load_audio_devices (QAudio::Mode mode, QComboBox * com } // load the available network interfaces into the selection combo box -void Configuration::impl::load_network_interfaces (QComboBox * combo_box, QString const& current) +void Configuration::impl::load_network_interfaces (CheckableItemComboBox * combo_box, QStringList const& current) { combo_box->clear (); - int current_index = -1; - for (auto const& interface : QNetworkInterface::allInterfaces ()) + for (auto const& net_if : QNetworkInterface::allInterfaces ()) { - if (interface.flags () & QNetworkInterface::IsUp) + auto flags = QNetworkInterface::IsUp | QNetworkInterface::CanMulticast; + if ((net_if.flags () & flags) == flags) { - auto const& name = interface.name (); - combo_box->addItem (interface.humanReadableName (), name); - // select the first loopback interface as a default to - // discourage spamming the network (possibly the Internet), - // particularly important with administratively scoped - // multicast UDP - if (name == current - || (!current.size () && (interface.flags () & QNetworkInterface::IsLoopBack))) + auto is_loopback = net_if.flags () & QNetworkInterface::IsLoopBack; + auto item = combo_box->addCheckItem (net_if.humanReadableName () + , net_if.name () + , is_loopback || current.contains (net_if.name ()) ? Qt::Checked : Qt::Unchecked); + item->setEnabled (!is_loopback); + auto tip = QString {"name(index): %1(%2) - %3"}.arg (net_if.name ()).arg (net_if.index ()) + .arg (net_if.flags () & QNetworkInterface::IsUp ? "Up" : "Down"); + auto hw_addr = net_if.hardwareAddress (); + if (hw_addr.size ()) { - current_index = combo_box->count () - 1; + tip += QString {"\nhw: %1"}.arg (net_if.hardwareAddress ()); } + auto aes = net_if.addressEntries (); + if (aes.size ()) + { + tip += "\naddresses:"; + for (auto const& ae : aes) + { + tip += QString {"\n ip: %1/%2"}.arg (ae.ip ().toString ()).arg (ae.prefixLength ()); + } + } + item->setToolTip (tip); } } - combo_box->setCurrentIndex (current_index); +} + +// get the select network interfaces from the selection combo box +QStringList Configuration::impl::get_selected_network_interfaces (CheckableItemComboBox * combo_box) +{ + QStringList interfaces; + auto model = static_cast (combo_box->model ()); + for (int row = 0; row < model->rowCount (); ++row) + { + if (Qt::Checked == model->item (row)->checkState ()) + { + interfaces << model->item (row)->data ().toString (); + } + } + return interfaces; } // enable only the channels that are supported by the selected audio device diff --git a/Configuration.hpp b/Configuration.hpp index 403673663..b0f6bf75b 100644 --- a/Configuration.hpp +++ b/Configuration.hpp @@ -3,6 +3,7 @@ #include #include +#include #include "Radio.hpp" #include "models/IARURegions.hpp" @@ -14,7 +15,6 @@ class QSettings; class QWidget; class QAudioDeviceInfo; -class QString; class QDir; class QNetworkAccessManager; class Bands; @@ -151,7 +151,7 @@ public: void opCall (QString const&); QString udp_server_name () const; port_type udp_server_port () const; - QString udp_interface_name () const; + QStringList udp_interface_names () const; int udp_TTL () const; QString n1mm_server_name () const; port_type n1mm_server_port () const; @@ -274,7 +274,7 @@ public: // // This signal is emitted when the UDP server changes // - Q_SIGNAL void udp_server_changed (QString& udp_server_name, QString const& network_interface) const; + Q_SIGNAL void udp_server_changed (QString& udp_server_name, QStringList const& network_interfaces) const; Q_SIGNAL void udp_server_port_changed (port_type server_port) const; Q_SIGNAL void udp_TTL_changed (int TTL) const; Q_SIGNAL void accept_udp_requests_changed (bool checked) const; diff --git a/Configuration.ui b/Configuration.ui index fd8fb8955..1b6ef8926 100644 --- a/Configuration.ui +++ b/Configuration.ui @@ -1893,17 +1893,17 @@ and DX Grid fields when a 73 or free text message is sent. - + - Outgoing interface: + Outgoing interfaces: - udp_interface_combo_box + udp_interfaces_combo_box - + @@ -3029,6 +3029,11 @@ Right click for insert and delete options. QComboBox
widgets/LazyFillComboBox.hpp
+ + CheckableItemComboBox + QComboBox +
widgets/CheckableItemComboBox.hpp
+
configuration_tabs @@ -3111,7 +3116,7 @@ Right click for insert and delete options. psk_reporter_tcpip_check_box udp_server_line_edit udp_server_port_spin_box - udp_interface_combo_box + udp_interfaces_combo_box udp_TTL_spin_box accept_udp_requests_check_box udpWindowToFront @@ -3221,13 +3226,13 @@ Right click for insert and delete options. - + - + - + diff --git a/Network/MessageClient.cpp b/Network/MessageClient.cpp index 3474f19bb..2e721d5f7 100644 --- a/Network/MessageClient.cpp +++ b/Network/MessageClient.cpp @@ -59,7 +59,7 @@ public: enum StreamStatus {Fail, Short, OK}; - void set_server (QString const& server_name, QString const& network_interface_name); + void set_server (QString const& server_name, QStringList const& network_interface_names); Q_SLOT void host_info_results (QHostInfo); void start (); void parse_message (QByteArray const&); @@ -67,12 +67,12 @@ public: void heartbeat (); void closedown (); StreamStatus check_status (QDataStream const&) const; - void send_message (QByteArray const&); - void send_message (QDataStream const& out, QByteArray const& message) + void send_message (QByteArray const&, bool queue_if_pending = true); + void send_message (QDataStream const& out, QByteArray const& message, bool queue_if_pending = true) { if (OK == check_status (out)) { - send_message (message); + send_message (message, queue_if_pending); } else { @@ -89,7 +89,7 @@ public: QHostAddress server_; port_type server_port_; int TTL_; - QNetworkInterface network_interface_; + std::vector network_interfaces_; quint32 schema_; QTimer * heartbeat_timer_; std::vector blocked_addresses_; @@ -101,10 +101,15 @@ public: #include "MessageClient.moc" -void MessageClient::impl::set_server (QString const& server_name, QString const& network_interface_name) +void MessageClient::impl::set_server (QString const& server_name, QStringList const& network_interface_names) { server_.setAddress (server_name); - network_interface_ = QNetworkInterface::interfaceFromName (network_interface_name); + network_interfaces_.clear (); + for (auto const& net_if_name : network_interface_names) + { + network_interfaces_.push_back (QNetworkInterface::interfaceFromName (net_if_name)); + } + if (server_.isNull () && server_name.size ()) // DNS lookup required { // queue a host address lookup @@ -162,33 +167,10 @@ void MessageClient::impl::start () return; } - TRACE_UDP ("Trying server:" << server_.toString () << "on interface:" << network_interface_.humanReadableName ()); - QHostAddress interface_ip {QHostAddress::Any}; - if (network_interface_.isValid ()) - { - if (is_multicast_address (server_) && !(network_interface_.flags () & QNetworkInterface::CanMulticast)) - { - Q_EMIT self_->error ("Network interface is not multicast capable, please try another"); - return; - } - for (auto const& ae : network_interface_.addressEntries ()) - { - auto const& ip = ae.ip (); - if (server_.protocol () == ip.protocol ()) - { - interface_ip = ip; - break; - } - } - if (QHostAddress {QHostAddress::Any} == interface_ip) - { - Q_EMIT self_->error ("Network interface has no suitable address for server IP protocol, please try another"); - pending_messages_.clear (); // discard - return; - } - } + TRACE_UDP ("Trying server:" << server_.toString ()); + QHostAddress interface_addr {IPv6Protocol == server_.protocol () ? QHostAddress::AnyIPv6 : QHostAddress::AnyIPv4}; - if (localAddress () != interface_ip) + if (localAddress () != interface_addr) { if (UnconnectedState != state () || state ()) { @@ -196,8 +178,8 @@ void MessageClient::impl::start () } // bind to an ephemeral port on the selected interface and set // up for sending datagrams - bind (interface_ip); - setMulticastInterface (network_interface_); + bind (interface_addr); + qDebug () << "Bound to UDP port:" << localPort () << "on:" << localAddress (); // set multicast TTL to limit scope when sending to multicast // group addresses @@ -210,7 +192,7 @@ void MessageClient::impl::start () // clear any backlog while (pending_messages_.size ()) { - send_message (pending_messages_.dequeue ()); + send_message (pending_messages_.dequeue (), false); } } @@ -438,14 +420,11 @@ void MessageClient::impl::heartbeat () if (server_port_ && !server_.isNull ()) { QByteArray message; - NetworkMessage::Builder hb {&message, NetworkMessage::Heartbeat, id_, schema_}; - hb << NetworkMessage::Builder::schema_number // maximum schema number accepted - << version_.toUtf8 () << revision_.toUtf8 (); - if (OK == check_status (hb)) - { - TRACE_UDP ("schema:" << schema_ << "max schema:" << NetworkMessage::Builder::schema_number << "version:" << version_ << "revision:" << revision_); - writeDatagram (message, server_, server_port_); - } + NetworkMessage::Builder out {&message, NetworkMessage::Heartbeat, id_, schema_}; + out << NetworkMessage::Builder::schema_number // maximum schema number accepted + << version_.toUtf8 () << revision_.toUtf8 (); + TRACE_UDP ("schema:" << schema_ << "max schema:" << NetworkMessage::Builder::schema_number << "version:" << version_ << "revision:" << revision_); + send_message (out, message, false); } } @@ -455,15 +434,12 @@ void MessageClient::impl::closedown () { QByteArray message; NetworkMessage::Builder out {&message, NetworkMessage::Close, id_, schema_}; - if (OK == check_status (out)) - { - TRACE_UDP (""); - writeDatagram (message, server_, server_port_); - } + TRACE_UDP (""); + send_message (out, message, false); } } -void MessageClient::impl::send_message (QByteArray const& message) +void MessageClient::impl::send_message (QByteArray const& message, bool queue_if_pending) { if (server_port_) { @@ -471,11 +447,15 @@ void MessageClient::impl::send_message (QByteArray const& message) { if (message != last_message_) // avoid duplicates { - writeDatagram (message, server_, server_port_); + for (auto const& net_if : network_interfaces_) + { + setMulticastInterface (net_if); + writeDatagram (message, server_, server_port_); + } last_message_ = message; } } - else + else if (queue_if_pending) { pending_messages_.enqueue (message); } @@ -509,7 +489,7 @@ auto MessageClient::impl::check_status (QDataStream const& stream) const -> Stre MessageClient::MessageClient (QString const& id, QString const& version, QString const& revision, QString const& server_name, port_type server_port, - QString const& network_interface_name, + QStringList const& network_interface_names, int TTL, QObject * self) : QObject {self} , m_ {id, version, revision, server_port, TTL, this} @@ -532,7 +512,7 @@ MessageClient::MessageClient (QString const& id, QString const& version, QString Q_EMIT error (m_->errorString ()); } }); - m_->set_server (server_name, network_interface_name); + m_->set_server (server_name, network_interface_names); } QHostAddress MessageClient::server_address () const @@ -545,9 +525,9 @@ auto MessageClient::server_port () const -> port_type return m_->server_port_; } -void MessageClient::set_server (QString const& server_name, QString const& network_interface_name) +void MessageClient::set_server (QString const& server_name, QStringList const& network_interface_names) { - m_->set_server (server_name, network_interface_name); + m_->set_server (server_name, network_interface_names); } void MessageClient::set_server_port (port_type server_port) diff --git a/Network/MessageClient.hpp b/Network/MessageClient.hpp index d0066dbe4..cc348be2d 100644 --- a/Network/MessageClient.hpp +++ b/Network/MessageClient.hpp @@ -36,7 +36,7 @@ public: // messages will be silently dropped until a server host lookup is complete MessageClient (QString const& id, QString const& version, QString const& revision, QString const& server_name, port_type server_port, - QString const& network_interface_name, + QStringList const& network_interface_names, int TTL, QObject * parent = nullptr); // query server details @@ -46,7 +46,7 @@ public: // initiate a new server host lookup or if the server name is empty // the sending of messages is disabled, if an interface is specified // then that interface is used for outgoing datagrams - Q_SLOT void set_server (QString const& server_name, QString const& network_interface_name); + Q_SLOT void set_server (QString const& server_name, QStringList const& network_interface_names); // change the server port messages are sent to Q_SLOT void set_server_port (port_type server_port = 0u); diff --git a/UDPExamples/MessageAggregatorMainWindow.cpp b/UDPExamples/MessageAggregatorMainWindow.cpp index d110bfc44..a0fe90c47 100644 --- a/UDPExamples/MessageAggregatorMainWindow.cpp +++ b/UDPExamples/MessageAggregatorMainWindow.cpp @@ -266,17 +266,17 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow () void MessageAggregatorMainWindow::restart_server () { QSet net_ifs; - if (network_interfaces_combo_box_->isVisible ()) - { - auto model = static_cast (network_interfaces_combo_box_->model ()); - for (int row = 0; row < model->rowCount (); ++row) - { - if (Qt::Checked == model->item (row)->checkState ()) - { - net_ifs << model->item (row)->data ().toString (); - } - } - } + if (network_interfaces_combo_box_->isVisible ()) + { + auto model = static_cast (network_interfaces_combo_box_->model ()); + for (int row = 0; row < model->rowCount (); ++row) + { + if (Qt::Checked == model->item (row)->checkState ()) + { + net_ifs << model->item (row)->data ().toString (); + } + } + } server_->start (port_spin_box_->value () , QHostAddress {multicast_group_line_edit_->text ()} , net_ifs); diff --git a/UDPExamples/UDPDaemon.cpp b/UDPExamples/UDPDaemon.cpp index 78d1a0c52..2d028f074 100644 --- a/UDPExamples/UDPDaemon.cpp +++ b/UDPExamples/UDPDaemon.cpp @@ -65,14 +65,14 @@ public: { if (f != dial_frequency_) { - std::cout << tr ("%1(%2): Dial frequency changed to %3") - .arg (key_.second).arg (key_.first.toString ()).arg (f).toStdString () << std::endl; + std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString () + << QString {"Dial frequency changed to %1"}.arg (f).toStdString () << std::endl; dial_frequency_ = f; } if (mode + sub_mode != mode_) { - std::cout << tr ("%1(%2): Mode changed to %3") - .arg (key_.second).arg (key_.first.toString ()).arg (mode + sub_mode).toStdString () << std::endl; + std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString () + << QString {"Mode changed to %1"}.arg (mode + sub_mode).toStdString () << std::endl; mode_ = mode + sub_mode; } } @@ -88,8 +88,8 @@ public: << "Dt:" << delta_time << "Df:" << delta_frequency << "mode:" << mode << "Confidence:" << (low_confidence ? "low" : "high") << "On air:" << !off_air; - std::cout << tr ("%1(%2): Decoded %3") - .arg (key_.second).arg (key_.first.toString ()).arg (message).toStdString () << std::endl; + std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString () + << QString {"Decoded %1"}.arg (message).toStdString () << std::endl; } } @@ -102,8 +102,9 @@ public: qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr << "Dt:" << delta_time << "Df:" << delta_frequency << "drift:" << drift; - std::cout << tr ("%1(%2): WSPR decode %3 grid %4 power: %5") - .arg (key_.second).arg (key_.first.toString ()).arg (callsign).arg (grid).arg (power).toStdString () + std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString () + << QString {"WSPR decode %1 grid %2 power: %3"} + .arg (callsign).arg (grid).arg (power).toStdString () << "On air:" << !off_air << std::endl; } } @@ -125,12 +126,13 @@ public: << "my_grid:" << my_grid << "exchange_sent:" << exchange_sent << "exchange_rcvd:" << exchange_rcvd << "prop_mode:" << prop_mode; std::cout << QByteArray {80, '-'}.data () << '\n'; - std::cout << tr ("%1(%2): Logged %3 grid: %4 power: %5 sent: %6 recd: %7 freq: %8 time_off: %9 op: %10 my_call: %11 my_grid: %12 exchange_sent: %13 exchange_rcvd: %14 comments: %15 prop_mode: %16") - .arg (key_.second).arg (key.first.toString ()).arg (dx_call).arg (dx_grid).arg (tx_power) - .arg (report_sent).arg (report_received) - .arg (dial_frequency).arg (time_off.toString("yyyy-MM-dd hh:mm:ss.z")).arg (operator_call) - .arg (my_call).arg (my_grid).arg (exchange_sent).arg (exchange_rcvd) - .arg (comments).arg (prop_mode).toStdString () + std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString () + << QString {"Logged %1 grid: %2 power: %3 sent: %4 recd: %5 freq: %6 time_off: %7 op: %8 my_call: %9 my_grid: %10 exchange_sent: %11 exchange_rcvd: %12 comments: %13 prop_mode: %14"} + .arg (dx_call).arg (dx_grid).arg (tx_power) + .arg (report_sent).arg (report_received) + .arg (dial_frequency).arg (time_off.toString("yyyy-MM-dd hh:mm:ss.z")).arg (operator_call) + .arg (my_call).arg (my_grid).arg (exchange_sent).arg (exchange_rcvd) + .arg (comments).arg (prop_mode).toStdString () << std::endl; } } diff --git a/widgets/CheckableItemComboBox.cpp b/widgets/CheckableItemComboBox.cpp index 3ef926243..13115c993 100644 --- a/widgets/CheckableItemComboBox.cpp +++ b/widgets/CheckableItemComboBox.cpp @@ -25,7 +25,7 @@ public: }; CheckableItemComboBox::CheckableItemComboBox (QWidget * parent) - : QComboBox {parent} + : LazyFillComboBox {parent} , model_ {new QStandardItemModel()} { setModel (model_.data ()); diff --git a/widgets/CheckableItemComboBox.hpp b/widgets/CheckableItemComboBox.hpp index 2aa3e9831..21e751167 100644 --- a/widgets/CheckableItemComboBox.hpp +++ b/widgets/CheckableItemComboBox.hpp @@ -1,9 +1,10 @@ #ifndef CHECKABLE_ITEM_COMBO_BOX_HPP__ #define CHECKABLE_ITEM_COMBO_BOX_HPP__ -#include #include +#include "LazyFillComboBox.hpp" + class QStandardItemModel; class QStandardItem; @@ -12,7 +13,7 @@ class QStandardItem; * http://stackoverflow.com/questions/8422760/combobox-of-checkboxes */ class CheckableItemComboBox - : public QComboBox + : public LazyFillComboBox { Q_OBJECT diff --git a/widgets/LazyFillComboBox.hpp b/widgets/LazyFillComboBox.hpp index 37af60f0b..7ac828fed 100644 --- a/widgets/LazyFillComboBox.hpp +++ b/widgets/LazyFillComboBox.hpp @@ -10,7 +10,7 @@ class QWidget; // // QComboBox derivative that signals show and hide of the pop up list. // -class LazyFillComboBox final +class LazyFillComboBox : public QComboBox { Q_OBJECT diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index e40009cb4..2a649b4a2 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -417,7 +417,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, m_messageClient {new MessageClient {QApplication::applicationName (), version (), revision (), m_config.udp_server_name (), m_config.udp_server_port (), - m_config.udp_interface_name (), m_config.udp_TTL (), + m_config.udp_interface_names (), m_config.udp_TTL (), this}}, m_psk_Reporter {&m_config, QString {"WSJT-X v" + version () + " " + m_revision}.simplified ()}, m_manual {&m_network_manager}, @@ -7837,7 +7837,7 @@ void MainWindow::networkError (QString const& e) , MessageBox::Cancel)) { // retry server lookup - m_messageClient->set_server (m_config.udp_server_name (), m_config.udp_interface_name ()); + m_messageClient->set_server (m_config.udp_server_name (), m_config.udp_interface_names ()); } } From bdaf51a0741db853ce753467cc7381e069a020a4 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Thu, 5 Nov 2020 11:30:48 +0000 Subject: [PATCH 13/16] Comment out diagnostic prints --- Configuration.cpp | 4 ++-- Network/MessageClient.cpp | 2 +- UDPExamples/MessageServer.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Configuration.cpp b/Configuration.cpp index 7f68bb417..a13dc2723 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -2418,7 +2418,7 @@ void Configuration::impl::on_udp_server_line_edit_editingFinished () if (server.size () && ha.isNull ()) { // queue a host address lookup - qDebug () << "server host DNS lookup:" << server; + // qDebug () << "server host DNS lookup:" << server; #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) dns_lookup_id_ = QHostInfo::lookupHost (server, this, &Configuration::impl::host_info_results); #else @@ -2443,7 +2443,7 @@ void Configuration::impl::host_info_results (QHostInfo host_info) else { auto const& server_addresses = host_info.addresses (); - qDebug () << "message server addresses:" << server_addresses; + // qDebug () << "message server addresses:" << server_addresses; if (server_addresses.size ()) { check_multicast (server_addresses[0]); diff --git a/Network/MessageClient.cpp b/Network/MessageClient.cpp index 2e721d5f7..5ac6a41c1 100644 --- a/Network/MessageClient.cpp +++ b/Network/MessageClient.cpp @@ -179,7 +179,7 @@ void MessageClient::impl::start () // bind to an ephemeral port on the selected interface and set // up for sending datagrams bind (interface_addr); - qDebug () << "Bound to UDP port:" << localPort () << "on:" << localAddress (); + // qDebug () << "Bound to UDP port:" << localPort () << "on:" << localAddress (); // set multicast TTL to limit scope when sending to multicast // group addresses diff --git a/UDPExamples/MessageServer.cpp b/UDPExamples/MessageServer.cpp index 6745fe0a7..8aadd08a6 100644 --- a/UDPExamples/MessageServer.cpp +++ b/UDPExamples/MessageServer.cpp @@ -434,7 +434,7 @@ MessageServer::MessageServer (QObject * parent, QString const& version, QString void MessageServer::start (port_type port, QHostAddress const& multicast_group_address , QSet const& network_interface_names) { - qDebug () << "MessageServer::start port:" << port << "multicast addr:" << multicast_group_address.toString () << "network interfaces:" << network_interface_names; + // qDebug () << "MessageServer::start port:" << port << "multicast addr:" << multicast_group_address.toString () << "network interfaces:" << network_interface_names; if (port != m_->localPort () || multicast_group_address != m_->multicast_group_address_ || network_interface_names != m_->network_interfaces_) From b816875b4601cb0c2751c812ce36e9143b7886e8 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Fri, 6 Nov 2020 00:33:53 +0000 Subject: [PATCH 14/16] Ensure multicast UDP is sent to at least the loop-back interface Also send multicast UDP to every selected network interface. --- Configuration.cpp | 44 ++++++++++++++++++++++++++++++++++----- Configuration.ui | 11 ++++++++-- Network/MessageClient.cpp | 15 +++++++++++-- 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/Configuration.cpp b/Configuration.cpp index a13dc2723..7bd94b5d4 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -443,7 +443,8 @@ private: void load_audio_devices (QAudio::Mode, QComboBox *, QAudioDeviceInfo *); void update_audio_channels (QComboBox const *, int, QComboBox *, bool); - void load_network_interfaces (CheckableItemComboBox *, QStringList const& current); + void load_network_interfaces (CheckableItemComboBox *, QStringList current); + void validate_network_interfaces (CheckableItemComboBox *); QStringList get_selected_network_interfaces (CheckableItemComboBox *); Q_SLOT void host_info_results (QHostInfo); void check_multicast (QHostAddress const&); @@ -656,6 +657,7 @@ private: int dns_lookup_id_; port_type udp_server_port_; QStringList udp_interface_names_; + QString loopback_interface_name_; int udp_TTL_; QString n1mm_server_name_; port_type n1mm_server_port_; @@ -1085,6 +1087,9 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network load_network_interfaces (ui_->udp_interfaces_combo_box, udp_interface_names_); QGuiApplication::restoreOverrideCursor (); }); + connect (ui_->udp_interfaces_combo_box, &QComboBox::currentTextChanged, [this] (QString const& /*text*/) { + validate_network_interfaces (ui_->udp_interfaces_combo_box); + }); // set up LoTW users CSV file fetching connect (&lotw_users_, &LotWUsers::load_finished, [this] () { @@ -2995,7 +3000,7 @@ void Configuration::impl::load_audio_devices (QAudio::Mode mode, QComboBox * com } // load the available network interfaces into the selection combo box -void Configuration::impl::load_network_interfaces (CheckableItemComboBox * combo_box, QStringList const& current) +void Configuration::impl::load_network_interfaces (CheckableItemComboBox * combo_box, QStringList current) { combo_box->clear (); for (auto const& net_if : QNetworkInterface::allInterfaces ()) @@ -3003,11 +3008,13 @@ void Configuration::impl::load_network_interfaces (CheckableItemComboBox * combo auto flags = QNetworkInterface::IsUp | QNetworkInterface::CanMulticast; if ((net_if.flags () & flags) == flags) { - auto is_loopback = net_if.flags () & QNetworkInterface::IsLoopBack; + if (net_if.flags () & QNetworkInterface::IsLoopBack) + { + loopback_interface_name_ = net_if.name (); + } auto item = combo_box->addCheckItem (net_if.humanReadableName () , net_if.name () - , is_loopback || current.contains (net_if.name ()) ? Qt::Checked : Qt::Unchecked); - item->setEnabled (!is_loopback); + , current.contains (net_if.name ()) ? Qt::Checked : Qt::Unchecked); auto tip = QString {"name(index): %1(%2) - %3"}.arg (net_if.name ()).arg (net_if.index ()) .arg (net_if.flags () & QNetworkInterface::IsUp ? "Up" : "Down"); auto hw_addr = net_if.hardwareAddress (); @@ -3029,6 +3036,33 @@ void Configuration::impl::load_network_interfaces (CheckableItemComboBox * combo } } +// get the select network interfaces from the selection combo box +void Configuration::impl::validate_network_interfaces (CheckableItemComboBox * combo_box) +{ + auto model = static_cast (combo_box->model ()); + bool has_checked {false}; + int loopback_row {-1}; + for (int row = 0; row < model->rowCount (); ++row) + { + if (model->item (row)->data ().toString () == loopback_interface_name_) + { + loopback_row = row; + } + else if (Qt::Checked == model->item (row)->checkState ()) + { + has_checked = true; + } + } + if (loopback_row >= 0) + { + if (!has_checked) + { + model->item (loopback_row)->setCheckState (Qt::Checked); + } + model->item (loopback_row)->setEnabled (has_checked); + } +} + // get the select network interfaces from the selection combo box QStringList Configuration::impl::get_selected_network_interfaces (CheckableItemComboBox * combo_box) { diff --git a/Configuration.ui b/Configuration.ui index 1b6ef8926..17fd1a326 100644 --- a/Configuration.ui +++ b/Configuration.ui @@ -1885,7 +1885,7 @@ and DX Grid fields when a 73 or free text message is sent. - <html><head/><body><p>Enter the service port number of the UDP server that WSJT-X should send updates to. If this is zero no updates will be broadcast.</p></body></html> + <html><head/><body><p>Enter the service port number of the UDP server that WSJT-X should send updates to. If this is zero no updates will be sent.</p></body></html> 65534 @@ -1903,10 +1903,17 @@ and DX Grid fields when a 73 or free text message is sent. - + + + <html><head/><body><p>When sending updates to a multicast group address it is necessary to specify which network interface(s) to send them to. If the loop-back interface is multicast capable then at least that one will be selected.</p><p>For most users the loop-back interface is all that is needed, that will allow multiple other applications on the same machine to interoperate with WSJT-X. If applications running on other hosts are to receive status updates then a suitable network interface should be used.</p><p>On some Linux systems it may be necessary to enable multicast on the loop-back network interface.</p></body></html> + + + + <html><head/><body><p>Sets the number or router hops that multicast datagrams are allowed to make. Almost everyone should set this to 1 to keep outgoing multicast traffic withn the local subnet.</p></body></html> + 255 diff --git a/Network/MessageClient.cpp b/Network/MessageClient.cpp index 5ac6a41c1..3cf00ee3b 100644 --- a/Network/MessageClient.cpp +++ b/Network/MessageClient.cpp @@ -103,6 +103,7 @@ public: void MessageClient::impl::set_server (QString const& server_name, QStringList const& network_interface_names) { + // qDebug () << "MessageClient server:" << server_name << "port:" << server_port_ << "interfaces:" << network_interface_names; server_.setAddress (server_name); network_interfaces_.clear (); for (auto const& net_if_name : network_interface_names) @@ -447,9 +448,19 @@ void MessageClient::impl::send_message (QByteArray const& message, bool queue_if { if (message != last_message_) // avoid duplicates { - for (auto const& net_if : network_interfaces_) + if (is_multicast_address (server_)) { - setMulticastInterface (net_if); + // send datagram on each selected network interface + std::for_each (network_interfaces_.begin (), network_interfaces_.end () + , [&] (QNetworkInterface const& net_if) { + setMulticastInterface (net_if); + // qDebug () << "Multicast UDP datagram sent to:" << server_ << "port:" << server_port_ << "on:" << multicastInterface ().humanReadableName (); + writeDatagram (message, server_, server_port_); + }); + } + else + { + // qDebug () << "Unicast UDP datagram sent to:" << server_ << "port:" << server_port_; writeDatagram (message, server_, server_port_); } last_message_ = message; From 988fcfac5aebf2151523583ed108ea0fcb61f640 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Fri, 6 Nov 2020 01:27:36 +0000 Subject: [PATCH 15/16] Ensure network interfaces validation is run on start up --- Configuration.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Configuration.cpp b/Configuration.cpp index 7bd94b5d4..4674087b0 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -444,7 +444,7 @@ private: void update_audio_channels (QComboBox const *, int, QComboBox *, bool); void load_network_interfaces (CheckableItemComboBox *, QStringList current); - void validate_network_interfaces (CheckableItemComboBox *); + Q_SLOT void validate_network_interfaces (QString const&); QStringList get_selected_network_interfaces (CheckableItemComboBox *); Q_SLOT void host_info_results (QHostInfo); void check_multicast (QHostAddress const&); @@ -1087,9 +1087,7 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network load_network_interfaces (ui_->udp_interfaces_combo_box, udp_interface_names_); QGuiApplication::restoreOverrideCursor (); }); - connect (ui_->udp_interfaces_combo_box, &QComboBox::currentTextChanged, [this] (QString const& /*text*/) { - validate_network_interfaces (ui_->udp_interfaces_combo_box); - }); + connect (ui_->udp_interfaces_combo_box, &QComboBox::currentTextChanged, this, &Configuration::impl::validate_network_interfaces); // set up LoTW users CSV file fetching connect (&lotw_users_, &LotWUsers::load_finished, [this] () { @@ -3037,9 +3035,9 @@ void Configuration::impl::load_network_interfaces (CheckableItemComboBox * combo } // get the select network interfaces from the selection combo box -void Configuration::impl::validate_network_interfaces (CheckableItemComboBox * combo_box) +void Configuration::impl::validate_network_interfaces (QString const& /*text*/) { - auto model = static_cast (combo_box->model ()); + auto model = static_cast (ui_->udp_interfaces_combo_box->model ()); bool has_checked {false}; int loopback_row {-1}; for (int row = 0; row < model->rowCount (); ++row) From 742c180967cefc15413ba03ddaf9778e931dbe89 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Fri, 6 Nov 2020 01:28:19 +0000 Subject: [PATCH 16/16] Allow message_aggregator example to not join on he loopback interface --- UDPExamples/MessageAggregatorMainWindow.cpp | 41 ++++++++++++++++++--- UDPExamples/MessageAggregatorMainWindow.hpp | 2 + 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/UDPExamples/MessageAggregatorMainWindow.cpp b/UDPExamples/MessageAggregatorMainWindow.cpp index a0fe90c47..63b1e45b5 100644 --- a/UDPExamples/MessageAggregatorMainWindow.cpp +++ b/UDPExamples/MessageAggregatorMainWindow.cpp @@ -107,13 +107,16 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow () auto flags = QNetworkInterface::IsRunning | QNetworkInterface::CanMulticast; if ((net_if.flags () & flags) == flags) { - auto is_loopback = net_if.flags () & QNetworkInterface::IsLoopBack; + if (net_if.flags () & QNetworkInterface::IsLoopBack) + { + loopback_interface_name_ = net_if.name (); + } auto item = network_interfaces_combo_box_->addCheckItem (net_if.humanReadableName () , net_if.name () - , is_loopback ? Qt::Checked : Qt::Unchecked); - item->setEnabled (!is_loopback); - auto tip = QString {"name(index): %1(%2) - %3"}.arg (net_if.name ()).arg (net_if.index ()) - .arg (net_if.flags () & QNetworkInterface::IsUp ? "Up" : "Down"); + , Qt::Unchecked); + auto tip = QString {"name(index): %1(%2) - %3"} + .arg (net_if.name ()).arg (net_if.index ()) + .arg (net_if.flags () & QNetworkInterface::IsUp ? "Up" : "Down"); auto hw_addr = net_if.hardwareAddress (); if (hw_addr.size ()) { @@ -131,6 +134,8 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow () item->setToolTip (tip); } } + connect (network_interfaces_combo_box_, &QComboBox::currentTextChanged, this, &MessageAggregatorMainWindow::validate_network_interfaces); + validate_network_interfaces (QString {}); log_table_view_->setModel (log_); log_table_view_->verticalHeader ()->hide (); @@ -369,4 +374,30 @@ void MessageAggregatorMainWindow::change_highlighting (QString const& call, QCol } } +void MessageAggregatorMainWindow::validate_network_interfaces (QString const& /*text*/) +{ + auto model = static_cast (network_interfaces_combo_box_->model ()); + bool has_checked {false}; + int loopback_row {-1}; + for (int row = 0; row < model->rowCount (); ++row) + { + if (model->item (row)->data ().toString () == loopback_interface_name_) + { + loopback_row = row; + } + else if (Qt::Checked == model->item (row)->checkState ()) + { + has_checked = true; + } + } + if (loopback_row >= 0) + { + if (!has_checked) + { + model->item (loopback_row)->setCheckState (Qt::Checked); + } + model->item (loopback_row)->setEnabled (has_checked); + } +} + #include "moc_MessageAggregatorMainWindow.cpp" diff --git a/UDPExamples/MessageAggregatorMainWindow.hpp b/UDPExamples/MessageAggregatorMainWindow.hpp index 1bff0d692..71ab89ffe 100644 --- a/UDPExamples/MessageAggregatorMainWindow.hpp +++ b/UDPExamples/MessageAggregatorMainWindow.hpp @@ -46,6 +46,7 @@ private: void remove_client (ClientKey const&); void change_highlighting (QString const& call, QColor const& bg = QColor {}, QColor const& fg = QColor {}, bool last_only = false); + Q_SLOT void validate_network_interfaces (QString const&); // maps client id to widgets using ClientsDictionary = QHash; @@ -59,6 +60,7 @@ private: QSpinBox * port_spin_box_; QLineEdit * multicast_group_line_edit_; CheckableItemComboBox * network_interfaces_combo_box_; + QString loopback_interface_name_; QLabel * network_interfaces_form_label_widget_; QTableView * log_table_view_; QListWidget * calls_of_interest_;