/* * ga-entry-group.c - Source for GaEntryGroup * Copyright (C) 2006-2007 Collabora Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "avahi-common/avahi-malloc.h" #include "ga-error.h" #include "ga-entry-group.h" #include "ga-entry-group-enumtypes.h" G_DEFINE_TYPE(GaEntryGroup, ga_entry_group, G_TYPE_OBJECT) static void _free_service(gpointer data); /* signal enum */ enum { STATE_CHANGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; /* properties */ enum { PROP_STATE = 1 }; /* private structures */ typedef struct _GaEntryGroupPrivate GaEntryGroupPrivate; struct _GaEntryGroupPrivate { GaEntryGroupState state; GaClient *client; AvahiEntryGroup *group; GHashTable *services; gboolean dispose_has_run; }; typedef struct _GaEntryGroupServicePrivate GaEntryGroupServicePrivate; struct _GaEntryGroupServicePrivate { GaEntryGroupService public; GaEntryGroup *group; gboolean frozen; GHashTable *entries; }; typedef struct _GaEntryGroupServiceEntry GaEntryGroupServiceEntry; struct _GaEntryGroupServiceEntry { guint8 *value; gsize size; }; #define GA_ENTRY_GROUP_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GA_TYPE_ENTRY_GROUP, GaEntryGroupPrivate)) static void ga_entry_group_init(GaEntryGroup * obj) { GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(obj); /* allocate any data required by the object here */ priv->state = GA_ENTRY_GROUP_STATE_UNCOMMITED; priv->client = NULL; priv->group = NULL; priv->services = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, _free_service); } static void ga_entry_group_dispose(GObject * object); static void ga_entry_group_finalize(GObject * object); static void ga_entry_group_get_property(GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { GaEntryGroup *group = GA_ENTRY_GROUP(object); GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group); switch (property_id) { case PROP_STATE: g_value_set_enum(value, priv->state); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void ga_entry_group_class_init(GaEntryGroupClass * ga_entry_group_class) { GObjectClass *object_class = G_OBJECT_CLASS(ga_entry_group_class); GParamSpec *param_spec; g_type_class_add_private(ga_entry_group_class, sizeof (GaEntryGroupPrivate)); object_class->dispose = ga_entry_group_dispose; object_class->finalize = ga_entry_group_finalize; object_class->get_property = ga_entry_group_get_property; param_spec = g_param_spec_enum("state", "Entry Group state", "The state of the avahi entry group", GA_TYPE_ENTRY_GROUP_STATE, GA_ENTRY_GROUP_STATE_UNCOMMITED, G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB); g_object_class_install_property(object_class, PROP_STATE, param_spec); signals[STATE_CHANGED] = g_signal_new("state-changed", G_OBJECT_CLASS_TYPE(ga_entry_group_class), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, g_cclosure_marshal_VOID__ENUM, G_TYPE_NONE, 1, GA_TYPE_ENTRY_GROUP_STATE); } void ga_entry_group_dispose(GObject * object) { GaEntryGroup *self = GA_ENTRY_GROUP(object); GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(self); if (priv->dispose_has_run) return; priv->dispose_has_run = TRUE; /* release any references held by the object here */ if (priv->group) { avahi_entry_group_free(priv->group); priv->group = NULL; } if (priv->client) { g_object_unref(priv->client); priv->client = NULL; } if (G_OBJECT_CLASS(ga_entry_group_parent_class)->dispose) G_OBJECT_CLASS(ga_entry_group_parent_class)->dispose(object); } void ga_entry_group_finalize(GObject * object) { GaEntryGroup *self = GA_ENTRY_GROUP(object); GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(self); /* free any data held directly by the object here */ g_hash_table_destroy(priv->services); priv->services = NULL; G_OBJECT_CLASS(ga_entry_group_parent_class)->finalize(object); } static void _free_service(gpointer data) { GaEntryGroupService *s = (GaEntryGroupService *) data; GaEntryGroupServicePrivate *p = (GaEntryGroupServicePrivate *) s; g_free(s->name); g_free(s->type); g_free(s->domain); g_free(s->host); g_hash_table_destroy(p->entries); g_free(s); } static GQuark detail_for_state(AvahiEntryGroupState state) { static struct { AvahiEntryGroupState state; const gchar *name; GQuark quark; } states[] = { { AVAHI_ENTRY_GROUP_UNCOMMITED, "uncommited", 0}, { AVAHI_ENTRY_GROUP_REGISTERING, "registering", 0}, { AVAHI_ENTRY_GROUP_ESTABLISHED, "established", 0}, { AVAHI_ENTRY_GROUP_COLLISION, "collistion", 0}, { AVAHI_ENTRY_GROUP_FAILURE, "failure", 0}, { 0, NULL, 0} }; int i; for (i = 0; states[i].name != NULL; i++) { if (state != states[i].state) continue; if (!states[i].quark) states[i].quark = g_quark_from_static_string(states[i].name); return states[i].quark; } g_assert_not_reached(); } static void _avahi_entry_group_cb(AvahiEntryGroup * g, AvahiEntryGroupState state, void *data) { GaEntryGroup *self = GA_ENTRY_GROUP(data); GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(self); /* Avahi can call the callback before return from _client_new */ if (priv->group == NULL) priv->group = g; g_assert(g == priv->group); priv->state = state; g_signal_emit(self, signals[STATE_CHANGED], detail_for_state(state), state); } GaEntryGroup *ga_entry_group_new(void) { return g_object_new(GA_TYPE_ENTRY_GROUP, NULL); } static guint _entry_hash(gconstpointer v) { const GaEntryGroupServiceEntry *entry = (const GaEntryGroupServiceEntry *) v; guint32 h = 0; guint i; for (i = 0; i < entry->size; i++) { h = (h << 5) - h + entry->value[i]; } return h; } static gboolean _entry_equal(gconstpointer a, gconstpointer b) { const GaEntryGroupServiceEntry *aentry = (const GaEntryGroupServiceEntry *) a; const GaEntryGroupServiceEntry *bentry = (const GaEntryGroupServiceEntry *) b; if (aentry->size != bentry->size) { return FALSE; } return memcmp(aentry->value, bentry->value, aentry->size) == 0; } static GaEntryGroupServiceEntry *_new_entry(const guint8 * value, gsize size) { GaEntryGroupServiceEntry *entry; if (value == NULL) { return NULL; } entry = g_slice_new(GaEntryGroupServiceEntry); entry->value = g_malloc(size + 1); memcpy(entry->value, value, size); /* for string keys, make sure it's NUL-terminated too */ entry->value[size] = 0; entry->size = size; return entry; } static void _set_entry(GHashTable * table, const guint8 * key, gsize ksize, const guint8 * value, gsize vsize) { g_hash_table_insert(table, _new_entry(key, ksize), _new_entry(value, vsize)); } static void _free_entry(gpointer data) { GaEntryGroupServiceEntry *entry = (GaEntryGroupServiceEntry *) data; if (entry == NULL) { return; } g_free(entry->value); g_slice_free(GaEntryGroupServiceEntry, entry); } static GHashTable *_string_list_to_hash(AvahiStringList * list) { GHashTable *ret; AvahiStringList *t; ret = g_hash_table_new_full(_entry_hash, _entry_equal, _free_entry, _free_entry); for (t = list; t != NULL; t = avahi_string_list_get_next(t)) { gchar *key; gchar *value; gsize size; int r; /* list_get_pair only fails if if memory allocation fails. Normal glib * behaviour is to assert/abort when that happens */ r = avahi_string_list_get_pair(t, &key, &value, &size); g_assert(r == 0); if (value == NULL) { _set_entry(ret, t->text, t->size, NULL, 0); } else { _set_entry(ret, (const guint8 *) key, strlen(key), (const guint8 *) value, size); } avahi_free(key); avahi_free(value); } return ret; } static void _hash_to_string_list_foreach(gpointer key, gpointer value, gpointer data) { AvahiStringList **list = (AvahiStringList **) data; GaEntryGroupServiceEntry *kentry = (GaEntryGroupServiceEntry *) key; GaEntryGroupServiceEntry *ventry = (GaEntryGroupServiceEntry *) value; if (value != NULL) { *list = avahi_string_list_add_pair_arbitrary(*list, (gchar *) kentry->value, ventry->value, ventry->size); } else { *list = avahi_string_list_add_arbitrary(*list, kentry->value, kentry->size); } } static AvahiStringList *_hash_to_string_list(GHashTable * table) { AvahiStringList *list = NULL; g_hash_table_foreach(table, _hash_to_string_list_foreach, (gpointer) & list); return list; } GaEntryGroupService *ga_entry_group_add_service_strlist(GaEntryGroup * group, const gchar * name, const gchar * type, guint16 port, GError ** error, AvahiStringList * txt) { return ga_entry_group_add_service_full_strlist(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, name, type, NULL, NULL, port, error, txt); } GaEntryGroupService *ga_entry_group_add_service_full_strlist(GaEntryGroup * group, AvahiIfIndex interface, AvahiProtocol protocol, AvahiPublishFlags flags, const gchar * name, const gchar * type, const gchar * domain, const gchar * host, guint16 port, GError ** error, AvahiStringList * txt) { GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group); GaEntryGroupServicePrivate *service = NULL; int ret; ret = avahi_entry_group_add_service_strlst(priv->group, interface, protocol, flags, name, type, domain, host, port, txt); if (ret) { if (error != NULL) { *error = g_error_new(GA_ERROR, ret, "Adding service to group failed: %s", avahi_strerror(ret)); } goto out; } service = g_new0(GaEntryGroupServicePrivate, 1); service->public.interface = interface; service->public.protocol = protocol; service->public.flags = flags; service->public.name = g_strdup(name); service->public.type = g_strdup(type); service->public.domain = g_strdup(domain); service->public.host = g_strdup(host); service->public.port = port; service->group = group; service->frozen = FALSE; service->entries = _string_list_to_hash(txt); g_hash_table_insert(priv->services, group, service); out: return (GaEntryGroupService *) service; } GaEntryGroupService *ga_entry_group_add_service(GaEntryGroup * group, const gchar * name, const gchar * type, guint16 port, GError ** error, ...) { GaEntryGroupService *ret; AvahiStringList *txt = NULL; va_list va; va_start(va, error); txt = avahi_string_list_new_va(va); ret = ga_entry_group_add_service_full_strlist(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, name, type, NULL, NULL, port, error, txt); avahi_string_list_free(txt); va_end(va); return ret; } GaEntryGroupService *ga_entry_group_add_service_full(GaEntryGroup * group, AvahiIfIndex interface, AvahiProtocol protocol, AvahiPublishFlags flags, const gchar * name, const gchar * type, const gchar * domain, const gchar * host, guint16 port, GError ** error, ...) { GaEntryGroupService *ret; AvahiStringList *txt = NULL; va_list va; va_start(va, error); txt = avahi_string_list_new_va(va); ret = ga_entry_group_add_service_full_strlist(group, interface, protocol, flags, name, type, domain, host, port, error, txt); avahi_string_list_free(txt); va_end(va); return ret; } gboolean ga_entry_group_add_record(GaEntryGroup * group, AvahiPublishFlags flags, const gchar * name, guint16 type, guint32 ttl, const void *rdata, gsize size, GError ** error) { return ga_entry_group_add_record_full(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, flags, name, AVAHI_DNS_CLASS_IN, type, ttl, rdata, size, error); } gboolean ga_entry_group_add_record_full(GaEntryGroup * group, AvahiIfIndex interface, AvahiProtocol protocol, AvahiPublishFlags flags, const gchar * name, guint16 clazz, guint16 type, guint32 ttl, const void *rdata, gsize size, GError ** error) { int ret; GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group); g_assert(group != NULL && priv->group != NULL); ret = avahi_entry_group_add_record(priv->group, interface, protocol, flags, name, clazz, type, ttl, rdata, size); if (ret) { if (error != NULL) { *error = g_error_new(GA_ERROR, ret, "Setting raw record failed: %s", avahi_strerror(ret)); } return FALSE; } return TRUE; } void ga_entry_group_service_freeze(GaEntryGroupService * service) { GaEntryGroupServicePrivate *p = (GaEntryGroupServicePrivate *) service; p->frozen = TRUE; } gboolean ga_entry_group_service_thaw(GaEntryGroupService * service, GError ** error) { GaEntryGroupServicePrivate *priv = (GaEntryGroupServicePrivate *) service; int ret; gboolean result = TRUE; AvahiStringList *txt = _hash_to_string_list(priv->entries); ret = avahi_entry_group_update_service_txt_strlst (GA_ENTRY_GROUP_GET_PRIVATE(priv->group)->group, service->interface, service->protocol, service->flags, service->name, service->type, service->domain, txt); if (ret) { if (error != NULL) { *error = g_error_new(GA_ERROR, ret, "Updating txt record failed: %s", avahi_strerror(ret)); } result = FALSE; } avahi_string_list_free(txt); priv->frozen = FALSE; return result; } gboolean ga_entry_group_service_set(GaEntryGroupService * service, const gchar * key, const gchar * value, GError ** error) { return ga_entry_group_service_set_arbitrary(service, key, (const guint8 *) value, strlen(value), error); } gboolean ga_entry_group_service_set_arbitrary(GaEntryGroupService * service, const gchar * key, const guint8 * value, gsize size, GError ** error) { GaEntryGroupServicePrivate *priv = (GaEntryGroupServicePrivate *) service; _set_entry(priv->entries, (const guint8 *) key, strlen(key), value, size); if (!priv->frozen) return ga_entry_group_service_thaw(service, error); else return TRUE; } gboolean ga_entry_group_service_remove_key(GaEntryGroupService * service, const gchar * key, GError ** error) { GaEntryGroupServicePrivate *priv = (GaEntryGroupServicePrivate *) service; GaEntryGroupServiceEntry entry; entry.value = (void*) key; entry.size = strlen(key); g_hash_table_remove(priv->entries, &entry); if (!priv->frozen) return ga_entry_group_service_thaw(service, error); else return TRUE; } gboolean ga_entry_group_attach(GaEntryGroup * group, GaClient * client, GError ** error) { GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group); g_return_val_if_fail(client->avahi_client, FALSE); g_assert(priv->client == NULL || priv->client == client); g_assert(priv->group == NULL); priv->client = client; g_object_ref(client); priv->group = avahi_entry_group_new(client->avahi_client, _avahi_entry_group_cb, group); if (priv->group == NULL) { if (error != NULL) { int aerrno = avahi_client_errno(client->avahi_client); *error = g_error_new(GA_ERROR, aerrno, "Attaching group failed: %s", avahi_strerror(aerrno)); } return FALSE; } return TRUE; } gboolean ga_entry_group_commit(GaEntryGroup * group, GError ** error) { GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group); int ret; ret = avahi_entry_group_commit(priv->group); if (ret) { if (error != NULL) { *error = g_error_new(GA_ERROR, ret, "Committing group failed: %s", avahi_strerror(ret)); } return FALSE; } return TRUE; } gboolean ga_entry_group_reset(GaEntryGroup * group, GError ** error) { GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group); int ret; ret = avahi_entry_group_reset(priv->group); if (ret) { if (error != NULL) { *error = g_error_new(GA_ERROR, ret, "Resetting group failed: %s", avahi_strerror(ret)); } return FALSE; } return TRUE; }