Skip to content
Snippets Groups Projects
UDPLink.cc 14.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • pixhawk's avatar
    pixhawk committed
    /*=====================================================================
    
    
    lm's avatar
    lm committed
    QGroundControl Open Source Ground Control Station
    
    pixhawk's avatar
    pixhawk committed
    
    
    (c) 2009 - 2015 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
    
    pixhawk's avatar
    pixhawk committed
    
    
    lm's avatar
    lm committed
    This file is part of the QGROUNDCONTROL project
    
    pixhawk's avatar
    pixhawk committed
    
    
    lm's avatar
    lm committed
        QGROUNDCONTROL is free software: you can redistribute it and/or modify
    
    pixhawk's avatar
    pixhawk committed
        it under the terms of the GNU General Public License as published by
        the Free Software Foundation, either version 3 of the License, or
        (at your option) any later version.
    
    
    lm's avatar
    lm committed
        QGROUNDCONTROL is distributed in the hope that it will be useful,
    
    pixhawk's avatar
    pixhawk committed
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.
    
        You should have received a copy of the GNU General Public License
    
    lm's avatar
    lm committed
        along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
    
    pixhawk's avatar
    pixhawk committed
    
    ======================================================================*/
    
    /**
     * @file
     *   @brief Definition of UDP connection (server) for unmanned vehicles
     *   @author Lorenz Meier <mavteam@student.ethz.ch>
     *
     */
    
    
    #include <QtGlobal>
    #if QT_VERSION > 0x050401
    #define UDP_BROKEN_SIGNAL 1
    #else
    #define UDP_BROKEN_SIGNAL 0
    #endif
    
    
    pixhawk's avatar
    pixhawk committed
    #include <QTimer>
    #include <QList>
    #include <QDebug>
    #include <QMutexLocker>
    
    #include <QNetworkProxy>
    
    pixhawk's avatar
    pixhawk committed
    #include <iostream>
    
    pixhawk's avatar
    pixhawk committed
    #include "UDPLink.h"
    
    #include <QHostInfo>
    
    pixhawk's avatar
    pixhawk committed
    
    
    #define REMOVE_GONE_HOSTS 0
    
    
    static const char* kZeroconfRegistration = "_qgroundcontrol._udp";
    
    
    static bool is_ip(const QString& address)
    {
        int a,b,c,d;
    
        if (sscanf(address.toStdString().c_str(), "%d.%d.%d.%d", &a, &b, &c, &d) != 4
                && strcmp("::1", address.toStdString().c_str())) {
    
            return false;
    
        } else {
            return true;
        }
    
    }
    
    static QString get_ip_address(const QString& address)
    {
        if(is_ip(address))
            return address;
        // Need to look it up
        QHostInfo info = QHostInfo::fromName(address);
        if (info.error() == QHostInfo::NoError)
        {
            QList<QHostAddress> hostAddresses = info.addresses();
            QHostAddress address;
            for (int i = 0; i < hostAddresses.size(); i++)
            {
                // Exclude all IPv6 addresses
                if (!hostAddresses.at(i).toString().contains(":"))
                {
                    return hostAddresses.at(i).toString();
                }
            }
        }
        return QString("");
    }
    
    
    UDPLink::UDPLink(UDPConfiguration* config)
        : _socket(NULL)
        , _connectState(false)
    
    Gus Grubba's avatar
    Gus Grubba committed
        #if defined(QGC_ZEROCONF_ENABLED)
    
        , _dnssServiceRef(NULL)
    
    Gus Grubba's avatar
    Gus Grubba committed
        #endif
    
    pixhawk's avatar
    pixhawk committed
    {
    
        Q_ASSERT(config != NULL);
        _config = config;
        _config->setLink(this);
    
    
        // We're doing it wrong - because the Qt folks got the API wrong:
        // http://blog.qt.digia.com/blog/2010/06/17/youre-doing-it-wrong/
        moveToThread(this);
    
        //qDebug() << "UDP Created " << _config->name();
    
    pixhawk's avatar
    pixhawk committed
    }
    
    UDPLink::~UDPLink()
    {
    
        // Disconnect link from configuration
        _config->setLink(NULL);
    
    Lorenz Meier's avatar
    Lorenz Meier committed
        // Tell the thread to exit
    
    Lorenz Meier's avatar
    Lorenz Meier committed
        // Wait for it to exit
        wait();
    
        while(_outQueue.count() > 0) {
            delete _outQueue.dequeue();
        }
    
    pixhawk's avatar
    pixhawk committed
    }
    
    /**
     * @brief Runs the thread
     *
     **/
    void UDPLink::run()
    {
    
            if(UDP_BROKEN_SIGNAL) {
                bool loop = false;
                while(true) {
                    //-- Anything to read?
                    loop = _socket->hasPendingDatagrams();
                    if(loop) {
                        readBytes();
                    }
                    //-- Loop right away if busy
                    if((_dequeBytes() || loop) && _running)
                        continue;
                    if(!_running)
                        break;
                    //-- Settle down (it gets here if there is nothing to read or write)
                    this->msleep(50);
                }
            } else {
                exec();
    
            _deregisterZeroconf();
    
            _socket->close();
        }
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    pixhawk's avatar
    pixhawk committed
    {
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    pixhawk's avatar
    pixhawk committed
    {
    
    }
    
    void UDPLink::addHost(const QString& host)
    {
    
    void UDPLink::removeHost(const QString& host)
    
    pixhawk's avatar
    pixhawk committed
    }
    
    void UDPLink::writeBytes(const char* data, qint64 size)
    {
    
        if (!_socket) {
            return;
        }
    
        if(UDP_BROKEN_SIGNAL) {
            QByteArray* qdata = new QByteArray(data, size);
            QMutexLocker lock(&_mutex);
            _outQueue.enqueue(qdata);
        } else {
            _sendBytes(data, size);
        }
    
    bool UDPLink::_dequeBytes()
    
    {
        QMutexLocker lock(&_mutex);
        if(_outQueue.count() > 0) {
            QByteArray* qdata = _outQueue.dequeue();
            lock.unlock();
    
            _sendBytes(qdata->data(), qdata->size());
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    void UDPLink::_sendBytes(const char* data, qint64 size)
    {
        QStringList goneHosts;
        // Send to all connected systems
        QString host;
        int port;
        if(_config->firstHost(host, port)) {
            do {
                QHostAddress currentHost(host);
                if(_socket->writeDatagram(data, size, currentHost, (quint16)port) < 0) {
                    // This host is gone. Add to list to be removed
    
                    // We should keep track of hosts that were manually added (static) and
                    // hosts that were added because we heard from them (dynamic). Only
                    // dynamic hosts should be removed and even then, after a few tries, not
                    // the first failure. In the mean time, we don't remove anything.
                    if(REMOVE_GONE_HOSTS) {
                        goneHosts.append(host);
                    }
                } else {
                    // Only log rate if data actually got sent. Not sure about this as
                    // "host not there" takes time too regardless of size of data. In fact,
                    // 1 byte or "UDP frame size" bytes are the same as that's the data
                    // unit sent by UDP.
                    _logOutputDataRate(size, QDateTime::currentMSecsSinceEpoch());
    
                }
            } while (_config->nextHost(host, port));
            //-- Remove hosts that are no longer there
            foreach (QString ghost, goneHosts) {
                _config->removeHost(ghost);
            }
        }
    }
    
    
    pixhawk's avatar
    pixhawk committed
    /**
     * @brief Read a number of bytes from the interface.
     **/
    
    pixhawk's avatar
    pixhawk committed
    {
    
        {
            QByteArray datagram;
    
            datagram.resize(_socket->pendingDatagramSize());
    
    pixhawk's avatar
    pixhawk committed
    
    
            QHostAddress sender;
            quint16 senderPort;
    
            _socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
    
    pixhawk's avatar
    pixhawk committed
    
    
            // FIXME TODO Check if this method is better than retrieving the data by individual processes
            emit bytesReceived(this, datagram);
    
    pixhawk's avatar
    pixhawk committed
    
    
            _logInputDataRate(datagram.length(), QDateTime::currentMSecsSinceEpoch());
    
    //        // Echo data for debugging purposes
    //        std::cerr << __FILE__ << __LINE__ << "Received datagram:" << std::endl;
    //        int i;
    //        for (i=0; i<s; i++)
    //        {
    //            unsigned int v=data[i];
    //            fprintf(stderr,"%02x ", v);
    //        }
    //        std::cerr << std::endl;
    
    pixhawk's avatar
    pixhawk committed
    
    
            // TODO This doesn't validade the sender. Anything sending UDP packets to this port gets
            // added to the list and will start receiving datagrams from here. Even a port scanner
            // would trigger this.
            // Add host to broadcast list if not yet present, or update its port
    
    dogmaphobic's avatar
    dogmaphobic committed
            _config->addHost(sender.toString(), (int)senderPort);
    
            if(UDP_BROKEN_SIGNAL && !_running)
    
    pixhawk's avatar
    pixhawk committed
        }
    }
    
    /**
     * @brief Disconnect the connection.
     *
     * @return True if connection has been disconnected, false if connection couldn't be disconnected.
     **/
    
    bool UDPLink::_disconnect(void)
    
    pixhawk's avatar
    pixhawk committed
    {
    
        if (_socket) {
            // Make sure delete happen on correct thread
            _socket->deleteLater();
            _socket = NULL;
    
            emit disconnected();
    
    pixhawk's avatar
    pixhawk committed
    }
    
    /**
     * @brief Connect the connection.
     *
     * @return True if connection has been established, false if connection couldn't be established.
     **/
    
    bool UDPLink::_connect(void)
    
    pixhawk's avatar
    pixhawk committed
    {
    
        if(this->isRunning() || _running)
    
        start(NormalPriority);
    
    oberion's avatar
    oberion committed
    }
    
    
    oberion's avatar
    oberion committed
    {
    
        if (_socket) {
            delete _socket;
            _socket = NULL;
        }
    
        QHostAddress host = QHostAddress::AnyIPv4;
    
        _socket->setProxy(QNetworkProxy::NoProxy);
    
        _connectState = _socket->bind(host, _config->localPort(), QAbstractSocket::ReuseAddressHint | QUdpSocket::ShareAddress);
    
            _registerZeroconf(_config->localPort(), kZeroconfRegistration);
    
            //-- Connect signal if this version of Qt is not broken
            if(!UDP_BROKEN_SIGNAL) {
                QObject::connect(_socket, SIGNAL(readyRead()), this, SLOT(readBytes()));
            }
    
        } else {
            emit communicationError("UDP Link Error", "Error binding UDP port");
    
    pixhawk's avatar
    pixhawk committed
    }
    
    /**
     * @brief Check if connection is active.
     *
     * @return True if link is connected, false otherwise.
     **/
    
    bool UDPLink::isConnected() const
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    qint64 UDPLink::getConnectionSpeed() const
    
    pixhawk's avatar
    pixhawk committed
    {
    
        return 54000000; // 54 Mbit
    }
    
    qint64 UDPLink::getCurrentInDataRate() const
    {
        return 0;
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    qint64 UDPLink::getCurrentOutDataRate() const
    
    pixhawk's avatar
    pixhawk committed
    {
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    void UDPLink::_registerZeroconf(uint16_t port, const std::string &regType)
    {
    #if defined(QGC_ZEROCONF_ENABLED)
        DNSServiceErrorType result = DNSServiceRegister(&_dnssServiceRef, 0, 0, 0,
            regType.c_str(),
            NULL,
            NULL,
            htons(port),
            0,
            NULL,
            NULL,
            NULL);
        if (result != kDNSServiceErr_NoError)
        {
            emit communicationError("UDP Link Error", "Error registering Zeroconf");
            _dnssServiceRef = NULL;
        }
    #else
        Q_UNUSED(port);
        Q_UNUSED(regType);
    #endif
    }
    
    void UDPLink::_deregisterZeroconf()
    {
    #if defined(QGC_ZEROCONF_ENABLED)
        if (_dnssServiceRef)
         {
             DNSServiceRefDeallocate(_dnssServiceRef);
             _dnssServiceRef = NULL;
         }
    #endif
    }
    
    
    //--------------------------------------------------------------------------
    //-- UDPConfiguration
    
    UDPConfiguration::UDPConfiguration(const QString& name) : LinkConfiguration(name)
    
    UDPConfiguration::UDPConfiguration(UDPConfiguration* source) : LinkConfiguration(source)
    
        _localPort = source->localPort();
        _hosts.clear();
        QString host;
        int port;
        if(source->firstHost(host, port)) {
            do {
                addHost(host, port);
            } while(source->nextHost(host, port));
        }
    
    void UDPConfiguration::copyFrom(LinkConfiguration *source)
    
        LinkConfiguration::copyFrom(source);
    
        UDPConfiguration* usource = dynamic_cast<UDPConfiguration*>(source);
        Q_ASSERT(usource != NULL);
        _localPort = usource->localPort();
    
        _hosts.clear();
    
        QString host;
        int port;
        if(usource->firstHost(host, port)) {
            do {
                addHost(host, port);
            } while(usource->nextHost(host, port));
        }
    }
    
    /**
     * @param host Hostname in standard formatt, e.g. localhost:14551 or 192.168.1.1:14551
     */
    void UDPConfiguration::addHost(const QString& host)
    {
    
        // Handle x.x.x.x:p
    
            addHost(host.split(":").first(), host.split(":").last().toInt());
    
        // If no port, use default
    
    dogmaphobic's avatar
    dogmaphobic committed
            addHost(host, (int)_localPort);
    
        }
    }
    
    void UDPConfiguration::addHost(const QString& host, int port)
    {
    
    dogmaphobic's avatar
    dogmaphobic committed
        QMutexLocker locker(&_confMutex);
        if(_hosts.contains(host)) {
            if(_hosts[host] != port) {
                _hosts[host] = port;
            }
        } else {
    
            QString ipAdd = get_ip_address(host);
            if(ipAdd.isEmpty()) {
    
                qWarning() << "UDP:" << "Could not resolve host:" << host << "port:" << port;
    
            } else {
                _hosts[ipAdd] = port;
    
                //qDebug() << "UDP:" << "Adding Host:" << ipAdd << ":" << port;
    
    dogmaphobic's avatar
    dogmaphobic committed
            }
        }
    
    }
    
    void UDPConfiguration::removeHost(const QString& host)
    {
    
    dogmaphobic's avatar
    dogmaphobic committed
        QMutexLocker locker(&_confMutex);
    
        QString tHost = host;
        if (tHost.contains(":")) {
            tHost = tHost.split(":").first();
        }
        tHost = tHost.trimmed();
        QMap<QString, int>::iterator i = _hosts.find(tHost);
        if(i != _hosts.end()) {
    
            //qDebug() << "UDP:" << "Removed host:" << host;
    
        } else {
            qWarning() << "UDP:" << "Could not remove unknown host:" << host;
    
        }
    }
    
    bool UDPConfiguration::firstHost(QString& host, int& port)
    {
    
    dogmaphobic's avatar
    dogmaphobic committed
        _confMutex.lock();
    
        _it = _hosts.begin();
        if(_it == _hosts.end()) {
    
    dogmaphobic's avatar
    dogmaphobic committed
            _confMutex.unlock();
    
    dogmaphobic's avatar
    dogmaphobic committed
        _confMutex.unlock();
    
        return nextHost(host, port);
    }
    
    bool UDPConfiguration::nextHost(QString& host, int& port)
    {
    
    dogmaphobic's avatar
    dogmaphobic committed
        QMutexLocker locker(&_confMutex);
    
        if(_it != _hosts.end()) {
            host = _it.key();
            port = _it.value();
            _it++;
            return true;
        }
        return false;
    }
    
    void UDPConfiguration::setLocalPort(quint16 port)
    {
        _localPort = port;
    }
    
    void UDPConfiguration::saveSettings(QSettings& settings, const QString& root)
    {
        _confMutex.lock();
        settings.beginGroup(root);
        settings.setValue("port", (int)_localPort);
        settings.setValue("hostCount", _hosts.count());
        int index = 0;
        QMap<QString, int>::const_iterator it = _hosts.begin();
        while(it != _hosts.end()) {
            QString hkey = QString("host%1").arg(index);
            settings.setValue(hkey, it.key());
            QString pkey = QString("port%1").arg(index);
            settings.setValue(pkey, it.value());
            it++;
            index++;
        }
        settings.endGroup();
        _confMutex.unlock();
    }
    
    void UDPConfiguration::loadSettings(QSettings& settings, const QString& root)
    {
        _confMutex.lock();
        _hosts.clear();
    
    dogmaphobic's avatar
    dogmaphobic committed
        _confMutex.unlock();
        settings.beginGroup(root);
    
        _localPort = (quint16)settings.value("port", QGC_UDP_LOCAL_PORT).toUInt();
    
        int hostCount = settings.value("hostCount", 0).toInt();
        for(int i = 0; i < hostCount; i++) {
            QString hkey = QString("host%1").arg(i);
            QString pkey = QString("port%1").arg(i);
            if(settings.contains(hkey) && settings.contains(pkey)) {
                addHost(settings.value(hkey).toString(), settings.value(pkey).toInt());
            }
        }
        settings.endGroup();
    }
    
    void UDPConfiguration::updateSettings()
    {
        if(_link) {
            UDPLink* ulink = dynamic_cast<UDPLink*>(_link);
            if(ulink) {
                ulink->_restartConnection();
            }
        }