• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OpenLightingProject / ola / 16900099157

12 Aug 2025 05:43AM UTC coverage: 45.72% (-0.02%) from 45.742%
16900099157

Pull #2016

github

web-flow
Merge c368ef6ae into eaf937e80
Pull Request #2016: Bump actions/checkout from 4 to 5

7586 of 17462 branches covered (43.44%)

22424 of 49046 relevant lines covered (45.72%)

51.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

20.48
/olad/AvahiDiscoveryAgent.cpp
1
/*
2
 * This program is free software; you can redistribute it and/or modify
3
 * it under the terms of the GNU General Public License as published by
4
 * the Free Software Foundation; either version 2 of the License, or
5
 * (at your option) any later version.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU Library General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
15
 *
16
 * AvahiDiscoveryAgent.cpp
17
 * The Avahi implementation of DiscoveryAgentInterface.
18
 * Copyright (C) 2013 Simon Newton
19
 */
20

21
#include "olad/AvahiDiscoveryAgent.h"
22

23
#include <avahi-common/alternative.h>
24
#include <avahi-common/error.h>
25
#include <avahi-common/malloc.h>
26
#include <avahi-common/strlst.h>
27
#include <avahi-common/timeval.h>
28

29
#include <algorithm>
30
#include <memory>
31
#include <string>
32
#include <vector>
33

34
#include "ola/Clock.h"
35
#include "ola/Logging.h"
36
#include "ola/stl/STLUtils.h"
37
#include "ola/StringUtils.h"
38

39
namespace ola {
40

41
using std::string;
42
using std::vector;
43

44
static std::string MakeServiceKey(const std::string &service_name,
×
45
                                  const std::string &type) {
46
  return service_name + "." + type;
×
47
}
48

49
/*
50
 * The userdata passed to the entry_callback function.
51
 */
52
struct EntryGroupParams {
×
53
  AvahiDiscoveryAgent *agent;
54
  string key;
55

56
  EntryGroupParams(AvahiDiscoveryAgent *agent, const string &key)
×
57
      : agent(agent), key(key) {
×
58
  }
×
59
};
60

61
namespace {
62

63
/*
64
 * Called when the client state changes. This is called once from
65
 * the thread that calls avahi_client_new, and then from the poll thread.
66
 */
67
static void client_callback(AvahiClient *client,
×
68
                            AvahiClientState state,
69
                            void *data) {
70
  AvahiDiscoveryAgent *agent = reinterpret_cast<AvahiDiscoveryAgent*>(data);
×
71
  agent->ClientStateChanged(state, client);
×
72
}
×
73

74
/*
75
 * Called when the group state changes.
76
 */
77
static void entry_callback(AvahiEntryGroup *group,
×
78
                           AvahiEntryGroupState state,
79
                           void *data)  {
80
  if (!group) {
×
81
    return;
82
  }
83

84
  const EntryGroupParams *params = reinterpret_cast<const EntryGroupParams*>(
×
85
      data);
86
  if (!params) {
×
87
    OLA_FATAL << "entry_callback passed null userdata!";
×
88
    return;
×
89
  }
90
  params->agent->GroupStateChanged(params->key, group, state);
×
91
}
92

93
static void reconnect_callback(AvahiTimeout*, void *data) {
677✔
94
  AvahiDiscoveryAgent *agent = reinterpret_cast<AvahiDiscoveryAgent*>(data);
677✔
95
  agent->ReconnectTimeout();
677✔
96
}
677✔
97
}  // namespace
98

99
AvahiDiscoveryAgent::ServiceEntry::ServiceEntry(
×
100
    const std::string &service_name,
101
    const std::string &type_spec,
102
    uint16_t port,
103
    const RegisterOptions &options)
×
104
    : RegisterOptions(options),
105
      service_name(service_name),
×
106
      actual_service_name(service_name),
×
107
      port(port),
×
108
      group(NULL),
×
109
      state(AVAHI_ENTRY_GROUP_UNCOMMITED),
×
110
      params(NULL),
×
111
      m_type_spec(type_spec) {
×
112
  vector<string> tokens;
×
113
  StringSplit(type_spec, &tokens, ",");
×
114
  m_type = tokens[0];
×
115
  if (tokens.size() > 1) {
×
116
    copy(tokens.begin() + 1, tokens.end(), std::back_inserter(m_sub_types));
×
117
  }
118
}
×
119

120
string AvahiDiscoveryAgent::ServiceEntry::key() const {
×
121
  return MakeServiceKey(actual_service_name, m_type_spec);
×
122
}
123

124
AvahiDiscoveryAgent::AvahiDiscoveryAgent()
1✔
125
    : m_threaded_poll(avahi_threaded_poll_new()),
1✔
126
      m_client(NULL),
1✔
127
      m_reconnect_timeout(NULL),
1✔
128
      m_backoff(new ExponentialBackoffPolicy(TimeInterval(1, 0),
2✔
129
                                             TimeInterval(60, 0))) {
1✔
130
}
1✔
131

132
AvahiDiscoveryAgent::~AvahiDiscoveryAgent() {
2✔
133
  avahi_threaded_poll_stop(m_threaded_poll);
1✔
134

135
  if (m_reconnect_timeout) {
1✔
136
    avahi_threaded_poll_get(m_threaded_poll)->timeout_free(
1✔
137
        m_reconnect_timeout);
138
  }
139

140
  DeregisterAllServices();
1✔
141
  STLDeleteValues(&m_services);
1✔
142

143
  if (m_client) {
1✔
144
    avahi_client_free(m_client);
×
145
  }
146
  avahi_threaded_poll_free(m_threaded_poll);
1✔
147
}
2✔
148

149
bool AvahiDiscoveryAgent::Init() {
1✔
150
  CreateNewClient();
1✔
151

152
  if (m_threaded_poll) {
1✔
153
    avahi_threaded_poll_start(m_threaded_poll);
1✔
154
    return true;
1✔
155
  } else {
156
    return false;
157
  }
158
}
159

160
void AvahiDiscoveryAgent::RegisterService(const string &service_name,
×
161
                                          const string &type,
162
                                          uint16_t port,
163
                                          const RegisterOptions &options) {
164
  if (!(m_threaded_poll && m_client)) {
×
165
    return;
×
166
  }
167
  const string key = MakeServiceKey(service_name, type);
×
168
  avahi_threaded_poll_lock(m_threaded_poll);
×
169

170
  ServiceEntry *service = STLFindOrNull(m_services, key);
×
171
  if (service) {
×
172
    OLA_WARN << "Service " << key << " is already registered";
×
173
    avahi_threaded_poll_unlock(m_threaded_poll);
×
174
    return;
×
175
  } else {
176
    service = new ServiceEntry(service_name, type, port, options);
×
177
    STLInsertIfNotPresent(&m_services, key, service);
×
178
  }
179

180
  // If we're not running, then we'll register the service when the client
181
  // transitions to the running state. Otherwise register it now.
182
  if (m_client && avahi_client_get_state(m_client) == AVAHI_CLIENT_S_RUNNING) {
×
183
    InternalRegisterService(service);
×
184
  }
185
  avahi_threaded_poll_unlock(m_threaded_poll);
×
186
}
×
187

188

189
/*
190
 * This is a bit tricky because it can be called from either the main thread on
191
 * startup or from the poll thread.
192
 */
193
void AvahiDiscoveryAgent::ClientStateChanged(AvahiClientState state,
×
194
                                             AvahiClient *client) {
195
  // The first time this is called is from the avahi_client_new context. In
196
  // that case m_client is still null so we set it here.
197
  if (!m_client) {
×
198
    m_client = client;
×
199
  }
200

201
  OLA_INFO << "Client state changed to " << ClientStateToString(state);
×
202

203
  switch (state) {
×
204
    case AVAHI_CLIENT_S_RUNNING:
×
205
      // The server has startup successfully and registered its host
206
      // name on the network, so it's time to create our services.
207
      // register_stuff
208
      UpdateServices();
×
209
      break;
×
210
    case AVAHI_CLIENT_FAILURE:
×
211
      DeregisterAllServices();
×
212
      SetUpReconnectTimeout();
×
213
      break;
×
214
    case AVAHI_CLIENT_S_COLLISION:
×
215
      // There was a hostname collision on the network.
216
      // Let's drop our registered services. When the server is back
217
      // in AVAHI_SERVER_RUNNING state we will register them again with the
218
      // new host name.
219
      DeregisterAllServices();
×
220
      break;
×
221
    case AVAHI_CLIENT_S_REGISTERING:
×
222
      // The server records are now being established. This
223
      // might be caused by a host name change. We need to wait
224
      // for our own records to register until the host name is
225
      // properly established.
226
      DeregisterAllServices();
×
227
      break;
×
228
    case AVAHI_CLIENT_CONNECTING:
229
      break;
230
  }
231
}
×
232

233
void AvahiDiscoveryAgent::GroupStateChanged(const string &service_key,
×
234
                                            AvahiEntryGroup *group,
235
                                            AvahiEntryGroupState state) {
236
  OLA_INFO << "State for " << service_key << ", group " << group
×
237
           << " changed to " << GroupStateToString(state);
×
238

239
  ServiceEntry *service = STLFindOrNull(m_services, service_key);
×
240
  if (!service) {
×
241
    OLA_WARN << "Unknown service " << service_key << " changed to state "
×
242
             << state;
×
243
    return;
×
244
  }
245

246
  if (service->group != group) {
×
247
    if (service->group) {
×
248
      OLA_WARN << "Service group for " << service_key << " : "
×
249
               << service->group << " does not match callback group "
×
250
               << group;
×
251
    }
252
    return;
×
253
  }
254

255
  service->state = state;
×
256

257
  switch (state) {
×
258
    case AVAHI_ENTRY_GROUP_ESTABLISHED:
259
      break;
260
    case AVAHI_ENTRY_GROUP_COLLISION:
×
261
      RenameAndRegister(service);
×
262
      break;
×
263
    case AVAHI_ENTRY_GROUP_FAILURE:
×
264
      OLA_WARN << "Failed to register " << service_key
×
265
               << ": " << avahi_strerror(avahi_client_errno(m_client));
×
266
      break;
×
267
    case AVAHI_ENTRY_GROUP_UNCOMMITED:
268
    case AVAHI_ENTRY_GROUP_REGISTERING:
269
      break;
270
  }
271
}
272

273
void AvahiDiscoveryAgent::ReconnectTimeout() {
677✔
274
  if (m_client) {
677✔
275
    avahi_client_free(m_client);
×
276
    m_client = NULL;
×
277
  }
278
  CreateNewClient();
677✔
279
}
677✔
280

281
bool AvahiDiscoveryAgent::InternalRegisterService(ServiceEntry *service) {
×
282
  if (!service->params) {
×
283
    service->params = new EntryGroupParams(this, service->key());
×
284
  }
285

286
  if (!service->group) {
×
287
    service->group = avahi_entry_group_new(m_client, entry_callback,
×
288
                                           service->params);
×
289
    if (!service->group) {
×
290
      OLA_WARN << "avahi_entry_group_new() failed: "
×
291
               << avahi_client_errno(m_client);
×
292
      return false;
×
293
    }
294
  }
295

296
  if (!avahi_entry_group_is_empty(service->group)) {
×
297
    OLA_INFO << "Service group was not empty!";
×
298
    return false;
×
299
  }
300

301
  AvahiStringList *txt_args = NULL;
×
302
  RegisterOptions::TxtData::const_iterator iter = service->txt_data.begin();
×
303
  for (; iter != service->txt_data.end(); ++iter) {
×
304
    txt_args = avahi_string_list_add_pair(
×
305
        txt_args, iter->first.c_str(), iter->second.c_str());
×
306
  }
307

308
  // Populate the entry group
309
  int r = avahi_entry_group_add_service_strlst(
×
310
      service->group,
311
      service->if_index > 0 ? service->if_index : AVAHI_IF_UNSPEC,
×
312
      AVAHI_PROTO_INET, static_cast<AvahiPublishFlags>(0),
313
      service->actual_service_name.c_str(), service->type().c_str(), NULL, NULL,
×
314
      service->port, txt_args);
×
315

316
  avahi_string_list_free(txt_args);
×
317

318
  if (r) {
×
319
    if (r == AVAHI_ERR_COLLISION) {
×
320
      OLA_WARN << "Collision with local service!";
×
321
      return RenameAndRegister(service);
×
322
    }
323
    OLA_WARN << "avahi_entry_group_add_service failed: " << avahi_strerror(r);
×
324
    avahi_entry_group_reset(service->group);
×
325
    return false;
×
326
  }
327

328
  // Add any subtypes
329
  const vector<string> &sub_types = service->sub_types();
×
330
  if (!sub_types.empty()) {
×
331
    vector<string>::const_iterator iter = sub_types.begin();
332
    for (; iter != sub_types.end(); ++iter) {
×
333
      const string sub_type = *iter + "._sub." + service->type();
×
334
      OLA_INFO << "Adding " << sub_type;
×
335
      r = avahi_entry_group_add_service_subtype(
×
336
          service->group,
337
          service->if_index > 0 ? service->if_index : AVAHI_IF_UNSPEC,
×
338
          AVAHI_PROTO_INET, static_cast<AvahiPublishFlags>(0),
339
          service->actual_service_name.c_str(), service->type().c_str(), NULL,
×
340
          sub_type.c_str());
341
      if (r) {
×
342
        OLA_WARN << "Failed to add " << sub_type << ": " << avahi_strerror(r);
×
343
      }
344
    }
×
345
  }
346

347
  r = avahi_entry_group_commit(service->group);
×
348
  if (r) {
×
349
    avahi_entry_group_reset(service->group);
×
350
    OLA_WARN << "avahi_entry_group_commit failed: " << avahi_strerror(r);
×
351
    return false;
×
352
  }
353
  return true;
354
}
×
355

356
void AvahiDiscoveryAgent::CreateNewClient() {
678✔
357
  if (m_client) {
678✔
358
    OLA_WARN << "CreateNewClient called but m_client is not NULL";
×
359
    return;
×
360
  }
361

362
  if (m_threaded_poll) {
678✔
363
    int error;
678✔
364
    // In the successful case, m_client is set in the ClientStateChanged method
365
    m_client = avahi_client_new(avahi_threaded_poll_get(m_threaded_poll),
678✔
366
                                AVAHI_CLIENT_NO_FAIL, client_callback, this,
367
                                &error);
368
    if (m_client) {
678✔
369
      m_backoff.Reset();
×
370
    } else {
371
      OLA_WARN << "Failed to create Avahi client: " << avahi_strerror(error);
678✔
372
      SetUpReconnectTimeout();
678✔
373
    }
374
  }
375
}
376

377
void AvahiDiscoveryAgent::UpdateServices() {
×
378
  Services::iterator iter = m_services.begin();
×
379
  for (; iter != m_services.end(); ++iter) {
×
380
    if (iter->second->state == AVAHI_ENTRY_GROUP_UNCOMMITED) {
×
381
      InternalRegisterService(iter->second);
×
382
    }
383
  }
384
}
×
385

386
/**
387
 * De-register all services and clean up the AvahiEntryGroup and
388
 * EntryGroupParams data.
389
 */
390
void AvahiDiscoveryAgent::DeregisterAllServices() {
1✔
391
  Services::iterator iter = m_services.begin();
1✔
392
  for (; iter != m_services.end(); ++iter) {
1✔
393
    AvahiEntryGroup *group = iter->second->group;
×
394
    if (group) {
×
395
      avahi_entry_group_reset(group);
×
396
      avahi_entry_group_free(group);
×
397
      iter->second->group = NULL;
×
398
    }
399
    if (iter->second->params) {
×
400
      delete iter->second->params;
×
401
      iter->second->params = NULL;
×
402
    }
403
    iter->second->state = AVAHI_ENTRY_GROUP_UNCOMMITED;
×
404
  }
405
}
1✔
406

407
void AvahiDiscoveryAgent::SetUpReconnectTimeout() {
678✔
408
  // We don't strictly need an ExponentialBackoffPolicy here because the client
409
  // goes into the AVAHI_CLIENT_CONNECTING state if the server isn't running.
410
  // Still, it's a useful defense against spinning rapidly if something goes
411
  // wrong.
412
  TimeInterval delay = m_backoff.Next();
678✔
413
  OLA_INFO << "Re-creating avahi client in " << delay << "s";
678✔
414
  struct timeval tv;
678✔
415
  delay.AsTimeval(&tv);
678✔
416

417
  const AvahiPoll *poll = avahi_threaded_poll_get(m_threaded_poll);
678✔
418
  if (m_reconnect_timeout) {
678✔
419
    poll->timeout_update(m_reconnect_timeout, &tv);
677✔
420
  } else {
421
    m_reconnect_timeout = poll->timeout_new(
1✔
422
        avahi_threaded_poll_get(m_threaded_poll),
423
        &tv,
424
        reconnect_callback,
425
        this);
426
  }
427
}
678✔
428

429
bool AvahiDiscoveryAgent::RenameAndRegister(ServiceEntry *service) {
×
430
  char *new_name_str =
×
431
      avahi_alternative_service_name(service->actual_service_name.c_str());
×
432
  string new_name(new_name_str);
×
433
  avahi_free(new_name_str);
×
434

435
  OLA_WARN << "Service name collision for " << service->actual_service_name
×
436
           << " renaming to " << new_name;
×
437
  service->actual_service_name = new_name;
×
438
  return InternalRegisterService(service);
×
439
}
×
440

441
string AvahiDiscoveryAgent::ClientStateToString(AvahiClientState state) {
×
442
  switch (state) {
×
443
    case AVAHI_CLIENT_S_REGISTERING:
×
444
      return "AVAHI_CLIENT_S_REGISTERING";
×
445
    case AVAHI_CLIENT_S_RUNNING:
×
446
      return "AVAHI_CLIENT_S_RUNNING";
×
447
    case AVAHI_CLIENT_S_COLLISION:
×
448
      return "AVAHI_CLIENT_S_COLLISION";
×
449
    case AVAHI_CLIENT_FAILURE:
×
450
      return "AVAHI_CLIENT_FAILURE";
×
451
    case AVAHI_CLIENT_CONNECTING:
×
452
      return "AVAHI_CLIENT_CONNECTING";
×
453
    default:
×
454
      return "Unknown state";
×
455
  }
456
}
457

458
string AvahiDiscoveryAgent::GroupStateToString(AvahiEntryGroupState state) {
×
459
  switch (state) {
×
460
    case AVAHI_ENTRY_GROUP_UNCOMMITED:
×
461
      return "AVAHI_ENTRY_GROUP_UNCOMMITED";
×
462
    case AVAHI_ENTRY_GROUP_REGISTERING:
×
463
      return "AVAHI_ENTRY_GROUP_REGISTERING";
×
464
    case AVAHI_ENTRY_GROUP_ESTABLISHED:
×
465
      return "AVAHI_ENTRY_GROUP_ESTABLISHED";
×
466
    case AVAHI_ENTRY_GROUP_COLLISION:
×
467
      return "AVAHI_ENTRY_GROUP_COLLISION";
×
468
    case AVAHI_ENTRY_GROUP_FAILURE:
×
469
      return "AVAHI_ENTRY_GROUP_FAILURE";
×
470
    default:
×
471
      return "Unknown state";
×
472
  }
473
}
474
}  // namespace ola
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc