remove all occurences of
[profile/ivi/pulseaudio.git] / src / modules / module-zeroconf-publish.c
1 /* $Id$ */
2
3 /***
4   This file is part of PulseAudio.
5  
6   PulseAudio is free software; you can redistribute it and/or modify
7   it under the terms of the GNU Lesser General Public License as
8   published by the Free Software Foundation; either version 2 of the
9   License, or (at your option) any later version.
10  
11   PulseAudio is distributed in the hope that it will be useful, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14   General Public License for more details.
15  
16   You should have received a copy of the GNU Lesser General Public
17   License along with PulseAudio; if not, write to the Free Software
18   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19   USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdio.h>
27 #include <assert.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31
32 #include <avahi-client/client.h>
33 #include <avahi-client/publish.h>
34 #include <avahi-common/alternative.h>
35 #include <avahi-common/error.h>
36
37 #include <pulse/xmalloc.h>
38 #include <pulse/util.h>
39
40 #include <pulsecore/autoload.h>
41 #include <pulsecore/sink.h>
42 #include <pulsecore/source.h>
43 #include <pulsecore/native-common.h>
44 #include <pulsecore/core-util.h>
45 #include <pulsecore/log.h>
46 #include <pulsecore/core-subscribe.h>
47 #include <pulsecore/dynarray.h>
48 #include <pulsecore/modargs.h>
49 #include <pulsecore/avahi-wrap.h>
50 #include <pulsecore/endianmacros.h>
51
52 #include "module-zeroconf-publish-symdef.h"
53
54 PA_MODULE_AUTHOR("Lennart Poettering")
55 PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher")
56 PA_MODULE_VERSION(PACKAGE_VERSION)
57 PA_MODULE_USAGE("port=<IP port number>")
58
59 #define SERVICE_TYPE_SINK "_pulse-sink._tcp"
60 #define SERVICE_TYPE_SOURCE "_pulse-source._tcp"
61 #define SERVICE_TYPE_SERVER "_pulse-server._tcp"
62
63 static const char* const valid_modargs[] = {
64     "port",
65     NULL
66 };
67
68 struct service {
69     struct userdata *userdata;
70     AvahiEntryGroup *entry_group;
71     char *service_name;
72     char *name;
73     enum  { UNPUBLISHED, PUBLISHED_REAL, PUBLISHED_AUTOLOAD } published ;
74
75     struct {
76         int valid;
77         pa_namereg_type_t type;
78         uint32_t index;
79     } loaded;
80
81     struct {
82         int valid;
83         pa_namereg_type_t type;
84         uint32_t index;
85     } autoload;
86 };
87
88 struct userdata {
89     pa_core *core;
90     AvahiPoll *avahi_poll;
91     AvahiClient *client;
92     pa_hashmap *services;
93     pa_dynarray *sink_dynarray, *source_dynarray, *autoload_dynarray;
94     pa_subscription *subscription;
95     char *service_name;
96
97     AvahiEntryGroup *main_entry_group;
98
99     uint16_t port;
100 };
101
102 static void get_service_data(struct userdata *u, struct service *s, pa_sample_spec *ret_ss, char **ret_description) {
103     assert(u && s && s->loaded.valid && ret_ss && ret_description);
104
105     if (s->loaded.type == PA_NAMEREG_SINK) {
106         pa_sink *sink = pa_idxset_get_by_index(u->core->sinks, s->loaded.index);
107         assert(sink);
108         *ret_ss = sink->sample_spec;
109         *ret_description = sink->description;
110     } else if (s->loaded.type == PA_NAMEREG_SOURCE) {
111         pa_source *source = pa_idxset_get_by_index(u->core->sources, s->loaded.index);
112         assert(source);
113         *ret_ss = source->sample_spec;
114         *ret_description = source->description;
115     } else
116         assert(0);
117 }
118
119 static AvahiStringList* txt_record_server_data(pa_core *c, AvahiStringList *l) {
120     char s[128];
121     assert(c);
122
123     l = avahi_string_list_add_pair(l, "server-version", PACKAGE_NAME" "PACKAGE_VERSION);
124     l = avahi_string_list_add_pair(l, "user-name", pa_get_user_name(s, sizeof(s)));
125     l = avahi_string_list_add_pair(l, "fqdn", pa_get_fqdn(s, sizeof(s)));
126     l = avahi_string_list_add_printf(l, "cookie=0x%08x", c->cookie);
127
128     return l;
129 }
130
131 static int publish_service(struct userdata *u, struct service *s);
132
133 static void service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
134     struct service *s = userdata;
135
136     if (state == AVAHI_ENTRY_GROUP_COLLISION) {
137         char *t;
138
139         t = avahi_alternative_service_name(s->service_name);
140         pa_xfree(s->service_name);
141         s->service_name = t;
142
143         publish_service(s->userdata, s);
144     }
145 }
146
147 static int publish_service(struct userdata *u, struct service *s) {
148     int r = -1;
149     AvahiStringList *txt = NULL;
150
151     assert(u);
152     assert(s);
153
154     if (!u->client || avahi_client_get_state(u->client) != AVAHI_CLIENT_S_RUNNING)
155         return 0;
156     
157     if ((s->published == PUBLISHED_REAL && s->loaded.valid) ||
158         (s->published == PUBLISHED_AUTOLOAD && s->autoload.valid && !s->loaded.valid))
159         return 0;
160
161     if (s->published != UNPUBLISHED) {
162         avahi_entry_group_reset(s->entry_group);
163         s->published = UNPUBLISHED;
164     } 
165     
166     if (s->loaded.valid || s->autoload.valid) {
167         pa_namereg_type_t type;
168
169         if (!s->entry_group) {
170             if (!(s->entry_group = avahi_entry_group_new(u->client, service_entry_group_callback, s))) {
171                 pa_log("avahi_entry_group_new(): %s", avahi_strerror(avahi_client_errno(u->client)));
172                 goto finish;
173             }
174         }
175         
176         txt = avahi_string_list_add_pair(txt, "device", s->name);
177         txt = txt_record_server_data(u->core, txt);
178         
179         if (s->loaded.valid) {
180             char *description;
181             pa_sample_spec ss;
182             
183             get_service_data(u, s, &ss, &description);
184             
185             txt = avahi_string_list_add_printf(txt, "rate=%u", ss.rate);
186             txt = avahi_string_list_add_printf(txt, "channels=%u", ss.channels);
187             txt = avahi_string_list_add_pair(txt, "format", pa_sample_format_to_string(ss.format));
188             if (description)
189                 txt = avahi_string_list_add_pair(txt, "description", description);
190             
191             type = s->loaded.type;
192         } else if (s->autoload.valid)
193             type = s->autoload.type;
194         
195         if (avahi_entry_group_add_service_strlst(
196                     s->entry_group,
197                     AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
198                     0,
199                     s->service_name,
200                     type == PA_NAMEREG_SINK ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE,
201                     NULL,
202                     NULL,
203                     u->port,
204                     txt) < 0) {
205             
206             pa_log("avahi_entry_group_add_service_strlst(): %s", avahi_strerror(avahi_client_errno(u->client)));
207             goto finish;
208         }
209         
210         if (avahi_entry_group_commit(s->entry_group) < 0) {
211             pa_log("avahi_entry_group_commit(): %s", avahi_strerror(avahi_client_errno(u->client)));
212             goto finish;
213         }
214         
215         if (s->loaded.valid)
216             s->published = PUBLISHED_REAL;
217         else if (s->autoload.valid)
218             s->published = PUBLISHED_AUTOLOAD;
219     }
220         
221     r = 0;
222     
223 finish:
224
225     if (s->published == UNPUBLISHED) {
226         /* Remove this service */
227
228         if (s->entry_group)
229             avahi_entry_group_free(s->entry_group);
230         
231         pa_hashmap_remove(u->services, s->name);
232         pa_xfree(s->name);
233         pa_xfree(s->service_name);
234         pa_xfree(s);
235     }
236
237     if (txt)
238         avahi_string_list_free(txt);
239     
240     return r;
241 }
242
243 static struct service *get_service(struct userdata *u, const char *name, const char *description) {
244     struct service *s;
245     char hn[64];
246     
247     if ((s = pa_hashmap_get(u->services, name)))
248         return s;
249     
250     s = pa_xnew(struct service, 1);
251     s->userdata = u;
252     s->entry_group = NULL;
253     s->published = UNPUBLISHED;
254     s->name = pa_xstrdup(name);
255     s->loaded.valid = s->autoload.valid = 0;
256     s->service_name = pa_sprintf_malloc("%s on %s", description ? description : s->name, pa_get_host_name(hn, sizeof(hn)));
257
258     pa_hashmap_put(u->services, s->name, s);
259
260     return s;
261 }
262
263 static int publish_sink(struct userdata *u, pa_sink *s) {
264     struct service *svc;
265     int ret;
266     assert(u && s);
267
268     svc = get_service(u, s->name, s->description);
269     if (svc->loaded.valid)
270         return publish_service(u, svc);
271
272     svc->loaded.valid = 1;
273     svc->loaded.type = PA_NAMEREG_SINK;
274     svc->loaded.index = s->index;
275
276     if ((ret = publish_service(u, svc)) < 0)
277         return ret;
278
279     pa_dynarray_put(u->sink_dynarray, s->index, svc);
280     return ret;
281 }
282
283 static int publish_source(struct userdata *u, pa_source *s) {
284     struct service *svc;
285     int ret;
286     
287     assert(u && s);
288
289     svc = get_service(u, s->name, s->description);
290     if (svc->loaded.valid)
291         return publish_service(u, svc);
292
293     svc->loaded.valid = 1;
294     svc->loaded.type = PA_NAMEREG_SOURCE;
295     svc->loaded.index = s->index;
296
297     pa_dynarray_put(u->source_dynarray, s->index, svc);
298     
299     if ((ret = publish_service(u, svc)) < 0)
300         return ret;
301
302     pa_dynarray_put(u->sink_dynarray, s->index, svc);
303     return ret;
304 }
305
306 static int publish_autoload(struct userdata *u, pa_autoload_entry *s) {
307     struct service *svc;
308     int ret;
309     
310     assert(u && s);
311
312     svc = get_service(u, s->name, NULL);
313     if (svc->autoload.valid)
314         return publish_service(u, svc);
315
316     svc->autoload.valid = 1;
317     svc->autoload.type = s->type;
318     svc->autoload.index = s->index;
319
320     if ((ret = publish_service(u, svc)) < 0)
321         return ret;
322     
323     pa_dynarray_put(u->autoload_dynarray, s->index, svc);
324     return ret;
325 }
326
327 static int remove_sink(struct userdata *u, uint32_t idx) {
328     struct service *svc;
329     assert(u && idx != PA_INVALID_INDEX);
330
331     if (!(svc = pa_dynarray_get(u->sink_dynarray, idx)))
332         return 0;
333
334     if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SINK)
335         return 0;
336
337     svc->loaded.valid = 0;
338     pa_dynarray_put(u->sink_dynarray, idx, NULL);
339     
340     return publish_service(u, svc);
341 }
342
343 static int remove_source(struct userdata *u, uint32_t idx) {
344     struct service *svc;
345     assert(u && idx != PA_INVALID_INDEX);
346     
347     if (!(svc = pa_dynarray_get(u->source_dynarray, idx)))
348         return 0;
349
350     if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SOURCE)
351         return 0;
352
353     svc->loaded.valid = 0;
354     pa_dynarray_put(u->source_dynarray, idx, NULL);
355
356     return publish_service(u, svc);
357 }
358
359 static int remove_autoload(struct userdata *u, uint32_t idx) {
360     struct service *svc;
361     assert(u && idx != PA_INVALID_INDEX);
362     
363     if (!(svc = pa_dynarray_get(u->autoload_dynarray, idx)))
364         return 0;
365
366     if (!svc->autoload.valid)
367         return 0;
368
369     svc->autoload.valid = 0;
370     pa_dynarray_put(u->autoload_dynarray, idx, NULL);
371
372     return publish_service(u, svc);
373 }
374
375 static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
376     struct userdata *u = userdata;
377     assert(u && c);
378
379     switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)
380         case PA_SUBSCRIPTION_EVENT_SINK: {
381             if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
382                 pa_sink *sink;
383
384                 if ((sink = pa_idxset_get_by_index(c->sinks, idx))) {
385                     if (publish_sink(u, sink) < 0)
386                         goto fail;
387                 }
388             } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
389                 if (remove_sink(u, idx) < 0)
390                     goto fail;
391             }
392         
393             break;
394
395         case PA_SUBSCRIPTION_EVENT_SOURCE:
396
397             if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
398                 pa_source *source;
399                 
400                 if ((source = pa_idxset_get_by_index(c->sources, idx))) {
401                     if (publish_source(u, source) < 0)
402                         goto fail;
403                 }
404             } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
405                 if (remove_source(u, idx) < 0)
406                     goto fail;
407             }
408             
409             break;
410
411         case PA_SUBSCRIPTION_EVENT_AUTOLOAD:
412             if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
413                 pa_autoload_entry *autoload;
414                     
415                 if ((autoload = pa_idxset_get_by_index(c->autoload_idxset, idx))) {
416                     if (publish_autoload(u, autoload) < 0)
417                         goto fail;
418                 }
419             } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
420                 if (remove_autoload(u, idx) < 0)
421                         goto fail;
422             }
423             
424             break;
425     }
426
427     return;
428
429 fail:
430     if (u->subscription) {
431         pa_subscription_free(u->subscription);
432         u->subscription = NULL;
433     }
434 }
435
436 static int publish_main_service(struct userdata *u);
437
438 static void main_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
439     struct userdata *u = userdata;
440     assert(u);
441
442     if (state == AVAHI_ENTRY_GROUP_COLLISION) {
443         char *t;
444
445         t = avahi_alternative_service_name(u->service_name);
446         pa_xfree(u->service_name);
447         u->service_name = t;
448
449         publish_main_service(u);
450     }
451 }
452
453 static int publish_main_service(struct userdata *u) {
454     AvahiStringList *txt = NULL;
455     int r = -1;
456     
457     if (!u->main_entry_group) {
458         if (!(u->main_entry_group = avahi_entry_group_new(u->client, main_entry_group_callback, u))) {
459             pa_log("avahi_entry_group_new() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
460             goto fail;
461         }
462     } else
463         avahi_entry_group_reset(u->main_entry_group);
464     
465     txt = txt_record_server_data(u->core, NULL);
466
467     if (avahi_entry_group_add_service_strlst(
468                 u->main_entry_group,
469                 AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
470                 0,
471                 u->service_name,
472                 SERVICE_TYPE_SERVER,
473                 NULL,
474                 NULL,
475                 u->port,
476                 txt) < 0) {
477         
478         pa_log("avahi_entry_group_add_service_strlst() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
479         goto fail;
480     }
481             
482     if (avahi_entry_group_commit(u->main_entry_group) < 0) {
483         pa_log("avahi_entry_group_commit() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
484         goto fail;
485     }
486
487     r = 0;
488     
489 fail:
490     avahi_string_list_free(txt);
491
492     return r;
493 }
494
495 static int publish_all_services(struct userdata *u) {
496     pa_sink *sink;
497     pa_source *source;
498     pa_autoload_entry *autoload;
499     int r = -1;
500     uint32_t idx;
501     
502     assert(u);
503
504     pa_log_debug("Publishing services in Zeroconf");
505
506     for (sink = pa_idxset_first(u->core->sinks, &idx); sink; sink = pa_idxset_next(u->core->sinks, &idx))
507         if (publish_sink(u, sink) < 0)
508             goto fail;
509
510     for (source = pa_idxset_first(u->core->sources, &idx); source; source = pa_idxset_next(u->core->sources, &idx))
511         if (publish_source(u, source) < 0)
512             goto fail;
513
514     if (u->core->autoload_idxset)
515         for (autoload = pa_idxset_first(u->core->autoload_idxset, &idx); autoload; autoload = pa_idxset_next(u->core->autoload_idxset, &idx))
516             if (publish_autoload(u, autoload) < 0)
517                 goto fail;
518
519     if (publish_main_service(u) < 0)
520         goto fail;
521     
522     r = 0;
523     
524 fail:
525     return r;
526 }
527
528 static void unpublish_all_services(struct userdata *u, int rem) {
529     void *state = NULL;
530     struct service *s;
531     
532     assert(u);
533
534     pa_log_debug("Unpublishing services in Zeroconf");
535
536     while ((s = pa_hashmap_iterate(u->services, &state, NULL))) {
537         if (s->entry_group) {
538             if (rem) {
539                 avahi_entry_group_free(s->entry_group);
540                 s->entry_group = NULL;
541             } else 
542                 avahi_entry_group_reset(s->entry_group);
543         }
544
545         s->published = UNPUBLISHED;
546     }
547
548     if (u->main_entry_group) {
549         if (rem) {
550             avahi_entry_group_free(u->main_entry_group);
551             u->main_entry_group = NULL;
552         } else
553             avahi_entry_group_reset(u->main_entry_group);
554     }
555 }
556
557 static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
558     struct userdata *u = userdata;
559     assert(c);
560
561     u->client = c;
562     
563     switch (state) {
564         case AVAHI_CLIENT_S_RUNNING:
565             publish_all_services(u);
566             break;
567             
568         case AVAHI_CLIENT_S_COLLISION:
569             unpublish_all_services(u, 0);
570             break;
571
572         case AVAHI_CLIENT_FAILURE:
573             if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) {
574                 int error;
575                 unpublish_all_services(u, 1);
576                 avahi_client_free(u->client);
577
578                 if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error)))
579                     pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error));
580             }
581             
582             break;
583
584         default: ;
585     }
586 }
587
588 int pa__init(pa_core *c, pa_module*m) {
589     struct userdata *u;
590     uint32_t port = PA_NATIVE_DEFAULT_PORT;
591     pa_modargs *ma = NULL;
592     char hn[256];
593     int error;
594
595     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
596         pa_log("failed to parse module arguments.");
597         goto fail;
598     }
599
600     if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port == 0 || port >= 0xFFFF) {
601         pa_log("invalid port specified.");
602         goto fail;
603     }
604
605     m->userdata = u = pa_xnew(struct userdata, 1);
606     u->core = c;
607     u->port = (uint16_t) port;
608
609     u->avahi_poll = pa_avahi_poll_new(c->mainloop);
610     
611     u->services = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
612     u->sink_dynarray = pa_dynarray_new();
613     u->source_dynarray = pa_dynarray_new();
614     u->autoload_dynarray = pa_dynarray_new();
615
616     u->subscription = pa_subscription_new(c,
617                                           PA_SUBSCRIPTION_MASK_SINK|
618                                           PA_SUBSCRIPTION_MASK_SOURCE|
619                                           PA_SUBSCRIPTION_MASK_AUTOLOAD, subscribe_callback, u);
620
621     u->main_entry_group = NULL;
622
623     u->service_name = pa_xstrdup(pa_get_host_name(hn, sizeof(hn)));
624
625     if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
626         pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error));
627         goto fail;
628     }
629
630     pa_modargs_free(ma);
631     
632     return 0;
633     
634 fail:
635     pa__done(c, m);
636
637     if (ma)
638         pa_modargs_free(ma);
639     
640     return -1;
641 }
642
643 static void service_free(void *p, void *userdata) {
644     struct service *s = p;
645     struct userdata *u = userdata;
646
647     assert(s);
648     assert(u);
649
650     if (s->entry_group)
651         avahi_entry_group_free(s->entry_group);
652     
653     pa_xfree(s->service_name);
654     pa_xfree(s->name);
655     pa_xfree(s);
656 }
657
658 void pa__done(pa_core *c, pa_module*m) {
659     struct userdata*u;
660     assert(c && m);
661
662     if (!(u = m->userdata))
663         return;
664
665     if (u->services)
666         pa_hashmap_free(u->services, service_free, u);
667
668     if (u->subscription)
669         pa_subscription_free(u->subscription);
670
671     if (u->sink_dynarray)
672         pa_dynarray_free(u->sink_dynarray, NULL, NULL);
673     if (u->source_dynarray)
674         pa_dynarray_free(u->source_dynarray, NULL, NULL);
675     if (u->autoload_dynarray)
676         pa_dynarray_free(u->autoload_dynarray, NULL, NULL);
677     
678
679     if (u->main_entry_group)
680         avahi_entry_group_free(u->main_entry_group);
681     
682     if (u->client)
683         avahi_client_free(u->client);
684     
685     if (u->avahi_poll)
686         pa_avahi_poll_free(u->avahi_poll);
687
688     pa_xfree(u->service_name);
689     pa_xfree(u);
690 }
691