kdelibs/kutils/kdnssd/kdnssd.cpp
Ivailo Monev e1f0788f3e kutils: assign service port from the uint-typed variable
Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2022-05-07 00:57:55 +00:00

435 lines
15 KiB
C++

/* This file is part of the KDE libraries
Copyright (C) 2022 Ivailo Monev <xakepa10@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2, as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kdnssd.h"
#include "kurl.h"
#include "kdebug.h"
#if defined(HAVE_AVAHI)
# include <avahi-client/client.h>
# include <avahi-client/publish.h>
# include <avahi-client/lookup.h>
# include <avahi-common/simple-watch.h>
# include <avahi-common/error.h>
#endif
class KDNSSDPrivate : public QObject
{
Q_OBJECT
public:
KDNSSDPrivate(QObject *parent);
~KDNSSDPrivate();
bool publishService(const QByteArray &servicetype, const uint serviceport, const QString &servicename);
bool unpublishService();
void startBrowse(const QByteArray &servicetype);
QList<KDNSSDService> services() const;
#if defined(HAVE_AVAHI)
static void clientCallback(AvahiClient *avahiclient, AvahiClientState avahistate, void *userdata);
static void groupCallback(AvahiEntryGroup *avahigroup, AvahiEntryGroupState avahistate, void *userdata);
static void browseCallback(AvahiServiceBrowser *avahibrowser, AvahiIfIndex avahiinterface,
AvahiProtocol avahiprotocol, AvahiBrowserEvent avahievent,
const char *avahiname, const char *avahitype, const char *avahidomain,
AvahiLookupResultFlags avahiflags,
void* userdata);
static void resolveCallback(AvahiServiceResolver *avahiresolver, AvahiIfIndex avahiinterface,
AvahiProtocol avahiprotocol, AvahiResolverEvent avahievent,
const char *avahiname, const char *avahitype, const char *avahidomain, const char *avahihostname,
const AvahiAddress *avahiaddress, uint16_t avahiport,
AvahiStringList *avahitxt,
AvahiLookupResultFlags avahiflags,
void* userdata);
static void serviceCallback(AvahiServiceTypeBrowser *avahiservice,
AvahiIfIndex avahiinterface,
AvahiProtocol avahiprotocol,
AvahiBrowserEvent avahievent,
const char *avahitype,
const char *avahidomain,
AvahiLookupResultFlags avahiflags,
void *userdata);
private:
int m_pollcounter;
AvahiSimplePoll *m_avahipoll;
AvahiClient *m_avahiclient;
AvahiEntryGroup *m_avahigroup;
QList<KDNSSDService> m_services;
QList<QByteArray> m_servicetypes;
#endif // HAVE_AVAHI
};
KDNSSDPrivate::KDNSSDPrivate(QObject *parent)
: QObject(parent)
#if defined(HAVE_AVAHI)
, m_pollcounter(0),
m_avahipoll(nullptr),
m_avahiclient(nullptr),
m_avahigroup(nullptr)
#endif
{
#if defined(HAVE_AVAHI)
m_avahipoll = avahi_simple_poll_new();
if (!m_avahipoll) {
kWarning() << "Could not create Avahi poll";
return;
}
int avahierror = 0;
m_avahiclient = avahi_client_new(
avahi_simple_poll_get(m_avahipoll), AVAHI_CLIENT_NO_FAIL,
KDNSSDPrivate::clientCallback, this,
&avahierror
);
if (!m_avahiclient) {
kWarning() << "Could not create Avahi client" << avahi_strerror(avahierror);
return;
}
#endif // HAVE_AVAHI
}
KDNSSDPrivate::~KDNSSDPrivate()
{
#if defined(HAVE_AVAHI)
m_pollcounter = 0;
if (m_avahigroup) {
avahi_entry_group_reset(m_avahigroup);
}
if (m_avahiclient) {
avahi_client_free(m_avahiclient);
}
if (m_avahipoll) {
avahi_simple_poll_free(m_avahipoll);
}
#endif // HAVE_AVAHI
}
bool KDNSSDPrivate::publishService(const QByteArray &servicetype, const uint serviceport, const QString &servicename)
{
#if defined(HAVE_AVAHI)
if (m_avahigroup) {
avahi_entry_group_reset(m_avahigroup);
}
m_avahigroup = avahi_entry_group_new(m_avahiclient, KDNSSDPrivate::groupCallback, this);
if (!m_avahigroup) {
kWarning() << "Could not create Avahi group";
return false;
}
const QByteArray servicenamebytes = servicename.toUtf8();
// qDebug() << Q_FUNC_INFO << servicenamebytes << servicetype;
int avahiresult = avahi_entry_group_add_service(
m_avahigroup,
AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
AvahiPublishFlags(0),
servicenamebytes.constData(), servicetype.constData(), NULL, NULL, serviceport,
NULL
);
if (avahiresult < 0) {
kWarning() << "Could not add Avahi service to group" << avahi_strerror(avahiresult);
return false;
}
avahiresult = avahi_entry_group_commit(m_avahigroup);
return (avahiresult >= 0);
#else
return false;
#endif
}
bool KDNSSDPrivate::unpublishService()
{
#if defined(HAVE_AVAHI)
if (!m_avahigroup) {
return true;
}
const int avahiresult = avahi_entry_group_reset(m_avahigroup);
if (avahiresult < 0) {
kWarning() << "Could not reset Avahi service group" << avahi_strerror(avahiresult);
return false;
}
return true;
#else
return false;
#endif
}
void KDNSSDPrivate::startBrowse(const QByteArray &servicetype)
{
#if defined(HAVE_AVAHI)
// qDebug() << Q_FUNC_INFO << servicetype;
m_pollcounter = 0;
QList<QByteArray> servicetypes;
if (servicetype.isEmpty()) {
AvahiServiceTypeBrowser* avahiservice = avahi_service_type_browser_new(
m_avahiclient,
AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, NULL,
AvahiLookupFlags(0),
KDNSSDPrivate::serviceCallback, this
);
if (!avahiservice) {
kWarning() << "Could not create Avahi service type browser" << avahi_strerror(avahi_client_errno(m_avahiclient));
return;
}
m_pollcounter++;
m_servicetypes.clear();
while (m_pollcounter) {
// qDebug() << Q_FUNC_INFO << m_pollcounter;
avahi_simple_poll_iterate(m_avahipoll, 0);
}
avahi_service_type_browser_free(avahiservice);
servicetypes = m_servicetypes;
} else {
servicetypes.append(servicetype);
}
m_pollcounter = 0;
m_services.clear();
foreach (const QByteArray &servicetypeit, servicetypes) {
AvahiServiceBrowser *avahibrowser = avahi_service_browser_new(
m_avahiclient,
AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, servicetypeit.constData(), NULL,
AvahiLookupFlags(0),
KDNSSDPrivate::browseCallback, this
);
if (!avahibrowser) {
kWarning() << "Could not create Avahi browser" << avahi_strerror(avahi_client_errno(m_avahiclient));
return;
}
m_pollcounter++;
while (m_pollcounter) {
// qDebug() << Q_FUNC_INFO << m_pollcounter;
avahi_simple_poll_iterate(m_avahipoll, 0);
}
avahi_service_browser_free(avahibrowser);
}
#endif // HAVE_AVAHI
}
QList<KDNSSDService> KDNSSDPrivate::services() const
{
#if defined(HAVE_AVAHI)
return m_services;
#else
static const QList<KDNSSDService> result;
return result;
#endif
}
#if defined(HAVE_AVAHI)
void KDNSSDPrivate::groupCallback(AvahiEntryGroup *avahigroup, AvahiEntryGroupState avahistate, void *userdata)
{
// qDebug() << Q_FUNC_INFO << avahigroup << avahistate << userdata;
if (avahistate == AVAHI_ENTRY_GROUP_FAILURE) {
kWarning() << avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(avahigroup)));
}
}
void KDNSSDPrivate::clientCallback(AvahiClient *avahiclient, AvahiClientState avahistate, void *userdata)
{
// qDebug() << Q_FUNC_INFO << avahistate << userdata;
if (avahistate == AVAHI_CLIENT_FAILURE) {
kWarning() << avahi_strerror(avahi_client_errno(avahiclient));
}
}
void KDNSSDPrivate::browseCallback(AvahiServiceBrowser *avahibrowser, AvahiIfIndex avahiinterface,
AvahiProtocol avahiprotocol, AvahiBrowserEvent avahievent,
const char *avahiname, const char *avahitype, const char *avahidomain,
AvahiLookupResultFlags avahiflags,
void* userdata)
{
// qDebug() << Q_FUNC_INFO << avahievent << avahiname << avahitype << avahidomain << userdata;
KDNSSDPrivate *kdnssdprivate = static_cast<KDNSSDPrivate*>(userdata);
AvahiClient *avahiclient = avahi_service_browser_get_client(avahibrowser);
switch (avahievent) {
case AVAHI_BROWSER_NEW: {
kDebug() << "New service" << avahiname << avahitype << avahidomain;
kdnssdprivate->m_pollcounter++;
AvahiServiceResolver *avahiresolver = avahi_service_resolver_new(
avahiclient,
avahiinterface, avahiprotocol,
avahiname, avahitype, avahidomain,
AVAHI_PROTO_UNSPEC, AvahiLookupFlags(0),
KDNSSDPrivate::resolveCallback,
userdata
);
if (!avahiresolver) {
kWarning() << avahi_strerror(avahi_client_errno(avahiclient));
}
break;
}
case AVAHI_BROWSER_ALL_FOR_NOW:
case AVAHI_BROWSER_CACHE_EXHAUSTED: {
kDebug() << "Done browsing";
kdnssdprivate->m_pollcounter--;
KDNSSD *kdnssd= qobject_cast<KDNSSD*>(kdnssdprivate->parent());
emit kdnssd->finished();
break;
}
case AVAHI_BROWSER_FAILURE: {
kWarning() << avahi_strerror(avahi_client_errno(avahiclient));
kdnssdprivate->m_pollcounter = 0;
break;
}
}
}
void KDNSSDPrivate::resolveCallback(AvahiServiceResolver *avahiresolver, AvahiIfIndex avahiinterface,
AvahiProtocol avahiprotocol, AvahiResolverEvent avahievent,
const char *avahiname, const char *avahitype, const char *avahidomain, const char *avahihostname,
const AvahiAddress *avahiaddress, uint16_t avahiport,
AvahiStringList *avahitxt,
AvahiLookupResultFlags avahiflags,
void* userdata)
{
// qDebug() << Q_FUNC_INFO << avahievent << avahiname << avahitype << avahidomain << userdata;
KDNSSDPrivate *kdnssdprivate = static_cast<KDNSSDPrivate*>(userdata);
switch (avahievent) {
case AVAHI_RESOLVER_FOUND: {
kDebug() << "Resolved service" << avahiname << avahitype << avahidomain;
const QByteArray kdnssdservicetype = QByteArray(avahitype);
const QString kdnssdservicedomain = QString::fromUtf8(avahidomain);
const QString kdnssdservicehostname = QString::fromUtf8(avahihostname);
const uint kdnssdserviceport = avahiport;
// first comes, first serves
bool isduplicate = false;
foreach (const KDNSSDService &kdnssdserviceit, kdnssdprivate->m_services) {
if (kdnssdserviceit.type == kdnssdservicetype
&& kdnssdserviceit.domain == kdnssdservicedomain
&& kdnssdserviceit.hostname == kdnssdservicehostname
&& kdnssdserviceit.port == kdnssdserviceport) {
isduplicate = true;
break;
}
}
if (!isduplicate) {
QString kdnssdserviceprotocol = QString::fromLatin1(avahitype);
kdnssdserviceprotocol = kdnssdserviceprotocol.mid(1);
const int dotindex = kdnssdserviceprotocol.indexOf(QLatin1Char('.'));
kdnssdserviceprotocol = kdnssdserviceprotocol.left(dotindex);
KUrl kdnssdserviceurl;
kdnssdserviceurl.setProtocol(kdnssdserviceprotocol);
kdnssdserviceurl.setHost(kdnssdservicehostname);
kdnssdserviceurl.setPort(kdnssdserviceport);
KDNSSDService kdnssdservice;
kdnssdservice.name = QString::fromUtf8(avahiname);
kdnssdservice.type = kdnssdservicetype;
kdnssdservice.domain = kdnssdservicedomain;
kdnssdservice.hostname = kdnssdservicehostname;
kdnssdservice.url = kdnssdserviceurl.prettyUrl();
kdnssdservice.port = kdnssdserviceport;
kdnssdprivate->m_services.append(kdnssdservice);
}
break;
}
case AVAHI_RESOLVER_FAILURE: {
kWarning() << avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(avahiresolver)));
break;
}
}
kdnssdprivate->m_pollcounter--;
avahi_service_resolver_free(avahiresolver);
}
void KDNSSDPrivate::serviceCallback(AvahiServiceTypeBrowser *avahiservice,
AvahiIfIndex avahiinterface,
AvahiProtocol avahiprotocol,
AvahiBrowserEvent avahievent,
const char *avahitype,
const char *avahidomain,
AvahiLookupResultFlags avahiflags,
void *userdata)
{
// qDebug() << Q_FUNC_INFO << avahievent << avahitype << avahidomain << userdata;
KDNSSDPrivate *kdnssdprivate = static_cast<KDNSSDPrivate*>(userdata);
AvahiClient *avahiclient = avahi_service_type_browser_get_client(avahiservice);
switch (avahievent) {
case AVAHI_BROWSER_NEW: {
kDebug() << "New service type" << avahitype << avahidomain;
const QByteArray kdnsservicetype(avahitype);
if (!kdnssdprivate->m_servicetypes.contains(kdnsservicetype)) {
kdnssdprivate->m_servicetypes.append(kdnsservicetype);
}
break;
}
case AVAHI_BROWSER_ALL_FOR_NOW:
case AVAHI_BROWSER_CACHE_EXHAUSTED: {
kDebug() << "Done browsing service types";
kdnssdprivate->m_pollcounter--;
break;
}
case AVAHI_BROWSER_FAILURE: {
kWarning() << avahi_strerror(avahi_client_errno(avahiclient));
kdnssdprivate->m_pollcounter = 0;
break;
}
}
}
#endif // HAVE_AVAHI
KDNSSD::KDNSSD(QObject *parent)
: QObject(parent),
d(new KDNSSDPrivate(this))
{
}
KDNSSD::~KDNSSD()
{
delete d;
}
bool KDNSSD::publishService(const QByteArray &servicetype, const uint serviceport, const QString &servicename)
{
return d->publishService(servicetype, serviceport, servicename);
}
bool KDNSSD::unpublishService()
{
return d->unpublishService();
}
QList<KDNSSDService> KDNSSD::services() const
{
return d->services();
}
void KDNSSD::startBrowse(const QByteArray &servicetype)
{
d->startBrowse(servicetype);
}
#include "kdnssd.moc"