diff options
Diffstat (limited to 'src/libs/zeroconf/servicebrowser.cpp')
-rw-r--r-- | src/libs/zeroconf/servicebrowser.cpp | 2103 |
1 files changed, 0 insertions, 2103 deletions
diff --git a/src/libs/zeroconf/servicebrowser.cpp b/src/libs/zeroconf/servicebrowser.cpp deleted file mode 100644 index 1ec63e8cda1..00000000000 --- a/src/libs/zeroconf/servicebrowser.cpp +++ /dev/null @@ -1,2103 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://www.qt.io/licensing. For further information -** use the contact form at http://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -****************************************************************************/ -#include "zeroconf_global.h" -#ifdef Q_OS_WIN -// Disable min max macros from windows headers, -// because we want to use the template methods from std. -# ifndef NOMINMAX -# define NOMINMAX -# endif -# include <winsock2.h> -#endif // Q_OS_WIN - -#include "mdnsderived.h" -#include "servicebrowser_p.h" - -#include <algorithm> -#include <errno.h> -#include <limits> -#include <signal.h> -#ifdef Q_OS_UNIX -// for select() -# include <unistd.h> -#endif - -#include <QAtomicPointer> -#include <QCoreApplication> -#include <QDateTime> -#include <QGlobalStatic> -#include <QMetaType> -#include <QMutexLocker> -#include <QNetworkInterface> -#include <QString> -#include <QStringList> -#include <QtEndian> -#include <QHostInfo> - -//the timeval struct under windows uses long instead of suseconds_t -#ifdef Q_OS_WIN -typedef long suseconds_t; -#endif - -/*! - \namespace ZeroConf - - \brief The ZeroConf namespace provides zeroconf (Bonjour/DNS-SD) - functionality, currently mostly for browsing services. -*/ - -namespace { // anonymous namespace for free functions -// ----------------- free functions ----------------- - -using namespace ZeroConf; -using namespace ZeroConf::Internal; - -QString toFullNameC(const char * const service, const char * const regtype, const char * const domain) -{ - char fullName[kDNSServiceMaxDomainName]; - myDNSServiceConstructFullName(fullName, service, regtype, domain); - fullName[kDNSServiceMaxDomainName - 1] = 0; // just to be sure - return QString::fromUtf8(fullName); -} - -int fromFullNameC(const char * const fullName, QString &service, QString ®type, QString &domain) -{ - char fullNameDecoded[kDNSServiceMaxDomainName]; - int encodedI = 0; - int decodedI = 0; - int oldPos[4]; - int iPos = 0; - while (fullName[encodedI] != 0 && encodedI <kDNSServiceMaxDomainName){ - char c = fullName[encodedI++]; - if (c == '\\'){ - c = fullName[++encodedI]; - if (c == 0 || encodedI == kDNSServiceMaxDomainName) - return 1; - if (c >= '0' && c <= '9'){ - int val = (c - '0') * 100; - c = fullName[++encodedI]; - if (c < '0' || c > '9' || encodedI == kDNSServiceMaxDomainName) - return 2; - val += (c - '0') * 10; - c = fullName[++encodedI]; - if (c < '0' || c > '9') - return 3; - val += (c - '0'); - fullNameDecoded[decodedI++] = static_cast<char>(static_cast<unsigned char>(val)); - } else { - fullNameDecoded[decodedI++] = c; - } - } else if (c == '.') { - if (iPos < 4) - oldPos[iPos++] = decodedI; - fullNameDecoded[decodedI++] = c; - } else { - fullNameDecoded[decodedI++] = c; - } - } - if (iPos != 4) return 5; - service = QString::fromUtf8(&fullNameDecoded[0], oldPos[0]); - regtype = QString::fromUtf8(&fullNameDecoded[oldPos[0] + 1], oldPos[3] - oldPos[0] - 1); - domain = QString::fromUtf8(&fullNameDecoded[oldPos[3] + 1], decodedI - oldPos[3] - 1); - return 0; -} - -/// singleton for lib setup -class ZeroConfLib -{ -public: - ZeroConfLib(); - ZConfLib::Ptr defaultLib(); - void setDefaultLib(LibUsage usage, const QString &avahiLibName, const QString &avahiVersion, - const QString &dnsSdLibName, const QString &dnsSdDaemonPath); - int nFallbacksTot() const; - -private: - static const char *defaultmDnsSdLibName; - static const char *defaultmDNSDaemonName; - int m_nFallbacksTot; - QMutex m_lock; - ZConfLib::Ptr m_defaultLib; -}; - -Q_GLOBAL_STATIC(ZeroConfLib, zeroConfLibInstance) - -#ifdef Q_OS_WIN - const char *ZeroConfLib::defaultmDnsSdLibName = "dnssd"; - const char *ZeroConfLib::defaultmDNSDaemonName = "mdnssd.exe"; -#else - const char *ZeroConfLib::defaultmDnsSdLibName = "dns_sd"; - const char *ZeroConfLib::defaultmDNSDaemonName = "mdnssd"; -#endif - -ZeroConfLib::ZeroConfLib(): m_lock(QMutex::Recursive), - m_defaultLib(ZConfLib::createDnsSdLib(QLatin1String(defaultmDnsSdLibName), - ZConfLib::createEmbeddedLib(QString(), - ZConfLib::createAvahiLib(QLatin1String("avahi-client"),QLatin1String("3"), - ZConfLib::createEmbeddedLib(QLatin1String(defaultmDNSDaemonName)))))) -{ - qRegisterMetaType<ZeroConf::Service::ConstPtr>("ZeroConf::Service::ConstPtr"); - qRegisterMetaType<ZeroConf::ErrorMessage::SeverityLevel>("ZeroConf::ErrorMessage::SeverityLevel"); - qRegisterMetaType<ZeroConf::ErrorMessage>("ZeroConf::ErrorMessage"); - qRegisterMetaType<QList<ZeroConf::ErrorMessage> >("QList<ZeroConf::ErrorMessage>"); - m_nFallbacksTot = (m_defaultLib ? m_defaultLib->nFallbacks() : 0); - -} - -ZConfLib::Ptr ZeroConfLib::defaultLib(){ - QMutexLocker l(&m_lock); - return m_defaultLib; -} - -void ZeroConfLib::setDefaultLib(LibUsage usage, const QString &avahiLibName, - const QString &avahiVersion, const QString &dnsSdLibName, - const QString &dnsSdDaemonPath){ - QMutexLocker l(&m_lock); - switch (usage){ - case (UseDnsSdOnly): - m_defaultLib = ZConfLib::createDnsSdLib(dnsSdLibName); - break; - case (UseEmbeddedOnly): - m_defaultLib = ZConfLib::createEmbeddedLib(dnsSdDaemonPath); - break; - case (UseAvahiOnly): - m_defaultLib = ZConfLib::createAvahiLib(avahiLibName, avahiVersion); - break; - case (UseAvahiOrDnsSd): - m_defaultLib = ZConfLib::createAvahiLib(avahiLibName, avahiVersion, - ZConfLib::createDnsSdLib(dnsSdLibName)); - break; - case (UseAvahiOrDnsSdOrEmbedded): - m_defaultLib = ZConfLib::createAvahiLib(avahiLibName, avahiVersion, - ZConfLib::createDnsSdLib(dnsSdLibName, - ZConfLib::createEmbeddedLib(dnsSdDaemonPath))); - break; - default: - qDebug() << "invalid usage " << usage; - } - int newNFallbacks = m_defaultLib->nFallbacks(); - if (m_nFallbacksTot < newNFallbacks) - m_nFallbacksTot = newNFallbacks; -} - -int ZeroConfLib::nFallbacksTot() const -{ - return m_nFallbacksTot; -} - -} // end anonymous namespace - -namespace ZeroConf { - -int gQuickStop = 0; - -// ----------------- ErrorMessage impl ----------------- -/*! - \class ZeroConf::ErrorMessage - - \brief The ErrorMessage class represents an error message. - - */ - -/// empty constructor (required by qRegisterMetaType) -ErrorMessage::ErrorMessage(): severity(FailureLevel) {} -/// constructor -ErrorMessage::ErrorMessage(SeverityLevel s, const QString &m): severity(s), msg(m) {} - -/// returns a human readable string for each severity level -QString ErrorMessage::severityLevelToString(ErrorMessage::SeverityLevel severity) -{ - const char *ctx = "Zeroconf::SeverityLevel"; - switch (severity) { - case NoteLevel: - return QCoreApplication::translate(ctx, "NOTE"); - case WarningLevel: - return QCoreApplication::translate(ctx, "WARNING"); - case ErrorLevel: - return QCoreApplication::translate(ctx, "ERROR"); - case FailureLevel: - return QCoreApplication::translate(ctx, "FATAL_ERROR"); - default: - return QCoreApplication::translate(ctx, "UNKNOWN_LEVEL_%1").arg(severity); - } -} - -QDebug operator<<(QDebug dbg, const ErrorMessage &eMsg) -{ - dbg << ErrorMessage::severityLevelToString(eMsg.severity) << eMsg.msg; - return dbg; -} - -// ----------------- Service impl ----------------- -/*! - \class ZeroConf::Service - - \brief The Service class represents a zeroconf service. - - Instances of this class are basically constant, but can be outdated. They are normally accessed - through a Shared pointer. - This design avoids race conditions when used though multiple threads. - - \threadsafe - */ - -Service::Service(const Service &o) : - m_name(o.m_name), - m_type(o.m_type), - m_domain(o.m_domain), - m_fullName(o.m_fullName), - m_port(o.m_port), - m_txtRecord(o.m_txtRecord), - m_host(o.m_host ? new QHostInfo(*o.m_host) : 0), - m_interfaceNr(o.m_interfaceNr), - m_outdated(o.m_outdated) -{ -} - -Service::Service() : m_host(0), m_interfaceNr(0), m_outdated(false) -{ } - -Service::~Service() -{ - delete m_host; -} - -/*! - Returns the interface on which the service is reachable. - */ -QNetworkInterface Service::networkInterface() const -{ - return QNetworkInterface::interfaceFromIndex(m_interfaceNr); -} - -QStringList Service::addresses(Service::AddressStyle style) const -{ - QStringList res; - if (host() == 0) - return res; - foreach (const QHostAddress &addr, host()->addresses()){ - QString addrStr; - if (addr.protocol() == QAbstractSocket::IPv6Protocol) { - // Add the interface to use to the address <address>%<interface> - // - // This is required only for link local addresses (like fe80:*) - // but as we have it we do it (and most likely addresses here are - // link local). - // - // on windows only addresses like fe80::42%22 work - // on OSX 10.5 only things like fe80::42%en4 work - // on later OSX versions and linux both <address>%<interface number> - // and <address>%<interface name> work, we use the interface name as - // it looks better -#ifdef Q_OS_WIN - QString interfaceStr = QString::number(networkInterface().index()); -#else - QString interfaceStr = networkInterface().name(); -#endif - addrStr = QString::fromLatin1("%1%%2").arg(addr.toString()).arg(interfaceStr); - if (style == QuoteIPv6Adresses) - addrStr = QString::fromLatin1("[%1]").arg(addrStr); - } else { - addrStr = addr.toString(); - } - res.append(addrStr); - } - return res; -} - -bool Service::operator==(const Service &o) const { - bool eq = m_fullName == o.m_fullName - && m_name == o.m_name && m_type == o.m_type - && m_domain == o.m_domain && m_port == o.m_port - && m_txtRecord == o.m_txtRecord && m_interfaceNr == o.m_interfaceNr - && m_outdated == o.m_outdated; - if (eq) { - if (m_host != o.m_host) { - if (m_host == 0 || o.m_host == 0) - return false; - return m_host->hostName() == o.m_host->hostName() - && m_host->addresses() == o.m_host->addresses(); - } - } - return eq; -} - -QDebug operator<<(QDebug dbg, const Service &service) -{ - dbg.maybeSpace() << "Service{ name:" << service.name() << ", " - << "type:" << service.type() << ", domain:" << service.domain() << ", " - << " fullName:" << service.fullName() << ", port:" << service.port() - << ", txtRecord:{"; - bool first = true; - QHashIterator<QString, QString> i(service.txtRecord()); - while (i.hasNext()){ - i.next(); - if (first) - first = false; - else - dbg << ", "; - dbg << i.key() << ":" << i.value(); - } - dbg << "}, "; - if (const QHostInfo *host = service.host()){ - dbg << "host:{" << host->hostName() << ", addresses["; - first = true; - foreach (const QHostAddress &addr, host->addresses()){ - if (first) - first = false; - else - dbg << ", "; - dbg << addr.toString(); - } - dbg << "], },"; - } else { - dbg << " host:*null*,"; - } - dbg << " interfaceNr:" << service.interfaceNr() << ", outdated:" << service.outdated() << " }"; - return dbg.space(); -} - -QDebug operator<<(QDebug dbg, const Service::ConstPtr &service){ - if (service.data() == 0) - dbg << "Service{*NULL*}"; - else - dbg << *service.data(); - return dbg; -} - -// inline methods -/*! - \fn bool Service::outdated() const - - Returns whether the service data is outdated. Its value might change even on - the (otherwise constant) objects returned by a ServiceBrowser. -*/ -/*! - \fn QString Service::name() const - - Returns the name of the service (non escaped). -*/ -/*! - \fn QString Service::type() const - - Returns the name of the service type (non escaped). -*/ -/*! - \fn QString Service::domain() const - - Returns the name of the domain (non escaped). -*/ -/*! - \fn QString Service::fullName() const - - Returns the full name (service.type.domain) with each component correctly escaped. -*/ -/*! - \fn QString Service::port() const - - Returns the port of the service (as a string, not as number). -*/ -/*! - \fn const Service::ServiceTxtRecord &txtRecord() const - - Returns the extra information on this service. -*/ -/*! - \fn const Service::QHostInfo *host() const - - Returns the host through which this service is reachable. -*/ -/*! - \fn int Service::interfaceNr() const - - Returns the interface on which the service is reachable, 1 based, 0 means to - try all interfaces. -*/ -/*! - \fn bool Service::invalidate() - - Marks this service as outdated. -*/ - -// ----------------- ServiceBrowser impl ----------------- -/*! - \class ZeroConf::ServiceBrowser - - \brief The ServiceBrowser class browses (searches) for a given zeronconf - service. - - The actual browsing starts only when startBrowsing() is called. If you want to receive all service - changes, connect before starting browsing. - - The current list of services can be gathered with the services() method. - - \threadsafe - */ - -/// starts the browsing, return true if successfull -void ServiceBrowser::startBrowsing(qint32 interfaceIndex) -{ - d->startBrowsing(interfaceIndex); -} - -/// create a new brower for the given service type -ServiceBrowser::ServiceBrowser(const QString &serviceType, const QString &domain, - AddressesSetting addressesSetting, QObject *parent) - : QObject(parent), timer(0), - d(new ServiceBrowserPrivate(serviceType, domain, addressesSetting == RequireAddresses, - MainConnectionPtr())) -{ - connect(this,SIGNAL(activateAutoRefresh()),this,SLOT(autoRefresh())); - d->q = this; -} - -ServiceBrowser::ServiceBrowser(const MainConnectionPtr &mainConnection, const QString &serviceType, - const QString &domain, AddressesSetting addressesSetting, QObject *parent) - : QObject(parent), timer(0), - d(new ServiceBrowserPrivate(serviceType, domain, addressesSetting == RequireAddresses, - mainConnection)) -{ - d->q = this; -} - -ServiceBrowser::~ServiceBrowser() -{ - delete d; -} - -/// returns the main connection used by this ServiceBrowser -MainConnectionPtr ServiceBrowser::mainConnection() const -{ - return d->mainConnection; -} - -/// stops browsing, but does not delete all services found -void ServiceBrowser::stopBrowsing() -{ - if (timer) { - timer->stop(); - delete timer; - timer = 0; - } - d->stopBrowsing(); -} - -/// starts an explicit browse (important especially with avahi) -void ServiceBrowser::triggerRefresh() -{ - d->triggerRefresh(); -} - -/// if the service is currently active -bool ServiceBrowser::isBrowsing() const -{ - return d->browsing; -} - -/// type of the service browsed (non escaped) -const QString& ServiceBrowser::serviceType() const -{ - return d->serviceType; -} - -/// domain that is browser (non escaped) -const QString& ServiceBrowser::domain() const -{ - return d->domain; -} - -/// if addresses should be resolved automatically for each service found -bool ServiceBrowser::adressesAutoResolved() const -{ - return d->autoResolveAddresses; -} - -/// if addresses are required to add the service to the list of available services -bool ServiceBrowser::addressesRequired() const -{ - return d->requireAddresses; -} - -/// list of current services (by copy on purpose) -QList<Service::ConstPtr> ServiceBrowser::services() const -{ - QMutexLocker l(d->mainConnection->lock()); - return d->activeServices; -} - -/// forces a full update of a service (call this after a failure to connect to the service) -/// this is an expensive call, use only when needed -void ServiceBrowser::reconfirmService(Service::ConstPtr service) -{ - d->reconfirmService(service); -} - -void ServiceBrowser::autoRefresh() -{ - QMutexLocker l(d->mainConnection->lock()); - if (!timer) { - timer = new QTimer(this); - connect(timer,SIGNAL(timeout()),this,SLOT(triggerRefresh())); - timer->setSingleShot(true); - } - timer->start(5000); -} - -// signals -/*! - \fn void ServiceBrowser::serviceChanged( - Service::ConstPtr oldService, Service::ConstPtr newService, ServiceBrowser *browser) - - This signal is called when a service is added, removed, or changed. - Both oldService or newService might be null (covers both add and remove). - The services list might not be synchronized with respect to this signal. -*/ -/*! - \fn void ServiceBrowser::serviceAdded(Service::ConstPtr service, ServiceBrowser *browser) - - This signal is called when a service is added (convenience method). - The services list might not be synchronized with respect to this signal. - - \sa serviceChanged() -*/ -/*! - \fn void ServiceBrowser::serviceRemoved(Service::ConstPtr service, ServiceBrowser *browser) - - This signal is called when a service is removed (convenience method). - The services list might not be synchronized with respect to this signal. - - \sa serviceChanged() -*/ -/*! - \fn void ServiceBrowser::servicesUpdated(ServiceBrowser *browser) - - This signal is called when the list is updated. - It might collect several serviceChanged signals together. If you use the list - returned by services(), use this signal, not serviceChanged(), serviceAdded(), - or serviceRemoved() to learn - about changes to the list. -*/ -/*! - \fn void errorMessage(ZeroConf::ErrorMessage::SeverityLevel severity, const QString &msg, ZeroConf::ServiceBrowser *browser) - - This signal is called every time a warning or error is emitted (for example when a library - cannot be used and another one has to be used). Zeroconf will still work if severity < FailureLevel. -*/ -/*! - \fn void hadFailure(const QList<ZeroConf::ErrorMessage> &messages, ZeroConf::ServiceBrowser *browser) - - This signal is emitted only when a full failure has happened, and all the previous errors/attempts to set up zeroconf - are passed in messages. -*/ -/*! - \fn void startedBrowsing(ZeroConf::ServiceBrowser *browser) - - This signal is emitted when browsing has actually started. - One can rely on either startedBrowsing or hadFailure to be emitted. -*/ - -// ----------------- library initialization impl ----------------- -/*! - Sets the library used by future Service Browsers to preform the mdns queries. - This changes the default library used by the next MainConnection, it does not change the already - instantiated connections. - \a usage can decide which libraries are tried, - \a libName should be the name (or path) to the libdns library, - \a daemonPath is the path to the daemon executable which should be started by the embedded library - if no daemon is found. - - \threadsafe -*/ -void setDefaultZConfLib(LibUsage usage, const QString &avahiLibName, const QString &version, - const QString &dnsSdLibName,const QString &dnsSdDaemonPath) -{ - zeroConfLibInstance()->setDefaultLib(usage, avahiLibName, version, dnsSdLibName, dnsSdDaemonPath); -} - -namespace Internal { -// ----------------- dns-sd C callbacks ----------------- - -extern "C" void DNSSD_API cServiceResolveReply(DNSServiceRef sdRef, - DNSServiceFlags flags, - uint32_t interfaceIndex, - DNSServiceErrorType errorCode, - const char *fullname, - const char *hosttarget, - uint16_t port, /* In network byte order */ - uint16_t txtLen, - const unsigned char *txtRecord, - void *context) -{ - if (DEBUG_ZEROCONF) - qDebug() << "cServiceResolveReply(" << ((size_t)sdRef) << ", " << ((quint32)flags) - << ", " << interfaceIndex << ", " << ((int)errorCode) << ", " << fullname - << ", " << hosttarget << ", " << qFromBigEndian(port) << ", " << txtLen << ", '" - << QString::fromUtf8((const char *)txtRecord, txtLen) << "', " - << ((size_t)context); - ServiceGatherer *ctxGatherer = reinterpret_cast<ServiceGatherer *>(context); - if (ctxGatherer){ - if (ctxGatherer->currentService->fullName() != QString::fromLocal8Bit(fullname)) { - qDebug() << "ServiceBrowser " << ctxGatherer->serviceBrowser->serviceType - << " for service " << ctxGatherer->currentService->name() - << " ignoring resolve reply for " << fullname << " vs. " - << ctxGatherer->currentService->fullName(); - return; - } - ctxGatherer->serviceResolveReply(flags, interfaceIndex, errorCode, hosttarget, - QString::number(qFromBigEndian(port)), txtLen, txtRecord); - } -} - -extern "C" void DNSSD_API cTxtRecordReply(DNSServiceRef sdRef, - DNSServiceFlags flags, - uint32_t interfaceIndex, - DNSServiceErrorType errorCode, - const char *fullname, - uint16_t rrtype, - uint16_t rrclass, - uint16_t rdlen, - const void *rdata, - uint32_t ttl, - void *context) -{ - if (DEBUG_ZEROCONF) - qDebug() << "cTxtRecordReply(" << ((size_t)sdRef) << ", " << ((int)flags) << ", " - << interfaceIndex << ", " << ((int)errorCode) << ", " << fullname << ", " - << rrtype << ", " << rrclass << ", " << ", " << rdlen - << QString::fromUtf8((const char *)rdata, rdlen) << "', " << ttl << ", " - << ((size_t)context); - ServiceGatherer *ctxGatherer = reinterpret_cast<ServiceGatherer *>(context); - if (ctxGatherer){ - if (rrtype != kDNSServiceType_TXT || rrclass != kDNSServiceClass_IN) { - qDebug() << "ServiceBrowser " << ctxGatherer->serviceBrowser->serviceType - << " for service " << ctxGatherer->currentService->fullName() - << " received an unexpected rrtype/class:" << rrtype << "/" << rrclass; - } - ctxGatherer->txtRecordReply(flags, errorCode, rdlen, rdata, ttl); - } -} - -extern "C" void DNSSD_API cAddrReply(DNSServiceRef sdRef, - DNSServiceFlags flags, - uint32_t interfaceIndex, - DNSServiceErrorType errorCode, - const char *hostname, - const struct sockaddr *address, - uint32_t ttl, - void *context) -{ - if (DEBUG_ZEROCONF) - qDebug() << "cAddrReply(" << ((size_t)sdRef) << ", " << ((int)flags) << ", " - << interfaceIndex << ", " << ((int)errorCode) << ", " << hostname << ", " - << QHostAddress(address).toString() << ", " << ttl << ", " << ((size_t)context); - ServiceGatherer *ctxGatherer = reinterpret_cast<ServiceGatherer *>(context); - if (ctxGatherer) - ctxGatherer->addrReply(flags, errorCode, hostname, address, ttl); -} - -/// callback for service browsing -extern "C" void DNSSD_API cBrowseReply(DNSServiceRef sdRef, - DNSServiceFlags flags, - uint32_t interfaceIndex, - DNSServiceErrorType errorCode, - const char *serviceName, - const char *regtype, - const char *replyDomain, - void *context) -{ - if (DEBUG_ZEROCONF) - qDebug() << "cBrowseReply(" << ((size_t)sdRef) << ", " << flags << ", " << interfaceIndex - << ", " << ((int)errorCode) << ", " << serviceName << ", " << regtype << ", " - << replyDomain << ", " << ((size_t)context); - ServiceBrowserPrivate *sb = (ServiceBrowserPrivate *)(context); - if (sb == 0){ - qDebug() << "ServiceBrowser ignoring reply because context was null "; - return; - } - sb->browseReply(flags, interfaceIndex, ZK_PROTO_IPv4_OR_IPv6, - errorCode, serviceName, regtype, replyDomain); -} - -// ----------------- ConnectionThread impl ----------------- - -void ConnectionThread::run() -{ -#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) - struct sigaction act; - // ignore SIGPIPE - act.sa_handler=SIG_IGN; - sigemptyset(&act.sa_mask); - act.sa_flags=0; - sigaction(SIGPIPE, &act, NULL); -#endif - connection.handleEvents(); -} - -ConnectionThread::ConnectionThread(MainConnection &mc, QObject *parent): - QThread(parent), connection(mc) -{ } - -// ----------------- ServiceGatherer impl ----------------- - -ZConfLib::Ptr ServiceGatherer::lib() -{ - return serviceBrowser->mainConnection->lib; -} - -QString ServiceGatherer::fullName(){ - return currentService->fullName(); -} - -bool ServiceGatherer::enactServiceChange() -{ - if (DEBUG_ZEROCONF) - qDebug() << "ServiceGatherer::enactServiceChange() for service " - << currentService->fullName(); - if (currentServiceCanBePublished()) { - if ((publishedService.data() == 0 && currentService == 0) - || (publishedService.data() != 0 && currentService != 0 - && *publishedService == *currentService)) - return false; - Service::Ptr nService = Service::Ptr(currentService); - if (publishedService) { - publishedService->invalidate(); - serviceBrowser->nextActiveServices.removeOne(publishedService); - serviceBrowser->serviceRemoved(publishedService, serviceBrowser->q); - } - serviceBrowser->serviceChanged(publishedService, nService, serviceBrowser->q); - publishedService = nService; - if (nService) { - serviceBrowser->nextActiveServices.append(nService); - serviceBrowser->serviceAdded(nService, serviceBrowser->q); - currentService = new Service(*currentService); - } - return true; - } - return false; -} - -void ServiceGatherer::retireService() -{ - if (publishedService) { - if (DEBUG_ZEROCONF) - qDebug() << "ServiceGatherer::retireService() for service " - << currentService->fullName(); - Service::Ptr nService; - serviceBrowser->nextActiveServices.removeOne(publishedService); - publishedService->invalidate(); - serviceBrowser->serviceRemoved(publishedService, serviceBrowser->q); - serviceBrowser->serviceChanged(publishedService, nService, serviceBrowser->q); - publishedService = nService; - } else if (DEBUG_ZEROCONF){ - qDebug() << "ServiceGatherer::retireService() for non published service " - << currentService->fullName(); - } -} - -void ServiceGatherer::stopResolve(ZK_IP_Protocol protocol) -{ - if ((protocol == ZK_PROTO_IPv4_OR_IPv6 || protocol == ZK_PROTO_IPv4) - && (status & ResolveConnectionActive) != 0) { - QMutexLocker l(serviceBrowser->mainConnection->lock()); - if (serviceBrowser->mainConnection->status() < MainConnection::Stopping) - lib()->refDeallocate(resolveConnection); - status &= ~ResolveConnectionActive; - serviceBrowser->updateFlowStatusForCancel(); - } - if ((protocol == ZK_PROTO_IPv4_OR_IPv6 || protocol == ZK_PROTO_IPv6) - && (status & ResolveConnectionV6Active) != 0) { - QMutexLocker l(serviceBrowser->mainConnection->lock()); - if (serviceBrowser->mainConnection->status() < MainConnection::Stopping) - lib()->refDeallocate(resolveConnectionV6); - status &= ~ResolveConnectionV6Active; - serviceBrowser->updateFlowStatusForCancel(); - } -} - -void ServiceGatherer::restartResolve(ZK_IP_Protocol protocol) -{ - stopResolve(protocol); - if (protocol == ZK_PROTO_IPv6) { - if (currentService->host()) { - QList<QHostAddress> addrNow = currentService->host()->addresses(); - QMutableListIterator<QHostAddress> addr(addrNow); - bool changed = false; - while (addr.hasNext()) { - if (addr.next().protocol() == QAbstractSocket::IPv6Protocol) { - addr.remove(); - changed = true; - } - } - if (changed) - currentService->m_host->setAddresses(addrNow); - } - DNSServiceErrorType err = lib()->resolve( - serviceBrowser->mainRef(), - &resolveConnectionV6, - currentService->interfaceNr(), protocol, - currentService->name().toUtf8().constData(), - currentService->type().toUtf8().constData(), - currentService->domain().toUtf8().constData(), this); - if (err != kDNSServiceErr_NoError) { - qDebug() << "ServiceBrowser " << serviceBrowser->serviceType - << " failed IPv6 discovery of service " << currentService->fullName() - << " due to error " << err; - status = status | ResolveConnectionV6Failed; - } else { - status = ((status & ~ResolveConnectionV6Failed) | ResolveConnectionV6Active); - } - } else { - if (currentService->host()) { - if (protocol == ZK_PROTO_IPv4_OR_IPv6) { - currentService->m_host->setAddresses(QList<QHostAddress>()); - } else { - QList<QHostAddress> addrNow = currentService->host()->addresses(); - QMutableListIterator<QHostAddress> addr(addrNow); - bool changed = false; - while (addr.hasNext()) { - if (addr.next().protocol() == QAbstractSocket::IPv4Protocol) { - addr.remove(); - changed = true; - } - } - if (changed) - currentService->m_host->setAddresses(addrNow); - } - } - DNSServiceErrorType err = lib()->resolve( - serviceBrowser->mainRef(), - &resolveConnection, - currentService->interfaceNr(), protocol, - currentService->name().toUtf8().constData(), - currentService->type().toUtf8().constData(), - currentService->domain().toUtf8().constData(), this); - if (err != kDNSServiceErr_NoError) { - qDebug() << "ServiceBrowser " << serviceBrowser->serviceType - << " failed discovery of service " << currentService->fullName() - << " due to error " << err; - status = status | ResolveConnectionFailed; - } else { - status = ((status & ~ResolveConnectionFailed) | ResolveConnectionActive); - } - } -} - -void ServiceGatherer::stopTxt() -{ - if ((status & TxtConnectionActive) == 0) return; - QMutexLocker l(serviceBrowser->mainConnection->lock()); - if (serviceBrowser->mainConnection->status() < MainConnection::Stopping) - lib()->refDeallocate(txtConnection); - status &= ~TxtConnectionActive; - serviceBrowser->updateFlowStatusForCancel(); -} - -void ServiceGatherer::restartTxt() -{ - stopTxt(); - DNSServiceErrorType err = lib()->queryRecord(serviceBrowser->mainRef(), &txtConnection, - currentService->interfaceNr(), - currentService->fullName().toUtf8().constData(), - this); - - if (err != kDNSServiceErr_NoError) { - qDebug() << "ServiceBrowser " << serviceBrowser->serviceType - << " failed query of TXT record of service " << currentService->fullName() - << " due to error " << err; - status = status | TxtConnectionFailed; - } else { - status = ((status & ~TxtConnectionFailed) | TxtConnectionActive); - } -} - -void ServiceGatherer::stopHostResolution() -{ - if ((status & AddrConnectionActive) == 0) return; - QMutexLocker l(serviceBrowser->mainConnection->lock()); - if (serviceBrowser->mainConnection->status() < MainConnection::Stopping) - lib()->refDeallocate(addrConnection); - status &= ~AddrConnectionActive; - serviceBrowser->updateFlowStatusForCancel(); -} - -void ServiceGatherer::restartHostResolution() -{ - stopHostResolution(); - if (DEBUG_ZEROCONF) - qDebug() << "ServiceGatherer::restartHostResolution for host " << hostName << " service " - << currentService->fullName(); - if (hostName.isEmpty()){ - qDebug() << "ServiceBrowser " << serviceBrowser->serviceType - << " cannot start host resolution without hostname for service " - << currentService->fullName(); - } - DNSServiceErrorType err = lib()->getAddrInfo(serviceBrowser->mainRef(), &addrConnection, - currentService->interfaceNr(), - 0 /* kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6 */, - hostName.toUtf8().constData(), this); - - if (err != kDNSServiceErr_NoError) { - qDebug() << "ServiceBrowser " << serviceBrowser->serviceType - << " failed starting resolution of host " << hostName << " for service " - << currentService->fullName() << " due to error " << err; - status = status | AddrConnectionFailed; - } else { - status = ((status & ~AddrConnectionFailed) | AddrConnectionActive); - } -} - -/// if the current service can be added -bool ServiceGatherer::currentServiceCanBePublished() -{ - return (currentService->host() && !currentService->host()->addresses().isEmpty()) - || !serviceBrowser->requireAddresses; -} - -ServiceGatherer::ServiceGatherer(const QString &newServiceName, const QString &newType, - const QString &newDomain, const QString &fullName, - uint32_t interfaceIndex, ZK_IP_Protocol protocol, - ServiceBrowserPrivate *serviceBrowser): - serviceBrowser(serviceBrowser), publishedService(0), currentService(new Service()), status(0) -{ - if (DEBUG_ZEROCONF) - qDebug() << " creating ServiceGatherer(" << newServiceName << ", " << newType << ", " - << newDomain << ", " << fullName << ", " << interfaceIndex << ", " - << ((size_t) serviceBrowser); - currentService->m_name = newServiceName; - currentService->m_type = newType; - currentService->m_domain = newDomain; - currentService->m_fullName = fullName; - currentService->m_interfaceNr = interfaceIndex; - if (fullName.isEmpty()) - currentService->m_fullName = toFullNameC(currentService->name().toUtf8().data(), - currentService->type().toUtf8().data(), - currentService->domain().toUtf8().data()); - restartResolve(protocol); - restartTxt(); -} - -ServiceGatherer::~ServiceGatherer() -{ - stopHostResolution(); - stopResolve(); - stopTxt(); - delete currentService; -} - -ServiceGatherer::Ptr ServiceGatherer::createGatherer( - const QString &newServiceName, const QString &newType, const QString &newDomain, - const QString &fullName, uint32_t interfaceIndex, ZK_IP_Protocol protocol, - ServiceBrowserPrivate *serviceBrowser) -{ - Ptr res(new ServiceGatherer(newServiceName, newType, newDomain, fullName, interfaceIndex, - protocol, serviceBrowser)); - res->self = res.toWeakRef(); - return res; -} - -ServiceGatherer::Ptr ServiceGatherer::gatherer() { - return self.toStrongRef(); -} - -void ServiceGatherer::serviceResolveReply(DNSServiceFlags flags, - uint32_t interfaceIndex, - DNSServiceErrorType errorCode, - const char *hosttarget, - const QString &port, - uint16_t txtLen, - const unsigned char *rawTxtRecord) -{ - if (errorCode != kDNSServiceErr_NoError){ - if (errorCode == kDNSServiceErr_Timeout){ - if ((status & ResolveConnectionSuccess) == 0){ - qDebug() << "ServiceBrowser " << serviceBrowser->serviceType - << " failed service resolution for service " - << currentService->fullName() << " as it did timeout"; - status |= ResolveConnectionFailed; - } - } else { - qDebug() << "ServiceBrowser " << serviceBrowser->serviceType - << " failed service resolution for service " << currentService->fullName() - << " with error " << errorCode; - status |= ResolveConnectionFailed; - } - if (status & ResolveConnectionActive) { - status &= ~ResolveConnectionActive; - lib()->refDeallocate(resolveConnection); - serviceBrowser->updateFlowStatusForCancel(); - } - return; - } - serviceBrowser->updateFlowStatusForFlags(flags); - uint16_t nKeys = txtRecordGetCount(txtLen, rawTxtRecord); - for (uint16_t i = 0; i < nKeys; ++i){ - enum { maxTxtLen = 256 }; - char keyBuf[maxTxtLen]; - uint8_t valLen; - const char *valueCStr; - DNSServiceErrorType txtErr = txtRecordGetItemAtIndex( - txtLen, rawTxtRecord, i, maxTxtLen, keyBuf, &valLen, (const void **)&valueCStr); - if (txtErr != kDNSServiceErr_NoError){ - qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " error " << txtErr - << " decoding txt record of service " << currentService->fullName(); - break; - } - keyBuf[maxTxtLen-1] = 0; // just to be sure - currentService->m_txtRecord.insert(QString::fromUtf8(keyBuf), - QString::fromUtf8(valueCStr, valLen)); - } - currentService->m_interfaceNr = interfaceIndex; - currentService->m_port = port; - if (hostName != QString::fromUtf8(hosttarget)) { - hostName = QString::fromUtf8(hosttarget); - if (!currentService->host()) - currentService->m_host = new QHostInfo(); - else - currentService->m_host->setAddresses(QList<QHostAddress>()); - currentService->m_host->setHostName(hostName); - if (serviceBrowser->autoResolveAddresses) - restartHostResolution(); - } - if (currentServiceCanBePublished()) - serviceBrowser->pendingGathererAdd(gatherer()); -} - -void ServiceGatherer::txtRecordReply(DNSServiceFlags flags, - DNSServiceErrorType errorCode, - uint16_t txtLen, - const void *rawTxtRecord, - uint32_t /*ttl*/) -{ - if (errorCode != kDNSServiceErr_NoError){ - if (errorCode == kDNSServiceErr_Timeout){ - if ((status & TxtConnectionSuccess) == 0){ - qDebug() << "ServiceBrowser " << serviceBrowser->serviceType - << " failed txt gathering for service " << currentService->fullName() - << " as it did timeout"; - status |= TxtConnectionFailed; - } - } else { - qDebug() << "ServiceBrowser " << serviceBrowser->serviceType - << " failed txt gathering for service " << currentService->fullName() - << " with error " << errorCode; - status |= TxtConnectionFailed; - } - if (status & TxtConnectionActive) { - status &= ~TxtConnectionActive; - lib()->refDeallocate(txtConnection); - serviceBrowser->updateFlowStatusForCancel(); - } - return; - } - serviceBrowser->updateFlowStatusForFlags(flags); - - uint16_t nKeys = txtRecordGetCount(txtLen, rawTxtRecord); - for (uint16_t i = 0; i < nKeys; ++i){ - char keyBuf[256]; - uint8_t valLen; - const char *valueCStr; - DNSServiceErrorType txtErr = txtRecordGetItemAtIndex(txtLen, rawTxtRecord, i, 256, keyBuf, - &valLen, (const void **)&valueCStr); - if (txtErr != kDNSServiceErr_NoError){ - qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " error " << txtErr - << " decoding txt record of service " << currentService->fullName(); - if ((flags & kDNSServiceFlagsAdd) == 0) - currentService->m_txtRecord.clear(); - break; - } - keyBuf[255] = 0; // just to be sure - if (flags & kDNSServiceFlagsAdd) { - currentService->m_txtRecord.insert(QString::fromUtf8(keyBuf), - QString::fromUtf8(valueCStr, valLen)); - } else { - currentService->m_txtRecord.remove(QString::fromUtf8(keyBuf)); // check value??? - } - } - if ((flags & kDNSServiceFlagsAdd) != 0) - status |= TxtConnectionSuccess; - if (currentService->m_txtRecord.count() != 0 && currentServiceCanBePublished()) - serviceBrowser->pendingGathererAdd(gatherer()); -} - -void ServiceGatherer::txtFieldReply(DNSServiceFlags flags, - DNSServiceErrorType errorCode, - uint16_t txtLen, - const void *rawTxtRecord, - uint32_t /*ttl*/){ - if (errorCode != kDNSServiceErr_NoError){ - if (errorCode == kDNSServiceErr_Timeout){ - if ((status & TxtConnectionSuccess) == 0){ - qDebug() << "ServiceBrowser " << serviceBrowser->serviceType - << " failed txt gathering for service " << currentService->fullName() - << " as it did timeout"; - status |= TxtConnectionFailed; - } - } else { - qDebug() << "ServiceBrowser " << serviceBrowser->serviceType - << " failed txt gathering for service " << currentService->fullName() - << " with error " << errorCode; - status |= TxtConnectionFailed; - } - if (status & TxtConnectionActive) { - status &= ~TxtConnectionActive; - lib()->refDeallocate(txtConnection); - serviceBrowser->updateFlowStatusForCancel(); - } - return; - } - serviceBrowser->updateFlowStatusForFlags(flags); - uint16_t keyLen = 0; - const char *txt = reinterpret_cast<const char *>(rawTxtRecord); - while (keyLen < txtLen) { - if (txt[keyLen]=='=') - break; - ++keyLen; - } - if (flags & kDNSServiceFlagsAdd) { - currentService->m_txtRecord.insert( - QString::fromUtf8(txt, keyLen), - QString::fromUtf8(txt + keyLen + 1, ((txtLen > keyLen)?txtLen - keyLen - 1:0))); - } else { - currentService->m_txtRecord.remove(QString::fromUtf8(txt, keyLen)); // check value??? - } - -} - -void ServiceGatherer::addrReply(DNSServiceFlags flags, - DNSServiceErrorType errorCode, - const char *hostname, - const struct sockaddr *address, - uint32_t /*ttl*/) // should we use this??? -{ - if (errorCode != kDNSServiceErr_NoError) { - if (errorCode == kDNSServiceErr_Timeout){ - if ((status & AddrConnectionSuccess) == 0){ - qDebug() << "ServiceBrowser " << serviceBrowser->serviceType - << " failed address resolve for service " << currentService->fullName() - << " as it did timeout"; - status |= AddrConnectionFailed; - } - qDebug() << "ServiceBrowser " << serviceBrowser->serviceType - << " failed addr resolve for service " << currentService->fullName() - << " with error " << errorCode; - status |= AddrConnectionFailed; - } - if (status & AddrConnectionActive){ - status &= ~AddrConnectionActive; - lib()->refDeallocate(addrConnection); - serviceBrowser->updateFlowStatusForCancel(); - } - return; - } - serviceBrowser->updateFlowStatusForFlags(flags); - if (!currentService->host()) - currentService->m_host = new QHostInfo(); - if (currentService->host()->hostName() != QString::fromUtf8(hostname)) { - if ((flags & kDNSServiceFlagsAdd) == 1) - currentService->m_host->setHostName(QString::fromUtf8(hostname)); - if (currentService->host()->addresses().isEmpty()) { - qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " for service " - << currentService->fullName() << " add with name " << hostname - << " while old name " << currentService->host()->hostName() - << " has still adresses, removing them"; - currentService->m_host->setAddresses(QList<QHostAddress>()); - } else { - qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " for service " - << currentService->fullName() << " ignoring remove for " << hostname - << " as current hostname is " << currentService->host()->hostName(); - return; - } - } - QHostAddress newAddr(address); - QList<QHostAddress> addrNow = currentService->host()->addresses(); - if ((flags & kDNSServiceFlagsAdd) == 0) { - if (addrNow.removeOne(newAddr)) - currentService->m_host->setAddresses(addrNow); - } else { - if (!addrNow.contains(newAddr)){ - switch (newAddr.protocol()){ -#ifdef Q_OS_WIN - case QAbstractSocket::IPv4Protocol: // prefers IPv4 addresses -#else - case QAbstractSocket::IPv6Protocol: // prefers IPv6 addresses -#endif - addrNow.insert(0, newAddr); - break; - default: - addrNow.append(newAddr); - break; - } - currentService->m_host->setAddresses(addrNow); - } - } - serviceBrowser->pendingGathererAdd(gatherer()); -} - -void ServiceGatherer::maybeRemove() -{ - // could trigger an update, but for now we just ignore it (less chatty) -} - -void ServiceGatherer::stop(ZK_IP_Protocol protocol) -{ - if ((protocol == ZK_PROTO_IPv4_OR_IPv6 || protocol == ZK_PROTO_IPv4) - && (status & ResolveConnectionActive) != 0){ - status &= ~ResolveConnectionActive; - lib()->refDeallocate(resolveConnection); - serviceBrowser->updateFlowStatusForCancel(); - } - if ((protocol == ZK_PROTO_IPv4_OR_IPv6 || protocol == ZK_PROTO_IPv6) - && (status & ResolveConnectionV6Active) != 0){ - status &= ~ResolveConnectionV6Active; - lib()->refDeallocate(resolveConnectionV6); - serviceBrowser->updateFlowStatusForCancel(); - } - if (status & TxtConnectionActive){ - status &= ~TxtConnectionActive; - lib()->refDeallocate(txtConnection); - serviceBrowser->updateFlowStatusForCancel(); - } - if (status & AddrConnectionActive) { - status &= ~AddrConnectionActive; - lib()->refDeallocate(addrConnection); - serviceBrowser->updateFlowStatusForCancel(); - } -} - -void ServiceGatherer::reload(qint32 interfaceIndex, ZK_IP_Protocol protocol) -{ - this->interfaceIndex = interfaceIndex; - stop(protocol); - restartResolve(protocol); - restartTxt(); - if (currentServiceCanBePublished()) // avoid??? - restartHostResolution(); -} - -void ServiceGatherer::remove() -{ - stop(); - if (serviceBrowser->gatherers.contains(currentService->fullName()) - && serviceBrowser->gatherers[currentService->fullName()] == this) - { - serviceBrowser->gatherers.remove(currentService->fullName()); - } -} -/// forces a full reload of the record -void ServiceGatherer::reconfirm() -{ - stop(); - /* DNSServiceErrorType err = DNSServiceReconfirmRecord(kDNSServiceFlagsShareConnection, - interfaceIndex, fullName.toUtf8().constData(), - kDNSServiceType_PTR, // kDNSServiceType_SRV could be another possibility - kDNSServiceClass_IN //, - // uint16_t rdlen, // not cached... what is the best solution??? - // const void *rdata - ); */ -} - -// ----------------- ServiceBrowserPrivate impl ----------------- - -ZConfLib::ConnectionRef ServiceBrowserPrivate::mainRef() -{ - return mainConnection->mainRef(); -} - -void ServiceBrowserPrivate::updateFlowStatusForCancel() -{ - mainConnection->updateFlowStatusForCancel(); -} - -void ServiceBrowserPrivate::updateFlowStatusForFlags(DNSServiceFlags flags) -{ - mainConnection->updateFlowStatusForFlags(flags); -} - -void ServiceBrowserPrivate::pendingGathererAdd(ServiceGatherer::Ptr gatherer) -{ - int ng = pendingGatherers.count(); - for (int i = 0; i < ng; ++i){ - const ServiceGatherer::Ptr &g = pendingGatherers.at(i); - if (g->fullName() == gatherer->fullName()){ - if (g != gatherer){ - gatherer->publishedService = g->publishedService; - pendingGatherers[i] = gatherer; - } - return; - } - } - pendingGatherers.append(gatherer); -} - -ServiceBrowserPrivate::ServiceBrowserPrivate(const QString &serviceType, const QString &domain, - bool requireAddresses, MainConnectionPtr mconn): - q(0), serviceType(serviceType), domain(domain), mainConnection(mconn), serviceConnection(0), flags(0), interfaceIndex(0), - delayDeletesUntil(std::numeric_limits<qint64>::min()), failed(false), browsing(false), - autoResolveAddresses(requireAddresses), requireAddresses(requireAddresses), shouldRefresh(false) -{ -} - -ServiceBrowserPrivate::~ServiceBrowserPrivate() -{ - if (DEBUG_ZEROCONF) - qDebug() << "destroying ServiceBrowserPrivate " << serviceType; - if (browsing) - stopBrowsing(); - if (mainConnection) - mainConnection->removeBrowser(this); -} - -void ServiceBrowserPrivate::insertGatherer(const QString &fullName) -{ - if (!gatherers.contains(fullName)){ - QString newServiceName, newType, newDomain; - if (fromFullNameC(fullName.toUtf8().data(), *&newServiceName, *&newType, *&newDomain)){ - qDebug() << "Error unescaping fullname " << fullName; - } else { - ServiceGatherer::Ptr serviceGatherer = ServiceGatherer::createGatherer( - newServiceName, newType, newDomain, fullName, 0, ZK_PROTO_IPv4_OR_IPv6, this); - gatherers[fullName] = serviceGatherer; - } - } -} - -void ServiceBrowserPrivate::maybeUpdateLists() -{ - if (mainConnection->flowStatus != MainConnection::MoreComingRFS - || pendingGatherers.count() > 50 || shouldRefresh) - { - qint64 now = QDateTime::currentMSecsSinceEpoch(); - QList<QString>::iterator i = knownServices.begin(), endi = knownServices.end(); - QMap<QString, ServiceGatherer::Ptr>::iterator j = gatherers.begin(); - bool hasServicesChanges = false; - while (i != endi && j != gatherers.end()) { - const QString vi = *i; - QString vj = j.value()->fullName(); - if (vi == vj){ - ++i; - ++j; - } else if (vi < vj) { - qDebug() << "ServiceBrowser " << serviceType << ", missing gatherer for " << vi; - insertGatherer(vi); - ++i; - } else if (delayDeletesUntil <= now) { - pendingGatherers.removeAll(j.value()); - j.value()->retireService(); - j = gatherers.erase(j); - hasServicesChanges = true; - } else { - ++j; - } - } - while (i != endi) { - qDebug() << "ServiceBrowser " << serviceType << ", missing gatherer for " << *i; - insertGatherer(*i); - } - while (j != gatherers.end()) { - if (delayDeletesUntil <= now) { - pendingGatherers.removeAll(j.value()); - j.value()->retireService(); - j = gatherers.erase(j); - hasServicesChanges = true; - } else { - ++j; - } - } - foreach (const ServiceGatherer::Ptr &g, pendingGatherers) - if (delayDeletesUntil <= now || ! g->publishedService) - hasServicesChanges = g->enactServiceChange() || hasServicesChanges; - if (hasServicesChanges) { - { - QMutexLocker l(mainConnection->lock()); - activeServices = nextActiveServices; - } - emit q->servicesUpdated(q); - } - } - { - QMutexLocker l(mainConnection->lock()); - if (shouldRefresh) - refresh(); - } -} - -/// callback announcing -void ServiceBrowserPrivate::browseReply(DNSServiceFlags flags, - uint32_t interfaceIndex, - ZK_IP_Protocol protocol, - DNSServiceErrorType errorCode, - const char *serviceName, - const char *regtype, - const char *replyDomain) -{ - if (DEBUG_ZEROCONF) - qDebug() << "browseReply(" << ((int)flags) << ", " << interfaceIndex << ", " - << ((int)errorCode) << ", " << serviceName << ", " << regtype << ", " - << replyDomain << ")"; - if (errorCode != kDNSServiceErr_NoError){ - qDebug() << "ServiceBrowser " << serviceType << " ignoring reply due to error " << errorCode; - return; - } - QString newServiceName = QString::fromUtf8(serviceName); - QString newType = serviceType; - QString newDomain = domain; - if (serviceType != QString::fromUtf8(regtype)) // discard? should not happen... - newType = QString::fromUtf8(regtype); - if (domain != QString::fromUtf8(replyDomain)) - domain = QString::fromUtf8(replyDomain); - QString fullName = toFullNameC(serviceName, regtype, replyDomain); - updateFlowStatusForFlags(flags); - if (flags & kDNSServiceFlagsAdd){ - ServiceGatherer::Ptr serviceGatherer; - if (!gatherers.contains(fullName)){ - serviceGatherer = ServiceGatherer::createGatherer(newServiceName, newType, newDomain, - fullName, interfaceIndex, protocol, - this); - gatherers[fullName] = serviceGatherer; - } else { - serviceGatherer = gatherers[fullName]; - serviceGatherer->reload(interfaceIndex, protocol); - } - QList<QString>::iterator pos = std::lower_bound(knownServices.begin(), knownServices.end(), - fullName); - // could order later (more efficient, but then we have to handle eventual duplicates) - if (pos == knownServices.end() || *pos != fullName) - knownServices.insert(pos, fullName); - } else { - if (gatherers.contains(fullName)) - gatherers[fullName]->maybeRemove(); - knownServices.removeOne(fullName); - } - maybeUpdateLists(); // avoid? -} - -void ServiceBrowserPrivate::activateAutoRefresh() -{ - emit q->activateAutoRefresh(); -} - -void ServiceBrowserPrivate::startBrowsing(quint32 interfaceIndex) -{ - this->interfaceIndex = interfaceIndex; - if (failed || browsing) - return; - if (mainConnection.isNull()) { - startupPhase(1, ServiceBrowser::tr("Starting Zeroconf Browsing")); - mainConnection = MainConnectionPtr(new MainConnection(this)); - } else { - mainConnection->addBrowser(this); - } -} - -bool ServiceBrowserPrivate::internalStartBrowsing() -{ - mainConnection->updateFlowStatusForFlags(0); - if (failed || browsing) - return false; - DNSServiceErrorType err; - err = mainConnection->lib->browse(mainRef(), &serviceConnection, interfaceIndex, - serviceType.toUtf8().constData(), - ((domain.isEmpty()) ? 0 : (domain.toUtf8().constData())), this); - if (err != kDNSServiceErr_NoError){ - qDebug() << "ServiceBrowser " << serviceType << " failed initializing serviceConnection"; - return false; - } - browsing = true; - if (DEBUG_ZEROCONF) - qDebug() << "startBrowsing(" << interfaceIndex << ") for serviceType:" << serviceType - << " domain:" << domain; - return true; -} - -void ServiceBrowserPrivate::triggerRefresh() -{ - { - QMutexLocker l(mainConnection->lock()); - const qint64 msecDelay = 5100; - delayDeletesUntil = QDateTime::currentMSecsSinceEpoch() + msecDelay; - stopBrowsing(); - shouldRefresh = true; - } - { - QMutexLocker l(mainConnection->mainThreadLock()); - if (!browsing) - refresh(); - } -} - -void ServiceBrowserPrivate::refresh() -{ - const qint64 msecDelay = 500; - delayDeletesUntil = QDateTime::currentMSecsSinceEpoch() + msecDelay; - shouldRefresh = false; - internalStartBrowsing(); -} - - -void ServiceBrowserPrivate::stopBrowsing() -{ - QMutexLocker l(mainConnection->lock()); - if (browsing){ - if (serviceConnection) { - mainConnection->lib->browserDeallocate(&serviceConnection); - updateFlowStatusForCancel(); - serviceConnection = 0; - } - knownServices.clear(); - browsing = false; - } -} - -void ServiceBrowserPrivate::reconfirmService(Service::ConstPtr s) -{ - if (!s->outdated()) - mainConnection->lib->reconfirmRecord( - mainRef(), s->interfaceNr(), s->name().toUtf8().data(), - s->type().toUtf8().data(), s->domain().toUtf8().data(), - s->fullName().toUtf8().data()); -} - -/// called when a service is added removed or changes. oldService or newService might be null -/// (covers both add and remove) -void ServiceBrowserPrivate::serviceChanged(const Service::ConstPtr &oldService, - const Service::ConstPtr &newService, - ServiceBrowser *browser) -{ - emit q->serviceChanged(oldService, newService, browser); -} - -/// called when a service is added (utility method) -void ServiceBrowserPrivate::serviceAdded(const Service::ConstPtr &service, ServiceBrowser *browser) -{ - emit q->serviceAdded(service, browser); -} - -/// called when a service is removed (utility method) -void ServiceBrowserPrivate::serviceRemoved(const Service::ConstPtr &service, ServiceBrowser *browser) -{ - emit q->serviceRemoved(service, browser); -} - -/// called when the list is updated (this might collect several serviceChanged signals together) -void ServiceBrowserPrivate::servicesUpdated(ServiceBrowser *browser) -{ - emit q->servicesUpdated(browser); -} - -/// called to describe the current startup phase -void ServiceBrowserPrivate::startupPhase(int progress, const QString &description) -{ - emit q->startupPhase(progress, description, q); -} - - -/// called when there is an error message -void ServiceBrowserPrivate::errorMessage(ErrorMessage::SeverityLevel severity, const QString &msg) -{ - if (severity == ErrorMessage::FailureLevel) - this->failed = true; - emit q->errorMessage(severity, msg, q); -} - -/// called when the browsing fails -void ServiceBrowserPrivate::hadFailure(const QList<ErrorMessage> &messages) -{ - emit q->hadFailure(messages, q); -} - -void ServiceBrowserPrivate::startedBrowsing() -{ - emit q->startedBrowsing(q); -} - -// ----------------- MainConnection impl ----------------- - -void MainConnection::stop(bool wait) -{ - { - if (QThread::currentThread() == m_thread) - qCritical() << "ERROR ZerocConf::MainConnection::stop called from m_thread"; - // This will most likely lock if called from the connection thread (as mainThreadLock is non recusive) - // Making it recursive would open a hole during the startup of the thread. - // As of now this should always be called during the destruction of a browser, - // so from another thread. - increaseStatusTo(Stopping); - QMutexLocker l(mainThreadLock()); - QMutexLocker l2(lock()); - } - if (m_mainRef) { - lib->stopConnection(m_mainRef); - m_mainRef = 0; - } - if (!m_thread) - increaseStatusTo(Stopped); - else if (wait && QThread::currentThread() != m_thread) - m_thread->wait(); -} - -MainConnection::MainConnection(ServiceBrowserPrivate *initialBrowser): - flowStatus(NormalRFS), - lib(zeroConfLibInstance()->defaultLib()), m_lock(QMutex::Recursive), - m_mainThreadLock(QMutex::NonRecursive), m_mainRef(0), m_failed(false), m_status(Starting), m_nErrs(0) -{ - if (initialBrowser) - addBrowser(initialBrowser); - if (lib.isNull()){ - appendError(ErrorMessage::FailureLevel, tr("Zeroconf could not load a valid library, failing.")); - } else { - m_thread = new ConnectionThread(*this); - m_mainThreadLock.lock(); - m_thread->start(); // delay startup?? - } -} - -MainConnection::~MainConnection() -{ - gQuickStop = 1; - stop(true); - gQuickStop = 0; - delete m_thread; -} - -bool MainConnection::increaseStatusTo(int s) -{ - int sAtt = status(); - while (sAtt < s){ - if (m_status.testAndSetRelaxed(sAtt, s)) - return true; - sAtt = status(); - } - return false; -} - -QMutex *MainConnection::lock() -{ - return &m_lock; -} - -QMutex *MainConnection::mainThreadLock() -{ - return &m_mainThreadLock; -} - -void MainConnection::waitStartup() -{ - int sAtt; - while (true){ - { - QMutexLocker l(lock()); - sAtt = status(); - if (sAtt >= Running) - return; - } - QThread::yieldCurrentThread(); - } -} - -void MainConnection::addBrowser(ServiceBrowserPrivate *browser) -{ - int actualStatus; - QList<ErrorMessage> errs; - { - QMutexLocker l(lock()); - actualStatus = status(); - m_browsers.append(browser); - errs = m_errors; - } - if (actualStatus == Running && browser->internalStartBrowsing()) - browser->startedBrowsing(); - bool didFail = false; - foreach (const ErrorMessage &msg, errs) { - browser->errorMessage(msg.severity, msg.msg); - didFail = didFail || msg.severity == ErrorMessage::FailureLevel; - } - if (didFail) - browser->hadFailure(errs); -} - -void MainConnection::removeBrowser(ServiceBrowserPrivate *browser) -{ - QMutexLocker l(lock()); - m_browsers.removeOne(browser); -} - -void MainConnection::updateFlowStatusForCancel(){ - flowStatus = ForceUpdateRFS; -} -void MainConnection::updateFlowStatusForFlags(DNSServiceFlags flags) -{ - if (flags & kDNSServiceFlagsMoreComing) { - if (flowStatus == NormalRFS) - flowStatus = MoreComingRFS; - } else { - flowStatus = NormalRFS; - } -} -void MainConnection::maybeUpdateLists() -{ - foreach (ServiceBrowserPrivate *sb, m_browsers) { - sb->maybeUpdateLists(); - } -} - -void MainConnection::gotoValidLib(){ - while (lib){ - if (lib->isOk()) break; - appendError(ErrorMessage::WarningLevel, tr("Zeroconf giving up on non working %1 (%2).") - .arg(lib->name()).arg(lib->errorMsg())); - lib = lib->fallbackLib; - } - if (!lib) { - appendError(ErrorMessage::FailureLevel, tr("Zeroconf has no valid library, aborting connection.")); - increaseStatusTo(Stopping); - } -} - -void MainConnection::abortLib(){ - if (!lib){ - appendError(ErrorMessage::FailureLevel, tr("Zeroconf has no valid library, aborting connection.")); - increaseStatusTo(Stopping); - } else if (lib->fallbackLib){ - appendError(ErrorMessage::WarningLevel, tr("Zeroconf giving up on %1, switching to %2.") - .arg(lib->name()).arg(lib->fallbackLib->name())); - lib = lib->fallbackLib; - m_nErrs = 0; - gotoValidLib(); - } else { - appendError(ErrorMessage::FailureLevel, tr("Zeroconf giving up on %1, no fallback provided, aborting connection.") - .arg(lib->name())); - increaseStatusTo(Stopping); - } -} - -void MainConnection::createConnection() -{ - gotoValidLib(); - while (status() <= Running) { - if (!lib) { - increaseStatusTo(Stopped); - break; - } - startupPhase(m_nErrs + zeroConfLibInstance()->nFallbacksTot() - lib->nFallbacks() + 2, - tr("Trying %1...").arg(lib->name())); - uint32_t version; - uint32_t size = (uint32_t)sizeof(uint32_t); - DNSServiceErrorType err = lib->getProperty(kDNSServiceProperty_DaemonVersion, &version, &size); - if (err == kDNSServiceErr_NoError){ - DNSServiceErrorType error = lib->createConnection(this, &m_mainRef); - if (error != kDNSServiceErr_NoError){ - appendError(ErrorMessage::WarningLevel, tr("Zeroconf using %1 failed the initialization of the main library connection with error %2.") - .arg(lib->name()).arg(error)); - ++m_nErrs; - if (m_nErrs > lib->nFallbacks() || !lib->isOk()) - abortLib(); - } else { - QList<ServiceBrowserPrivate *> waitingBrowsers; - { - QMutexLocker l(lock()); - waitingBrowsers = m_browsers; - increaseStatusTo(Running); - } - for (int i = waitingBrowsers.count(); i-- != 0; ){ - ServiceBrowserPrivate *actualBrowser = waitingBrowsers[i]; - if (actualBrowser && !actualBrowser->browsing - && actualBrowser->internalStartBrowsing()) - actualBrowser->startedBrowsing(); - } - break; - } - } else if (err == kDNSServiceErr_ServiceNotRunning) { - appendError(ErrorMessage::WarningLevel, tr("Zeroconf using %1 failed because no daemon is running.") - .arg(lib->name())); - ++m_nErrs; - if (m_nErrs > lib->maxErrors() || !lib->isOk()) { - abortLib(); - } else if (lib->tryStartDaemon(this)) { - appendError(ErrorMessage::WarningLevel, tr("Starting the Zeroconf daemon using %1 seems successful, continuing.") - .arg(lib->name())); - } else { - appendError(ErrorMessage::WarningLevel, tr("Zeroconf using %1 failed because no daemon is running.") - .arg(lib->name())); - abortLib(); - } - } else { - appendError(ErrorMessage::WarningLevel, tr("Zeroconf using %1 failed getProperty call with error %2.") - .arg(lib->name()).arg(err)); - abortLib(); - } - } - if (status() < Stopping) { - startupPhase(zeroConfLibInstance()->nFallbacksTot() + 3, tr("Succeeded using %1.").arg(lib->name())); - appendError(ErrorMessage::NoteLevel, - tr("MainConnection could successfully create a connection using %1.") - .arg(lib->name())); - } -} - -ZConfLib::RunLoopStatus MainConnection::handleEvent() -{ - qint64 now = QDateTime::currentMSecsSinceEpoch(); - qint64 nextEvent = now; // worth keeping a heap to quickly calculate this? - foreach (ServiceBrowserPrivate *bAtt, m_browsers) { - if (nextEvent < bAtt->delayDeletesUntil) - nextEvent = bAtt->delayDeletesUntil; - } - if (nextEvent <= now) - nextEvent = -1; - else - nextEvent -= now; - maybeUpdateLists(); - ZConfLib::RunLoopStatus err = lib->processOneEvent(this, m_mainRef, nextEvent); - if (err != ZConfLib::ProcessedOk && err != ZConfLib::ProcessedIdle) { - qDebug() << "processOneEvent returned " << err; - ++m_nErrs; - } else { - m_nErrs = 0; - } - return err; -} - -void MainConnection::destroyConnection() -{ - // multithreading issues if we support multiple cycles of createConnection/destroyConnection - for (int i = m_browsers.count(); i-- != 0;){ - ServiceBrowserPrivate *bAtt = m_browsers[i]; - if (bAtt->browsing) - bAtt->stopBrowsing(); - } - if (m_mainRef != 0) - lib->destroyConnection(&m_mainRef); - m_mainRef = 0; -} - -void MainConnection::handleEvents() -{ - try { - if (!m_status.testAndSetAcquire(Starting, Started)){ - appendError(ErrorMessage::WarningLevel, tr("Zeroconf, unexpected start status, aborting.")); - increaseStatusTo(Stopped); - return; - } - m_nErrs = 0; - createConnection(); - } catch(...) { - mainThreadLock()->unlock(); - throw; - } - mainThreadLock()->unlock(); - increaseStatusTo(Running); - while (status() < Stopping) { - QMutexLocker l(mainThreadLock()); - if (m_nErrs > 10) - increaseStatusTo(Stopping); - switch (handleEvent()) { - case ZConfLib::ProcessedOk : - case ZConfLib::ProcessedIdle : - break; - case ZConfLib::ProcessedError : - ++m_nErrs; - break; - case ZConfLib::ProcessedFailure : - increaseStatusTo(Stopping); - ++m_nErrs; - break; - case ZConfLib::ProcessedQuit : - increaseStatusTo(Stopping); - break; - default: - appendError(ErrorMessage::FailureLevel, tr("Zeroconf detected an unexpected return status of handleEvent.")); - increaseStatusTo(Stopping); - break; - } - } - destroyConnection(); - if (m_nErrs > 0){ - QString browsersNames = (m_browsers.isEmpty() ? QString() : m_browsers.at(0)->serviceType) - + ((m_browsers.count() > 1) ? QString::fromLatin1(",...") : QString()); - if (isOk()) - appendError(ErrorMessage::FailureLevel, - tr("Zeroconf for [%1] accumulated %n consecutive errors, aborting.", 0, m_nErrs) - .arg(browsersNames)); - } - increaseStatusTo(Stopped); -} - -ZConfLib::ConnectionRef MainConnection::mainRef() -{ - while (status() < Running){ - QThread::yieldCurrentThread(); - } - return m_mainRef; -} - -MainConnection::Status MainConnection::status() -{ - return static_cast<MainConnection::Status>(m_status.load()); -} - -QList<ErrorMessage> MainConnection::errors() -{ - QMutexLocker l(lock()); - return m_errors; -} - -void MainConnection::appendError(ErrorMessage::SeverityLevel severity, const QString &msg) -{ - QList<ServiceBrowserPrivate *> browsersAtt; - QList<ErrorMessage> errs; - { - QMutexLocker l(lock()); - m_errors.append(ErrorMessage(severity, msg)); - errs = m_errors; - browsersAtt = m_browsers; - m_failed = severity == ErrorMessage::FailureLevel || m_failed; - } - foreach (ServiceBrowserPrivate *b, browsersAtt) { - b->errorMessage(severity, msg); - if (severity == ErrorMessage::FailureLevel) - b->hadFailure(errs); - } -} - -void MainConnection::startupPhase(int progress, const QString &description) -{ - QList<ServiceBrowserPrivate *> browsersAtt; - { - QMutexLocker l(lock()); - browsersAtt = m_browsers; - } - foreach (ServiceBrowserPrivate *b, browsersAtt) - b->startupPhase(progress, description); -} - -bool MainConnection::isOk() -{ - return !m_failed; -} - -// ----------------- ZConfLib impl ----------------- - -bool ZConfLib::tryStartDaemon(ErrorMessage::ErrorLogger * /* logger */) -{ - return false; -} - -QString ZConfLib::name(){ - return QString::fromLatin1("ZeroConfLib@%1").arg(size_t(this), 0, 16); -} - -ZConfLib::ZConfLib(ZConfLib::Ptr f) : fallbackLib(f), m_isOk(true), m_maxErrors(4) -{ } - -ZConfLib::~ZConfLib() -{ } - -bool ZConfLib::isOk() -{ - return m_isOk; -} - -QString ZConfLib::errorMsg() -{ - return m_errorMsg; -} - -void ZConfLib::setError(bool failure, const QString &eMsg) -{ - m_errorMsg = eMsg; - m_isOk = !failure; -} - -int ZConfLib::maxErrors() const -{ - return m_maxErrors; -} - -int ZConfLib::nFallbacks() const -{ - return (m_isOk ? ((m_maxErrors > 0) ? m_maxErrors : 1) : 0) - + (fallbackLib ? fallbackLib->nFallbacks() : 0); -} - -ZConfLib::RunLoopStatus ZConfLib::processOneEvent(MainConnection *mainConnection, - ConnectionRef cRef, qint64 maxMsBlock) -{ - if (maxMsBlock < 0) { // just block - maxMsBlock = MAX_SEC_FOR_READ * static_cast<qint64>(1000); - } - // some stuff could be extracted for maximal performance - int dns_sd_fd = (cRef ? refSockFD(cRef) : -1); - int nfds = dns_sd_fd + 1; - fd_set readfds; - struct timeval tv; - int result; - dns_sd_fd = (cRef ? refSockFD(cRef) : -1); - if (dns_sd_fd < 0) - return ProcessedError; - nfds = dns_sd_fd + 1; - FD_ZERO(&readfds); - FD_SET(dns_sd_fd, &readfds); - - if (maxMsBlock > MAX_SEC_FOR_READ * static_cast<qint64>(1000)) { - tv.tv_sec = MAX_SEC_FOR_READ; - tv.tv_usec = 0; - } else { - tv.tv_sec = static_cast<time_t>(maxMsBlock / 1000); - tv.tv_usec = static_cast<suseconds_t>((maxMsBlock % 1000) * 1000); - } - QMutex *lock = (mainConnection ? mainConnection->mainThreadLock() : 0); - if (lock) - lock->unlock(); - result = select(nfds, &readfds, (fd_set *)NULL, (fd_set *)NULL, &tv); - if (lock) - lock->lock(); - if (result > 0) { - if (FD_ISSET(dns_sd_fd, &readfds)) - return processOneEventBlock(cRef); - } else if (result == 0) { - return ProcessedIdle; - } else if (errno != EINTR) { - if (DEBUG_ZEROCONF) - qDebug() << "select() returned " << result << " errno " << errno - << strerror(errno); - return ProcessedError; - } - return ProcessedIdle; // change? should never happen anyway -} - -} - -int ServiceBrowser::maxProgress() const -{ - return zeroConfLibInstance()->nFallbacksTot() + 3; -} - -// namespace Internal -} // namespace ZeroConf |