1/*
2 * ga-entry-group.c - Source for GaEntryGroup
3 * Copyright (C) 2006-2007 Collabora Ltd.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19
20#ifdef HAVE_CONFIG_H
21#include <config.h>
22#endif
23
24#include <stdarg.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include "avahi-common/avahi-malloc.h"
29
30#include "ga-error.h"
31#include "ga-entry-group.h"
32#include "ga-entry-group-enumtypes.h"
33
34G_DEFINE_TYPE(GaEntryGroup, ga_entry_group, G_TYPE_OBJECT)
35
36static void _free_service(gpointer data);
37
38/* signal enum */
39enum {
40    STATE_CHANGED,
41    LAST_SIGNAL
42};
43
44static guint signals[LAST_SIGNAL] = { 0 };
45
46/* properties */
47enum {
48    PROP_STATE = 1
49};
50
51/* private structures */
52typedef struct _GaEntryGroupPrivate GaEntryGroupPrivate;
53
54struct _GaEntryGroupPrivate {
55    GaEntryGroupState state;
56    GaClient *client;
57    AvahiEntryGroup *group;
58    GHashTable *services;
59    gboolean dispose_has_run;
60};
61
62typedef struct _GaEntryGroupServicePrivate GaEntryGroupServicePrivate;
63
64struct _GaEntryGroupServicePrivate {
65    GaEntryGroupService public;
66    GaEntryGroup *group;
67    gboolean frozen;
68    GHashTable *entries;
69};
70
71typedef struct _GaEntryGroupServiceEntry GaEntryGroupServiceEntry;
72
73struct _GaEntryGroupServiceEntry {
74    guint8 *value;
75    gsize size;
76};
77
78
79#define GA_ENTRY_GROUP_GET_PRIVATE(o)     (G_TYPE_INSTANCE_GET_PRIVATE ((o), GA_TYPE_ENTRY_GROUP, GaEntryGroupPrivate))
80
81static void ga_entry_group_init(GaEntryGroup * obj) {
82    GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(obj);
83    /* allocate any data required by the object here */
84    priv->state = GA_ENTRY_GROUP_STATE_UNCOMMITED;
85    priv->client = NULL;
86    priv->group = NULL;
87    priv->services = g_hash_table_new_full(g_direct_hash,
88                                           g_direct_equal,
89                                           NULL, _free_service);
90}
91
92static void ga_entry_group_dispose(GObject * object);
93static void ga_entry_group_finalize(GObject * object);
94
95static void ga_entry_group_get_property(GObject * object,
96                            guint property_id,
97                            GValue * value, GParamSpec * pspec) {
98    GaEntryGroup *group = GA_ENTRY_GROUP(object);
99    GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group);
100
101    switch (property_id) {
102        case PROP_STATE:
103            g_value_set_enum(value, priv->state);
104            break;
105        default:
106            G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
107            break;
108    }
109}
110
111static void ga_entry_group_class_init(GaEntryGroupClass * ga_entry_group_class) {
112    GObjectClass *object_class = G_OBJECT_CLASS(ga_entry_group_class);
113    GParamSpec *param_spec;
114
115    g_type_class_add_private(ga_entry_group_class,
116                             sizeof (GaEntryGroupPrivate));
117
118    object_class->dispose = ga_entry_group_dispose;
119    object_class->finalize = ga_entry_group_finalize;
120    object_class->get_property = ga_entry_group_get_property;
121
122    param_spec = g_param_spec_enum("state", "Entry Group state",
123                                   "The state of the avahi entry group",
124                                   GA_TYPE_ENTRY_GROUP_STATE,
125                                   GA_ENTRY_GROUP_STATE_UNCOMMITED,
126                                   G_PARAM_READABLE |
127                                   G_PARAM_STATIC_NAME |
128                                   G_PARAM_STATIC_BLURB);
129    g_object_class_install_property(object_class, PROP_STATE, param_spec);
130
131    signals[STATE_CHANGED] =
132            g_signal_new("state-changed",
133                         G_OBJECT_CLASS_TYPE(ga_entry_group_class),
134                         G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
135                         0,
136                         NULL, NULL,
137                         g_cclosure_marshal_VOID__ENUM,
138                         G_TYPE_NONE, 1, GA_TYPE_ENTRY_GROUP_STATE);
139}
140
141void ga_entry_group_dispose(GObject * object) {
142    GaEntryGroup *self = GA_ENTRY_GROUP(object);
143    GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(self);
144
145    if (priv->dispose_has_run)
146        return;
147    priv->dispose_has_run = TRUE;
148
149    /* release any references held by the object here */
150    if (priv->group) {
151        avahi_entry_group_free(priv->group);
152        priv->group = NULL;
153    }
154
155    if (priv->client) {
156        g_object_unref(priv->client);
157        priv->client = NULL;
158    }
159
160    if (G_OBJECT_CLASS(ga_entry_group_parent_class)->dispose)
161        G_OBJECT_CLASS(ga_entry_group_parent_class)->dispose(object);
162}
163
164void ga_entry_group_finalize(GObject * object) {
165    GaEntryGroup *self = GA_ENTRY_GROUP(object);
166    GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(self);
167
168    /* free any data held directly by the object here */
169    g_hash_table_destroy(priv->services);
170    priv->services = NULL;
171
172    G_OBJECT_CLASS(ga_entry_group_parent_class)->finalize(object);
173}
174
175static void _free_service(gpointer data) {
176    GaEntryGroupService *s = (GaEntryGroupService *) data;
177    GaEntryGroupServicePrivate *p = (GaEntryGroupServicePrivate *) s;
178    g_free(s->name);
179    g_free(s->type);
180    g_free(s->domain);
181    g_free(s->host);
182    g_hash_table_destroy(p->entries);
183    g_free(s);
184}
185
186static GQuark detail_for_state(AvahiEntryGroupState state) {
187    static struct {
188        AvahiEntryGroupState state;
189        const gchar *name;
190        GQuark quark;
191    } states[] = {
192        { AVAHI_ENTRY_GROUP_UNCOMMITED, "uncommited", 0},
193        { AVAHI_ENTRY_GROUP_REGISTERING, "registering", 0},
194        { AVAHI_ENTRY_GROUP_ESTABLISHED, "established", 0},
195        { AVAHI_ENTRY_GROUP_COLLISION, "collistion", 0},
196        { AVAHI_ENTRY_GROUP_FAILURE, "failure", 0},
197        { 0, NULL, 0}
198    };
199    int i;
200
201    for (i = 0; states[i].name != NULL; i++) {
202        if (state != states[i].state)
203            continue;
204
205        if (!states[i].quark)
206            states[i].quark = g_quark_from_static_string(states[i].name);
207        return states[i].quark;
208    }
209    g_assert_not_reached();
210}
211
212static void _avahi_entry_group_cb(AvahiEntryGroup * g,
213                      AvahiEntryGroupState state, void *data) {
214    GaEntryGroup *self = GA_ENTRY_GROUP(data);
215    GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(self);
216
217    /* Avahi can call the callback before return from _client_new */
218    if (priv->group == NULL)
219        priv->group = g;
220
221    g_assert(g == priv->group);
222    priv->state = state;
223    g_signal_emit(self, signals[STATE_CHANGED],
224                  detail_for_state(state), state);
225}
226
227GaEntryGroup *ga_entry_group_new(void) {
228    return g_object_new(GA_TYPE_ENTRY_GROUP, NULL);
229}
230
231static guint _entry_hash(gconstpointer v) {
232    const GaEntryGroupServiceEntry *entry =
233            (const GaEntryGroupServiceEntry *) v;
234    guint32 h = 0;
235    guint i;
236
237    for (i = 0; i < entry->size; i++) {
238        h = (h << 5) - h + entry->value[i];
239    }
240
241    return h;
242}
243
244static gboolean _entry_equal(gconstpointer a, gconstpointer b) {
245    const GaEntryGroupServiceEntry *aentry =
246            (const GaEntryGroupServiceEntry *) a;
247    const GaEntryGroupServiceEntry *bentry =
248            (const GaEntryGroupServiceEntry *) b;
249
250    if (aentry->size != bentry->size) {
251        return FALSE;
252    }
253
254    return memcmp(aentry->value, bentry->value, aentry->size) == 0;
255}
256
257static GaEntryGroupServiceEntry *_new_entry(const guint8 * value, gsize size) {
258    GaEntryGroupServiceEntry *entry;
259
260    if (value == NULL) {
261        return NULL;
262    }
263
264    entry = g_slice_new(GaEntryGroupServiceEntry);
265    entry->value = g_malloc(size + 1);
266    memcpy(entry->value, value, size);
267    /* for string keys, make sure it's NUL-terminated too */
268    entry->value[size] = 0;
269    entry->size = size;
270
271    return entry;
272}
273
274static void _set_entry(GHashTable * table, const guint8 * key, gsize ksize,
275           const guint8 * value, gsize vsize) {
276
277    g_hash_table_insert(table, _new_entry(key, ksize),
278                        _new_entry(value, vsize));
279}
280
281static void _free_entry(gpointer data) {
282    GaEntryGroupServiceEntry *entry = (GaEntryGroupServiceEntry *) data;
283
284    if (entry == NULL) {
285        return;
286    }
287
288    g_free(entry->value);
289    g_slice_free(GaEntryGroupServiceEntry, entry);
290}
291
292static GHashTable *_string_list_to_hash(AvahiStringList * list) {
293    GHashTable *ret;
294    AvahiStringList *t;
295
296    ret = g_hash_table_new_full(_entry_hash,
297                                _entry_equal, _free_entry, _free_entry);
298
299    for (t = list; t != NULL; t = avahi_string_list_get_next(t)) {
300        gchar *key;
301        gchar *value;
302        gsize size;
303        int r;
304
305        /* list_get_pair only fails if if memory allocation fails. Normal glib
306         * behaviour is to assert/abort when that happens */
307        r = avahi_string_list_get_pair(t, &key, &value, &size);
308        g_assert(r == 0);
309
310        if (value == NULL) {
311            _set_entry(ret, t->text, t->size, NULL, 0);
312        } else {
313            _set_entry(ret, (const guint8 *) key, strlen(key),
314                       (const guint8 *) value, size);
315        }
316        avahi_free(key);
317        avahi_free(value);
318    }
319    return ret;
320}
321
322static void _hash_to_string_list_foreach(gpointer key, gpointer value, gpointer data) {
323    AvahiStringList **list = (AvahiStringList **) data;
324    GaEntryGroupServiceEntry *kentry = (GaEntryGroupServiceEntry *) key;
325    GaEntryGroupServiceEntry *ventry = (GaEntryGroupServiceEntry *) value;
326
327    if (value != NULL) {
328        *list = avahi_string_list_add_pair_arbitrary(*list,
329                                                     (gchar *) kentry->value,
330                                                     ventry->value,
331                                                     ventry->size);
332    } else {
333        *list = avahi_string_list_add_arbitrary(*list,
334                                                kentry->value, kentry->size);
335    }
336}
337
338static AvahiStringList *_hash_to_string_list(GHashTable * table) {
339    AvahiStringList *list = NULL;
340    g_hash_table_foreach(table, _hash_to_string_list_foreach,
341                         (gpointer) & list);
342    return list;
343}
344
345GaEntryGroupService *ga_entry_group_add_service_strlist(GaEntryGroup * group,
346                                                        const gchar * name,
347                                                        const gchar * type,
348                                                        guint16 port,
349                                                        GError ** error,
350                                                        AvahiStringList *
351                                                        txt) {
352    return ga_entry_group_add_service_full_strlist(group, AVAHI_IF_UNSPEC,
353                                                   AVAHI_PROTO_UNSPEC, 0,
354                                                   name, type, NULL, NULL,
355                                                   port, error, txt);
356}
357
358GaEntryGroupService *ga_entry_group_add_service_full_strlist(GaEntryGroup *
359                                                             group,
360                                                             AvahiIfIndex
361                                                             interface,
362                                                             AvahiProtocol
363                                                             protocol,
364                                                             AvahiPublishFlags
365                                                             flags,
366                                                             const gchar *
367                                                             name,
368                                                             const gchar *
369                                                             type,
370                                                             const gchar *
371                                                             domain,
372                                                             const gchar *
373                                                             host,
374                                                             guint16 port,
375                                                             GError ** error,
376                                                             AvahiStringList *
377                                                             txt) {
378    GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group);
379    GaEntryGroupServicePrivate *service = NULL;
380    int ret;
381
382    ret = avahi_entry_group_add_service_strlst(priv->group,
383                                               interface, protocol,
384                                               flags,
385                                               name, type,
386                                               domain, host, port, txt);
387    if (ret) {
388        if (error != NULL) {
389            *error = g_error_new(GA_ERROR, ret,
390                                 "Adding service to group failed: %s",
391                                 avahi_strerror(ret));
392        }
393        goto out;
394    }
395
396    service = g_new0(GaEntryGroupServicePrivate, 1);
397    service->public.interface = interface;
398    service->public.protocol = protocol;
399    service->public.flags = flags;
400    service->public.name = g_strdup(name);
401    service->public.type = g_strdup(type);
402    service->public.domain = g_strdup(domain);
403    service->public.host = g_strdup(host);
404    service->public.port = port;
405    service->group = group;
406    service->frozen = FALSE;
407    service->entries = _string_list_to_hash(txt);
408    g_hash_table_insert(priv->services, group, service);
409  out:
410    return (GaEntryGroupService *) service;
411}
412
413GaEntryGroupService *ga_entry_group_add_service(GaEntryGroup * group,
414                                                const gchar * name,
415                                                const gchar * type,
416                                                guint16 port,
417                                                GError ** error, ...) {
418    GaEntryGroupService *ret;
419    AvahiStringList *txt = NULL;
420    va_list va;
421    va_start(va, error);
422    txt = avahi_string_list_new_va(va);
423
424    ret = ga_entry_group_add_service_full_strlist(group,
425                                                  AVAHI_IF_UNSPEC,
426                                                  AVAHI_PROTO_UNSPEC,
427                                                  0,
428                                                  name, type,
429                                                  NULL, NULL,
430                                                  port, error, txt);
431    avahi_string_list_free(txt);
432    va_end(va);
433    return ret;
434}
435
436GaEntryGroupService *ga_entry_group_add_service_full(GaEntryGroup * group,
437                                                     AvahiIfIndex interface,
438                                                     AvahiProtocol protocol,
439                                                     AvahiPublishFlags flags,
440                                                     const gchar * name,
441                                                     const gchar * type,
442                                                     const gchar * domain,
443                                                     const gchar * host,
444                                                     guint16 port,
445                                                     GError ** error, ...) {
446    GaEntryGroupService *ret;
447    AvahiStringList *txt = NULL;
448    va_list va;
449
450    va_start(va, error);
451    txt = avahi_string_list_new_va(va);
452
453    ret = ga_entry_group_add_service_full_strlist(group,
454                                                  interface, protocol,
455                                                  flags,
456                                                  name, type,
457                                                  domain, host,
458                                                  port, error, txt);
459    avahi_string_list_free(txt);
460    va_end(va);
461    return ret;
462}
463
464gboolean ga_entry_group_add_record(GaEntryGroup * group,
465                          AvahiPublishFlags flags,
466                          const gchar * name,
467                          guint16 type,
468                          guint32 ttl,
469                          const void *rdata, gsize size, GError ** error) {
470    return ga_entry_group_add_record_full(group,
471                                          AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
472                                          flags, name, AVAHI_DNS_CLASS_IN,
473                                          type, ttl, rdata, size, error);
474}
475
476gboolean ga_entry_group_add_record_full(GaEntryGroup * group,
477                               AvahiIfIndex interface,
478                               AvahiProtocol protocol,
479                               AvahiPublishFlags flags,
480                               const gchar * name,
481                               guint16 clazz,
482                               guint16 type,
483                               guint32 ttl,
484                               const void *rdata,
485                               gsize size, GError ** error) {
486    int ret;
487    GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group);
488    g_assert(group != NULL && priv->group != NULL);
489
490    ret = avahi_entry_group_add_record(priv->group, interface, protocol,
491                                       flags, name, clazz, type, ttl, rdata,
492                                       size);
493    if (ret) {
494        if (error != NULL) {
495            *error = g_error_new(GA_ERROR, ret,
496                                 "Setting raw record failed: %s",
497                                 avahi_strerror(ret));
498        }
499        return FALSE;
500    }
501    return TRUE;
502}
503
504
505void ga_entry_group_service_freeze(GaEntryGroupService * service) {
506    GaEntryGroupServicePrivate *p = (GaEntryGroupServicePrivate *) service;
507    p->frozen = TRUE;
508}
509
510gboolean ga_entry_group_service_thaw(GaEntryGroupService * service, GError ** error) {
511    GaEntryGroupServicePrivate *priv = (GaEntryGroupServicePrivate *) service;
512    int ret;
513    gboolean result = TRUE;
514
515    AvahiStringList *txt = _hash_to_string_list(priv->entries);
516    ret = avahi_entry_group_update_service_txt_strlst
517            (GA_ENTRY_GROUP_GET_PRIVATE(priv->group)->group,
518             service->interface, service->protocol, service->flags,
519             service->name, service->type, service->domain, txt);
520    if (ret) {
521        if (error != NULL) {
522            *error = g_error_new(GA_ERROR, ret,
523                                 "Updating txt record failed: %s",
524                                 avahi_strerror(ret));
525        }
526        result = FALSE;
527    }
528
529    avahi_string_list_free(txt);
530    priv->frozen = FALSE;
531    return result;
532}
533
534gboolean ga_entry_group_service_set(GaEntryGroupService * service,
535                           const gchar * key, const gchar * value,
536                           GError ** error) {
537    return ga_entry_group_service_set_arbitrary(service, key,
538                                                (const guint8 *) value,
539                                                strlen(value), error);
540
541}
542
543gboolean ga_entry_group_service_set_arbitrary(GaEntryGroupService * service,
544                                     const gchar * key, const guint8 * value,
545                                     gsize size, GError ** error) {
546    GaEntryGroupServicePrivate *priv = (GaEntryGroupServicePrivate *) service;
547
548    _set_entry(priv->entries, (const guint8 *) key, strlen(key), value, size);
549
550    if (!priv->frozen)
551        return ga_entry_group_service_thaw(service, error);
552    else
553        return TRUE;
554}
555
556gboolean ga_entry_group_service_remove_key(GaEntryGroupService * service,
557                                  const gchar * key, GError ** error) {
558    GaEntryGroupServicePrivate *priv = (GaEntryGroupServicePrivate *) service;
559    GaEntryGroupServiceEntry entry;
560
561    entry.value = (void*) key;
562    entry.size = strlen(key);
563
564    g_hash_table_remove(priv->entries, &entry);
565
566    if (!priv->frozen)
567        return ga_entry_group_service_thaw(service, error);
568    else
569        return TRUE;
570}
571
572
573gboolean ga_entry_group_attach(GaEntryGroup * group,
574                      GaClient * client, GError ** error) {
575    GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group);
576
577    g_return_val_if_fail(client->avahi_client, FALSE);
578    g_assert(priv->client == NULL || priv->client == client);
579    g_assert(priv->group == NULL);
580
581    priv->client = client;
582    g_object_ref(client);
583
584    priv->group = avahi_entry_group_new(client->avahi_client,
585                                        _avahi_entry_group_cb, group);
586    if (priv->group == NULL) {
587        if (error != NULL) {
588            int aerrno = avahi_client_errno(client->avahi_client);
589            *error = g_error_new(GA_ERROR, aerrno,
590                                 "Attaching group failed: %s",
591                                 avahi_strerror(aerrno));
592        }
593        return FALSE;
594    }
595    return TRUE;
596}
597
598gboolean ga_entry_group_commit(GaEntryGroup * group, GError ** error) {
599    GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group);
600    int ret;
601    ret = avahi_entry_group_commit(priv->group);
602    if (ret) {
603        if (error != NULL) {
604            *error = g_error_new(GA_ERROR, ret,
605                                 "Committing group failed: %s",
606                                 avahi_strerror(ret));
607        }
608        return FALSE;
609    }
610    return TRUE;
611}
612
613gboolean ga_entry_group_reset(GaEntryGroup * group, GError ** error) {
614    GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group);
615    int ret;
616    ret = avahi_entry_group_reset(priv->group);
617    if (ret) {
618        if (error != NULL) {
619            *error = g_error_new(GA_ERROR, ret,
620                                 "Resetting group failed: %s",
621                                 avahi_strerror(ret));
622        }
623        return FALSE;
624    }
625    return TRUE;
626}
627