hashmap: Add the ability to free keys
[platform/upstream/pulseaudio.git] / src / modules / macosx / module-bonjour-publish.c
1 /***
2   This file is part of PulseAudio.
3
4   Copyright 2009 Daniel Mack
5   based on module-zeroconf-publish.c
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 #include <dns_sd.h>
32
33 #include <CoreFoundation/CoreFoundation.h>
34
35 #include <pulse/xmalloc.h>
36 #include <pulse/util.h>
37
38 #include <pulsecore/parseaddr.h>
39 #include <pulsecore/sink.h>
40 #include <pulsecore/source.h>
41 #include <pulsecore/native-common.h>
42 #include <pulsecore/core-util.h>
43 #include <pulsecore/log.h>
44 #include <pulsecore/dynarray.h>
45 #include <pulsecore/modargs.h>
46 #include <pulsecore/protocol-native.h>
47
48 #include "module-bonjour-publish-symdef.h"
49
50 PA_MODULE_AUTHOR("Daniel Mack");
51 PA_MODULE_DESCRIPTION("Mac OS X Bonjour Service Publisher");
52 PA_MODULE_VERSION(PACKAGE_VERSION);
53 PA_MODULE_LOAD_ONCE(true);
54
55 #define SERVICE_TYPE_SINK "_pulse-sink._tcp"
56 #define SERVICE_TYPE_SOURCE "_pulse-source._tcp"
57 #define SERVICE_TYPE_SERVER "_pulse-server._tcp"
58
59 static const char* const valid_modargs[] = {
60     NULL
61 };
62
63 enum service_subtype {
64     SUBTYPE_HARDWARE,
65     SUBTYPE_VIRTUAL,
66     SUBTYPE_MONITOR
67 };
68
69 struct service {
70     struct userdata *userdata;
71     DNSServiceRef service;
72     DNSRecordRef rec, rec2;
73     char *service_name;
74     pa_object *device;
75     enum service_subtype subtype;
76 };
77
78 struct userdata {
79     pa_core *core;
80     pa_module *module;
81
82     pa_hashmap *services;
83     char *service_name;
84
85     pa_hook_slot *sink_new_slot, *source_new_slot, *sink_unlink_slot, *source_unlink_slot, *sink_changed_slot, *source_changed_slot;
86
87     pa_native_protocol *native;
88     DNSServiceRef main_service;
89 };
90
91 static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_channel_map *ret_map, const char **ret_name, pa_proplist **ret_proplist, enum service_subtype *ret_subtype) {
92     pa_assert(s);
93     pa_assert(ret_ss);
94     pa_assert(ret_proplist);
95     pa_assert(ret_subtype);
96
97     if (pa_sink_isinstance(s->device)) {
98         pa_sink *sink = PA_SINK(s->device);
99
100         *ret_ss = sink->sample_spec;
101         *ret_map = sink->channel_map;
102         *ret_name = sink->name;
103         *ret_proplist = sink->proplist;
104         *ret_subtype = sink->flags & PA_SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL;
105
106     } else if (pa_source_isinstance(s->device)) {
107         pa_source *source = PA_SOURCE(s->device);
108
109         *ret_ss = source->sample_spec;
110         *ret_map = source->channel_map;
111         *ret_name = source->name;
112         *ret_proplist = source->proplist;
113         *ret_subtype = source->monitor_of ? SUBTYPE_MONITOR : (source->flags & PA_SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL);
114
115     } else
116         pa_assert_not_reached();
117 }
118
119 static void txt_record_server_data(pa_core *c, TXTRecordRef *txt) {
120     char s[128];
121     char *t;
122
123     pa_assert(c);
124
125     TXTRecordSetValue(txt, "server-version", strlen(PACKAGE_NAME" "PACKAGE_VERSION), PACKAGE_NAME" "PACKAGE_VERSION);
126
127     t = pa_get_user_name_malloc();
128     TXTRecordSetValue(txt, "user-name", strlen(t), t);
129     pa_xfree(t);
130
131     t = pa_machine_id();
132     TXTRecordSetValue(txt, "machine-id", strlen(t), t);
133     pa_xfree(t);
134
135     t = pa_uname_string();
136     TXTRecordSetValue(txt, "uname", strlen(t), t);
137     pa_xfree(t);
138
139     t = pa_get_fqdn(s, sizeof(s));
140     TXTRecordSetValue(txt, "fqdn", strlen(t), t);
141
142     snprintf(s, sizeof(s), "0x%08x", c->cookie);
143     TXTRecordSetValue(txt, "cookie", strlen(s), s);
144 }
145
146 static void service_free(struct service *s);
147
148 static void dns_service_register_reply(DNSServiceRef sdRef,
149                                        DNSServiceFlags flags,
150                                        DNSServiceErrorType errorCode,
151                                        const char *name,
152                                        const char *regtype,
153                                        const char *domain,
154                                        void *context) {
155     struct service *s = context;
156
157     pa_assert(s);
158
159     switch (errorCode) {
160     case kDNSServiceErr_NameConflict:
161         pa_log("DNS service reported kDNSServiceErr_NameConflict\n");
162         service_free(s);
163         break;
164
165     case kDNSServiceErr_NoError:
166     default:
167         break;
168     }
169 }
170
171 static uint16_t compute_port(struct userdata *u) {
172     pa_strlist *i;
173
174     pa_assert(u);
175
176     for (i = pa_native_protocol_servers(u->native); i; i = pa_strlist_next(i)) {
177         pa_parsed_address a;
178
179         if (pa_parse_address(pa_strlist_data(i), &a) >= 0 &&
180             (a.type == PA_PARSED_ADDRESS_TCP4 ||
181              a.type == PA_PARSED_ADDRESS_TCP6 ||
182              a.type == PA_PARSED_ADDRESS_TCP_AUTO) &&
183             a.port > 0) {
184
185             pa_xfree(a.path_or_host);
186             return a.port;
187         }
188
189         pa_xfree(a.path_or_host);
190     }
191
192     return PA_NATIVE_DEFAULT_PORT;
193 }
194
195 static int publish_service(struct service *s) {
196     int r = -1;
197     TXTRecordRef txt;
198     DNSServiceErrorType err;
199     const char *name = NULL, *t;
200     pa_proplist *proplist = NULL;
201     pa_sample_spec ss;
202     pa_channel_map map;
203     char cm[PA_CHANNEL_MAP_SNPRINT_MAX], tmp[64];
204     enum service_subtype subtype;
205
206     const char * const subtype_text[] = {
207         [SUBTYPE_HARDWARE] = "hardware",
208         [SUBTYPE_VIRTUAL] = "virtual",
209         [SUBTYPE_MONITOR] = "monitor"
210     };
211
212     pa_assert(s);
213
214     if (s->service) {
215         DNSServiceRefDeallocate(s->service);
216         s->service = NULL;
217     }
218
219     TXTRecordCreate(&txt, 0, NULL);
220
221     txt_record_server_data(s->userdata->core, &txt);
222
223     get_service_data(s, &ss, &map, &name, &proplist, &subtype);
224     TXTRecordSetValue(&txt, "device", strlen(name), name);
225
226     snprintf(tmp, sizeof(tmp), "%u", ss.rate);
227     TXTRecordSetValue(&txt, "rate", strlen(tmp), tmp);
228
229     snprintf(tmp, sizeof(tmp), "%u", ss.channels);
230     TXTRecordSetValue(&txt, "channels", strlen(tmp), tmp);
231
232     t = pa_sample_format_to_string(ss.format);
233     TXTRecordSetValue(&txt, "format", strlen(t), t);
234
235     t = pa_channel_map_snprint(cm, sizeof(cm), &map);
236     TXTRecordSetValue(&txt, "channel_map", strlen(t), t);
237
238     t = subtype_text[subtype];
239     TXTRecordSetValue(&txt, "subtype", strlen(t), t);
240
241     if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_DESCRIPTION)))
242         TXTRecordSetValue(&txt, "description", strlen(t), t);
243     if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_ICON_NAME)))
244         TXTRecordSetValue(&txt, "icon-name", strlen(t), t);
245     if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_VENDOR_NAME)))
246         TXTRecordSetValue(&txt, "vendor-name", strlen(t), t);
247     if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_PRODUCT_NAME)))
248         TXTRecordSetValue(&txt, "product-name", strlen(t), t);
249     if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_CLASS)))
250         TXTRecordSetValue(&txt, "class", strlen(t), t);
251     if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_FORM_FACTOR)))
252         TXTRecordSetValue(&txt, "form-factor", strlen(t), t);
253
254     err = DNSServiceRegister(&s->service,
255                              0,         /* flags */
256                              kDNSServiceInterfaceIndexAny,
257                              s->service_name,
258                              pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE,
259                              NULL,      /* domain */
260                              NULL,      /* host */
261                              compute_port(s->userdata),
262                              TXTRecordGetLength(&txt),
263                              TXTRecordGetBytesPtr(&txt),
264                              dns_service_register_reply, s);
265
266     if (err != kDNSServiceErr_NoError) {
267         pa_log("DNSServiceRegister() returned err %d", err);
268         goto finish;
269     }
270
271     pa_log_debug("Successfully registered Bonjour services for >%s<.", s->service_name);
272     return 0;
273
274 finish:
275
276     /* Remove this service */
277     if (r < 0)
278         service_free(s);
279
280     TXTRecordDeallocate(&txt);
281
282     return r;
283 }
284
285 static struct service *get_service(struct userdata *u, pa_object *device) {
286     struct service *s;
287     char *hn, *un;
288     const char *n;
289
290     pa_assert(u);
291     pa_object_assert_ref(device);
292
293     if ((s = pa_hashmap_get(u->services, device)))
294         return s;
295
296     s = pa_xnew0(struct service, 1);
297     s->userdata = u;
298     s->device = device;
299
300     if (pa_sink_isinstance(device)) {
301         if (!(n = pa_proplist_gets(PA_SINK(device)->proplist, PA_PROP_DEVICE_DESCRIPTION)))
302             n = PA_SINK(device)->name;
303     } else {
304         if (!(n = pa_proplist_gets(PA_SOURCE(device)->proplist, PA_PROP_DEVICE_DESCRIPTION)))
305             n = PA_SOURCE(device)->name;
306     }
307
308     hn = pa_get_host_name_malloc();
309     un = pa_get_user_name_malloc();
310
311     s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", un, hn, n), kDNSServiceMaxDomainName-1);
312
313     pa_xfree(un);
314     pa_xfree(hn);
315
316     pa_hashmap_put(u->services, s->device, s);
317
318     return s;
319 }
320
321 static void service_free(struct service *s) {
322     pa_assert(s);
323
324     pa_hashmap_remove(s->userdata->services, s->device);
325
326     if (s->service)
327         DNSServiceRefDeallocate(s->service);
328
329     pa_xfree(s->service_name);
330     pa_xfree(s);
331 }
332
333 static bool shall_ignore(pa_object *o) {
334     pa_object_assert_ref(o);
335
336     if (pa_sink_isinstance(o))
337         return !!(PA_SINK(o)->flags & PA_SINK_NETWORK);
338
339     if (pa_source_isinstance(o))
340         return PA_SOURCE(o)->monitor_of || (PA_SOURCE(o)->flags & PA_SOURCE_NETWORK);
341
342     pa_assert_not_reached();
343 }
344
345 static pa_hook_result_t device_new_or_changed_cb(pa_core *c, pa_object *o, struct userdata *u) {
346     pa_assert(c);
347     pa_object_assert_ref(o);
348
349     if (!shall_ignore(o))
350         publish_service(get_service(u, o));
351
352     return PA_HOOK_OK;
353 }
354
355 static pa_hook_result_t device_unlink_cb(pa_core *c, pa_object *o, struct userdata *u) {
356     struct service *s;
357
358     pa_assert(c);
359     pa_object_assert_ref(o);
360
361     if ((s = pa_hashmap_get(u->services, o)))
362         service_free(s);
363
364     return PA_HOOK_OK;
365 }
366
367 static int publish_main_service(struct userdata *u) {
368     DNSServiceErrorType err;
369     TXTRecordRef txt;
370
371     pa_assert(u);
372
373     if (u->main_service) {
374         DNSServiceRefDeallocate(u->main_service);
375         u->main_service = NULL;
376     }
377
378     TXTRecordCreate(&txt, 0, NULL);
379     txt_record_server_data(u->core, &txt);
380
381     err = DNSServiceRegister(&u->main_service,
382                              0, /* flags */
383                              kDNSServiceInterfaceIndexAny,
384                              u->service_name,
385                              SERVICE_TYPE_SERVER,
386                              NULL, /* domain */
387                              NULL, /* host */
388                              compute_port(u),
389                              TXTRecordGetLength(&txt),
390                              TXTRecordGetBytesPtr(&txt),
391                              NULL, NULL);
392
393     if (err != kDNSServiceErr_NoError) {
394         pa_log("%s(): DNSServiceRegister() returned err %d", __func__, err);
395         return err;
396     }
397
398     TXTRecordDeallocate(&txt);
399
400     return 0;
401 }
402
403 static int publish_all_services(struct userdata *u) {
404     pa_sink *sink;
405     pa_source *source;
406     uint32_t idx;
407
408     pa_assert(u);
409
410     pa_log_debug("Publishing services in Bonjour");
411
412     for (sink = PA_SINK(pa_idxset_first(u->core->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(u->core->sinks, &idx)))
413         if (!shall_ignore(PA_OBJECT(sink)))
414             publish_service(get_service(u, PA_OBJECT(sink)));
415
416     for (source = PA_SOURCE(pa_idxset_first(u->core->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(u->core->sources, &idx)))
417         if (!shall_ignore(PA_OBJECT(source)))
418             publish_service(get_service(u, PA_OBJECT(source)));
419
420     return publish_main_service(u);
421 }
422
423 static void unpublish_all_services(struct userdata *u) {
424     void *state = NULL;
425     struct service *s;
426
427     pa_assert(u);
428
429     pa_log_debug("Unpublishing services in Bonjour");
430
431     while ((s = pa_hashmap_iterate(u->services, &state, NULL)))
432         service_free(s);
433
434     if (u->main_service)
435         DNSServiceRefDeallocate(u->main_service);
436 }
437
438 int pa__init(pa_module*m) {
439
440     struct userdata *u;
441     pa_modargs *ma = NULL;
442     char *hn, *un;
443
444     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
445         pa_log("Failed to parse module arguments.");
446         goto fail;
447     }
448
449     m->userdata = u = pa_xnew0(struct userdata, 1);
450     u->core = m->core;
451     u->module = m;
452     u->native = pa_native_protocol_get(u->core);
453
454     u->services = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
455
456     u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
457     u->sink_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
458     u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u);
459     u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
460     u->source_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
461     u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u);
462
463     un = pa_get_user_name_malloc();
464     hn = pa_get_host_name_malloc();
465     u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", un, hn), kDNSServiceMaxDomainName-1);
466     pa_xfree(un);
467     pa_xfree(hn);
468
469     publish_all_services(u);
470     pa_modargs_free(ma);
471
472     return 0;
473
474 fail:
475     pa__done(m);
476
477     if (ma)
478         pa_modargs_free(ma);
479
480     return -1;
481 }
482
483 void pa__done(pa_module*m) {
484     struct userdata*u;
485     pa_assert(m);
486
487     if (!(u = m->userdata))
488         return;
489
490     unpublish_all_services(u);
491
492     if (u->services)
493         pa_hashmap_free(u->services);
494
495     if (u->sink_new_slot)
496         pa_hook_slot_free(u->sink_new_slot);
497     if (u->source_new_slot)
498         pa_hook_slot_free(u->source_new_slot);
499     if (u->sink_changed_slot)
500         pa_hook_slot_free(u->sink_changed_slot);
501     if (u->source_changed_slot)
502         pa_hook_slot_free(u->source_changed_slot);
503     if (u->sink_unlink_slot)
504         pa_hook_slot_free(u->sink_unlink_slot);
505     if (u->source_unlink_slot)
506         pa_hook_slot_free(u->source_unlink_slot);
507
508     if (u->native)
509         pa_native_protocol_unref(u->native);
510
511     pa_xfree(u->service_name);
512     pa_xfree(u);
513 }