c7efe9a5a82147a6d43770953d7243b09d633f01
[platform/upstream/pulseaudio.git] / src / modules / raop / module-raop-discover.c
1 /***
2   This file is part of PulseAudio.
3
4   Copyright 2004-2006 Lennart Poettering
5   Copyright 2008 Colin Guthrie
6
7   PulseAudio is free software; you can redistribute it and/or modify
8   it under the terms of the GNU Lesser General Public License as
9   published by the Free Software Foundation; either version 2.1 of the
10   License, or (at your option) any later version.
11
12   PulseAudio is distributed in the hope that it will be useful, but
13   WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15   General Public License for more details.
16
17   You should have received a copy of the GNU Lesser General Public
18   License along with PulseAudio; if not, write to the Free Software
19   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20   USA.
21 ***/
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31
32 #include <avahi-client/client.h>
33 #include <avahi-client/lookup.h>
34 #include <avahi-common/alternative.h>
35 #include <avahi-common/error.h>
36 #include <avahi-common/domain.h>
37 #include <avahi-common/malloc.h>
38
39 #include <pulse/xmalloc.h>
40
41 #include <pulsecore/core-util.h>
42 #include <pulsecore/log.h>
43 #include <pulsecore/hashmap.h>
44 #include <pulsecore/modargs.h>
45 #include <pulsecore/namereg.h>
46 #include <pulsecore/avahi-wrap.h>
47
48 #include "module-raop-discover-symdef.h"
49
50 PA_MODULE_AUTHOR("Colin Guthrie");
51 PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery of RAOP devices");
52 PA_MODULE_VERSION(PACKAGE_VERSION);
53 PA_MODULE_LOAD_ONCE(true);
54
55 #define SERVICE_TYPE_SINK "_raop._tcp"
56
57 static const char* const valid_modargs[] = {
58     NULL
59 };
60
61 struct tunnel {
62     AvahiIfIndex interface;
63     AvahiProtocol protocol;
64     char *name, *type, *domain;
65     uint32_t module_index;
66 };
67
68 struct userdata {
69     pa_core *core;
70     pa_module *module;
71     AvahiPoll *avahi_poll;
72     AvahiClient *client;
73     AvahiServiceBrowser *sink_browser;
74
75     pa_hashmap *tunnels;
76 };
77
78 static unsigned tunnel_hash(const void *p) {
79     const struct tunnel *t = p;
80
81     return
82         (unsigned) t->interface +
83         (unsigned) t->protocol +
84         pa_idxset_string_hash_func(t->name) +
85         pa_idxset_string_hash_func(t->type) +
86         pa_idxset_string_hash_func(t->domain);
87 }
88
89 static int tunnel_compare(const void *a, const void *b) {
90     const struct tunnel *ta = a, *tb = b;
91     int r;
92
93     if (ta->interface != tb->interface)
94         return 1;
95     if (ta->protocol != tb->protocol)
96         return 1;
97     if ((r = strcmp(ta->name, tb->name)))
98         return r;
99     if ((r = strcmp(ta->type, tb->type)))
100         return r;
101     if ((r = strcmp(ta->domain, tb->domain)))
102         return r;
103
104     return 0;
105 }
106
107 static struct tunnel *tunnel_new(
108         AvahiIfIndex interface, AvahiProtocol protocol,
109         const char *name, const char *type, const char *domain) {
110
111     struct tunnel *t;
112     t = pa_xnew(struct tunnel, 1);
113     t->interface = interface;
114     t->protocol = protocol;
115     t->name = pa_xstrdup(name);
116     t->type = pa_xstrdup(type);
117     t->domain = pa_xstrdup(domain);
118     t->module_index = PA_IDXSET_INVALID;
119     return t;
120 }
121
122 static void tunnel_free(struct tunnel *t) {
123     pa_assert(t);
124     pa_xfree(t->name);
125     pa_xfree(t->type);
126     pa_xfree(t->domain);
127     pa_xfree(t);
128 }
129
130 static void resolver_cb(
131         AvahiServiceResolver *r,
132         AvahiIfIndex interface, AvahiProtocol protocol,
133         AvahiResolverEvent event,
134         const char *name, const char *type, const char *domain,
135         const char *host_name, const AvahiAddress *a, uint16_t port,
136         AvahiStringList *txt,
137         AvahiLookupResultFlags flags,
138         void *userdata) {
139
140     struct userdata *u = userdata;
141     struct tunnel *tnl;
142
143     pa_assert(u);
144
145     tnl = tunnel_new(interface, protocol, name, type, domain);
146
147     if (event != AVAHI_RESOLVER_FOUND)
148         pa_log("Resolving of '%s' failed: %s", name, avahi_strerror(avahi_client_errno(u->client)));
149     else {
150         char *device = NULL, *nicename, *dname, *vname, *args;
151         char at[AVAHI_ADDRESS_STR_MAX];
152         AvahiStringList *l;
153         pa_module *m;
154
155         if ((nicename = strstr(name, "@"))) {
156             ++nicename;
157             if (strlen(nicename) > 0) {
158                 pa_log_debug("Found RAOP: %s", nicename);
159                 nicename = pa_escape(nicename, "\"'");
160             } else
161                 nicename = NULL;
162         }
163
164         for (l = txt; l; l = l->next) {
165             char *key, *value;
166             pa_assert_se(avahi_string_list_get_pair(l, &key, &value, NULL) == 0);
167
168             pa_log_debug("Found key: '%s' with value: '%s'", key, value);
169             if (pa_streq(key, "device")) {
170                 pa_xfree(device);
171                 device = value;
172                 value = NULL;
173             }
174             avahi_free(key);
175             avahi_free(value);
176         }
177
178         if (device)
179             dname = pa_sprintf_malloc("raop.%s.%s", host_name, device);
180         else
181             dname = pa_sprintf_malloc("raop.%s", host_name);
182
183         if (!(vname = pa_namereg_make_valid_name(dname))) {
184             pa_log("Cannot construct valid device name from '%s'.", dname);
185             avahi_free(device);
186             pa_xfree(dname);
187             goto finish;
188         }
189         pa_xfree(dname);
190
191         if (nicename) {
192             args = pa_sprintf_malloc("server=[%s]:%u "
193                                      "sink_name=%s "
194                                      "sink_properties='device.description=\"%s\"'",
195                                      avahi_address_snprint(at, sizeof(at), a), port,
196                                      vname,
197                                      nicename);
198             pa_xfree(nicename);
199         } else {
200             args = pa_sprintf_malloc("server=[%s]:%u "
201                                      "sink_name=%s",
202                                      avahi_address_snprint(at, sizeof(at), a), port,
203                                      vname);
204         }
205
206         pa_log_debug("Loading module-raop-sink with arguments '%s'", args);
207
208         if ((m = pa_module_load(u->core, "module-raop-sink", args))) {
209             tnl->module_index = m->index;
210             pa_hashmap_put(u->tunnels, tnl, tnl);
211             tnl = NULL;
212         }
213
214         pa_xfree(vname);
215         pa_xfree(args);
216         avahi_free(device);
217     }
218
219 finish:
220
221     avahi_service_resolver_free(r);
222
223     if (tnl)
224         tunnel_free(tnl);
225 }
226
227 static void browser_cb(
228         AvahiServiceBrowser *b,
229         AvahiIfIndex interface, AvahiProtocol protocol,
230         AvahiBrowserEvent event,
231         const char *name, const char *type, const char *domain,
232         AvahiLookupResultFlags flags,
233         void *userdata) {
234
235     struct userdata *u = userdata;
236     struct tunnel *t;
237
238     pa_assert(u);
239
240     if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
241         return;
242
243     t = tunnel_new(interface, protocol, name, type, domain);
244
245     if (event == AVAHI_BROWSER_NEW) {
246
247         if (!pa_hashmap_get(u->tunnels, t))
248             if (!(avahi_service_resolver_new(u->client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolver_cb, u)))
249                 pa_log("avahi_service_resolver_new() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
250
251         /* We ignore the returned resolver object here, since the we don't
252          * need to attach any special data to it, and we can still destroy
253          * it from the callback */
254
255     } else if (event == AVAHI_BROWSER_REMOVE) {
256         struct tunnel *t2;
257
258         if ((t2 = pa_hashmap_get(u->tunnels, t))) {
259             pa_module_unload_request_by_index(u->core, t2->module_index, true);
260             pa_hashmap_remove(u->tunnels, t2);
261             tunnel_free(t2);
262         }
263     }
264
265     tunnel_free(t);
266 }
267
268 static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
269     struct userdata *u = userdata;
270
271     pa_assert(c);
272     pa_assert(u);
273
274     u->client = c;
275
276     switch (state) {
277         case AVAHI_CLIENT_S_REGISTERING:
278         case AVAHI_CLIENT_S_RUNNING:
279         case AVAHI_CLIENT_S_COLLISION:
280
281             if (!u->sink_browser) {
282
283                 if (!(u->sink_browser = avahi_service_browser_new(
284                               c,
285                               AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
286                               SERVICE_TYPE_SINK,
287                               NULL,
288                               0,
289                               browser_cb, u))) {
290
291                     pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c)));
292                     pa_module_unload_request(u->module, true);
293                 }
294             }
295
296             break;
297
298         case AVAHI_CLIENT_FAILURE:
299             if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) {
300                 int error;
301
302                 pa_log_debug("Avahi daemon disconnected.");
303
304                 if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
305                     pa_log("avahi_client_new() failed: %s", avahi_strerror(error));
306                     pa_module_unload_request(u->module, true);
307                 }
308             }
309
310             /* Fall through */
311
312         case AVAHI_CLIENT_CONNECTING:
313
314             if (u->sink_browser) {
315                 avahi_service_browser_free(u->sink_browser);
316                 u->sink_browser = NULL;
317             }
318
319             break;
320
321         default: ;
322     }
323 }
324
325 int pa__init(pa_module*m) {
326
327     struct userdata *u;
328     pa_modargs *ma = NULL;
329     int error;
330
331     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
332         pa_log("Failed to parse module arguments.");
333         goto fail;
334     }
335
336     m->userdata = u = pa_xnew(struct userdata, 1);
337     u->core = m->core;
338     u->module = m;
339     u->sink_browser = NULL;
340
341     u->tunnels = pa_hashmap_new(tunnel_hash, tunnel_compare);
342
343     u->avahi_poll = pa_avahi_poll_new(m->core->mainloop);
344
345     if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
346         pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error));
347         goto fail;
348     }
349
350     pa_modargs_free(ma);
351
352     return 0;
353
354 fail:
355     pa__done(m);
356
357     if (ma)
358         pa_modargs_free(ma);
359
360     return -1;
361 }
362
363 void pa__done(pa_module*m) {
364     struct userdata*u;
365     pa_assert(m);
366
367     if (!(u = m->userdata))
368         return;
369
370     if (u->client)
371         avahi_client_free(u->client);
372
373     if (u->avahi_poll)
374         pa_avahi_poll_free(u->avahi_poll);
375
376     if (u->tunnels) {
377         struct tunnel *t;
378
379         while ((t = pa_hashmap_steal_first(u->tunnels))) {
380             pa_module_unload_request_by_index(u->core, t->module_index, true);
381             tunnel_free(t);
382         }
383
384         pa_hashmap_free(u->tunnels, NULL);
385     }
386
387     pa_xfree(u);
388 }