/************************************************************************************************** --------------------------------------------------------------------------------------------------- Copyright (C) 2015 Jonathan Bagg This file is part of QtZeroConf. QtZeroConf is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. QtZeroConf 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with QtZeroConf. If not, see . --------------------------------------------------------------------------------------------------- Project name : QtZeroConf File name : avahiclient.cpp Created : 20 July 2015 Author(s) : Jonathan Bagg --------------------------------------------------------------------------------------------------- Avahi-client wrapper for use in Desktop Linux systems --------------------------------------------------------------------------------------------------- **************************************************************************************************/ //#include // #include "avahi-qt/qt-watch.h" // fixme don't depend on avahi-qt until it supports Qt5 #include #include #include #include #include "qzeroconf.h" class QZeroConfPrivate { public: QZeroConfPrivate(QZeroConf *parent) { qint32 error; txt = NULL; pub = parent; group = NULL; browser = NULL; poll = avahi_qt_poll_get(); if (!poll) { return; } client = avahi_client_new(poll, AVAHI_CLIENT_IGNORE_USER_CONFIG, NULL, this, &error); if (!client) { return; } } static void groupCallback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) { QZeroConfPrivate *ref = static_cast(userdata); switch (state) { case AVAHI_ENTRY_GROUP_ESTABLISHED: emit ref->pub->servicePublished(); break; case AVAHI_ENTRY_GROUP_COLLISION: avahi_entry_group_free(g); ref->group = NULL; emit ref->pub->error(QZeroConf::serviceNameCollision); break; case AVAHI_ENTRY_GROUP_FAILURE: avahi_entry_group_free(g); ref->group = NULL; emit ref->pub->error(QZeroConf::serviceRegistrationFailed); break; case AVAHI_ENTRY_GROUP_UNCOMMITED: break; case AVAHI_ENTRY_GROUP_REGISTERING: break; } } static void browseCallback(AvahiServiceBrowser *, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void* userdata) { QString key = name + QString::number(interface); QZeroConfPrivate *ref = static_cast(userdata); QZeroConfService zcs; switch (event) { case AVAHI_BROWSER_FAILURE: ref->broswerCleanUp(); emit ref->pub->error(QZeroConf::browserFailed); break; case AVAHI_BROWSER_NEW: if (!ref->resolvers.contains(key)) ref->resolvers.insert(key, avahi_service_resolver_new(ref->client, interface, protocol, name, type, domain, ref->aProtocol, AVAHI_LOOKUP_USE_MULTICAST, resolveCallback, ref)); break; case AVAHI_BROWSER_REMOVE: if (!ref->resolvers.contains(key)) return; avahi_service_resolver_free(ref->resolvers[key]); ref->resolvers.remove(key); if (!ref->pub->services.contains(key)) return; zcs = ref->pub->services[key]; ref->pub->services.remove(key); emit ref->pub->serviceRemoved(zcs); break; case AVAHI_BROWSER_ALL_FOR_NOW: case AVAHI_BROWSER_CACHE_EXHAUSTED: break; } } static void resolveCallback( AVAHI_GCC_UNUSED AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIndex interface, AVAHI_GCC_UNUSED AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags, AVAHI_GCC_UNUSED void* userdata) { bool newRecord = 0; QZeroConfService zcs; QZeroConfPrivate *ref = static_cast(userdata); QString key = name + QString::number(interface); if (event == AVAHI_RESOLVER_FOUND) { if (ref->pub->services.contains(key)) zcs = ref->pub->services[key]; else { zcs = QZeroConfService(new QZeroConfServiceData); newRecord = 1; zcs->m_name = name; zcs->m_type = type; zcs->m_domain = domain; zcs->m_host = host_name; zcs->m_interfaceIndex = interface; zcs->m_port = port; while (txt) // get txt records { QByteArray avahiText((const char *)txt->text, txt->size); const ssize_t pos = avahiText.indexOf('='); if (pos < 0) zcs->m_txt[avahiText] = ""; else zcs->m_txt[avahiText.left(pos)] = avahiText.mid(pos + 1, -1); txt = txt->next; } ref->pub->services.insert(key, zcs); } char a[AVAHI_ADDRESS_STR_MAX]; avahi_address_snprint(a, sizeof(a), address); QHostAddress addr(a); zcs->setIp(addr); if (newRecord) emit ref->pub->serviceAdded(zcs); else emit ref->pub->serviceUpdated(zcs); } else if (ref->pub->services.contains(key)) { // delete service if exists and unable to resolve zcs = ref->pub->services[key]; ref->pub->services.remove(key); emit ref->pub->serviceRemoved(zcs); // don't delete the resolver here...we need to keep it around so Avahi will keep updating....might be able to resolve the service in the future } } void broswerCleanUp(void) { if (!browser) return; avahi_service_browser_free(browser); browser = NULL; QMap::iterator i; for (i = pub->services.begin(); i != pub->services.end(); i++) { emit pub->serviceRemoved(i.value()); } pub->services.clear(); QMap::iterator r; for (r = resolvers.begin(); r != resolvers.end(); r++) avahi_service_resolver_free(*r); resolvers.clear(); } QZeroConf *pub; const AvahiPoll *poll; AvahiClient *client; AvahiEntryGroup *group; AvahiServiceBrowser *browser; AvahiProtocol aProtocol; QMap resolvers; AvahiStringList *txt; }; QZeroConf::QZeroConf(QObject *parent) : QObject (parent) { pri = new QZeroConfPrivate(this); qRegisterMetaType("QZeroConfService"); } QZeroConf::~QZeroConf() { avahi_string_list_free(pri->txt); pri->broswerCleanUp(); if (pri->client) avahi_client_free(pri->client); delete pri; } void QZeroConf::startServicePublish(const char *name, const char *type, const char *domain, quint16 port, quint32 interface) { if (!pri->client || pri->group) { // check client is ok (avahi daemon is running) and group is not already configured emit error(QZeroConf::serviceRegistrationFailed); return; } if (interface <= 0) { interface = AVAHI_IF_UNSPEC; } pri->group = avahi_entry_group_new(pri->client, QZeroConfPrivate::groupCallback, pri); int ret = avahi_entry_group_add_service_strlst(pri->group, interface, AVAHI_PROTO_UNSPEC, AVAHI_PUBLISH_UPDATE, name, type, domain, NULL, port, pri->txt); if (ret < 0) { avahi_entry_group_free(pri->group); pri->group = NULL; emit error(QZeroConf::serviceRegistrationFailed); return; } ret = avahi_entry_group_commit(pri->group); if (ret < 0) { pri->group = NULL; avahi_entry_group_free(pri->group); emit error(QZeroConf::serviceRegistrationFailed); } if (!pri->group) emit error(QZeroConf::serviceRegistrationFailed); } void QZeroConf::stopServicePublish(void) { if (pri->group) { avahi_entry_group_free(pri->group); pri->group = NULL; } } bool QZeroConf::publishExists(void) { if (pri->group) return true; else return false; } // http://www.zeroconf.org/rendezvous/txtrecords.html void QZeroConf::addServiceTxtRecord(QString nameOnly) { pri->txt = avahi_string_list_add(pri->txt, nameOnly.toUtf8()); } void QZeroConf::addServiceTxtRecord(QString name, QString value) { name.append("="); name.append(value); addServiceTxtRecord(name); } void QZeroConf::clearServiceTxtRecords() { avahi_string_list_free(pri->txt); pri->txt = NULL; } void QZeroConf::startBrowser(QString type, QAbstractSocket::NetworkLayerProtocol protocol) { if (!pri->client || pri->browser) { // check client is ok (avahi daemon is running) and browser is not already started emit error(QZeroConf::browserFailed); return; } switch (protocol) { case QAbstractSocket::IPv4Protocol: pri->aProtocol = AVAHI_PROTO_INET; break; case QAbstractSocket::IPv6Protocol: pri->aProtocol = AVAHI_PROTO_INET6; break; default: qDebug("QZeroConf::startBrowser() - unsupported protocol, using IPv4"); pri->aProtocol = AVAHI_PROTO_INET; break; }; pri->browser = avahi_service_browser_new(pri->client, AVAHI_IF_UNSPEC, pri->aProtocol, type.toUtf8(), NULL, AVAHI_LOOKUP_USE_MULTICAST, QZeroConfPrivate::browseCallback, pri); if (!pri->browser) emit error(QZeroConf::browserFailed); } void QZeroConf::stopBrowser(void) { pri->broswerCleanUp(); } bool QZeroConf::browserExists(void) { if (pri->browser) return true; else return false; }