qcp/libqtavahi/qtavahi.cpp

583 lines
16 KiB
C++

#include "qtavahi.h"
#include <avahi-client/client.h>
#include <avahi-client/lookup.h>
#include <avahi-client/publish.h>
#include <avahi-common/alternative.h>
#include <avahi-common/error.h>
#include <avahi-common/malloc.h>
#include <avahi-qt4/qt-watch.h>
#include <QSize>
#include <QtEndian>
extern "C" {
#include <netinet/in.h>
#include <net/if.h>
}
static QString ifidx2name(unsigned int ifindex) {
char buf[IF_NAMESIZE];
char *n = ::if_indextoname(ifindex, buf);
if (!n) return QString();
return QString::fromUtf8(n, qstrnlen(n, IF_NAMESIZE));
}
namespace Avahi {
static QHostAddress fromAvahiAddress(const AvahiAddress *address, AvahiIfIndex interface) {
switch (address->proto) {
case AVAHI_PROTO_INET:
return QHostAddress(qFromBigEndian((quint32) address->data.ipv4.address));
case AVAHI_PROTO_INET6: {
QHostAddress addr((quint8*) address->data.ipv6.address);
if (interface >= 0) addr.setScopeId(ifidx2name(interface));
return addr;
}
case AVAHI_PROTO_UNSPEC:
return QHostAddress();
}
return QHostAddress();
}
namespace priv {
static void browsep_client_callback(AvahiClient *c, AvahiClientState state, void * userdata);
static void browsep_browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event,
const char *name, const char *type, const char *domain, AvahiLookupResultFlags flags, void* userdata);
static void browsep_resolve_callback(AvahiServiceResolver *r, AvahiIfIndex interface, 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 flags, void* userdata);
}
}
namespace Avahi {
namespace priv {
class BrowseP {
public:
static const ::AvahiPoll *m_qtpoll;
QString m_serviceType;
bool m_initialized;
bool m_running;
::AvahiClient *m_client;
::AvahiServiceBrowser *m_browser;
QByteArray m_service_type;
Avahi::Browse *m_parent;
QHash<ServiceKey, Service> m_list;
BrowseP(QString servicetype, Browse *parent)
: m_serviceType(servicetype),
m_initialized(false), m_running(false), m_client(0), m_browser(0),
m_service_type(servicetype.toUtf8()), m_parent(parent) {
int err;
if (!m_qtpoll) {
if (!(m_qtpoll = avahi_qt_poll_get())) {
qWarning("avahi_qt_poll_get failed");
return;
}
}
if (!(m_client = avahi_client_new(m_qtpoll, (AvahiClientFlags) 0, browsep_client_callback, this, &err))) {
qWarning("avahi_client_new failed: %s", avahi_strerror(err));
return;
}
m_initialized = true;
}
void clearList() {
QList<Service> l = m_list.values();
m_list.clear();
for (int i = 0; i < l.size(); ++i) {
m_parent->lostService(l.at(i));
}
}
void release() {
stop();
if (!m_initialized) return;
if (m_client) {
avahi_client_free(m_client);
m_client = 0;
}
m_initialized = false;
}
~BrowseP() {
release();
}
void client_callback(AvahiClient *c, AvahiClientState state) {
switch (state) {
case AVAHI_CLIENT_S_RUNNING:
break;
case AVAHI_CLIENT_FAILURE:
qWarning("avahi client failure: %s", avahi_strerror(avahi_client_errno(c)));
release();
break;
case AVAHI_CLIENT_S_COLLISION:
case AVAHI_CLIENT_S_REGISTERING:
break;
case AVAHI_CLIENT_CONNECTING:
break;
}
}
void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event,
const char *name, const char *type, const char *domain, AvahiLookupResultFlags /* flags */) {
if (!m_browser) m_browser = b;
Q_ASSERT(m_browser == b);
if (protocol != AVAHI_PROTO_INET && protocol != AVAHI_PROTO_INET6) return; /* ignore non IPv[46] for now */
/* Called whenever a new services becomes available on the LAN or is removed from the LAN */
switch (event) {
case AVAHI_BROWSER_FAILURE:
qWarning("(Browser) %s", avahi_strerror(avahi_client_errno(m_client)));
release();
return;
case AVAHI_BROWSER_NEW:
qDebug("(Browser) NEW: service '%s' of type '%s' in domain '%s'", name, type, domain);
/* We ignore the returned resolver object. In the callback
* function we free it. If the server is terminated before
* the callback function is called the server will free
* the resolver for us. */
if (!(avahi_service_resolver_new(m_client, interface, protocol, name, type, domain, protocol, (AvahiLookupFlags) 0, browsep_resolve_callback, this))) {
qWarning("Failed to resolve service '%s' on '%s': %s", name, domain, avahi_strerror(avahi_client_errno(m_client)));
}
break;
case AVAHI_BROWSER_REMOVE: {
ServiceKey k(interface, protocol, name, domain);
Service s = m_list.value(k);
if (s.valid()) {
m_list.remove(k);
m_parent->lostService(s);
}
}
break;
case AVAHI_BROWSER_ALL_FOR_NOW:
break;
case AVAHI_BROWSER_CACHE_EXHAUSTED:
break;
}
}
void resolve_callback(AvahiServiceResolver *r, AvahiIfIndex interface, 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 /* flags */) {
Q_ASSERT(r);
// if (protocol != AVAHI_PROTO_INET) goto done; /* ignore non IPv4 for now */
if (protocol != AVAHI_PROTO_INET && protocol != AVAHI_PROTO_INET6) goto done; /* ignore non IPv[46] for now */
/* Called whenever a service has been resolved successfully or timed out */
switch (event) {
case AVAHI_RESOLVER_FAILURE:
qDebug("(Resolver) Failed to resolve service '%s' of type '%s' at '%s' in domain '%s': %s", name, type, host_name, domain, avahi_strerror(avahi_client_errno(m_client)));
break;
case AVAHI_RESOLVER_FOUND: {
qDebug("(Resolver) found service '%s' of type '%s' at '%s' in domain '%s'", name, type, host_name, domain);
QHash<QString, QString> qtxt;
AvahiStringList *psl;
for (psl = txt ; psl ; psl = psl->next) {
QString e = QString::fromUtf8((const char*) psl->text, psl->size), key, value;
int eqPos = e.indexOf(QChar('='));
if (eqPos == 0) continue; /* ignore empty keys */
if (eqPos < 0) { /* "boolean" */
key = e;
} else {
key = e.left(eqPos);
value = e.mid(eqPos + 1);
}
key = key.toLower();
// qDebug() << "Resolver: key = " << key << ", value = " << value;
qtxt.insert(key, value);
}
ServiceKey k(interface, protocol, name, domain);
Service s = m_list.value(k);
if (s.valid()) {
m_list.remove(k);
m_parent->lostService(s);
}
s = Service(QString::fromUtf8(name), QString::fromUtf8(host_name), QString::fromUtf8(domain), fromAvahiAddress(address, interface), interface, protocol, port, qtxt);
m_list.insert(k, s);
m_parent->foundService(s);
}
}
done:
avahi_service_resolver_free(r);
}
bool start() {
if (m_running) return true;
if (!m_initialized) return false;
clearList();
if (NULL == (m_browser = avahi_service_browser_new(m_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, m_service_type.constData(), NULL, (AvahiLookupFlags) 0, browsep_browse_callback, this))) {
qWarning("Failed to create service browser: %s", avahi_strerror(avahi_client_errno(m_client)));
return false;
}
emit m_parent->started();
m_running = true;
return true;
}
void stop() {
clearList();
if (m_running) {
emit m_parent->stopped();
m_running = false;
}
if (m_browser) {
avahi_service_browser_free(m_browser);
m_browser = 0;
}
}
};
const AvahiPoll *BrowseP::m_qtpoll = 0;
static void browsep_client_callback(AvahiClient *c, AvahiClientState state, void * userdata) {
BrowseP *p = static_cast<BrowseP*>(userdata);
p->client_callback(c, state);
}
static void browsep_browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event,
const char *name, const char *type, const char *domain, AvahiLookupResultFlags flags, void* userdata) {
BrowseP *p = static_cast<BrowseP*>(userdata);
p->browse_callback(b, interface, protocol, event, name, type, domain, flags);
}
static void browsep_resolve_callback(AvahiServiceResolver *r, AvahiIfIndex interface, 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 flags, void* userdata) {
BrowseP *p = static_cast<BrowseP*>(userdata);
p->resolve_callback(r, interface, protocol, event, name, type, domain, host_name, address, port, txt, flags);
}
static void announcep_client_callback(AvahiClient *c, AvahiClientState state, void * userdata);
static void announcep_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata);
class AnnounceP {
public:
static const AvahiPoll *m_qtpoll;
QString m_serviceType, m_name;
quint16 m_port;
QHash<QString,QString> m_txt;
bool m_initialized;
bool m_running;
AvahiClient *m_client;
AvahiEntryGroup *m_group;
QByteArray m_service_type;
Announce *m_parent;
char *m_raw_name; /* avahi string */
QList<QByteArray> m_txt_list;
::AvahiStringList *m_raw_txt;
AnnounceP(QString serviceType, QString name, quint16 port, QHash<QString,QString> txt, Announce *parent)
: m_serviceType(serviceType), m_name(name), m_port(port), m_txt(txt),
m_initialized(false), m_running(false), m_client(0), m_group(0),
m_service_type(serviceType.toUtf8()), m_parent(parent),
m_raw_name(avahi_strdup(name.toUtf8().constData())), m_raw_txt(0) {
int err;
QHashIterator<QString, QString> i(m_txt);
while (i.hasNext()) {
QByteArray a;
i.next();
if (i.value().isNull()) {
m_txt_list.append(a = i.key().toUtf8());
} else {
m_txt_list.append(a = QString("%1=%2").arg(i.key(), i.value()).toUtf8());
}
AvahiStringList *l = avahi_string_list_new(a.constData(), NULL);
l->next = m_raw_txt;
m_raw_txt = l;
}
if (!m_qtpoll) {
if (!(m_qtpoll = avahi_qt_poll_get())) {
qWarning("avahi_qt_poll_get failed");
return;
}
}
if (!(m_client = avahi_client_new(m_qtpoll, (AvahiClientFlags) 0, announcep_client_callback, this, &err))) {
qWarning("avahi_client_new failed: %s", avahi_strerror(err));
return;
}
m_initialized = true;
start();
}
void release() {
if (m_running) {
m_running = false;
}
if (!m_initialized) return;
if (m_group) {
qDebug("avahi unregister service");
avahi_entry_group_reset(m_group);
avahi_entry_group_free(m_group);
m_group = 0;
}
if (m_client) {
avahi_client_free(m_client);
m_client = 0;
}
m_initialized = false;
emit m_parent->stopped();
}
~AnnounceP() {
release();
if (m_raw_name) {
avahi_free(m_raw_name);
m_raw_name = 0;
}
if (m_raw_txt) {
avahi_string_list_free(m_raw_txt);
m_raw_txt = 0;
}
}
void client_callback(AvahiClient *c, AvahiClientState state) {
m_client = c;
switch (state) {
case AVAHI_CLIENT_S_RUNNING:
create_services();
break;
case AVAHI_CLIENT_FAILURE:
qDebug("avahi client failure");
release();
break;
case AVAHI_CLIENT_S_COLLISION:
case AVAHI_CLIENT_S_REGISTERING:
if (m_group) {
avahi_entry_group_reset(m_group);
}
break;
case AVAHI_CLIENT_CONNECTING:
break;
}
}
void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state) {
m_group = g;
switch (state) {
case AVAHI_ENTRY_GROUP_ESTABLISHED :
emit m_parent->established();
break;
case AVAHI_ENTRY_GROUP_COLLISION :
{
char *n = avahi_alternative_service_name(m_raw_name);
avahi_free(m_raw_name);
m_raw_name = n;
m_name = QString::fromUtf8(m_raw_name);
}
/* And recreate the services */
avahi_entry_group_reset(m_group);
create_services();
/* same name should result in AVAHI_ERR_COLLISION in create_services, emit collision there. */
break;
case AVAHI_ENTRY_GROUP_FAILURE :
qDebug("Entry group failure: %s", avahi_strerror(avahi_client_errno(m_client)));
release();
break;
case AVAHI_ENTRY_GROUP_UNCOMMITED:
case AVAHI_ENTRY_GROUP_REGISTERING:
break;
}
}
bool create_services() {
int ret;
if (!m_group) {
if (!(m_group = avahi_entry_group_new(m_client, announcep_entry_group_callback, this))) {
qDebug("avahi_entry_group_new failed: %s", avahi_strerror(avahi_client_errno(m_client)));
release();
return false;
}
}
again:
if (avahi_entry_group_is_empty(m_group)) {
if ((ret = avahi_entry_group_add_service_strlst(m_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, (AvahiPublishFlags) 0, m_raw_name, m_service_type.constData(), NULL, NULL, m_port, m_raw_txt)) < 0) {
if (ret == AVAHI_ERR_COLLISION)
goto collision;
qDebug("Failed to add '%s': %s", m_service_type.constData(), avahi_strerror(ret));
release();
return false;
}
if ((ret = avahi_entry_group_commit(m_group)) < 0) {
qDebug("Failed to commit entry group: %s", avahi_strerror(ret));
release();
return false;
}
}
return true;
collision:
qDebug("avahi name collision: %s, trying a new one", m_raw_name);
{
char *n = avahi_alternative_service_name(m_raw_name);
avahi_free(m_raw_name);
m_raw_name = n;
m_name = QString::fromUtf8(m_raw_name);
}
avahi_entry_group_reset(m_group);
emit m_parent->collision();
goto again;
}
bool start() {
if (m_running) return true;
if (!m_initialized) return false;
m_running = true;
emit m_parent->started();
return true;
}
void stop() {
release();
}
};
const AvahiPoll *AnnounceP::m_qtpoll = 0;
static void announcep_client_callback(AvahiClient *c, AvahiClientState state, void * userdata) {
AnnounceP *p = static_cast<AnnounceP*>(userdata);
p->client_callback(c, state);
}
static void announcep_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
AnnounceP *p = static_cast<AnnounceP*>(userdata);
p->entry_group_callback(g, state);
}
}
/* public interfaces */
Browse::Browse(QString serviceType, QObject *parent)
: QObject(parent), m_priv(new priv::BrowseP(serviceType, this)) {
}
Browse::~Browse() {
delete m_priv;
m_priv = 0;
}
bool Browse::start() {
return m_priv->start();
}
void Browse::stop() {
m_priv->stop();
}
QString Browse::serviceType() const {
return m_priv->m_serviceType;
}
QList<Service> Browse::currentList() const {
return m_priv->m_list.values();
}
void Browse::foundService(Avahi::Service s) {
emit found(s);
}
void Browse::lostService(Avahi::Service s) {
emit lost(s);
if (s.m_priv && s.m_priv->m_watcher) {
emit s.m_priv->m_watcher->lost(s);
}
}
Announce::Announce(QString serviceType, QString name, quint16 port, QHash<QString,QString> txt, QObject *parent)
: QObject(parent), m_priv(new priv::AnnounceP(serviceType, name, port, txt, this)) {
}
Announce::~Announce() {
delete m_priv;
m_priv = 0;
}
bool Announce::start() {
return m_priv->start();
}
void Announce::stop() {
m_priv->stop();
}
QString Announce::serviceType() const {
return m_priv->m_serviceType;
}
QString Announce::name() const {
return m_priv->m_name;
}
quint16 Announce::port() const {
return m_priv->m_port;
}
QHash<QString, QString> Announce::txt() const {
return m_priv->m_txt;
}
QString Announce::txt(QString key) const {
return m_priv->m_txt.value(key);
}
}