1/*
2 * Rendezvous support with avahi
3 *
4 * Copyright (C) 2005 Sebastian Dr��ge <slomo@ubuntu.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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 <errno.h>
29#include <assert.h>
30#include <pthread.h>
31#include <net/if.h>
32
33
34#include <avahi-client/client.h>
35#include <avahi-client/publish.h>
36
37#include <avahi-client/client.h>
38#include <avahi-common/alternative.h>
39#include <avahi-common/error.h>
40#include <avahi-common/simple-watch.h>
41#include <avahi-common/timeval.h>
42#include <avahi-common/malloc.h>
43
44#include "daapd.h"
45#include "err.h"
46
47static AvahiClient *mdns_client = NULL;
48static AvahiEntryGroup *mdns_group = NULL;
49static AvahiSimplePoll *simple_poll = NULL;
50
51typedef struct tag_rend_avahi_group_entry {
52    char *name;
53    char *type;
54    int port;
55    struct tag_rend_avahi_group_entry *next;
56} REND_AVAHI_GROUP_ENTRY;
57
58static REND_AVAHI_GROUP_ENTRY rend_avahi_entries = { NULL, NULL, 0, NULL };
59
60static pthread_t rend_tid;
61static pthread_cond_t rend_avahi_cond;
62static pthread_mutex_t rend_avahi_mutex;
63
64static void _rend_avahi_signal(void);
65static void _rend_avahi_wait_on(void *what);
66static void _rend_avahi_lock(void);
67static void _rend_avahi_unlock(void);
68static int _rend_avahi_create_services(void);
69
70/* add a new group entry node */
71static int _rend_avahi_add_group_entry(char *name, char *type, int port) {
72    REND_AVAHI_GROUP_ENTRY *pge;
73
74    pge = (REND_AVAHI_GROUP_ENTRY *)malloc(sizeof(REND_AVAHI_GROUP_ENTRY));
75    if(!pge)
76        return 0;
77
78    pge->name = strdup(name);
79    pge->type = strdup(type);
80    pge->port = port;
81
82    _rend_avahi_lock();
83
84    pge->next = rend_avahi_entries.next;
85    rend_avahi_entries.next = pge;
86
87    _rend_avahi_unlock();
88    return 1;
89}
90
91static void *rend_poll(void *arg) {
92    int ret;
93    while((ret = avahi_simple_poll_iterate(simple_poll,-1)) == 0);
94
95    if(ret < 0) {
96        DPRINTF(E_WARN,L_REND,"Avahi poll thread quit iwth error: %s\n",
97                avahi_strerror(avahi_client_errno(mdns_client)));
98    } else {
99        DPRINTF(E_DBG,L_REND,"Avahi poll thread quit\n");
100    }
101
102    return NULL;
103}
104
105static void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) {
106    //    assert(g == mdns_group);
107
108    switch (state) {
109    case AVAHI_ENTRY_GROUP_ESTABLISHED:
110        DPRINTF(E_DBG, L_REND, "Successfully added mdns services\n");
111        _rend_avahi_signal();
112        break;
113    case AVAHI_ENTRY_GROUP_COLLISION:
114        DPRINTF(E_DBG, L_REND, "Group collision\n");
115        /*
116          new_name = avahi_alternative_service_name(mdns_name);
117          DPRINTF(E_WARN, L_REND, "mdns service name collision. Renamed service %s -> %s\n", mdns_name, new_name);
118          free(mdns_name);
119          mdns_name = new_name;
120          add_services(avahi_entry_group_get_client(g));
121        */
122        break;
123    case AVAHI_ENTRY_GROUP_FAILURE :
124        avahi_simple_poll_quit(simple_poll);
125        break;
126    case AVAHI_ENTRY_GROUP_UNCOMMITED:
127    case AVAHI_ENTRY_GROUP_REGISTERING:
128        break;
129    }
130}
131
132void _rend_avahi_lock(void) {
133    if(pthread_mutex_lock(&rend_avahi_mutex))
134        DPRINTF(E_FATAL,L_REND,"Could not lock mutex\n");
135}
136
137void _rend_avahi_unlock(void) {
138    pthread_mutex_unlock(&rend_avahi_mutex);
139}
140
141void _rend_avahi_signal(void) {
142    /* kick the condition for waiters */
143    _rend_avahi_lock();
144    pthread_cond_signal(&rend_avahi_cond);
145    _rend_avahi_unlock();
146}
147
148void _rend_avahi_wait_on(void *what) {
149    DPRINTF(E_DBG,L_REND,"Waiting on something...\n");
150    if(pthread_mutex_lock(&rend_avahi_mutex))
151        DPRINTF(E_FATAL,L_REND,"Could not lock mutex\n");
152    while(!what) {
153        pthread_cond_wait(&rend_avahi_cond,&rend_avahi_mutex);
154    }
155    _rend_avahi_unlock();
156    DPRINTF(E_DBG,L_REND,"Done waiting.\n");
157}
158
159int rend_register(char *name, char *type, int port) {
160    DPRINTF(E_DBG,L_REND,"Adding %s/%s\n",name,type);
161    _rend_avahi_add_group_entry(name,type,port);
162    if(mdns_group) {
163        DPRINTF(E_DBG,L_MISC,"Resetting mdns group\n");
164        avahi_entry_group_reset(mdns_group);
165    }
166    DPRINTF(E_DBG,L_REND,"Creating service group (again?)\n");
167    _rend_avahi_create_services();
168    return 0;
169}
170
171
172/**
173 * register the block of services
174 *
175 * @returns true if successful, false otherwise
176 */
177int _rend_avahi_create_services(void) {
178    int ret = 0;
179    REND_AVAHI_GROUP_ENTRY *pentry;
180    AvahiStringList *psl;
181    unsigned char count=0;
182
183    DPRINTF(E_DBG,L_REND,"Creting service group\n");
184
185    if(!rend_avahi_entries.next) {
186        DPRINTF(E_DBG,L_REND,"No entries yet... skipping service create\n");
187        return 1;
188    }
189
190    if (mdns_group == NULL) {
191        if (!(mdns_group = avahi_entry_group_new(mdns_client,
192                                                 entry_group_callback,
193                                                 NULL))) {
194            DPRINTF(E_WARN, L_REND, "Could not create AvahiEntryGroup: %s\n",
195                    avahi_strerror(avahi_client_errno(mdns_client)));
196            return 0;
197        }
198    }
199
200    /* wait for entry group to be created */
201    _rend_avahi_wait_on(mdns_group);
202
203    _rend_avahi_lock();
204    pentry = rend_avahi_entries.next;
205    while(pentry) {
206        /* TODO: honor iface parameter */
207        DPRINTF(E_DBG,L_REND,"Re-registering %s/%s\n",pentry->name,pentry->type);
208
209        /* build a string list */
210        psl = NULL;
211	psl=avahi_string_list_add(psl,"txtvers=1");
212	psl=avahi_string_list_add(psl,"Database ID=beddab1edeadbea7");
213
214        if ((ret = avahi_entry_group_add_service_strlst(mdns_group,
215                                                        AVAHI_IF_UNSPEC,
216                                                 AVAHI_PROTO_UNSPEC, 0,
217                                                 avahi_strdup(pentry->name),
218                                                 avahi_strdup(pentry->type),
219                                                 NULL, NULL,pentry->port,
220                                                 psl)) < 0) {
221            DPRINTF(E_WARN, L_REND, "Could not add mdns services: %s\n", avahi_strerror(ret));
222            avahi_string_list_free(psl);
223            _rend_avahi_unlock();
224            return 0;
225        }
226        pentry = pentry->next;
227    }
228
229    _rend_avahi_unlock();
230
231    if ((ret = avahi_entry_group_commit(mdns_group)) < 0) {
232        DPRINTF(E_WARN, L_REND, "Could not commit mdns services: %s\n",
233                avahi_strerror(avahi_client_errno(mdns_client)));
234        return 0;
235    }
236
237    return 1;
238}
239
240static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) {
241    assert(c);
242    switch(state) {
243    case AVAHI_CLIENT_S_RUNNING:
244        DPRINTF(E_LOG,L_REND,"Client running\n");
245        if(!mdns_group)
246            _rend_avahi_create_services();
247        break;
248    case AVAHI_CLIENT_S_COLLISION:
249        DPRINTF(E_LOG,L_REND,"Client collision\n");
250        if(mdns_group)
251            avahi_entry_group_reset(mdns_group);
252        break;
253    case AVAHI_CLIENT_FAILURE:
254        DPRINTF(E_LOG,L_REND,"Client failure\n");
255        avahi_simple_poll_quit(simple_poll);
256        break;
257    case AVAHI_CLIENT_S_REGISTERING:
258        DPRINTF(E_LOG,L_REND,"Client registering\n");
259        if(mdns_group)
260            avahi_entry_group_reset(mdns_group);
261        break;
262    case AVAHI_CLIENT_CONNECTING:
263        break;
264    }
265}
266
267int rend_init(char *user) {
268    int error;
269
270    if(pthread_cond_init(&rend_avahi_cond,NULL)) {
271        DPRINTF(E_LOG,L_REND,"Could not initialize rendezvous condition\n");
272        return -1;
273    }
274
275    if(pthread_mutex_init(&rend_avahi_mutex,NULL)) {
276        DPRINTF(E_LOG,L_REND,"Could not initialize rendezvous mutex\n");
277        return -1;
278    }
279
280    DPRINTF(E_DBG, L_REND, "Initializing avahi\n");
281    if(!(simple_poll = avahi_simple_poll_new())) {
282        DPRINTF(E_LOG,L_REND,"Error starting poll thread\n");
283        return -1;
284    }
285
286    /*
287        mdns_name = strdup(name);
288        mdns_port = port;
289        //      if ((interface != NULL) && (if_nametoindex(interface) != 0))
290        //              mdns_interface = if_nametoindex(interface);
291        //      else
292        mdns_interface = AVAHI_IF_UNSPEC;
293    */
294
295    if (!(mdns_client = avahi_client_new(avahi_simple_poll_get(simple_poll),
296                                         0,client_callback,NULL,&error))) {
297        DPRINTF(E_WARN, L_REND, "avahi_client_new: Error in avahi: %s\n",
298                avahi_strerror(avahi_client_errno(mdns_client)));
299        avahi_simple_poll_free(simple_poll);
300        return -1;
301    }
302
303    DPRINTF(E_DBG, L_REND, "Starting avahi polling thread\n");
304    if(pthread_create(&rend_tid, NULL, rend_poll, NULL)) {
305        DPRINTF(E_FATAL,L_REND,"Could not start avahi polling thread.\n");
306    }
307
308    return 0;
309}
310
311int rend_stop() {
312    avahi_simple_poll_quit(simple_poll);
313    if (mdns_client != NULL)
314        avahi_client_free(mdns_client);
315    return 0;
316}
317
318int rend_running(void) {
319    return 1;
320}
321
322int rend_unregister(char *name, char *type, int port) {
323    return 0;
324}
325
326