Merge commit 'origin/master-tx'
[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 #include <pulse/util.h>
41
42 #include <pulsecore/sink.h>
43 #include <pulsecore/source.h>
44 #include <pulsecore/native-common.h>
45 #include <pulsecore/core-util.h>
46 #include <pulsecore/log.h>
47 #include <pulsecore/core-subscribe.h>
48 #include <pulsecore/hashmap.h>
49 #include <pulsecore/modargs.h>
50 #include <pulsecore/namereg.h>
51 #include <pulsecore/avahi-wrap.h>
52
53 #include "module-raop-discover-symdef.h"
54
55 PA_MODULE_AUTHOR("Colin Guthrie");
56 PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery of RAOP devices");
57 PA_MODULE_VERSION(PACKAGE_VERSION);
58 PA_MODULE_LOAD_ONCE(TRUE);
59
60 #define SERVICE_TYPE_SINK "_raop._tcp"
61
62 static const char* const valid_modargs[] = {
63     NULL
64 };
65
66 struct tunnel {
67     AvahiIfIndex interface;
68     AvahiProtocol protocol;
69     char *name, *type, *domain;
70     uint32_t module_index;
71 };
72
73 struct userdata {
74     pa_core *core;
75     pa_module *module;
76     AvahiPoll *avahi_poll;
77     AvahiClient *client;
78     AvahiServiceBrowser *sink_browser;
79
80     pa_hashmap *tunnels;
81 };
82
83 static unsigned tunnel_hash(const void *p) {
84     const struct tunnel *t = p;
85
86     return
87         (unsigned) t->interface +
88         (unsigned) t->protocol +
89         pa_idxset_string_hash_func(t->name) +
90         pa_idxset_string_hash_func(t->type) +
91         pa_idxset_string_hash_func(t->domain);
92 }
93
94 static int tunnel_compare(const void *a, const void *b) {
95     const struct tunnel *ta = a, *tb = b;
96     int r;
97
98     if (ta->interface != tb->interface)
99         return 1;
100     if (ta->protocol != tb->protocol)
101         return 1;
102     if ((r = strcmp(ta->name, tb->name)))
103         return r;
104     if ((r = strcmp(ta->type, tb->type)))
105         return r;
106     if ((r = strcmp(ta->domain, tb->domain)))
107         return r;
108
109     return 0;
110 }
111
112 static struct tunnel *tunnel_new(
113         AvahiIfIndex interface, AvahiProtocol protocol,
114         const char *name, const char *type, const char *domain) {
115
116     struct tunnel *t;
117     t = pa_xnew(struct tunnel, 1);
118     t->interface = interface;
119     t->protocol = protocol;
120     t->name = pa_xstrdup(name);
121     t->type = pa_xstrdup(type);
122     t->domain = pa_xstrdup(domain);
123     t->module_index = PA_IDXSET_INVALID;
124     return t;
125 }
126
127 static void tunnel_free(struct tunnel *t) {
128     pa_assert(t);
129     pa_xfree(t->name);
130     pa_xfree(t->type);
131     pa_xfree(t->domain);
132     pa_xfree(t);
133 }
134
135 static void resolver_cb(
136         AvahiServiceResolver *r,
137         AvahiIfIndex interface, AvahiProtocol protocol,
138         AvahiResolverEvent event,
139         const char *name, const char *type, const char *domain,
140         const char *host_name, const AvahiAddress *a, uint16_t port,
141         AvahiStringList *txt,
142         AvahiLookupResultFlags flags,
143         void *userdata) {
144
145     struct userdata *u = userdata;
146     struct tunnel *tnl;
147
148     pa_assert(u);
149
150     tnl = tunnel_new(interface, protocol, name, type, domain);
151
152     if (event != AVAHI_RESOLVER_FOUND)
153         pa_log("Resolving of '%s' failed: %s", name, avahi_strerror(avahi_client_errno(u->client)));
154     else {
155         char *device = NULL, *nicename, *dname, *vname, *args;
156         char at[AVAHI_ADDRESS_STR_MAX];
157         AvahiStringList *l;
158         pa_module *m;
159
160         if ((nicename = strstr(name, "@"))) {
161             ++nicename;
162             if (strlen(nicename) > 0) {
163                 pa_log_debug("Found RAOP: %s", nicename);
164             }
165         }
166
167         for (l = txt; l; l = l->next) {
168             char *key, *value;
169             pa_assert_se(avahi_string_list_get_pair(l, &key, &value, NULL) == 0);
170
171             pa_log_debug("Found key: '%s' with value: '%s'", key, value);
172             if (strcmp(key, "device") == 0) {
173                 pa_xfree(device);
174                 device = value;
175                 value = NULL;
176             }
177             avahi_free(key);
178             avahi_free(value);
179         }
180
181         if (device)
182             dname = pa_sprintf_malloc("raop.%s.%s", host_name, device);
183         else
184             dname = pa_sprintf_malloc("raop.%s", host_name);
185
186         if (!(vname = pa_namereg_make_valid_name(dname))) {
187             pa_log("Cannot construct valid device name from '%s'.", dname);
188             avahi_free(device);
189             pa_xfree(dname);
190             goto finish;
191         }
192         pa_xfree(dname);
193
194         /*
195          TODO: allow this syntax of server name in things....
196         args = pa_sprintf_malloc("server=[%s]:%u "
197                                  "sink_name=%s",
198                                  avahi_address_snprint(at, sizeof(at), a), port,
199                                  vname);*/
200         if (nicename) {
201             args = pa_sprintf_malloc("server=%s "
202                                      "sink_name=%s "
203                                      "description=\"%s\"",
204                                      avahi_address_snprint(at, sizeof(at), a),
205                                      vname,
206                                      nicename);
207
208         } else {
209             args = pa_sprintf_malloc("server=%s "
210                                      "sink_name=%s",
211                                      avahi_address_snprint(at, sizeof(at), a),
212                                      vname);
213         }
214
215         pa_log_debug("Loading module-raop-sink with arguments '%s'", args);
216
217         if ((m = pa_module_load(u->core, "module-raop-sink", args))) {
218             tnl->module_index = m->index;
219             pa_hashmap_put(u->tunnels, tnl, tnl);
220             tnl = NULL;
221         }
222
223         pa_xfree(vname);
224         pa_xfree(args);
225         avahi_free(device);
226     }
227
228 finish:
229
230     avahi_service_resolver_free(r);
231
232     if (tnl)
233         tunnel_free(tnl);
234 }
235
236 static void browser_cb(
237         AvahiServiceBrowser *b,
238         AvahiIfIndex interface, AvahiProtocol protocol,
239         AvahiBrowserEvent event,
240         const char *name, const char *type, const char *domain,
241         AvahiLookupResultFlags flags,
242         void *userdata) {
243
244     struct userdata *u = userdata;
245     struct tunnel *t;
246
247     pa_assert(u);
248
249     if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
250         return;
251
252     t = tunnel_new(interface, protocol, name, type, domain);
253
254     if (event == AVAHI_BROWSER_NEW) {
255
256         if (!pa_hashmap_get(u->tunnels, t))
257             if (!(avahi_service_resolver_new(u->client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolver_cb, u)))
258                 pa_log("avahi_service_resolver_new() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
259
260         /* We ignore the returned resolver object here, since the we don't
261          * need to attach any special data to it, and we can still destroy
262          * it from the callback */
263
264     } else if (event == AVAHI_BROWSER_REMOVE) {
265         struct tunnel *t2;
266
267         if ((t2 = pa_hashmap_get(u->tunnels, t))) {
268             pa_module_unload_by_index(u->core, t2->module_index, TRUE);
269             pa_hashmap_remove(u->tunnels, t2);
270             tunnel_free(t2);
271         }
272     }
273
274     tunnel_free(t);
275 }
276
277 static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
278     struct userdata *u = userdata;
279
280     pa_assert(c);
281     pa_assert(u);
282
283     u->client = c;
284
285     switch (state) {
286         case AVAHI_CLIENT_S_REGISTERING:
287         case AVAHI_CLIENT_S_RUNNING:
288         case AVAHI_CLIENT_S_COLLISION:
289
290             if (!u->sink_browser) {
291
292                 if (!(u->sink_browser = avahi_service_browser_new(
293                               c,
294                               AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
295                               SERVICE_TYPE_SINK,
296                               NULL,
297                               0,
298                               browser_cb, u))) {
299
300                     pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c)));
301                     pa_module_unload_request(u->module, TRUE);
302                 }
303             }
304
305             break;
306
307         case AVAHI_CLIENT_FAILURE:
308             if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) {
309                 int error;
310
311                 pa_log_debug("Avahi daemon disconnected.");
312
313                 if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
314                     pa_log("avahi_client_new() failed: %s", avahi_strerror(error));
315                     pa_module_unload_request(u->module, TRUE);
316                 }
317             }
318
319             /* Fall through */
320
321         case AVAHI_CLIENT_CONNECTING:
322
323             if (u->sink_browser) {
324                 avahi_service_browser_free(u->sink_browser);
325                 u->sink_browser = NULL;
326             }
327
328             break;
329
330         default: ;
331     }
332 }
333
334 int pa__init(pa_module*m) {
335
336     struct userdata *u;
337     pa_modargs *ma = NULL;
338     int error;
339
340     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
341         pa_log("Failed to parse module arguments.");
342         goto fail;
343     }
344
345     m->userdata = u = pa_xnew(struct userdata, 1);
346     u->core = m->core;
347     u->module = m;
348     u->sink_browser = NULL;
349
350     u->tunnels = pa_hashmap_new(tunnel_hash, tunnel_compare);
351
352     u->avahi_poll = pa_avahi_poll_new(m->core->mainloop);
353
354     if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
355         pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error));
356         goto fail;
357     }
358
359     pa_modargs_free(ma);
360
361     return 0;
362
363 fail:
364     pa__done(m);
365
366     if (ma)
367         pa_modargs_free(ma);
368
369     return -1;
370 }
371
372 void pa__done(pa_module*m) {
373     struct userdata*u;
374     pa_assert(m);
375
376     if (!(u = m->userdata))
377         return;
378
379     if (u->client)
380         avahi_client_free(u->client);
381
382     if (u->avahi_poll)
383         pa_avahi_poll_free(u->avahi_poll);
384
385     if (u->tunnels) {
386         struct tunnel *t;
387
388         while ((t = pa_hashmap_steal_first(u->tunnels))) {
389             pa_module_unload_by_index(u->core, t->module_index, TRUE);
390             tunnel_free(t);
391         }
392
393         pa_hashmap_free(u->tunnels, NULL, NULL);
394     }
395
396     pa_xfree(u);
397 }