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