File indexing completed on 2024-04-28 15:22:05

0001 /*
0002     This file is part of the KDE project
0003 
0004     SPDX-FileCopyrightText: 2004, 2005 Jakub Stachowski <qbast@go2.pl>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "avahi-publicservice_p.h"
0010 
0011 #include <QCoreApplication>
0012 #include <QStringList>
0013 
0014 #include "publicservice.h"
0015 
0016 #include <config-kdnssd.h>
0017 #if HAVE_SYS_TYPES_H
0018 #include <sys/types.h>
0019 #endif
0020 #include "avahi_entrygroup_interface.h"
0021 #include "avahi_server_interface.h"
0022 #include "servicebrowser.h"
0023 
0024 namespace KDNSSD
0025 {
0026 PublicService::PublicService(const QString &name, const QString &type, unsigned int port, const QString &domain, const QStringList &subtypes)
0027     : QObject()
0028     , ServiceBase(new PublicServicePrivate(this, name, type, domain, port))
0029 {
0030     KDNSSD_D;
0031     if (domain.isNull()) {
0032         d->m_domain = "local.";
0033     }
0034     d->m_subtypes = subtypes;
0035 }
0036 
0037 PublicService::~PublicService()
0038 {
0039     stop();
0040 }
0041 
0042 void PublicServicePrivate::tryApply()
0043 {
0044     if (fillEntryGroup()) {
0045         commit();
0046     } else {
0047         m_parent->stop();
0048         Q_EMIT m_parent->published(false);
0049     }
0050 }
0051 
0052 void PublicServicePrivate::gotGlobalStateChanged(int state, const QString &error, QDBusMessage msg)
0053 {
0054     if (!isOurMsg(msg)) {
0055         return;
0056     }
0057     groupStateChanged(state, error);
0058 }
0059 
0060 void PublicService::setServiceName(const QString &serviceName)
0061 {
0062     KDNSSD_D;
0063     d->m_serviceName = serviceName;
0064     if (d->m_running) {
0065         d->m_group->Reset();
0066         d->tryApply();
0067     }
0068 }
0069 
0070 void PublicService::setDomain(const QString &domain)
0071 {
0072     KDNSSD_D;
0073     d->m_domain = domain;
0074     if (d->m_running) {
0075         d->m_group->Reset();
0076         d->tryApply();
0077     }
0078 }
0079 
0080 void PublicService::setType(const QString &type)
0081 {
0082     KDNSSD_D;
0083     d->m_type = type;
0084     if (d->m_running) {
0085         d->m_group->Reset();
0086         d->tryApply();
0087     }
0088 }
0089 
0090 void PublicService::setSubTypes(const QStringList &subtypes)
0091 {
0092     KDNSSD_D;
0093     d->m_subtypes = subtypes;
0094     if (d->m_running) {
0095         d->m_group->Reset();
0096         d->tryApply();
0097     }
0098 }
0099 
0100 QStringList PublicService::subtypes() const
0101 {
0102     KDNSSD_D;
0103     return d->m_subtypes;
0104 }
0105 
0106 void PublicService::setPort(unsigned short port)
0107 {
0108     KDNSSD_D;
0109     d->m_port = port;
0110     if (d->m_running) {
0111         d->m_group->Reset();
0112         d->tryApply();
0113     }
0114 }
0115 
0116 void PublicService::setTextData(const QMap<QString, QByteArray> &textData)
0117 {
0118     KDNSSD_D;
0119     d->m_textData = textData;
0120     if (d->m_running) {
0121         d->m_group->Reset();
0122         d->tryApply();
0123     }
0124 }
0125 
0126 bool PublicService::isPublished() const
0127 {
0128     KDNSSD_D;
0129     return d->m_published;
0130 }
0131 
0132 bool PublicService::publish()
0133 {
0134     KDNSSD_D;
0135     publishAsync();
0136     while (d->m_running && !d->m_published) {
0137         QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
0138     }
0139     return d->m_published;
0140 }
0141 
0142 void PublicService::stop()
0143 {
0144     KDNSSD_D;
0145     if (d->m_group) {
0146         d->m_group->Reset();
0147     }
0148     d->m_running = false;
0149     d->m_published = false;
0150 }
0151 bool PublicServicePrivate::fillEntryGroup()
0152 {
0153     registerTypes();
0154     if (!m_group) {
0155         // Do not race!
0156         // https://github.com/lathiat/avahi/issues/9
0157         // Avahi's DBus API is incredibly racey with signals getting fired
0158         // immediately after a request was made even though we may not yet be
0159         // listening. In lieu of a proper upstream fix for this we'll unfortunately
0160         // have to resort to this hack:
0161         // We register to all signals regardless of path and then filter them once
0162         // we know what "our" path is. This is much more fragile than a proper
0163         // QDBusInterface assisted signal connection but unfortunately the only way
0164         // we can reliably prevent signals getting lost in the race.
0165         // This uses a fancy trick whereby using QDBusMessage as last argument will
0166         // give us the correct signal argument types as well as the underlying
0167         // message so that we may check the message path.
0168         QDBusConnection::systemBus().connect("org.freedesktop.Avahi",
0169                                              "",
0170                                              "org.freedesktop.Avahi.EntryGroup",
0171                                              "StateChanged",
0172                                              this,
0173                                              SLOT(gotGlobalStateChanged(int, QString, QDBusMessage)));
0174         m_dbusObjectPath.clear();
0175 
0176         QDBusReply<QDBusObjectPath> rep = m_server->EntryGroupNew();
0177         if (!rep.isValid()) {
0178             return false;
0179         }
0180 
0181         m_dbusObjectPath = rep.value().path();
0182 
0183         m_group = new org::freedesktop::Avahi::EntryGroup("org.freedesktop.Avahi", m_dbusObjectPath, QDBusConnection::systemBus());
0184     }
0185     if (m_serviceName.isNull()) {
0186         QDBusReply<QString> rep = m_server->GetHostName();
0187         if (!rep.isValid()) {
0188             return false;
0189         }
0190         m_serviceName = rep.value();
0191     }
0192 
0193     QList<QByteArray> txt;
0194     QMap<QString, QByteArray>::ConstIterator itEnd = m_textData.constEnd();
0195     for (QMap<QString, QByteArray>::ConstIterator it = m_textData.constBegin(); it != itEnd; ++it)
0196         if (it.value().isNull()) {
0197             txt.append(it.key().toLatin1());
0198         } else {
0199             txt.append(it.key().toLatin1() + '=' + it.value());
0200         }
0201 
0202     for (;;) {
0203         QDBusReply<void> ret = m_group->AddService(-1, -1, 0, m_serviceName, m_type, domainToDNS(m_domain), m_hostName, m_port, txt);
0204         if (ret.isValid()) {
0205             break;
0206         }
0207 
0208         // serious error, bail out
0209         if (ret.error().name() != QLatin1String("org.freedesktop.Avahi.CollisionError")) {
0210             return false;
0211         }
0212 
0213         // name collision, try another
0214         QDBusReply<QString> rep = m_server->GetAlternativeServiceName(m_serviceName);
0215         if (rep.isValid()) {
0216             m_serviceName = rep.value();
0217         } else {
0218             return false;
0219         }
0220     }
0221 
0222     for (const QString &subtype : std::as_const(m_subtypes)) {
0223         m_group->AddServiceSubtype(-1, -1, 0, m_serviceName, m_type, domainToDNS(m_domain), subtype);
0224     }
0225     return true;
0226 }
0227 
0228 void PublicServicePrivate::serverStateChanged(int s, const QString &)
0229 {
0230     if (!m_running) {
0231         return;
0232     }
0233     switch (s) {
0234     case AVAHI_SERVER_INVALID:
0235         m_parent->stop();
0236         Q_EMIT m_parent->published(false);
0237         break;
0238     case AVAHI_SERVER_REGISTERING:
0239     case AVAHI_SERVER_COLLISION:
0240         if (m_group) {
0241             m_group->Reset();
0242         }
0243         m_collision = true;
0244         break;
0245     case AVAHI_SERVER_RUNNING:
0246         if (m_collision) {
0247             m_collision = false;
0248             tryApply();
0249         }
0250     }
0251 }
0252 
0253 void PublicService::publishAsync()
0254 {
0255     KDNSSD_D;
0256     if (d->m_running) {
0257         stop();
0258     }
0259 
0260     if (!d->m_server) {
0261         d->m_server = new org::freedesktop::Avahi::Server(QStringLiteral("org.freedesktop.Avahi"), QStringLiteral("/"), QDBusConnection::systemBus());
0262         connect(d->m_server, SIGNAL(StateChanged(int, QString)), d, SLOT(serverStateChanged(int, QString)));
0263     }
0264 
0265     int state = AVAHI_SERVER_INVALID;
0266     QDBusReply<int> rep = d->m_server->GetState();
0267 
0268     if (rep.isValid()) {
0269         state = rep.value();
0270     }
0271     d->m_running = true;
0272     d->m_collision = true; // make it look like server is getting out of collision to force registering
0273     d->serverStateChanged(state, QString());
0274 }
0275 
0276 void PublicServicePrivate::groupStateChanged(int s, const QString &reason)
0277 {
0278     switch (s) {
0279     case AVAHI_ENTRY_GROUP_COLLISION: {
0280         QDBusReply<QString> rep = m_server->GetAlternativeServiceName(m_serviceName);
0281         if (rep.isValid()) {
0282             m_parent->setServiceName(rep.value());
0283         } else {
0284             serverStateChanged(AVAHI_SERVER_INVALID, reason);
0285         }
0286         break;
0287     }
0288     case AVAHI_ENTRY_GROUP_ESTABLISHED:
0289         m_published = true;
0290         Q_EMIT m_parent->published(true);
0291         break;
0292     case AVAHI_ENTRY_GROUP_FAILURE:
0293         serverStateChanged(AVAHI_SERVER_INVALID, reason);
0294         break;
0295     }
0296 }
0297 
0298 void PublicService::virtual_hook(int, void *)
0299 {
0300 }
0301 
0302 }
0303 
0304 #include "moc_avahi-publicservice_p.cpp"
0305 #include "moc_publicservice.cpp"