diff src/protocols/sametime/meanwhile/srvc_aware.c @ 10969:3ef77720e577

[gaim-migrate @ 12790] importing meanwhile library for use in the sametime plugin committer: Tailor Script <tailor@pidgin.im>
author Christopher O'Brien <siege@pidgin.im>
date Sun, 05 Jun 2005 02:50:13 +0000
parents
children 57fccea36e36
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/srvc_aware.c	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,1283 @@
+
+/*
+  Meanwhile - Unofficial Lotus Sametime Community Client Library
+  Copyright (C) 2004  Christopher (siege) O'Brien
+  
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 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
+  Library General Public License for more details.
+  
+  You should have received a copy of the GNU Library General Public
+  License along with this library; if not, write to the Free
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include <glib.h>
+#include <glib/ghash.h>
+#include <glib/glist.h>
+#include <string.h>
+
+#include "mw_channel.h"
+#include "mw_debug.h"
+#include "mw_error.h"
+#include "mw_message.h"
+#include "mw_service.h"
+#include "mw_session.h"
+#include "mw_srvc_aware.h"
+#include "mw_util.h"
+
+
+struct mwServiceAware {
+  struct mwService service;
+
+  struct mwAwareHandler *handler;
+
+  /** map of ENTRY_KEY(aware_entry):aware_entry */
+  GHashTable *entries;
+
+  /** set of guint32:attrib_watch_entry attribute keys */
+  GHashTable *attribs;
+
+  /** collection of lists of awareness for this service. Each item is
+      a mwAwareList */
+  GList *lists;
+
+  /** the buddy list channel */
+  struct mwChannel *channel;
+};
+
+
+struct mwAwareList {
+
+  /** the owning service */
+  struct mwServiceAware *service;
+
+  /** map of ENTRY_KEY(aware_entry):aware_entry */
+  GHashTable *entries;
+
+  /** set of guint32:attrib_watch_entry attribute keys */
+  GHashTable *attribs;
+
+  struct mwAwareListHandler *handler;
+  struct mw_datum client_data;
+};
+
+
+struct mwAwareAttribute {
+  guint32 key;
+  struct mwOpaque data;
+};
+
+
+struct attrib_entry {
+  guint32 key;
+  GList *membership;
+};
+
+
+/** an actual awareness entry, belonging to any number of aware lists */
+struct aware_entry {
+  struct mwAwareSnapshot aware;
+
+  /** list of mwAwareList containing this entry */
+  GList *membership;
+
+  /** collection of attribute values for this entry.
+      map of ATTRIB_KEY(mwAwareAttribute):mwAwareAttribute */
+  GHashTable *attribs;
+};
+
+
+#define ENTRY_KEY(entry) &entry->aware.id
+
+
+/** the channel send types used by this service */
+enum msg_types {
+  msg_AWARE_ADD       = 0x0068,  /**< remove an aware */
+  msg_AWARE_REMOVE    = 0x0069,  /**< add an aware */
+
+  msg_OPT_DO_SET      = 0x00c9,  /**< set an attribute */
+  msg_OPT_DO_UNSET    = 0x00ca,  /**< unset an attribute */
+  msg_OPT_WATCH       = 0x00cb,  /**< set the attribute watch list */
+
+  msg_AWARE_SNAPSHOT  = 0x01f4,  /**< recv aware snapshot */
+  msg_AWARE_UPDATE    = 0x01f5,  /**< recv aware update */
+  msg_AWARE_GROUP     = 0x01f6,  /**< recv group aware */
+
+  msg_OPT_GOT_SET     = 0x0259,  /**< recv attribute set update */
+  msg_OPT_GOT_UNSET   = 0x025a,  /**< recv attribute unset update */
+
+  msg_OPT_DID_SET     = 0x025d,  /**< attribute set response */
+  msg_OPT_DID_UNSET   = 0x025e,  /**< attribute unset response */
+  msg_OPT_DID_ERROR   = 0x025f,  /**< attribute set/unset error */
+};
+
+
+static void aware_entry_free(struct aware_entry *ae) {
+  mwAwareSnapshot_clear(&ae->aware);
+  g_list_free(ae->membership);
+  g_hash_table_destroy(ae->attribs);
+  g_free(ae);
+}
+
+
+static void attrib_entry_free(struct attrib_entry *ae) {
+  g_list_free(ae->membership);
+  g_free(ae);
+}
+
+
+static void attrib_free(struct mwAwareAttribute *attrib) {
+  mwOpaque_clear(&attrib->data);
+  g_free(attrib);
+}
+
+
+static struct aware_entry *aware_find(struct mwServiceAware *srvc,
+				      struct mwAwareIdBlock *srch) {
+  g_assert(srvc != NULL);
+  g_assert(srvc->entries != NULL);
+  g_return_val_if_fail(srch != NULL, NULL);
+  
+  return g_hash_table_lookup(srvc->entries, srch);
+}
+
+
+static struct aware_entry *list_aware_find(struct mwAwareList *list,
+					   struct mwAwareIdBlock *srch) {
+  g_return_val_if_fail(list != NULL, NULL);
+  g_return_val_if_fail(list->entries != NULL, NULL);
+  g_return_val_if_fail(srch != NULL, NULL);
+
+  return g_hash_table_lookup(list->entries, srch);
+}
+
+
+static void compose_list(struct mwPutBuffer *b, GList *id_list) {
+  guint32_put(b, g_list_length(id_list));
+  for(; id_list; id_list = id_list->next)
+    mwAwareIdBlock_put(b, id_list->data);
+}
+
+
+static int send_add(struct mwChannel *chan, GList *id_list) {
+  struct mwPutBuffer *b = mwPutBuffer_new();
+  struct mwOpaque o;
+  int ret;
+
+  compose_list(b, id_list);
+
+  mwPutBuffer_finalize(&o, b);
+
+  ret = mwChannel_send(chan, msg_AWARE_ADD, &o);
+  mwOpaque_clear(&o);
+
+  return ret;  
+}
+
+
+static int send_rem(struct mwChannel *chan, GList *id_list) {
+  struct mwPutBuffer *b = mwPutBuffer_new();
+  struct mwOpaque o;
+  int ret;
+
+  compose_list(b, id_list);
+  mwPutBuffer_finalize(&o, b);
+
+  ret = mwChannel_send(chan, msg_AWARE_REMOVE, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+static gboolean collect_dead(gpointer key, gpointer val, gpointer data) {
+  struct aware_entry *aware = val;
+  GList **dead = data;
+
+  if(aware->membership == NULL) {
+    g_info(" removing %s, %s",
+	   NSTR(aware->aware.id.user), NSTR(aware->aware.id.community));
+    *dead = g_list_append(*dead, aware);
+    return TRUE;
+
+  } else {
+    return FALSE;
+  }
+}
+
+
+static int remove_unused(struct mwServiceAware *srvc) {
+  /* - create a GList of all the unused aware entries
+     - remove each unused aware from the service
+     - if the service is alive, send a removal message for the collected
+     unused.
+  */
+
+  int ret = 0;
+  GList *dead = NULL, *l;
+
+  if(srvc->entries) {
+    g_info("bring out your dead *clang*");
+    g_hash_table_foreach_steal(srvc->entries, collect_dead, &dead);
+  }
+ 
+  if(dead) {
+    if(MW_SERVICE_IS_LIVE(srvc))
+      ret = send_rem(srvc->channel, dead) || ret;
+    
+    for(l = dead; l; l = l->next)
+      aware_entry_free(l->data);
+
+    g_list_free(dead);
+  }
+
+  return ret;
+}
+
+
+static int send_attrib_list(struct mwServiceAware *srvc) {
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+
+  int tmp;
+  GList *l;
+
+  l = map_collect_keys(srvc->attribs);
+  tmp = g_list_length(l);
+
+  b = mwPutBuffer_new();
+  guint32_put(b, 0x00);
+  guint32_put(b, tmp);
+  
+  for(; l; l = g_list_delete_link(l, l)) {
+    guint32_put(b, GPOINTER_TO_UINT(l->data));
+  }
+
+  mwPutBuffer_finalize(&o, b);
+  tmp = mwChannel_send(srvc->channel, msg_OPT_WATCH, &o);
+  mwOpaque_clear(&o);
+
+  return tmp;
+}
+
+
+static gboolean collect_attrib_dead(gpointer key, gpointer val,
+				    gpointer data) {
+
+  struct attrib_entry *attrib = val;
+  GList **dead = data;
+
+  if(attrib->membership == NULL) {
+    g_info(" removing 0x%08x", GPOINTER_TO_UINT(key));
+    *dead = g_list_append(*dead, attrib);
+    return TRUE;
+
+  } else {
+    return FALSE;
+  }
+}
+
+
+static int remove_unused_attrib(struct mwServiceAware *srvc) {
+  GList *dead = NULL;
+
+  if(srvc->entries) {
+    g_info("collecting dead attributes");
+    g_hash_table_foreach_steal(srvc->attribs, collect_attrib_dead, &dead);
+  }
+ 
+  /* since we stole them, we'll have to clean 'em up manually */
+  for(; dead; dead = g_list_delete_link(dead, dead)) {
+    attrib_entry_free(dead->data);
+  }
+
+  return send_attrib_list(srvc);
+}
+
+
+static void recv_accept(struct mwServiceAware *srvc,
+			struct mwChannel *chan,
+			struct mwMsgChannelAccept *msg) {
+
+  g_return_if_fail(srvc->channel != NULL);
+  g_return_if_fail(srvc->channel == chan);
+
+  if(MW_SERVICE_IS_STARTING(MW_SERVICE(srvc))) {
+    GList *list = NULL;
+
+    list = map_collect_values(srvc->entries);
+    send_add(chan, list);
+    g_list_free(list);
+
+    send_attrib_list(srvc);
+
+    mwService_started(MW_SERVICE(srvc));
+
+  } else {
+    mwChannel_destroy(chan, ERR_FAILURE, NULL);
+  }
+}
+
+
+static void recv_destroy(struct mwServiceAware *srvc,
+			 struct mwChannel *chan,
+			 struct mwMsgChannelDestroy *msg) {
+
+  srvc->channel = NULL;
+  mwService_stop(MW_SERVICE(srvc));
+
+  /** @todo session sense service and mwService_start */
+}
+
+
+/** called from SNAPSHOT_recv, UPDATE_recv, and
+    mwServiceAware_setStatus */
+static void status_recv(struct mwServiceAware *srvc,
+			struct mwAwareSnapshot *idb) {
+
+  struct aware_entry *aware;
+  GList *l;
+
+  aware = aware_find(srvc, &idb->id);
+
+  if(! aware) {
+    /* we don't deal with receiving status for something we're not
+       monitoring, but it will happen sometimes, eg from manually set
+       status */
+    return;
+  }
+  
+  /* clear the existing status, then clone in the new status */
+  mwAwareSnapshot_clear(&aware->aware);
+  mwAwareSnapshot_clone(&aware->aware, idb);
+  
+  /* trigger each of the entry's lists */
+  for(l = aware->membership; l; l = l->next) {
+    struct mwAwareList *alist = l->data;
+    struct mwAwareListHandler *handler = alist->handler;
+
+    if(handler && handler->on_aware)
+      handler->on_aware(alist, idb);
+  }
+}
+
+
+static void attrib_recv(struct mwServiceAware *srvc,
+			struct mwAwareIdBlock *idb,
+			struct mwAwareAttribute *attrib) {
+
+  struct aware_entry *aware;
+  struct mwAwareAttribute *old_attrib;
+  GList *l;
+  guint32 key;
+  gpointer k;
+
+  aware = aware_find(srvc, idb);
+  g_return_if_fail(aware != NULL);
+
+  key = attrib->key;
+  k = GUINT_TO_POINTER(key);
+
+  old_attrib = g_hash_table_lookup(aware->attribs, k);
+  if(! old_attrib) {
+    old_attrib = g_new0(struct mwAwareAttribute, 1);
+    old_attrib->key = key;
+    g_hash_table_insert(aware->attribs, k, old_attrib);
+  }
+  
+  mwOpaque_clear(&old_attrib->data);
+  mwOpaque_clone(&old_attrib->data, &attrib->data);
+  
+  for(l = aware->membership; l; l = l->next) {
+    struct mwAwareList *list = l->data;
+    struct mwAwareListHandler *h = list->handler;
+
+    if(h && h->on_attrib && g_hash_table_lookup(list->attribs, k))
+      h->on_attrib(list, idb, old_attrib);
+  }
+}
+
+
+gboolean list_add(struct mwAwareList *list, struct mwAwareIdBlock *id) {
+
+  struct mwServiceAware *srvc = list->service;
+  struct aware_entry *aware;
+
+  g_return_val_if_fail(id->user != NULL, FALSE);
+  g_return_val_if_fail(strlen(id->user) > 0, FALSE);
+
+  aware = list_aware_find(list, id);
+  if(aware) return FALSE;
+
+  aware = aware_find(srvc, id);
+  if(! aware) {
+    aware = g_new0(struct aware_entry, 1);
+    aware->attribs = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
+					   (GDestroyNotify) attrib_free);
+    mwAwareIdBlock_clone(ENTRY_KEY(aware), id);
+
+    g_hash_table_insert(srvc->entries, ENTRY_KEY(aware), aware);
+  }
+
+  aware->membership = g_list_append(aware->membership, list);
+  g_hash_table_insert(list->entries, ENTRY_KEY(aware), aware);
+  return TRUE;
+}
+
+
+static void group_member_recv(struct mwServiceAware *srvc,
+			      struct mwAwareSnapshot *idb) {
+  /* @todo
+     - look up group by id
+     - find each list group belongs to
+     - add user to lists
+  */
+
+  struct mwAwareIdBlock gsrch = { mwAware_GROUP, idb->group, NULL };
+  struct aware_entry *grp;
+  GList *l, *m;
+
+  grp = aware_find(srvc, &gsrch);
+  g_return_if_fail(grp != NULL); /* this could happen, with timing. */
+
+  l = g_list_prepend(NULL, &idb->id);
+
+  for(m = grp->membership; m; m = m->next) {
+
+    /* if we just list_add, we won't receive updates for attributes,
+       so annoyingly we have to turn around and send out an add aware
+       message for each incoming group member */
+
+    /* list_add(m->data, &idb->id); */
+    mwAwareList_addAware(m->data, l);
+  }
+
+  g_list_free(l);
+}
+
+
+static void recv_SNAPSHOT(struct mwServiceAware *srvc,
+			  struct mwGetBuffer *b) {
+
+  guint32 count;
+
+  struct mwAwareSnapshot *snap;
+  snap = g_new0(struct mwAwareSnapshot, 1);
+
+  guint32_get(b, &count);
+
+  while(count--) {
+    mwAwareSnapshot_get(b, snap);
+
+    if(mwGetBuffer_error(b)) {
+      mwAwareSnapshot_clear(snap);
+      break;
+    }
+
+    if(snap->group)
+      group_member_recv(srvc, snap);
+
+    status_recv(srvc, snap);
+    mwAwareSnapshot_clear(snap);
+  }
+
+  g_free(snap);
+}
+
+
+static void recv_UPDATE(struct mwServiceAware *srvc,
+			struct mwGetBuffer *b) {
+
+  struct mwAwareSnapshot *snap;
+
+  snap = g_new0(struct mwAwareSnapshot, 1);
+  mwAwareSnapshot_get(b, snap);
+
+  if(snap->group)
+    group_member_recv(srvc, snap);
+
+  if(! mwGetBuffer_error(b))
+    status_recv(srvc, snap);
+
+  mwAwareSnapshot_clear(snap);
+  g_free(snap);
+}
+
+
+static void recv_GROUP(struct mwServiceAware *srvc,
+		       struct mwGetBuffer *b) {
+
+  struct mwAwareIdBlock idb = { 0, 0, 0 };
+
+  /* really nothing to be done with this. The group should have
+     already been added to the list and service, and is now simply
+     awaiting a snapshot/update with users listed as belonging in said
+     group. */
+
+  mwAwareIdBlock_get(b, &idb);
+  mwAwareIdBlock_clear(&idb);
+}
+
+
+static void recv_OPT_GOT_SET(struct mwServiceAware *srvc,
+			     struct mwGetBuffer *b) {
+
+  struct mwAwareAttribute attrib;
+  struct mwAwareIdBlock idb;
+  guint32 junk, check;
+
+  guint32_get(b, &junk);
+  mwAwareIdBlock_get(b, &idb);
+  guint32_get(b, &junk);
+  guint32_get(b, &check);
+  guint32_get(b, &junk);
+  guint32_get(b, &attrib.key);
+
+  if(check) {
+    mwOpaque_get(b, &attrib.data);
+  } else {
+    attrib.data.len = 0;
+    attrib.data.data = NULL;
+  }
+
+  attrib_recv(srvc, &idb, &attrib);
+
+  mwAwareIdBlock_clear(&idb);
+  mwOpaque_clear(&attrib.data);
+}
+
+
+static void recv_OPT_GOT_UNSET(struct mwServiceAware *srvc,
+			       struct mwGetBuffer *b) {
+
+  struct mwAwareAttribute attrib;
+  struct mwAwareIdBlock idb;
+  guint32 junk;
+
+  attrib.key = 0;
+  attrib.data.len = 0;
+  attrib.data.data = NULL;
+
+  guint32_get(b, &junk);
+  mwAwareIdBlock_get(b, &idb);
+  guint32_get(b, &attrib.key);
+
+  attrib_recv(srvc, &idb, &attrib);
+
+  mwAwareIdBlock_clear(&idb);
+}
+
+
+static void recv(struct mwService *srvc, struct mwChannel *chan,
+		 guint16 type, struct mwOpaque *data) {
+
+  struct mwServiceAware *srvc_aware = (struct mwServiceAware *) srvc;
+  struct mwGetBuffer *b;
+
+  g_return_if_fail(srvc_aware->channel == chan);
+  g_return_if_fail(srvc->session == mwChannel_getSession(chan));
+  g_return_if_fail(data != NULL);
+
+  b = mwGetBuffer_wrap(data);
+
+  switch(type) {
+  case msg_AWARE_SNAPSHOT:
+    recv_SNAPSHOT(srvc_aware, b);
+    break;
+
+  case msg_AWARE_UPDATE:
+    recv_UPDATE(srvc_aware, b);
+    break;
+
+  case msg_AWARE_GROUP:
+    recv_GROUP(srvc_aware, b);
+    break;
+
+  case msg_OPT_GOT_SET:
+    recv_OPT_GOT_SET(srvc_aware, b);
+    break;
+
+  case msg_OPT_GOT_UNSET:
+    recv_OPT_GOT_UNSET(srvc_aware, b);
+    break;
+
+  case msg_OPT_DID_SET:
+  case msg_OPT_DID_UNSET:
+  case msg_OPT_DID_ERROR:
+    break;
+
+  default:
+    mw_debug_mailme(data, "unknown message in aware service: 0x%04x", type);   
+  }
+
+  mwGetBuffer_free(b);  
+}
+
+
+static void clear(struct mwService *srvc) {
+  struct mwServiceAware *srvc_aware = (struct mwServiceAware *) srvc;
+
+  g_return_if_fail(srvc != NULL);
+
+  while(srvc_aware->lists)
+    mwAwareList_free( (struct mwAwareList *) srvc_aware->lists->data );
+
+  g_hash_table_destroy(srvc_aware->entries);
+  srvc_aware->entries = NULL;
+}
+
+
+static const char *name(struct mwService *srvc) {
+  return "Presence Awareness";
+}
+
+
+static const char *desc(struct mwService *srvc) {
+  return "Buddy list service with support for server-side groups";
+}
+
+
+static struct mwChannel *make_blist(struct mwServiceAware *srvc,
+				    struct mwChannelSet *cs) {
+
+  struct mwChannel *chan = mwChannel_newOutgoing(cs);
+
+  mwChannel_setService(chan, MW_SERVICE(srvc));
+  mwChannel_setProtoType(chan, 0x00000011);
+  mwChannel_setProtoVer(chan, 0x00030005);
+
+  return mwChannel_create(chan)? NULL: chan;
+}
+
+
+static void start(struct mwService *srvc) {
+  struct mwServiceAware *srvc_aware;
+  struct mwChannel *chan;
+
+  srvc_aware = (struct mwServiceAware *) srvc;
+  chan = make_blist(srvc_aware, mwSession_getChannels(srvc->session));
+
+  if(chan != NULL) {
+    srvc_aware->channel = chan;
+  } else {
+    mwService_stopped(srvc);
+  }
+}
+
+
+static void stop(struct mwService *srvc) {
+  struct mwServiceAware *srvc_aware;
+
+  srvc_aware = (struct mwServiceAware *) srvc;
+  mwChannel_destroy(srvc_aware->channel, ERR_SUCCESS, NULL);
+  mwService_stopped(srvc);
+}
+
+
+struct mwServiceAware *
+mwServiceAware_new(struct mwSession *session,
+		   struct mwAwareHandler *handler) {
+
+  struct mwService *service;
+  struct mwServiceAware *srvc;
+
+  g_return_val_if_fail(session != NULL, NULL);
+  g_return_val_if_fail(handler != NULL, NULL);
+
+  srvc = g_new0(struct mwServiceAware, 1);
+  srvc->handler = handler;
+  srvc->entries = g_hash_table_new_full((GHashFunc) mwAwareIdBlock_hash,
+					(GEqualFunc) mwAwareIdBlock_equal,
+					NULL,
+					(GDestroyNotify) aware_entry_free);
+
+  srvc->attribs = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
+					(GDestroyNotify) attrib_entry_free);
+
+  service = MW_SERVICE(srvc);
+  mwService_init(service, session, SERVICE_AWARE);
+
+  service->recv_accept = (mwService_funcRecvAccept) recv_accept;
+  service->recv_destroy = (mwService_funcRecvDestroy) recv_destroy;
+  service->recv = recv;
+  service->start = start;
+  service->stop = stop;
+  service->clear = clear;
+  service->get_name = name;
+  service->get_desc = desc;
+
+  return srvc;
+}
+
+
+int mwServiceAware_setAttribute(struct mwServiceAware *srvc,
+				guint32 key, struct mwOpaque *data) {
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  int ret;
+
+  b = mwPutBuffer_new();
+
+  guint32_put(b, 0x00);
+  guint32_put(b, data->len);
+  guint32_put(b, 0x00);
+  guint32_put(b, key);
+  mwOpaque_put(b, data);
+
+  mwPutBuffer_finalize(&o, b);
+  ret = mwChannel_send(srvc->channel, msg_OPT_DO_SET, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+int mwServiceAware_setAttributeBoolean(struct mwServiceAware *srvc,
+				       guint32 key, gboolean val) {
+  int ret;
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+ 
+  b = mwPutBuffer_new();
+
+  gboolean_put(b, FALSE);
+  gboolean_put(b, val);
+
+  mwPutBuffer_finalize(&o, b);
+
+  ret = mwServiceAware_setAttribute(srvc, key, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+int mwServiceAware_setAttributeInteger(struct mwServiceAware *srvc,
+				       guint32 key, guint32 val) {
+  int ret;
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  
+  b = mwPutBuffer_new();
+  guint32_put(b, val);
+
+  mwPutBuffer_finalize(&o, b);
+
+  ret = mwServiceAware_setAttribute(srvc, key, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+int mwServiceAware_setAttributeString(struct mwServiceAware *srvc,
+				      guint32 key, const char *str) {
+  int ret;
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+
+  b = mwPutBuffer_new();
+  mwString_put(b, str);
+
+  mwPutBuffer_finalize(&o, b);
+
+  ret = mwServiceAware_setAttribute(srvc, key, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+int mwServiceAware_unsetAttribute(struct mwServiceAware *srvc,
+				  guint32 key) {
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  int ret;
+
+  b = mwPutBuffer_new();
+
+  guint32_put(b, 0x00);
+  guint32_put(b, key);
+  
+  mwPutBuffer_finalize(&o, b);
+  ret = mwChannel_send(srvc->channel, msg_OPT_DO_UNSET, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+guint32 mwAwareAttribute_getKey(const struct mwAwareAttribute *attrib) {
+  g_return_val_if_fail(attrib != NULL, 0x00);
+  return attrib->key;
+}
+
+
+gboolean mwAwareAttribute_asBoolean(const struct mwAwareAttribute *attrib) {
+  struct mwGetBuffer *b;
+  gboolean ret;
+  
+  if(! attrib) return FALSE;
+
+  b = mwGetBuffer_wrap(&attrib->data);
+  if(attrib->data.len >= 4) {
+    guint32 r32 = 0x00;
+    guint32_get(b, &r32);
+    ret = !! r32;
+
+  } else if(attrib->data.len >= 2) {
+    guint16 r16 = 0x00;
+    guint16_get(b, &r16);
+    ret = !! r16;
+
+  } else if(attrib->data.len) {
+    gboolean_get(b, &ret);
+  }
+
+  mwGetBuffer_free(b);
+
+  return ret;
+}
+
+
+guint32 mwAwareAttribute_asInteger(const struct mwAwareAttribute *attrib) {
+  struct mwGetBuffer *b;
+  guint32 r32 = 0x00;
+  
+  if(! attrib) return 0x00;
+
+  b = mwGetBuffer_wrap(&attrib->data);
+  if(attrib->data.len >= 4) {
+    guint32_get(b, &r32);
+
+  } else if(attrib->data.len == 3) {
+    gboolean rb = FALSE;
+    guint16 r16 = 0x00;
+    gboolean_get(b, &rb);
+    guint16_get(b, &r16);
+    r32 = (guint32) r16;
+
+  } else if(attrib->data.len == 2) {
+    guint16 r16 = 0x00;
+    guint16_get(b, &r16);
+    r32 = (guint32) r16;
+
+  } else if(attrib->data.len) {
+    gboolean rb = FALSE;
+    gboolean_get(b, &rb);
+    r32 = (guint32) rb;
+  }
+
+  mwGetBuffer_free(b);
+
+  return r32;
+}
+
+
+char *mwAwareAttribute_asString(const struct mwAwareAttribute *attrib) {
+  struct mwGetBuffer *b;
+  char *ret = NULL;
+
+  if(! attrib) return NULL;
+
+  b = mwGetBuffer_wrap(&attrib->data);
+  mwString_get(b, &ret);
+  mwGetBuffer_free(b);
+
+  return ret;
+}
+
+
+const struct mwOpaque *
+mwAwareAttribute_asOpaque(const struct mwAwareAttribute *attrib) {
+  g_return_val_if_fail(attrib != NULL, NULL);
+  return &attrib->data;
+}
+			  
+
+struct mwAwareList *
+mwAwareList_new(struct mwServiceAware *srvc,
+		struct mwAwareListHandler *handler) {
+
+  struct mwAwareList *al;
+
+  g_return_val_if_fail(srvc != NULL, NULL);
+  g_return_val_if_fail(handler != NULL, NULL);
+
+  al = g_new0(struct mwAwareList, 1);
+  al->service = srvc;
+  al->entries = g_hash_table_new((GHashFunc) mwAwareIdBlock_hash,
+				 (GEqualFunc) mwAwareIdBlock_equal);
+  al->attribs = g_hash_table_new(g_direct_hash, g_direct_equal);
+  al->handler = handler;
+
+  return al;
+}
+
+
+void mwAwareList_free(struct mwAwareList *list) {
+  struct mwServiceAware *srvc;
+  struct mwAwareListHandler *handler;
+
+  g_return_if_fail(list != NULL);
+  g_return_if_fail(list->entries != NULL);
+  g_return_if_fail(list->service != NULL);
+
+  handler = list->handler;
+  if(handler && handler->clear) {
+    handler->clear(list);
+    list->handler = NULL;
+  }
+
+  mw_datum_clear(&list->client_data);
+
+  srvc = list->service;
+  srvc->lists = g_list_remove(srvc->lists, list);
+
+  mwAwareList_unwatchAllAttributes(list);
+  mwAwareList_removeAllAware(list);
+}
+
+
+struct mwAwareListHandler *mwAwareList_getHandler(struct mwAwareList *list) {
+  g_return_val_if_fail(list != NULL, NULL);
+  return list->handler;
+}
+
+
+static void watch_add(struct mwAwareList *list, guint32 key) {
+  struct mwServiceAware *srvc;
+  struct attrib_entry *watch;
+  gpointer k = GUINT_TO_POINTER(key);
+
+  if(g_hash_table_lookup(list->attribs, k))
+    return;
+
+  srvc = list->service;
+
+  watch = g_hash_table_lookup(srvc->attribs, k);
+  if(! watch) {
+    watch = g_new0(struct attrib_entry, 1);
+    watch->key = key;
+    g_hash_table_insert(srvc->attribs, k, watch);
+  }
+
+  g_hash_table_insert(list->attribs, k, watch);
+  watch->membership = g_list_prepend(watch->membership, list);
+}
+
+
+static void watch_remove(struct mwAwareList *list, guint32 key) {
+  struct attrib_entry *watch;
+  gpointer k = GUINT_TO_POINTER(key);
+
+  watch = g_hash_table_lookup(list->attribs, k);
+  g_return_if_fail(watch != NULL);
+
+  g_hash_table_remove(list->attribs, k);
+  watch->membership = g_list_remove(watch->membership, list);
+}
+
+
+int mwAwareList_watchAttributeArray(struct mwAwareList *list,
+				    guint32 *keys) {
+  guint32 k;
+
+  g_return_val_if_fail(list != NULL, -1);
+  g_return_val_if_fail(list->service != NULL, -1);
+
+  if(! keys) return 0;
+
+  for(k = *keys; k; keys++)
+    watch_add(list, k);
+
+  return send_attrib_list(list->service);
+}
+
+
+int mwAwareList_watchAttributes(struct mwAwareList *list,
+				guint32 key, ...) {
+  guint32 k;
+  va_list args;
+
+  g_return_val_if_fail(list != NULL, -1);
+  g_return_val_if_fail(list->service != NULL, -1);
+
+  va_start(args, key);
+  for(k = key; k; k = va_arg(args, guint32))
+    watch_add(list, k);
+  va_end(args);
+
+  return send_attrib_list(list->service);
+}
+
+
+int mwAwareList_unwatchAttributeArray(struct mwAwareList *list,
+				      guint32 *keys) {
+  guint32 k;
+
+  g_return_val_if_fail(list != NULL, -1);
+  g_return_val_if_fail(list->service != NULL, -1);
+
+  if(! keys) return 0;
+
+  for(k = *keys; k; keys++)
+    watch_add(list, k);
+
+  remove_unused_attrib(list->service);
+  return send_attrib_list(list->service);
+}
+
+
+int mwAwareList_unwatchAttributes(struct mwAwareList *list,
+				  guint32 key, ...) {
+  guint32 k;
+  va_list args;
+
+  g_return_val_if_fail(list != NULL, -1);
+  g_return_val_if_fail(list->service != NULL, -1);
+
+  va_start(args, key);
+  for(k = key; k; k = va_arg(args, guint32))
+    watch_remove(list, k);
+  va_end(args);
+
+  remove_unused_attrib(list->service);
+  return send_attrib_list(list->service);
+}
+
+
+static void dismember_attrib(gpointer k, struct attrib_entry *watch,
+			    struct mwAwareList *list) {
+
+  watch->membership = g_list_remove(watch->membership, list);
+}
+
+
+int mwAwareList_unwatchAllAttributes(struct mwAwareList *list) {
+  
+  struct mwServiceAware *srvc;
+
+  g_return_val_if_fail(list != NULL, -1);
+  srvc = list->service;
+
+  g_hash_table_foreach(list->attribs, (GHFunc) dismember_attrib, list);
+  g_hash_table_destroy(list->attribs);
+  list->attribs = g_hash_table_new(g_direct_hash, g_direct_equal);
+
+  remove_unused_attrib(srvc);
+  return send_attrib_list(list->service);
+}
+
+
+static void collect_attrib_keys(gpointer key, struct attrib_entry *attrib,
+				guint32 **ck) {
+  guint32 *keys = (*ck)++;
+  *keys = GPOINTER_TO_UINT(key);
+}
+
+
+guint32 *mwAwareList_getWatchedAttributes(struct mwAwareList *list) {
+  guint32 *keys, **ck;
+  guint count;
+
+  g_return_val_if_fail(list != NULL, NULL);
+  g_return_val_if_fail(list->attribs != NULL, NULL);
+  
+  count = g_hash_table_size(list->attribs);
+  keys = g_new0(guint32, count + 1);
+
+  ck = &keys;
+  g_hash_table_foreach(list->attribs, (GHFunc) collect_attrib_keys, ck);
+
+  return keys;
+}
+
+
+int mwAwareList_addAware(struct mwAwareList *list, GList *id_list) {
+
+  /* for each awareness id:
+     - if it's already in the list, continue
+     - if it's not in the service list:
+       - create an awareness
+       - add it to the service list
+     - add this list to the membership
+     - add to the list
+  */
+
+  struct mwServiceAware *srvc;
+  GList *additions = NULL;
+  int ret = 0;
+
+  g_return_val_if_fail(list != NULL, -1);
+
+  srvc = list->service;
+  g_return_val_if_fail(srvc != NULL, -1);
+
+  for(; id_list; id_list = id_list->next) {
+    if(list_add(list, id_list->data))
+      additions = g_list_prepend(additions, id_list->data);
+  }
+
+  /* if the service is alive-- or getting there-- we'll need to send
+     these additions upstream */
+  if(MW_SERVICE_IS_LIVE(srvc) && additions)
+    ret = send_add(srvc->channel, additions);
+
+  g_list_free(additions);
+  return ret;
+}
+
+
+int mwAwareList_removeAware(struct mwAwareList *list, GList *id_list) {
+
+  /* for each awareness id:
+     - if it's not in the list, forget it
+     - remove from the list
+     - remove list from the membership
+
+     - call remove round
+  */
+
+  struct mwServiceAware *srvc;
+  struct mwAwareIdBlock *id;
+  struct aware_entry *aware;
+
+  g_return_val_if_fail(list != NULL, -1);
+
+  srvc = list->service;
+  g_return_val_if_fail(srvc != NULL, -1);
+
+  for(; id_list; id_list = id_list->next) {
+    id = id_list->data;
+    aware = list_aware_find(list, id);
+
+    if(! aware) {
+      g_warning("buddy %s, %s not in list",
+		NSTR(id->user),
+		NSTR(id->community));
+      continue;
+    }
+
+    aware->membership = g_list_remove(aware->membership, list);
+    g_hash_table_remove(list->entries, id);
+  }
+
+  return remove_unused(srvc);
+}
+
+
+static void dismember_aware(gpointer k, struct aware_entry *aware,
+			    struct mwAwareList *list) {
+
+  aware->membership = g_list_remove(aware->membership, list);
+}
+
+
+int mwAwareList_removeAllAware(struct mwAwareList *list) {
+  struct mwServiceAware *srvc;
+
+  g_return_val_if_fail(list != NULL, -1);
+  srvc = list->service;
+
+  g_return_val_if_fail(srvc != NULL, -1);
+
+  /* for each entry, remove the aware list from the service entry's
+     membership collection */
+  g_hash_table_foreach(list->entries, (GHFunc) dismember_aware, list);
+  g_hash_table_destroy(list->entries);
+  g_free(list);
+
+  return remove_unused(srvc);
+}
+
+
+void mwAwareList_setClientData(struct mwAwareList *list,
+			       gpointer data, GDestroyNotify clear) {
+
+  g_return_if_fail(list != NULL);
+  mw_datum_set(&list->client_data, data, clear);
+}
+
+
+gpointer mwAwareList_getClientData(struct mwAwareList *list) {
+  g_return_val_if_fail(list != NULL, NULL);
+  return mw_datum_get(&list->client_data);
+}
+
+
+void mwAwareList_removeClientData(struct mwAwareList *list) {
+  g_return_if_fail(list != NULL);
+  mw_datum_clear(&list->client_data);
+}
+
+
+void mwServiceAware_setStatus(struct mwServiceAware *srvc,
+			      struct mwAwareIdBlock *user,
+			      struct mwUserStatus *stat) {
+
+  struct mwAwareSnapshot idb;
+
+  g_return_if_fail(srvc != NULL);
+  g_return_if_fail(user != NULL);
+  g_return_if_fail(stat != NULL);
+
+  /* just reference the strings. then we don't need to free them */
+  idb.id.type = user->type;
+  idb.id.user = user->user;
+  idb.id.community = user->community;
+
+  idb.group = NULL;
+  idb.online = TRUE;
+  idb.alt_id = NULL;
+
+  idb.status.status = stat->status;
+  idb.status.time = stat->time;
+  idb.status.desc = stat->desc;
+
+  idb.name = NULL;
+
+  status_recv(srvc, &idb);
+}
+
+
+const struct mwAwareAttribute *
+mwServiceAware_getAttribute(struct mwServiceAware *srvc,
+			    struct mwAwareIdBlock *user,
+			    guint32 key) {
+
+  struct aware_entry *aware;
+
+  g_return_val_if_fail(srvc != NULL, NULL);
+  g_return_val_if_fail(user != NULL, NULL);
+  g_return_val_if_fail(key != 0x00, NULL);
+
+  aware = aware_find(srvc, user);
+  g_return_val_if_fail(aware != NULL, NULL);
+
+  return g_hash_table_lookup(aware->attribs, GUINT_TO_POINTER(key));
+}
+
+
+const char *mwServiceAware_getText(struct mwServiceAware *srvc,
+				   struct mwAwareIdBlock *user) {
+
+  struct aware_entry *aware;
+
+  g_return_val_if_fail(srvc != NULL, NULL);
+  g_return_val_if_fail(user != NULL, NULL);
+
+  aware = aware_find(srvc, user);
+  g_return_val_if_fail(aware != NULL, NULL);
+
+  return aware->aware.status.desc;
+}
+
+