Home | History | Annotate | Download | only in avahi-gobject
      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 
     34 G_DEFINE_TYPE(GaEntryGroup, ga_entry_group, G_TYPE_OBJECT)
     35 
     36 static void _free_service(gpointer data);
     37 
     38 /* signal enum */
     39 enum {
     40     STATE_CHANGED,
     41     LAST_SIGNAL
     42 };
     43 
     44 static guint signals[LAST_SIGNAL] = { 0 };
     45 
     46 /* properties */
     47 enum {
     48     PROP_STATE = 1
     49 };
     50 
     51 /* private structures */
     52 typedef struct _GaEntryGroupPrivate GaEntryGroupPrivate;
     53 
     54 struct _GaEntryGroupPrivate {
     55     GaEntryGroupState state;
     56     GaClient *client;
     57     AvahiEntryGroup *group;
     58     GHashTable *services;
     59     gboolean dispose_has_run;
     60 };
     61 
     62 typedef struct _GaEntryGroupServicePrivate GaEntryGroupServicePrivate;
     63 
     64 struct _GaEntryGroupServicePrivate {
     65     GaEntryGroupService public;
     66     GaEntryGroup *group;
     67     gboolean frozen;
     68     GHashTable *entries;
     69 };
     70 
     71 typedef struct _GaEntryGroupServiceEntry GaEntryGroupServiceEntry;
     72 
     73 struct _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 
     81 static 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 
     92 static void ga_entry_group_dispose(GObject * object);
     93 static void ga_entry_group_finalize(GObject * object);
     94 
     95 static 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 
    111 static 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 
    141 void 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 
    164 void 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 
    175 static 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 
    186 static 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 
    212 static 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 
    227 GaEntryGroup *ga_entry_group_new(void) {
    228     return g_object_new(GA_TYPE_ENTRY_GROUP, NULL);
    229 }
    230 
    231 static 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 
    244 static 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 
    257 static 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 
    274 static 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 
    281 static 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 
    292 static 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 
    322 static 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 
    338 static 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 
    345 GaEntryGroupService *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 
    358 GaEntryGroupService *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 
    413 GaEntryGroupService *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 
    436 GaEntryGroupService *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 
    464 gboolean 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 
    476 gboolean 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 
    505 void ga_entry_group_service_freeze(GaEntryGroupService * service) {
    506     GaEntryGroupServicePrivate *p = (GaEntryGroupServicePrivate *) service;
    507     p->frozen = TRUE;
    508 }
    509 
    510 gboolean 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 
    534 gboolean 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 
    543 gboolean 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 
    556 gboolean 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 
    573 gboolean 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 
    598 gboolean 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 
    613 gboolean 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