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, see <http://www.gnu.org/licenses/>.
19***/
20
21#ifdef HAVE_CONFIG_H
22#include <config.h>
23#endif
24
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <unistd.h>
29
30#include <avahi-client/client.h>
31#include <avahi-client/lookup.h>
32#include <avahi-common/alternative.h>
33#include <avahi-common/error.h>
34#include <avahi-common/domain.h>
35#include <avahi-common/malloc.h>
36
37#include <pulse/xmalloc.h>
38
39#include <pulsecore/core-util.h>
40#include <pulsecore/log.h>
41#include <pulsecore/hashmap.h>
42#include <pulsecore/modargs.h>
43#include <pulsecore/namereg.h>
44#include <pulsecore/avahi-wrap.h>
45
46#include "raop-util.h"
47
48PA_MODULE_AUTHOR("Colin Guthrie");
49PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery of RAOP devices");
50PA_MODULE_VERSION(PACKAGE_VERSION);
51PA_MODULE_LOAD_ONCE(true);
52PA_MODULE_USAGE(
53        "latency_msec=<audio latency - applies to all devices> ");
54
55#define SERVICE_TYPE_SINK "_raop._tcp"
56
57struct userdata {
58    pa_core *core;
59    pa_module *module;
60
61    AvahiPoll *avahi_poll;
62    AvahiClient *client;
63    AvahiServiceBrowser *sink_browser;
64
65    pa_hashmap *tunnels;
66
67    bool latency_set;
68    uint32_t latency;
69};
70
71static const char* const valid_modargs[] = {
72    "latency_msec",
73    NULL
74};
75
76struct tunnel {
77    AvahiIfIndex interface;
78    AvahiProtocol protocol;
79    char *name, *type, *domain;
80    uint32_t module_index;
81};
82
83static 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
94static 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
112static struct tunnel* tunnel_new(
113        AvahiIfIndex interface, AvahiProtocol protocol,
114        const char *name, const char *type, const char *domain) {
115    struct tunnel *t;
116
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
125    return t;
126}
127
128static void tunnel_free(struct tunnel *t) {
129    pa_assert(t);
130    pa_xfree(t->name);
131    pa_xfree(t->type);
132    pa_xfree(t->domain);
133    pa_xfree(t);
134}
135
136/* This functions returns RAOP audio latency as guessed by the
137 * device model header.
138 * Feel free to complete the possible values after testing with
139 * your hardware.
140 */
141static uint32_t guess_latency_from_device(const char *model) {
142    uint32_t default_latency = RAOP_DEFAULT_LATENCY;
143
144    if (pa_streq(model, "PIONEER,1")) {
145        /* Pioneer N-30 */
146        default_latency = 2352;
147    } else if (pa_streq(model, "ShairportSync")) {
148        /* Shairport - software AirPort server */
149        default_latency = 2352;
150    }
151
152    pa_log_debug("Default latency is %u ms for device model %s.", default_latency, model);
153    return default_latency;
154}
155
156static void resolver_cb(
157        AvahiServiceResolver *r,
158        AvahiIfIndex interface, AvahiProtocol protocol,
159        AvahiResolverEvent event,
160        const char *name, const char *type, const char *domain,
161        const char *host_name, const AvahiAddress *a, uint16_t port,
162        AvahiStringList *txt,
163        AvahiLookupResultFlags flags,
164        void *userdata) {
165    struct userdata *u = userdata;
166    struct tunnel *tnl;
167    char *device = NULL, *nicename, *dname, *vname, *args;
168    char *tp = NULL, *et = NULL, *cn = NULL;
169    char *ch = NULL, *ss = NULL, *sr = NULL;
170    char *dm = NULL;
171    char *t = NULL;
172    char at[AVAHI_ADDRESS_STR_MAX];
173    AvahiStringList *l;
174    pa_module *m;
175    uint32_t latency = RAOP_DEFAULT_LATENCY;
176
177    pa_assert(u);
178
179    tnl = tunnel_new(interface, protocol, name, type, domain);
180
181    if (event != AVAHI_RESOLVER_FOUND) {
182        pa_log("Resolving of '%s' failed: %s", name, avahi_strerror(avahi_client_errno(u->client)));
183        goto finish;
184    }
185
186    if ((nicename = strstr(name, "@"))) {
187        ++nicename;
188        if (strlen(nicename) > 0) {
189            pa_log_debug("Found RAOP: %s", nicename);
190            nicename = pa_escape(nicename, "\"'");
191        } else
192            nicename = NULL;
193    }
194
195    for (l = txt; l; l = l->next) {
196        char *key, *value;
197        pa_assert_se(avahi_string_list_get_pair(l, &key, &value, NULL) == 0);
198
199        pa_log_debug("Found key: '%s' with value: '%s'", key, value);
200        if (pa_streq(key, "device")) {
201            device = value;
202            value = NULL;
203        } else if (pa_streq(key, "tp")) {
204            /* Transport protocol:
205             *  - TCP = only TCP,
206             *  - UDP = only UDP,
207             *  - TCP,UDP = both supported (UDP should be preferred) */
208            pa_xfree(tp);
209            if (pa_str_in_list(value, ",", "UDP"))
210                tp = pa_xstrdup("UDP");
211            else if (pa_str_in_list(value, ",", "TCP"))
212                tp = pa_xstrdup("TCP");
213            else
214                tp = pa_xstrdup(value);
215        } else if (pa_streq(key, "et")) {
216            /* Supported encryption types:
217             *  - 0 = none,
218             *  - 1 = RSA,
219             *  - 2 = FairPlay,
220             *  - 3 = MFiSAP,
221             *  - 4 = FairPlay SAPv2.5. */
222            pa_xfree(et);
223            if (pa_str_in_list(value, ",", "1"))
224                et = pa_xstrdup("RSA");
225            else
226                et = pa_xstrdup("none");
227        } else if (pa_streq(key, "cn")) {
228            /* Suported audio codecs:
229             *  - 0 = PCM,
230             *  - 1 = ALAC,
231             *  - 2 = AAC,
232             *  - 3 = AAC ELD. */
233            pa_xfree(cn);
234            if (pa_str_in_list(value, ",", "1"))
235                cn = pa_xstrdup("ALAC");
236            else
237                cn = pa_xstrdup("PCM");
238        } else if (pa_streq(key, "md")) {
239            /* Supported metadata types:
240             *  - 0 = text,
241             *  - 1 = artwork,
242             *  - 2 = progress. */
243        } else if (pa_streq(key, "pw")) {
244            /* Requires password ? (true/false) */
245        } else if (pa_streq(key, "ch")) {
246            /* Number of channels */
247            pa_xfree(ch);
248            ch = pa_xstrdup(value);
249        } else if (pa_streq(key, "ss")) {
250            /* Sample size */
251            pa_xfree(ss);
252            ss = pa_xstrdup(value);
253        } else if (pa_streq(key, "sr")) {
254            /* Sample rate */
255            pa_xfree(sr);
256            sr = pa_xstrdup(value);
257        } else if (pa_streq(key, "am")) {
258            /* Device model */
259            pa_xfree(dm);
260            dm = pa_xstrdup(value);
261        }
262
263        avahi_free(key);
264        avahi_free(value);
265    }
266
267    if (device)
268        dname = pa_sprintf_malloc("raop_output.%s.%s", host_name, device);
269    else
270        dname = pa_sprintf_malloc("raop_output.%s", host_name);
271
272    if (!(vname = pa_namereg_make_valid_name(dname))) {
273        pa_log("Cannot construct valid device name from '%s'.", dname);
274        avahi_free(device);
275        pa_xfree(dname);
276        pa_xfree(tp);
277        pa_xfree(et);
278        pa_xfree(cn);
279        pa_xfree(ch);
280        pa_xfree(ss);
281        pa_xfree(sr);
282        pa_xfree(dm);
283        goto finish;
284    }
285
286    avahi_free(device);
287    pa_xfree(dname);
288
289    avahi_address_snprint(at, sizeof(at), a);
290
291    if (nicename == NULL)
292        nicename = pa_xstrdup("RAOP");
293
294    if (dm == NULL)
295        dm = pa_xstrdup(_("Unknown device model"));
296
297    latency = guess_latency_from_device(dm);
298
299    args = pa_sprintf_malloc("server=[%s]:%u "
300                             "sink_name=%s "
301                             "sink_properties='device.description=\"%s\" device.model=\"%s\"'",
302                             at, port,
303                             vname,
304                             nicename,
305                             dm);
306    pa_xfree(nicename);
307    pa_xfree(dm);
308
309    if (tp != NULL) {
310        t = args;
311        args = pa_sprintf_malloc("%s protocol=%s", args, tp);
312        pa_xfree(tp);
313        pa_xfree(t);
314    }
315    if (et != NULL) {
316        t = args;
317        args = pa_sprintf_malloc("%s encryption=%s", args, et);
318        pa_xfree(et);
319        pa_xfree(t);
320    }
321    if (cn != NULL) {
322        t = args;
323        args = pa_sprintf_malloc("%s codec=%s", args, cn);
324        pa_xfree(cn);
325        pa_xfree(t);
326    }
327    if (ch != NULL) {
328        t = args;
329        args = pa_sprintf_malloc("%s channels=%s", args, ch);
330        pa_xfree(ch);
331        pa_xfree(t);
332    }
333    if (ss != NULL) {
334        t = args;
335        args = pa_sprintf_malloc("%s format=%s", args, ss);
336        pa_xfree(ss);
337        pa_xfree(t);
338    }
339    if (sr != NULL) {
340        t = args;
341        args = pa_sprintf_malloc("%s rate=%s", args, sr);
342        pa_xfree(sr);
343        pa_xfree(t);
344    }
345
346    if (u->latency_set)
347        latency = u->latency;
348
349    t = args;
350    args = pa_sprintf_malloc("%s latency_msec=%u", args, latency);
351    pa_xfree(t);
352
353    pa_log_debug("Loading module-raop-sink with arguments '%s'", args);
354
355    if (pa_module_load(&m, u->core, "module-raop-sink", args) >= 0) {
356        tnl->module_index = m->index;
357        pa_hashmap_put(u->tunnels, tnl, tnl);
358        tnl = NULL;
359    }
360
361    pa_xfree(vname);
362    pa_xfree(args);
363
364finish:
365    avahi_service_resolver_free(r);
366
367    if (tnl)
368        tunnel_free(tnl);
369}
370
371static void browser_cb(
372        AvahiServiceBrowser *b,
373        AvahiIfIndex interface, AvahiProtocol protocol,
374        AvahiBrowserEvent event,
375        const char *name, const char *type, const char *domain,
376        AvahiLookupResultFlags flags,
377        void *userdata) {
378    struct userdata *u = userdata;
379    struct tunnel *t;
380
381    pa_assert(u);
382
383    if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
384        return;
385
386    t = tunnel_new(interface, protocol, name, type, domain);
387
388    if (event == AVAHI_BROWSER_NEW) {
389
390        if (!pa_hashmap_get(u->tunnels, t))
391            if (!(avahi_service_resolver_new(u->client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolver_cb, u)))
392                pa_log("avahi_service_resolver_new() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
393
394        /* We ignore the returned resolver object here, since the we don't
395         * need to attach any special data to it, and we can still destroy
396         * it from the callback. */
397
398    } else if (event == AVAHI_BROWSER_REMOVE) {
399        struct tunnel *t2;
400
401        if ((t2 = pa_hashmap_get(u->tunnels, t))) {
402            pa_module_unload_request_by_index(u->core, t2->module_index, true);
403            pa_hashmap_remove(u->tunnels, t2);
404            tunnel_free(t2);
405        }
406    }
407
408    tunnel_free(t);
409}
410
411static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
412    struct userdata *u = userdata;
413
414    pa_assert(c);
415    pa_assert(u);
416
417    u->client = c;
418
419    switch (state) {
420        case AVAHI_CLIENT_S_REGISTERING:
421        case AVAHI_CLIENT_S_RUNNING:
422        case AVAHI_CLIENT_S_COLLISION:
423            if (!u->sink_browser) {
424                if (!(u->sink_browser = avahi_service_browser_new(
425                              c,
426                              AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
427                              SERVICE_TYPE_SINK,
428                              NULL,
429                              0,
430                              browser_cb, u))) {
431
432                    pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c)));
433                    pa_module_unload_request(u->module, true);
434                }
435            }
436
437            break;
438
439        case AVAHI_CLIENT_FAILURE:
440            if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) {
441                int error;
442
443                pa_log_debug("Avahi daemon disconnected.");
444
445                /* Try to reconnect. */
446                if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
447                    pa_log("avahi_client_new() failed: %s", avahi_strerror(error));
448                    pa_module_unload_request(u->module, true);
449                }
450            }
451
452            /* Fall through. */
453
454        case AVAHI_CLIENT_CONNECTING:
455            if (u->sink_browser) {
456                avahi_service_browser_free(u->sink_browser);
457                u->sink_browser = NULL;
458            }
459
460            break;
461
462        default:
463            break;
464    }
465}
466
467int pa__init(pa_module *m) {
468    struct userdata *u;
469    pa_modargs *ma = NULL;
470    int error;
471
472    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
473        pa_log("Failed to parse module arguments.");
474        goto fail;
475    }
476
477    m->userdata = u = pa_xnew0(struct userdata, 1);
478    u->core = m->core;
479    u->module = m;
480
481    if (pa_modargs_get_value(ma, "latency_msec", NULL) != NULL) {
482        u->latency_set = true;
483        if (pa_modargs_get_value_u32(ma, "latency_msec", &u->latency) < 0) {
484            pa_log("Failed to parse latency_msec argument.");
485            goto fail;
486        }
487    }
488
489    u->tunnels = pa_hashmap_new(tunnel_hash, tunnel_compare);
490
491    u->avahi_poll = pa_avahi_poll_new(m->core->mainloop);
492
493    if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
494        pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error));
495        goto fail;
496    }
497
498    pa_modargs_free(ma);
499
500    return 0;
501
502fail:
503    pa__done(m);
504
505    if (ma)
506        pa_modargs_free(ma);
507
508    return -1;
509}
510
511void pa__done(pa_module *m) {
512    struct userdata *u;
513
514    pa_assert(m);
515
516    if (!(u = m->userdata))
517        return;
518
519    if (u->client)
520        avahi_client_free(u->client);
521
522    if (u->avahi_poll)
523        pa_avahi_poll_free(u->avahi_poll);
524
525    if (u->tunnels) {
526        struct tunnel *t;
527
528        while ((t = pa_hashmap_steal_first(u->tunnels))) {
529            pa_module_unload_request_by_index(u->core, t->module_index, true);
530            tunnel_free(t);
531        }
532
533        pa_hashmap_free(u->tunnels);
534    }
535
536    pa_xfree(u);
537}
538