]> code.delx.au - pulseaudio/blob - src/modules/module-zeroconf-publish.c
* more s/pulseaudio/PulseAudio/ replacements
[pulseaudio] / 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 <pulse/xmalloc.h>
33 #include <pulse/util.h>
34
35 #include <pulsecore/autoload.h>
36 #include <pulsecore/sink.h>
37 #include <pulsecore/source.h>
38 #include <pulsecore/native-common.h>
39 #include <pulsecore/core-util.h>
40 #include <pulsecore/log.h>
41 #include <pulsecore/core-subscribe.h>
42 #include <pulsecore/dynarray.h>
43 #include <pulsecore/modargs.h>
44
45 #include "../pulsecore/endianmacros.h"
46
47 #include "howl-wrap.h"
48
49 #include "module-zeroconf-publish-symdef.h"
50
51 PA_MODULE_AUTHOR("Lennart Poettering")
52 PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher")
53 PA_MODULE_VERSION(PACKAGE_VERSION)
54 PA_MODULE_USAGE("port=<IP port number>")
55
56 #define SERVICE_NAME_SINK "_pulse-sink._tcp"
57 #define SERVICE_NAME_SOURCE "_pulse-source._tcp"
58 #define SERVICE_NAME_SERVER "_pulse-server._tcp"
59
60 static const char* const valid_modargs[] = {
61 "port",
62 NULL
63 };
64
65 struct service {
66 sw_discovery_oid oid;
67 char *name;
68 int published; /* 0 -> not yet registered, 1 -> registered with data from real device, 2 -> registered with data from autoload device */
69
70 struct {
71 int valid;
72 pa_namereg_type_t type;
73 uint32_t index;
74 } loaded;
75
76 struct {
77 int valid;
78 pa_namereg_type_t type;
79 uint32_t index;
80 } autoload;
81 };
82
83 struct userdata {
84 pa_core *core;
85 pa_howl_wrapper *howl_wrapper;
86 pa_hashmap *services;
87 pa_dynarray *sink_dynarray, *source_dynarray, *autoload_dynarray;
88 pa_subscription *subscription;
89
90 uint16_t port;
91 sw_discovery_oid server_oid;
92 };
93
94 static sw_result publish_reply(sw_discovery discovery, sw_discovery_publish_status status, sw_discovery_oid oid, sw_opaque extra) {
95 return SW_OKAY;
96 }
97
98 static void get_service_data(struct userdata *u, struct service *s, pa_sample_spec *ret_ss, char **ret_description) {
99 assert(u && s && s->loaded.valid && ret_ss && ret_description);
100
101 if (s->loaded.type == PA_NAMEREG_SINK) {
102 pa_sink *sink = pa_idxset_get_by_index(u->core->sinks, s->loaded.index);
103 assert(sink);
104 *ret_ss = sink->sample_spec;
105 *ret_description = sink->description;
106 } else if (s->loaded.type == PA_NAMEREG_SOURCE) {
107 pa_source *source = pa_idxset_get_by_index(u->core->sources, s->loaded.index);
108 assert(source);
109 *ret_ss = source->sample_spec;
110 *ret_description = source->description;
111 } else
112 assert(0);
113 }
114
115 static void txt_record_server_data(pa_core *c, sw_text_record t) {
116 char s[256];
117 assert(c);
118
119 sw_text_record_add_key_and_string_value(t, "server-version", PACKAGE_NAME" "PACKAGE_VERSION);
120 sw_text_record_add_key_and_string_value(t, "user-name", pa_get_user_name(s, sizeof(s)));
121 sw_text_record_add_key_and_string_value(t, "fqdn", pa_get_fqdn(s, sizeof(s)));
122 snprintf(s, sizeof(s), "0x%08x", c->cookie);
123 sw_text_record_add_key_and_string_value(t, "cookie", s);
124 }
125
126 static int publish_service(struct userdata *u, struct service *s) {
127 char t[256];
128 char hn[256];
129 int r = -1;
130 sw_text_record txt;
131 int free_txt = 0;
132 assert(u && s);
133
134 if ((s->published == 1 && s->loaded.valid) ||
135 (s->published == 2 && s->autoload.valid && !s->loaded.valid))
136 return 0;
137
138 if (s->published) {
139 sw_discovery_cancel(pa_howl_wrapper_get_discovery(u->howl_wrapper), s->oid);
140 s->published = 0;
141 }
142
143 snprintf(t, sizeof(t), "%s on %s", s->name, pa_get_host_name(hn, sizeof(hn)));
144
145 if (sw_text_record_init(&txt) != SW_OKAY) {
146 pa_log(__FILE__": sw_text_record_init() failed");
147 goto finish;
148 }
149 free_txt = 1;
150
151 sw_text_record_add_key_and_string_value(txt, "device", s->name);
152
153 txt_record_server_data(u->core, txt);
154
155 if (s->loaded.valid) {
156 char z[64], *description;
157 pa_sample_spec ss;
158
159 get_service_data(u, s, &ss, &description);
160
161 snprintf(z, sizeof(z), "%u", ss.rate);
162 sw_text_record_add_key_and_string_value(txt, "rate", z);
163 snprintf(z, sizeof(z), "%u", ss.channels);
164 sw_text_record_add_key_and_string_value(txt, "channels", z);
165 sw_text_record_add_key_and_string_value(txt, "format", pa_sample_format_to_string(ss.format));
166
167 sw_text_record_add_key_and_string_value(txt, "description", description);
168
169 if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u->howl_wrapper), 0, t,
170 s->loaded.type == PA_NAMEREG_SINK ? SERVICE_NAME_SINK : SERVICE_NAME_SOURCE,
171 NULL, NULL, u->port, sw_text_record_bytes(txt), sw_text_record_len(txt),
172 publish_reply, s, &s->oid) != SW_OKAY) {
173 pa_log(__FILE__": failed to register sink on zeroconf.");
174 goto finish;
175 }
176
177 s->published = 1;
178 } else if (s->autoload.valid) {
179
180 if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u->howl_wrapper), 0, t,
181 s->autoload.type == PA_NAMEREG_SINK ? SERVICE_NAME_SINK : SERVICE_NAME_SOURCE,
182 NULL, NULL, u->port, sw_text_record_bytes(txt), sw_text_record_len(txt),
183 publish_reply, s, &s->oid) != SW_OKAY) {
184 pa_log(__FILE__": failed to register sink on zeroconf.");
185 goto finish;
186 }
187
188 s->published = 2;
189 }
190
191 r = 0;
192
193 finish:
194
195 if (!s->published) {
196 /* Remove this service */
197 pa_hashmap_remove(u->services, s->name);
198 pa_xfree(s->name);
199 pa_xfree(s);
200 }
201
202 if (free_txt)
203 sw_text_record_fina(txt);
204
205 return r;
206 }
207
208 static struct service *get_service(struct userdata *u, const char *name) {
209 struct service *s;
210
211 if ((s = pa_hashmap_get(u->services, name)))
212 return s;
213
214 s = pa_xmalloc(sizeof(struct service));
215 s->published = 0;
216 s->name = pa_xstrdup(name);
217 s->loaded.valid = s->autoload.valid = 0;
218
219 pa_hashmap_put(u->services, s->name, s);
220
221 return s;
222 }
223
224 static int publish_sink(struct userdata *u, pa_sink *s) {
225 struct service *svc;
226 assert(u && s);
227
228 svc = get_service(u, s->name);
229 if (svc->loaded.valid)
230 return 0;
231
232 svc->loaded.valid = 1;
233 svc->loaded.type = PA_NAMEREG_SINK;
234 svc->loaded.index = s->index;
235
236 pa_dynarray_put(u->sink_dynarray, s->index, svc);
237
238 return publish_service(u, svc);
239 }
240
241 static int publish_source(struct userdata *u, pa_source *s) {
242 struct service *svc;
243 assert(u && s);
244
245 svc = get_service(u, s->name);
246 if (svc->loaded.valid)
247 return 0;
248
249 svc->loaded.valid = 1;
250 svc->loaded.type = PA_NAMEREG_SOURCE;
251 svc->loaded.index = s->index;
252
253 pa_dynarray_put(u->source_dynarray, s->index, svc);
254
255 return publish_service(u, svc);
256 }
257
258 static int publish_autoload(struct userdata *u, pa_autoload_entry *s) {
259 struct service *svc;
260 assert(u && s);
261
262 svc = get_service(u, s->name);
263 if (svc->autoload.valid)
264 return 0;
265
266 svc->autoload.valid = 1;
267 svc->autoload.type = s->type;
268 svc->autoload.index = s->index;
269
270 pa_dynarray_put(u->autoload_dynarray, s->index, svc);
271
272 return publish_service(u, svc);
273 }
274
275 static int remove_sink(struct userdata *u, uint32_t idx) {
276 struct service *svc;
277 assert(u && idx != PA_INVALID_INDEX);
278
279 if (!(svc = pa_dynarray_get(u->sink_dynarray, idx)))
280 return 0;
281
282 if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SINK)
283 return 0;
284
285 svc->loaded.valid = 0;
286 pa_dynarray_put(u->sink_dynarray, idx, NULL);
287
288 return publish_service(u, svc);
289 }
290
291 static int remove_source(struct userdata *u, uint32_t idx) {
292 struct service *svc;
293 assert(u && idx != PA_INVALID_INDEX);
294
295 if (!(svc = pa_dynarray_get(u->source_dynarray, idx)))
296 return 0;
297
298 if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SOURCE)
299 return 0;
300
301 svc->loaded.valid = 0;
302 pa_dynarray_put(u->source_dynarray, idx, NULL);
303
304 return publish_service(u, svc);
305 }
306
307 static int remove_autoload(struct userdata *u, uint32_t idx) {
308 struct service *svc;
309 assert(u && idx != PA_INVALID_INDEX);
310
311 if (!(svc = pa_dynarray_get(u->autoload_dynarray, idx)))
312 return 0;
313
314 if (!svc->autoload.valid)
315 return 0;
316
317 svc->autoload.valid = 0;
318 pa_dynarray_put(u->autoload_dynarray, idx, NULL);
319
320 return publish_service(u, svc);
321 }
322
323 static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
324 struct userdata *u = userdata;
325 assert(u && c);
326
327 switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)
328 case PA_SUBSCRIPTION_EVENT_SINK: {
329 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
330 pa_sink *sink;
331
332 if ((sink = pa_idxset_get_by_index(c->sinks, idx))) {
333 if (publish_sink(u, sink) < 0)
334 goto fail;
335 }
336 } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
337 if (remove_sink(u, idx) < 0)
338 goto fail;
339 }
340
341 break;
342
343 case PA_SUBSCRIPTION_EVENT_SOURCE:
344
345 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
346 pa_source *source;
347
348 if ((source = pa_idxset_get_by_index(c->sources, idx))) {
349 if (publish_source(u, source) < 0)
350 goto fail;
351 }
352 } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
353 if (remove_source(u, idx) < 0)
354 goto fail;
355 }
356
357 break;
358
359 case PA_SUBSCRIPTION_EVENT_AUTOLOAD:
360 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
361 pa_autoload_entry *autoload;
362
363 if ((autoload = pa_idxset_get_by_index(c->autoload_idxset, idx))) {
364 if (publish_autoload(u, autoload) < 0)
365 goto fail;
366 }
367 } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
368 if (remove_autoload(u, idx) < 0)
369 goto fail;
370 }
371
372 break;
373 }
374
375 return;
376
377 fail:
378 if (u->subscription) {
379 pa_subscription_free(u->subscription);
380 u->subscription = NULL;
381 }
382 }
383
384 int pa__init(pa_core *c, pa_module*m) {
385 struct userdata *u;
386 uint32_t idx, port = PA_NATIVE_DEFAULT_PORT;
387 pa_sink *sink;
388 pa_source *source;
389 pa_autoload_entry *autoload;
390 pa_modargs *ma = NULL;
391 char t[256], hn[256];
392 int free_txt = 0;
393 sw_text_record txt;
394
395 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
396 pa_log(__FILE__": failed to parse module arguments.");
397 goto fail;
398 }
399
400 if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port == 0 || port >= 0xFFFF) {
401 pa_log(__FILE__": invalid port specified.");
402 goto fail;
403 }
404
405 m->userdata = u = pa_xmalloc(sizeof(struct userdata));
406 u->core = c;
407 u->port = (uint16_t) port;
408
409 if (!(u->howl_wrapper = pa_howl_wrapper_get(c)))
410 goto fail;
411
412 u->services = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
413 u->sink_dynarray = pa_dynarray_new();
414 u->source_dynarray = pa_dynarray_new();
415 u->autoload_dynarray = pa_dynarray_new();
416
417 u->subscription = pa_subscription_new(c,
418 PA_SUBSCRIPTION_MASK_SINK|
419 PA_SUBSCRIPTION_MASK_SOURCE|
420 PA_SUBSCRIPTION_MASK_AUTOLOAD, subscribe_callback, u);
421
422 for (sink = pa_idxset_first(c->sinks, &idx); sink; sink = pa_idxset_next(c->sinks, &idx))
423 if (publish_sink(u, sink) < 0)
424 goto fail;
425
426 for (source = pa_idxset_first(c->sources, &idx); source; source = pa_idxset_next(c->sources, &idx))
427 if (publish_source(u, source) < 0)
428 goto fail;
429
430 if (c->autoload_idxset)
431 for (autoload = pa_idxset_first(c->autoload_idxset, &idx); autoload; autoload = pa_idxset_next(c->autoload_idxset, &idx))
432 if (publish_autoload(u, autoload) < 0)
433 goto fail;
434
435 snprintf(t, sizeof(t), "%s", pa_get_host_name(hn, sizeof(hn)));
436
437 if (sw_text_record_init(&txt) != SW_OKAY) {
438 pa_log(__FILE__": sw_text_record_init() failed");
439 goto fail;
440 }
441 free_txt = 1;
442
443 txt_record_server_data(u->core, txt);
444
445 if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u->howl_wrapper), 0, t,
446 SERVICE_NAME_SERVER,
447 NULL, NULL, u->port, sw_text_record_bytes(txt), sw_text_record_len(txt),
448 publish_reply, u, &u->server_oid) != SW_OKAY) {
449 pa_log(__FILE__": failed to register server on zeroconf.");
450 goto fail;
451 }
452
453 sw_text_record_fina(txt);
454 pa_modargs_free(ma);
455
456 return 0;
457
458 fail:
459 pa__done(c, m);
460
461 if (ma)
462 pa_modargs_free(ma);
463
464 if (free_txt)
465 sw_text_record_fina(txt);
466
467 return -1;
468 }
469
470 static void service_free(void *p, void *userdata) {
471 struct service *s = p;
472 struct userdata *u = userdata;
473 assert(s && u);
474 sw_discovery_cancel(pa_howl_wrapper_get_discovery(u->howl_wrapper), s->oid);
475 pa_xfree(s->name);
476 pa_xfree(s);
477 }
478
479 void pa__done(pa_core *c, pa_module*m) {
480 struct userdata*u;
481 assert(c && m);
482
483 if (!(u = m->userdata))
484 return;
485
486 if (u->services)
487 pa_hashmap_free(u->services, service_free, u);
488
489 if (u->sink_dynarray)
490 pa_dynarray_free(u->sink_dynarray, NULL, NULL);
491 if (u->source_dynarray)
492 pa_dynarray_free(u->source_dynarray, NULL, NULL);
493 if (u->autoload_dynarray)
494 pa_dynarray_free(u->autoload_dynarray, NULL, NULL);
495
496 if (u->subscription)
497 pa_subscription_free(u->subscription);
498
499 if (u->howl_wrapper)
500 pa_howl_wrapper_unref(u->howl_wrapper);
501
502
503 pa_xfree(u);
504 }
505