2010-07-24 16:16:44 +00:00
# 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>
# include <netinet/in.h>
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 : {
struct sockaddr_in6 a ;
memset ( & a , 0 , sizeof ( a ) ) ;
a . sin6_family = AF_INET6 ;
a . sin6_scope_id = interface ;
memcpy ( a . sin6_addr . s6_addr , address - > data . ipv6 . address , 16 ) ;
return QHostAddress ( ( const sockaddr * ) & a ) ;
}
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 ) {
2010-07-25 11:09:05 +00:00
m_parent - > lostService ( l . at ( i ) ) ;
2010-07-24 16:16:44 +00:00
}
}
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 ) ;
2010-07-25 11:09:05 +00:00
m_parent - > lostService ( s ) ;
2010-07-24 16:16:44 +00:00
}
}
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 < - 1 ) { /* "boolean" */
key = e ;
} else {
key = e . left ( eqPos - 1 ) ;
value = e . mid ( eqPos + 1 ) ;
}
key = key . toLower ( ) ;
qtxt . insert ( key , value ) ;
}
ServiceKey k ( interface , protocol , name , domain ) ;
Service s = m_list . value ( k ) ;
if ( s . valid ( ) ) {
m_list . remove ( k ) ;
2010-07-25 11:09:05 +00:00
m_parent - > lostService ( s ) ;
2010-07-24 16:16:44 +00:00
}
s = Service ( QString : : fromUtf8 ( name ) , QString : : fromUtf8 ( host_name ) , QString : : fromUtf8 ( domain ) , fromAvahiAddress ( address , interface ) , interface , protocol , port , qtxt ) ;
m_list . insert ( k , s ) ;
2010-07-25 11:09:05 +00:00
m_parent - > foundService ( s ) ;
2010-07-24 16:16:44 +00:00
}
}
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 ( ) ;
}
2010-07-25 11:09:05 +00:00
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 ) ;
}
}
2010-07-24 16:16:44 +00:00
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 ) ;
}
}