LCOV - code coverage report
Current view: top level - lunchbox/avahi - servus.h (source / functions) Hit Total Coverage
Test: lcov2.info Lines: 138 189 73.0 %
Date: 2014-10-01 Functions: 21 22 95.5 %

          Line data    Source code
       1             : 
       2             : /* Copyright (c) 2014, Stefan.Eilemann@epfl.ch
       3             :  *
       4             :  * This library is free software; you can redistribute it and/or modify it under
       5             :  * the terms of the GNU Lesser General Public License version 2.1 as published
       6             :  * by the Free Software Foundation.
       7             :  *
       8             :  * This library is distributed in the hope that it will be useful, but WITHOUT
       9             :  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
      10             :  * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
      11             :  * details.
      12             :  *
      13             :  * You should have received a copy of the GNU Lesser General Public License
      14             :  * along with this library; if not, write to the Free Software Foundation, Inc.,
      15             :  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
      16             :  */
      17             : 
      18             : #include "../clock.h"
      19             : #include "../debug.h"
      20             : #include "../os.h"
      21             : 
      22             : #include <avahi-client/client.h>
      23             : #include <avahi-client/lookup.h>
      24             : #include <avahi-client/publish.h>
      25             : #include <avahi-common/error.h>
      26             : #include <avahi-common/simple-watch.h>
      27             : 
      28             : #include <net/if.h>
      29             : #include <stdexcept>
      30             : 
      31             : namespace lunchbox
      32             : {
      33             : namespace avahi
      34             : {
      35             : class Servus : public detail::Servus
      36             : {
      37             : public:
      38           3 :     explicit Servus( const std::string& name )
      39             :         : _name( name )
      40           3 :         , _poll( avahi_simple_poll_new( ))
      41             :         , _client( 0 )
      42             :         , _browser( 0 )
      43             :         , _group( 0 )
      44             :         , _result( lunchbox::Servus::Result::PENDING )
      45             :         , _port( 0 )
      46             :         , _announcable( false )
      47           6 :         , _scope( lunchbox::Servus::IF_ALL )
      48             :     {
      49           3 :         if( !_poll )
      50           0 :             LBTHROW( std::runtime_error( "Can't setup avahi poll device" ));
      51             : 
      52           3 :         int error = 0;
      53             :         _client = avahi_client_new( avahi_simple_poll_get( _poll ),
      54             :                                     (AvahiClientFlags)(0), _clientCBS, this,
      55           3 :                                     &error );
      56           3 :         if( !_client )
      57           0 :             LBTHROW( std::runtime_error(
      58             :                          std::string( "Can't setup avahi client: " ) +
      59             :                          avahi_strerror( error )));
      60           3 :     }
      61             : 
      62           6 :     virtual ~Servus()
      63           6 :     {
      64           3 :         withdraw();
      65           3 :         endBrowsing();
      66             : 
      67           3 :         if( _client )
      68           3 :             avahi_client_free( _client );
      69           3 :         if( _poll )
      70           3 :             avahi_simple_poll_free( _poll );
      71           6 :     }
      72             : 
      73           3 :     lunchbox::Servus::Result announce( const unsigned short port,
      74             :                                        const std::string& instance ) final
      75             :     {
      76           3 :         _result = lunchbox::Servus::Result::PENDING;
      77           3 :         _port = port;
      78           3 :         if( instance.empty( ))
      79           0 :             _announce = getHostname();
      80             :         else
      81           3 :             _announce = instance;
      82             : 
      83           3 :         if( _announcable )
      84           3 :             _createServices();
      85             :         else
      86             :         {
      87           0 :             lunchbox::Clock clock;
      88           0 :             while( !_announcable &&
      89           0 :                    _result == lunchbox::Servus::Result::PENDING &&
      90           0 :                    clock.getTime64() < ANNOUNCE_TIMEOUT )
      91             :             {
      92           0 :                 avahi_simple_poll_iterate( _poll, ANNOUNCE_TIMEOUT );
      93           0 :             }
      94             :         }
      95             : 
      96           3 :         return lunchbox::Servus::Result( _result );
      97             :     }
      98             : 
      99           4 :     void withdraw() final
     100             :     {
     101           4 :         _announce.clear();
     102           4 :         _port = 0;
     103           4 :         if( _group )
     104           3 :             avahi_entry_group_reset( _group );
     105           4 :     }
     106             : 
     107           0 :     bool isAnnounced() const final
     108             :     {
     109           0 :         return ( _group && !avahi_entry_group_is_empty( _group ));
     110             :     }
     111             : 
     112           4 :     lunchbox::Servus::Result beginBrowsing(
     113             :                                   const lunchbox::Servus::Interface addr ) final
     114             :     {
     115           4 :         _scope = addr;
     116           4 :         if( _browser )
     117           1 :             return lunchbox::Servus::Result( lunchbox::Servus::Result::PENDING);
     118             : 
     119           3 :         _instanceMap.clear();
     120           3 :         return _browse();
     121             :     }
     122             : 
     123           5 :     lunchbox::Servus::Result browse( const int32_t timeout ) final
     124             :     {
     125           5 :         _result = lunchbox::Servus::Result::PENDING;
     126           5 :         lunchbox::Clock clock;
     127             : 
     128          49 :         do
     129             :         {
     130          49 :             if( avahi_simple_poll_iterate( _poll, timeout ) != 0 )
     131             :             {
     132           0 :                 _result = lunchbox::Servus::Result::POLL_ERROR;
     133           0 :                 break;
     134             :             }
     135             :         }
     136          49 :         while( clock.getTime64() < timeout );
     137             : 
     138           5 :         if( _result != lunchbox::Servus::Result::POLL_ERROR )
     139           5 :             _result = lunchbox::Servus::Result::SUCCESS;
     140             : 
     141           5 :         return lunchbox::Servus::Result( _result );
     142             :     }
     143             : 
     144           6 :     void endBrowsing() final
     145             :     {
     146           6 :         if( _browser )
     147           3 :             avahi_service_browser_free( _browser );
     148           6 :         _browser = 0;
     149           6 :     }
     150             : 
     151           5 :     bool isBrowsing() const final { return _browser; }
     152             : 
     153           2 :     Strings discover( const lunchbox::Servus::Interface addr,
     154             :                       const unsigned browseTime ) final
     155             :     {
     156           2 :         const lunchbox::Servus::Result& result = beginBrowsing( addr );
     157           2 :         if( !result && result != lunchbox::Servus::Result::PENDING )
     158           0 :             return getInstances();
     159             : 
     160           2 :         LBASSERT( _browser );
     161           2 :         browse( browseTime );
     162           2 :         if( result != lunchbox::Servus::Result::PENDING )
     163           2 :             endBrowsing();
     164           2 :         return getInstances();
     165             :     }
     166             : 
     167             : private:
     168             :     const std::string _name;
     169             :     AvahiSimplePoll* _poll;
     170             :     AvahiClient* _client;
     171             :     AvahiServiceBrowser* _browser;
     172             :     AvahiEntryGroup* _group;
     173             :     int32_t _result;
     174             :     std::string _announce;
     175             :     unsigned short _port;
     176             :     bool _announcable;
     177             :     lunchbox::Servus::Interface _scope;
     178             : 
     179           3 :     lunchbox::Servus::Result _browse()
     180             :     {
     181           3 :         _result = lunchbox::Servus::Result::SUCCESS;
     182             : 
     183             :         _browser = avahi_service_browser_new( _client, AVAHI_IF_UNSPEC,
     184             :                                               AVAHI_PROTO_UNSPEC, _name.c_str(),
     185             :                                               0, (AvahiLookupFlags)(0),
     186           3 :                                               _browseCBS, this );
     187           3 :         if( _browser )
     188           3 :             return lunchbox::Servus::Result( _result );
     189             : 
     190           0 :         _result = avahi_client_errno( _client );
     191           0 :         LBWARN << "Failed to create browser: " << avahi_strerror( _result )
     192           0 :                << std::endl;
     193           0 :         return lunchbox::Servus::Result( _result );
     194             :     }
     195             : 
     196             :     // Client state change
     197           3 :     static void _clientCBS( AvahiClient*, AvahiClientState state,
     198             :                             void* servus )
     199             :     {
     200           3 :         ((Servus*)servus)->_clientCB( state );
     201           3 :     }
     202             : 
     203           3 :     void _clientCB( AvahiClientState state )
     204             :     {
     205           3 :         switch (state)
     206             :         {
     207             :         case AVAHI_CLIENT_S_RUNNING:
     208           3 :             _announcable = true;
     209           3 :             if( !_announce.empty( ))
     210           0 :                 _createServices();
     211           3 :             break;
     212             : 
     213             :         case AVAHI_CLIENT_FAILURE:
     214           0 :             _result = avahi_client_errno( _client );
     215           0 :             LBWARN << "Client failure: " << avahi_strerror( _result )
     216           0 :                    << std::endl;
     217           0 :             avahi_simple_poll_quit( _poll );
     218           0 :             break;
     219             : 
     220             :         case AVAHI_CLIENT_S_COLLISION:
     221             :             // Can't setup client
     222           0 :             _result = EEXIST;
     223           0 :             avahi_simple_poll_quit( _poll );
     224           0 :             break;
     225             : 
     226             :         case AVAHI_CLIENT_S_REGISTERING:
     227             :             /* The server records are now being established. This might be
     228             :              * caused by a host name change. We need to wait for our own records
     229             :              * to register until the host name is properly esatblished. */
     230           0 :             LBUNIMPLEMENTED; // withdraw & _createServices ?
     231           0 :             break;
     232             : 
     233             :         case AVAHI_CLIENT_CONNECTING:
     234             :             /*nop*/;
     235             :         }
     236           3 :     }
     237             : 
     238             :     // Browsing
     239          16 :     static void _browseCBS( AvahiServiceBrowser*, AvahiIfIndex ifIndex,
     240             :                             AvahiProtocol protocol, AvahiBrowserEvent event,
     241             :                             const char* name, const char* type,
     242             :                             const char* domain, AvahiLookupResultFlags,
     243             :                             void* servus )
     244             :     {
     245             :         ((Servus*)servus)->_browseCB( ifIndex, protocol, event, name, type,
     246          16 :                                       domain );
     247          16 :     }
     248             : 
     249          16 :     void _browseCB( const AvahiIfIndex ifIndex, const AvahiProtocol protocol,
     250             :                     const AvahiBrowserEvent event, const char* name,
     251             :                     const char* type, const char* domain )
     252             :     {
     253          16 :         LBVERB << "Browse event " << int(event) << " for "
     254           0 :                << (name ? name : "none") << " type " <<  (type ? type : "none")
     255          16 :                << std::endl;
     256          16 :         switch( event )
     257             :         {
     258             :         case AVAHI_BROWSER_FAILURE:
     259           0 :             _result = avahi_client_errno( _client );
     260           0 :             LBWARN << "Browser failure: " << avahi_strerror( _result )
     261           0 :                    << std::endl;
     262           0 :             avahi_simple_poll_quit( _poll );
     263           0 :             break;
     264             : 
     265             :         case AVAHI_BROWSER_NEW:
     266             :             /* We ignore the returned resolver object. In the callback function
     267             :                we free it. If the server is terminated before the callback
     268             :                function is called the server will free the resolver for us. */
     269           8 :             if( !avahi_service_resolver_new( _client, ifIndex, protocol, name,
     270             :                                              type, domain, AVAHI_PROTO_UNSPEC,
     271             :                                              (AvahiLookupFlags)(0),
     272           8 :                                              _resolveCBS, this ))
     273             :             {
     274           0 :                 _result = avahi_client_errno( _client );
     275           0 :                 LBWARN << "Error creating resolver: "
     276           0 :                        << avahi_strerror( _result ) << std::endl;
     277           0 :                 avahi_simple_poll_quit( _poll );
     278           0 :                 break;
     279             :             }
     280             : 
     281             :         case AVAHI_BROWSER_REMOVE:
     282          10 :             _instanceMap.erase( name );
     283          10 :             break;
     284             : 
     285             :         case AVAHI_BROWSER_ALL_FOR_NOW:
     286             :         case AVAHI_BROWSER_CACHE_EXHAUSTED:
     287           6 :             _result = lunchbox::Result::SUCCESS;
     288           6 :             break;
     289             :         }
     290          16 :     }
     291             : 
     292             :     // Resolving
     293           8 :     static void _resolveCBS( AvahiServiceResolver* resolver,
     294             :                              AvahiIfIndex, AvahiProtocol,
     295             :                              AvahiResolverEvent event, const char* name,
     296             :                              const char*, const char*,
     297             :                              const char* host, const AvahiAddress*,
     298             :                              uint16_t, AvahiStringList *txt,
     299             :                              AvahiLookupResultFlags, void* servus )
     300             :     {
     301           8 :         ((Servus*)servus)->_resolveCB( resolver, event, name, host, txt );
     302           8 :     }
     303             : 
     304           8 :     void _resolveCB( AvahiServiceResolver* resolver,
     305             :                      const AvahiResolverEvent event, const char* name,
     306             :                      const char* host, AvahiStringList *txt )
     307             :     {
     308             :         // If browsing through the local interface,
     309             :         // consider only the local instances
     310           8 :         if( _scope == lunchbox::Servus::IF_LOCAL )
     311             :         {
     312           8 :             const std::string& hostStr( host );
     313             :             // host in "hostname.local" format
     314           8 :             const size_t pos = hostStr.find_last_of( "." );
     315          16 :             const std::string hostName = hostStr.substr( 0, pos );
     316             : 
     317           8 :             if( hostName != getHostname( ))
     318          16 :                 return;
     319             :         }
     320             : 
     321           8 :         switch( event )
     322             :         {
     323             :         case AVAHI_RESOLVER_FAILURE:
     324           0 :             _result = avahi_client_errno( _client );
     325           0 :             LBWARN << "Resolver error: " << avahi_strerror( _result )
     326           0 :                    << std::endl;
     327           0 :             break;
     328             : 
     329             :         case AVAHI_RESOLVER_FOUND:
     330             :             {
     331           8 :                 detail::ValueMap& values = _instanceMap[ name ];
     332           8 :                 values[ "servus_host" ] = host;
     333          18 :                 for( ; txt; txt = txt->next )
     334             :                 {
     335             :                     const std::string entry(
     336             :                                 reinterpret_cast< const char* >( txt->text ),
     337          10 :                                 txt->size );
     338          10 :                     const size_t pos = entry.find_first_of( "=" );
     339          20 :                     const std::string key = entry.substr( 0, pos );
     340          20 :                     const std::string value = entry.substr( pos + 1 );
     341          10 :                     values[ key ] = value;
     342          10 :                 }
     343           8 :             } break;
     344             :         }
     345             : 
     346           8 :         avahi_service_resolver_free( resolver );
     347             :     }
     348             : 
     349             :     // Announcing
     350           2 :     void _updateRecord() final
     351             :     {
     352           2 :         if( _announce.empty() || !_announcable )
     353           3 :             return;
     354             : 
     355           1 :         if( _group )
     356           1 :             avahi_entry_group_reset( _group );
     357           1 :         _createServices();
     358             :     }
     359             : 
     360           4 :     void _createServices()
     361             :     {
     362           4 :         if( !_group )
     363           2 :             _group = avahi_entry_group_new( _client, _groupCBS, this );
     364             :         else
     365           2 :             avahi_entry_group_reset( _group );
     366             : 
     367           4 :         if( !_group )
     368           0 :             return;
     369             : 
     370           4 :         AvahiStringList* data = 0;
     371           7 :         for( detail::ValueMapCIter i = _data.begin(); i != _data.end(); ++i )
     372           3 :             data = avahi_string_list_add_pair( data, i->first.c_str(),
     373           6 :                                                i->second.c_str( ));
     374             : 
     375             :         _result = avahi_entry_group_add_service_strlst(
     376             :             _group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
     377             :                 (AvahiPublishFlags)(0), _announce.c_str(), _name.c_str(), 0, 0,
     378           4 :                 _port, data );
     379             : 
     380           4 :         if( data )
     381           2 :             avahi_string_list_free( data );
     382             : 
     383           4 :         if( _result != lunchbox::Result::SUCCESS )
     384             :         {
     385           0 :             avahi_simple_poll_quit( _poll );
     386           0 :             return;
     387             :         }
     388             : 
     389           4 :         _result = avahi_entry_group_commit( _group );
     390           4 :         if( _result != lunchbox::Result::SUCCESS )
     391           0 :             avahi_simple_poll_quit( _poll );
     392             :     }
     393             : 
     394           9 :     static void _groupCBS( AvahiEntryGroup*, AvahiEntryGroupState state,
     395             :                            void* servus )
     396             :     {
     397           9 :         ((Servus*)servus)->_groupCB( state );
     398           9 :     }
     399             : 
     400           9 :     void _groupCB( const AvahiEntryGroupState state )
     401             :     {
     402           9 :         switch( state )
     403             :         {
     404             :         case AVAHI_ENTRY_GROUP_ESTABLISHED:
     405           2 :             break;
     406             : 
     407             :         case AVAHI_ENTRY_GROUP_COLLISION:
     408             :         case AVAHI_ENTRY_GROUP_FAILURE:
     409           0 :             _result = EEXIST;
     410           0 :             avahi_simple_poll_quit( _poll );
     411           0 :             break;
     412             : 
     413             :         case AVAHI_ENTRY_GROUP_UNCOMMITED:
     414             :         case AVAHI_ENTRY_GROUP_REGISTERING:
     415             :             /*nop*/ ;
     416             :         }
     417           9 :     }
     418             : };
     419             : 
     420             : }
     421             : }

Generated by: LCOV version 1.10