changeset 31836:8d6630912021

Add two hash tables to the oscar code that deals with your server stored buddy list. One hash table is indexed by a combination of group id and buddy id. The other hash table is indexed by a combination of item type and item name. This should reduce the number of times we iterate through a linked list by A LOT. This is a modified version of Oliver's patch from #4816. Fixes #4816.
author Mark Doliner <mark@kingant.net>
date Sun, 20 Mar 2011 20:20:52 +0000
parents 36011b558d3e
children dc120ca9f523
files ChangeLog libpurple/protocols/oscar/authorization.c libpurple/protocols/oscar/family_feedbag.c libpurple/protocols/oscar/oscar.c libpurple/protocols/oscar/oscar.h libpurple/protocols/oscar/oscar_data.c libpurple/protocols/oscar/userinfo.c libpurple/protocols/oscar/visibility.c
diffstat 8 files changed, 235 insertions(+), 189 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sat Mar 19 19:38:55 2011 +0000
+++ b/ChangeLog	Sun Mar 20 20:20:52 2011 +0000
@@ -1,5 +1,9 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 3.0.0 (??/??/????):
+	AIM and ICQ:
+	* Make buddy list management code more efficient. (Oliver) (#4816)
+
 version 2.8.0 (??/??/????):
 	General:
 	* Implement simple silence suppression for voice calls, preventing wasted
--- a/libpurple/protocols/oscar/authorization.c	Sat Mar 19 19:38:55 2011 +0000
+++ b/libpurple/protocols/oscar/authorization.c	Sun Mar 20 20:20:52 2011 +0000
@@ -51,7 +51,7 @@
 		purple_debug_info("oscar", "ssi: adding buddy %s to group %s\n",
 				   bname, gname);
 		aim_ssi_sendauthrequest(od, data->name, msg ? msg : _("Please authorize me so I can add you to my buddy list."));
-		if (!aim_ssi_itemlist_finditem(od->ssi.local, gname, bname, AIM_SSI_TYPE_BUDDY))
+		if (!aim_ssi_itemlist_finditem(&od->ssi.local, gname, bname, AIM_SSI_TYPE_BUDDY))
 		{
 			aim_ssi_addbuddy(od, bname, gname, NULL, purple_buddy_get_alias_only(buddy), NULL, NULL, TRUE);
 
@@ -150,4 +150,4 @@
 	purple_account_request_authorization(account, data->name, NULL, data->nick,
 		reason, purple_find_buddy(account, data->name) != NULL,
 		oscar_auth_grant, oscar_auth_dontgrant_msgprompt, data);
-}
\ No newline at end of file
+}
--- a/libpurple/protocols/oscar/family_feedbag.c	Sat Mar 19 19:38:55 2011 +0000
+++ b/libpurple/protocols/oscar/family_feedbag.c	Sun Mar 20 20:20:52 2011 +0000
@@ -21,9 +21,9 @@
 /*
  * Family 0x0013 - Server-Side/Stored Information.
  *
- * Relatively new facility that allows certain types of information, such as
- * a user's buddy list, permit/deny list, and permit/deny preferences, to be
- * stored on the server, so that they can be accessed from any client.
+ * Deals with storing certain types of information, such as a user's buddy
+ * list, permit/deny list, and permit/deny preferences, on the server, so
+ * that they can be accessed from any client.
  *
  * We keep 2 copies of SSI data:
  * 1) An exact copy of what is stored on the AIM servers.
@@ -40,14 +40,41 @@
  *
  * This is entirely too complicated.
  * You don't know the half of it.
- *
  */
 
 #include "oscar.h"
+#include "oscarcommon.h"
 #include "debug.h"
 
 static int aim_ssi_addmoddel(OscarData *od);
 
+static void aim_ssi_item_free(struct aim_ssi_item *item)
+{
+	g_free(item->name);
+	aim_tlvlist_free(item->data);
+	g_free(item);
+}
+
+static void aim_ssi_item_set_name(struct aim_ssi_itemlist *list, struct aim_ssi_item *item, const char *name)
+{
+	gchar key[3000];
+
+	if (item->name) {
+		/* Remove old name from hash table */
+		snprintf(key, sizeof(key), "%hx%s", item->type, oscar_normalize(NULL, item->name));
+		g_hash_table_remove(list->idx_all_named_items, key);
+	}
+
+	g_free(item->name);
+	item->name = g_strdup(name);
+
+	if (name) {
+		/* Add new name to hash table */
+		snprintf(key, sizeof(key), "%hx%s", item->type, oscar_normalize(NULL, item->name));
+		g_hash_table_insert(list->idx_all_named_items, g_strdup(key), item);
+	}
+}
+
 /**
  * List types based on http://dev.aol.com/aim/oscar/#FEEDBAG (archive.org)
  * and http://iserverd.khstu.ru/oscar/ssi_item.html
@@ -112,7 +139,7 @@
  * @return Return a pointer to the modified item.
  */
 static void
-aim_ssi_itemlist_rebuildgroup(struct aim_ssi_item *list, const char *name)
+aim_ssi_itemlist_rebuildgroup(struct aim_ssi_itemlist *list, const char *name)
 {
 	int newlen;
 	struct aim_ssi_item *cur, *group;
@@ -124,11 +151,11 @@
 	/* Find the length for the new additional data */
 	newlen = 0;
 	if (group->gid == 0x0000) {
-		for (cur=list; cur; cur=cur->next)
+		for (cur=list->data; cur; cur=cur->next)
 			if ((cur->type == AIM_SSI_TYPE_GROUP) && (cur->gid != 0x0000))
 				newlen += 2;
 	} else {
-		for (cur=list; cur; cur=cur->next)
+		for (cur=list->data; cur; cur=cur->next)
 			if ((cur->gid == group->gid) && (cur->type == AIM_SSI_TYPE_BUDDY))
 				newlen += 2;
 	}
@@ -137,14 +164,14 @@
 	if (newlen > 0) {
 		guint8 *newdata;
 
-		newdata = (guint8 *)g_malloc((newlen)*sizeof(guint8));
+		newdata = g_new(guint8, newlen);
 		newlen = 0;
 		if (group->gid == 0x0000) {
-			for (cur=list; cur; cur=cur->next)
+			for (cur=list->data; cur; cur=cur->next)
 				if ((cur->type == AIM_SSI_TYPE_GROUP) && (cur->gid != 0x0000))
 						newlen += aimutil_put16(newdata+newlen, cur->gid);
 		} else {
-			for (cur=list; cur; cur=cur->next)
+			for (cur=list->data; cur; cur=cur->next)
 				if ((cur->gid == group->gid) && (cur->type == AIM_SSI_TYPE_BUDDY))
 						newlen += aimutil_put16(newdata+newlen, cur->bid);
 		}
@@ -166,15 +193,12 @@
  * @param data The additional data for the new item.
  * @return A pointer to the newly created item.
  */
-static struct aim_ssi_item *aim_ssi_itemlist_add(struct aim_ssi_item **list, const char *name, guint16 gid, guint16 bid, guint16 type, GSList *data)
+static struct aim_ssi_item *aim_ssi_itemlist_add(struct aim_ssi_itemlist *list, const char *name, guint16 gid, guint16 bid, guint16 type, GSList *data)
 {
 	gboolean exists;
 	struct aim_ssi_item *cur, *new;
 
-	new = g_new(struct aim_ssi_item, 1);
-
-	/* Set the name */
-	new->name = g_strdup(name);
+	new = g_new0(struct aim_ssi_item, 1);
 
 	/* Set the group ID# and buddy ID# */
 	new->gid = gid;
@@ -184,7 +208,7 @@
 			do {
 				new->gid += 0x0001;
 				exists = FALSE;
-				for (cur = *list; cur != NULL; cur = cur->next)
+				for (cur = list->data; cur != NULL; cur = cur->next)
 					if ((cur->type == AIM_SSI_TYPE_GROUP) && (cur->gid == new->gid)) {
 						exists = TRUE;
 						break;
@@ -201,7 +225,7 @@
 			do {
 				new->bid += 0x0001;
 				exists = FALSE;
-				for (cur = *list; cur != NULL; cur = cur->next)
+				for (cur = list->data; cur != NULL; cur = cur->next)
 					if (cur->bid == new->bid || cur->gid == new->bid) {
 						exists = TRUE;
 						break;
@@ -213,7 +237,7 @@
 			do {
 				new->bid += 0x0001;
 				exists = FALSE;
-				for (cur = *list; cur != NULL; cur = cur->next)
+				for (cur = list->data; cur != NULL; cur = cur->next)
 					if (cur->bid == new->bid && cur->gid == new->gid) {
 						exists = TRUE;
 						break;
@@ -225,23 +249,29 @@
 	/* Set the type */
 	new->type = type;
 
+	/* Add it to the gid+bid hashtable */
+	g_hash_table_insert(list->idx_gid_bid, GINT_TO_POINTER((new->gid << 16) + new->bid), new);
+
+	/* Set the name - do this *AFTER* setting the type because type is used for the key */
+	aim_ssi_item_set_name(list, new, name);
+
 	/* Set the TLV list */
 	new->data = aim_tlvlist_copy(data);
 
 	/* Add the item to the list in the correct numerical position.  Fancy, eh? */
-	if (*list) {
-		if ((new->gid < (*list)->gid) || ((new->gid == (*list)->gid) && (new->bid < (*list)->bid))) {
-			new->next = *list;
-			*list = new;
+	if (list->data) {
+		if ((new->gid < list->data->gid) || ((new->gid == list->data->gid) && (new->bid < list->data->bid))) {
+			new->next = list->data;
+			list->data = new;
 		} else {
 			struct aim_ssi_item *prev;
-			for ((prev=*list, cur=(*list)->next); (cur && ((new->gid > cur->gid) || ((new->gid == cur->gid) && (new->bid > cur->bid)))); prev=cur, cur=cur->next);
+			for ((prev=list->data, cur=list->data->next); (cur && ((new->gid > cur->gid) || ((new->gid == cur->gid) && (new->bid > cur->bid)))); prev=cur, cur=cur->next);
 			new->next = prev->next;
 			prev->next = new;
 		}
 	} else {
-		new->next = *list;
-		*list = new;
+		new->next = list->data;
+		list->data = new;
 	}
 
 	return new;
@@ -254,25 +284,31 @@
  * @param del A pointer to the item you want to remove from the list.
  * @return Return 0 if no errors, otherwise return the error number.
  */
-static int aim_ssi_itemlist_del(struct aim_ssi_item **list, struct aim_ssi_item *del)
+static int aim_ssi_itemlist_del(struct aim_ssi_itemlist *list, struct aim_ssi_item *del)
 {
-	if (!(*list) || !del)
+	gchar key[3000];
+
+	if (!(list->data) || !del)
 		return -EINVAL;
 
 	/* Remove the item from the list */
-	if (*list == del) {
-		*list = (*list)->next;
+	if (list->data == del) {
+		list->data = list->data->next;
 	} else {
 		struct aim_ssi_item *cur;
-		for (cur=*list; (cur->next && (cur->next!=del)); cur=cur->next);
+		for (cur=list->data; (cur->next && (cur->next!=del)); cur=cur->next);
 		if (cur->next)
 			cur->next = del->next;
 	}
 
+	/* Remove from the hashtables */
+	g_hash_table_remove(list->idx_gid_bid, GINT_TO_POINTER((del->gid << 16) + del->bid));
+
+	snprintf(key, sizeof(key), "%hx%s", del->type, oscar_normalize(NULL, del->name));
+	g_hash_table_remove(list->idx_all_named_items, key);
+
 	/* Free the removed item */
-	g_free(del->name);
-	aim_tlvlist_free(del->data);
-	g_free(del);
+	aim_ssi_item_free(del);
 
 	return 0;
 }
@@ -319,10 +355,10 @@
 	return 0;
 }
 
-static gboolean aim_ssi_itemlist_valid(struct aim_ssi_item *list, struct aim_ssi_item *item)
+static gboolean aim_ssi_itemlist_valid(struct aim_ssi_itemlist *list, struct aim_ssi_item *item)
 {
 	struct aim_ssi_item *cur;
-	for (cur=list; cur; cur=cur->next)
+	for (cur=list->data; cur; cur=cur->next)
 		if (cur == item)
 			return TRUE;
 	return FALSE;
@@ -336,13 +372,10 @@
  * @param bid The buddy ID# of the desired item.
  * @return Return a pointer to the item if found, else return NULL;
  */
-struct aim_ssi_item *aim_ssi_itemlist_find(struct aim_ssi_item *list, guint16 gid, guint16 bid)
+struct aim_ssi_item *aim_ssi_itemlist_find(struct aim_ssi_itemlist *list, guint16 gid, guint16 bid)
 {
-	struct aim_ssi_item *cur;
-	for (cur=list; cur; cur=cur->next)
-		if ((cur->gid == gid) && (cur->bid == bid))
-			return cur;
-	return NULL;
+	guint32 id_key = (gid << 16) + bid;
+	return g_hash_table_lookup(list->idx_gid_bid, GINT_TO_POINTER(id_key));
 }
 
 /**
@@ -355,37 +388,30 @@
  * @param type The type of the desired item.
  * @return Return a pointer to the item if found, else return NULL.
  */
-struct aim_ssi_item *aim_ssi_itemlist_finditem(struct aim_ssi_item *list, const char *gn, const char *bn, guint16 type)
+struct aim_ssi_item *aim_ssi_itemlist_finditem(struct aim_ssi_itemlist *list, const char *gn, const char *bn, guint16 type)
 {
 	struct aim_ssi_item *cur;
-	if (!list)
+	gchar key[3000];
+
+	if (!list->data)
 		return NULL;
 
 	if (gn && bn) { /* For finding buddies in groups */
-		for (cur=list; cur; cur=cur->next)
+		g_return_val_if_fail(type == AIM_SSI_TYPE_BUDDY, NULL);
+		for (cur=list->data; cur; cur=cur->next)
 			if ((cur->type == type) && (cur->name) && !(oscar_util_name_compare(cur->name, bn))) {
 				struct aim_ssi_item *curg;
-				for (curg=list; curg; curg=curg->next)
+				for (curg=list->data; curg; curg=curg->next)
 					if ((curg->type == AIM_SSI_TYPE_GROUP) && (curg->gid == cur->gid) && (curg->name) && !(oscar_util_name_compare(curg->name, gn)))
 						return cur;
 			}
 
-	} else if (gn) { /* For finding groups */
-		for (cur=list; cur; cur=cur->next) {
-			if ((cur->type == type) && (cur->bid == 0x0000) && (cur->name) && !(oscar_util_name_compare(cur->name, gn))) {
-				return cur;
-			}
-		}
-
-	} else if (bn) { /* For finding permits, denies, and ignores */
-		for (cur=list; cur; cur=cur->next) {
-			if ((cur->type == type) && (cur->name) && !(oscar_util_name_compare(cur->name, bn))) {
-				return cur;
-			}
-		}
+	} else if (gn || bn) { /* For finding groups, permits, denies and ignores */
+		snprintf(key, sizeof(key), "%hx%s", type, oscar_normalize(NULL, gn ? gn : bn));
+		return g_hash_table_lookup(list->idx_all_named_items, key);
 
 	/* For stuff without names--permit deny setting, visibility mask, etc. */
-	} else for (cur=list; cur; cur=cur->next) {
+	} else for (cur=list->data; cur; cur=cur->next) {
 		if ((cur->type == type) && (!cur->name))
 			return cur;
 	}
@@ -400,7 +426,7 @@
  * @param bn The group name of the desired item.
  * @return Return a pointer to the name of the item if found, else return NULL;
  */
-struct aim_ssi_item *aim_ssi_itemlist_exists(struct aim_ssi_item *list, const char *bn)
+struct aim_ssi_item *aim_ssi_itemlist_exists(struct aim_ssi_itemlist *list, const char *bn)
 {
 	if (!bn)
 		return NULL;
@@ -414,10 +440,10 @@
  * @param bn The buddy name of the desired item.
  * @return Return a pointer to the name of the item if found, else return NULL;
  */
-char *aim_ssi_itemlist_findparentname(struct aim_ssi_item *list, const char *bn)
+char *aim_ssi_itemlist_findparentname(struct aim_ssi_itemlist *list, const char *bn)
 {
 	struct aim_ssi_item *cur, *curg;
-	if (!list || !bn)
+	if (!list->data || !bn)
 		return NULL;
 	if (!(cur = aim_ssi_itemlist_exists(list, bn)))
 		return NULL;
@@ -432,7 +458,7 @@
  * @param list A pointer to the current list of items.
  * @return Return the current SSI permit deny setting, or 0 if no setting was found.
  */
-int aim_ssi_getpermdeny(struct aim_ssi_item *list)
+int aim_ssi_getpermdeny(struct aim_ssi_itemlist *list)
 {
 	struct aim_ssi_item *cur = aim_ssi_itemlist_finditem(list, NULL, NULL, AIM_SSI_TYPE_PDINFO);
 	if (cur) {
@@ -450,7 +476,7 @@
  * @param list A pointer to the current list of items.
  * @return Return the current set of preferences.
  */
-guint32 aim_ssi_getpresence(struct aim_ssi_item *list)
+guint32 aim_ssi_getpresence(struct aim_ssi_itemlist *list)
 {
 	struct aim_ssi_item *cur = aim_ssi_itemlist_finditem(list, NULL, NULL, AIM_SSI_TYPE_PRESENCEPREFS);
 	if (cur) {
@@ -471,17 +497,23 @@
  *         alias, or NULL if the buddy has no alias.  You should free
  *         this returned value!
  */
-char *aim_ssi_getalias(struct aim_ssi_item *list, const char *gn, const char *bn)
+char *aim_ssi_getalias(struct aim_ssi_itemlist *list, const char *gn, const char *bn)
 {
-	struct aim_ssi_item *cur = aim_ssi_itemlist_finditem(list, gn, bn, AIM_SSI_TYPE_BUDDY);
-	if (cur) {
-		aim_tlv_t *tlv = aim_tlv_gettlv(cur->data, 0x0131, 1);
-		if (tlv && tlv->length)
-			return g_strndup((const gchar *)tlv->value, tlv->length);
+	struct aim_ssi_item *item = aim_ssi_itemlist_finditem(list, gn, bn, AIM_SSI_TYPE_BUDDY);
+	if (item) {
+		return aim_ssi_getalias_from_item(item);
 	}
 	return NULL;
 }
 
+char *aim_ssi_getalias_from_item(struct aim_ssi_item *item)
+{
+	aim_tlv_t *tlv = aim_tlv_gettlv(item->data, 0x0131, 1);
+	if (tlv && tlv->length)
+		return g_strndup((const gchar *)tlv->value, tlv->length);
+	return NULL;
+}
+
 /**
  * Locally find the comment of the given buddy.
  *
@@ -492,7 +524,7 @@
  *         comment, or NULL if the buddy has no comment.  You should free
  *         this returned value!
  */
-char *aim_ssi_getcomment(struct aim_ssi_item *list, const char *gn, const char *bn)
+char *aim_ssi_getcomment(struct aim_ssi_itemlist *list, const char *gn, const char *bn)
 {
 	struct aim_ssi_item *cur = aim_ssi_itemlist_finditem(list, gn, bn, AIM_SSI_TYPE_BUDDY);
 	if (cur) {
@@ -512,7 +544,7 @@
  * @param bn The name of the buddy.
  * @return 1 if you are waiting for authorization; 0 if you are not
  */
-gboolean aim_ssi_waitingforauth(struct aim_ssi_item *list, const char *gn, const char *bn)
+gboolean aim_ssi_waitingforauth(struct aim_ssi_itemlist *list, const char *gn, const char *bn)
 {
 	struct aim_ssi_item *cur = aim_ssi_itemlist_finditem(list, gn, bn, AIM_SSI_TYPE_BUDDY);
 	if (cur) {
@@ -560,8 +592,8 @@
 
 	/* Deletions */
 	if (!od->ssi.pending) {
-		for (cur1=od->ssi.official; cur1 && (n < 15); cur1=cur1->next) {
-			if (!aim_ssi_itemlist_find(od->ssi.local, cur1->gid, cur1->bid)) {
+		for (cur1=od->ssi.official.data; cur1 && (n < 15); cur1=cur1->next) {
+			if (!aim_ssi_itemlist_find(&od->ssi.local, cur1->gid, cur1->bid)) {
 				n++;
 				new = g_new(struct aim_ssi_tmp, 1);
 				new->action = SNAC_SUBTYPE_FEEDBAG_DEL;
@@ -574,15 +606,15 @@
 					cur->next = new;
 				} else
 					od->ssi.pending = new;
-			       	aim_ssi_item_debug_append(debugstr, "Deleting item ", cur1);
+					aim_ssi_item_debug_append(debugstr, "Deleting item ", cur1);
 			}
 		}
 	}
 
 	/* Additions */
 	if (!od->ssi.pending) {
-		for (cur1=od->ssi.local; cur1 && (n < 15); cur1=cur1->next) {
-			if (!aim_ssi_itemlist_find(od->ssi.official, cur1->gid, cur1->bid)) {
+		for (cur1=od->ssi.local.data; cur1 && (n < 15); cur1=cur1->next) {
+			if (!aim_ssi_itemlist_find(&od->ssi.official, cur1->gid, cur1->bid)) {
 				n++;
 				new = g_new(struct aim_ssi_tmp, 1);
 				new->action = SNAC_SUBTYPE_FEEDBAG_ADD;
@@ -595,15 +627,15 @@
 					cur->next = new;
 				} else
 					od->ssi.pending = new;
-			       	aim_ssi_item_debug_append(debugstr, "Adding item ", cur1);
+					aim_ssi_item_debug_append(debugstr, "Adding item ", cur1);
 			}
 		}
 	}
 
 	/* Modifications */
 	if (!od->ssi.pending) {
-		for (cur1=od->ssi.local; cur1 && (n < 15); cur1=cur1->next) {
-			cur2 = aim_ssi_itemlist_find(od->ssi.official, cur1->gid, cur1->bid);
+		for (cur1=od->ssi.local.data; cur1 && (n < 15); cur1=cur1->next) {
+			cur2 = aim_ssi_itemlist_find(&od->ssi.official, cur1->gid, cur1->bid);
 			if (cur2 && (aim_ssi_itemlist_cmp(cur1, cur2))) {
 				n++;
 				new = g_new(struct aim_ssi_tmp, 1);
@@ -617,15 +649,15 @@
 					cur->next = new;
 				} else
 					od->ssi.pending = new;
-			       	aim_ssi_item_debug_append(debugstr, "Modifying item ", cur1);
+					aim_ssi_item_debug_append(debugstr, "Modifying item ", cur1);
 			}
 		}
 	}
 	if (debugstr->len > 0) {
 		purple_debug_info("oscar", "%s", debugstr->str);
 		if (purple_debug_is_verbose()) {
-	    		g_string_truncate(debugstr, 0);
-			for (cur1 = od->ssi.local; cur1; cur1 = cur1->next) 
+			g_string_truncate(debugstr, 0);
+			for (cur1 = od->ssi.local.data; cur1; cur1 = cur1->next)
 				aim_ssi_item_debug_append(debugstr, "\t", cur1);
 			purple_debug_misc("oscar", "Dumping item list of account %s:\n%s",
 				purple_connection_get_account(od->gc)->username, debugstr->str);
@@ -672,22 +704,18 @@
 	struct aim_ssi_item *cur, *del;
 	struct aim_ssi_tmp *curtmp, *deltmp;
 
-	cur = od->ssi.official;
+	cur = od->ssi.official.data;
 	while (cur) {
 		del = cur;
 		cur = cur->next;
-		g_free(del->name);
-		aim_tlvlist_free(del->data);
-		g_free(del);
+		aim_ssi_item_free(del);
 	}
 
-	cur = od->ssi.local;
+	cur = od->ssi.local.data;
 	while (cur) {
 		del = cur;
 		cur = cur->next;
-		g_free(del->name);
-		aim_tlvlist_free(del->data);
-		g_free(del);
+		aim_ssi_item_free(del);
 	}
 
 	curtmp = od->ssi.pending;
@@ -698,8 +726,8 @@
 	}
 
 	od->ssi.numitems = 0;
-	od->ssi.official = NULL;
-	od->ssi.local = NULL;
+	od->ssi.official.data = NULL;
+	od->ssi.local.data = NULL;
 	od->ssi.pending = NULL;
 	od->ssi.timestamp = (time_t)0;
 }
@@ -725,7 +753,7 @@
 	/* DESTROY any buddies that are directly in the master group. */
 	/* Do the same for buddies that are in a non-existant group. */
 	/* This will kind of mess up if you hit the item limit, but this function isn't too critical */
-	cur = od->ssi.local;
+	cur = od->ssi.local.data;
 	while (cur) {
 		next = cur->next;
 		if (!cur->name) {
@@ -733,8 +761,8 @@
 				aim_ssi_delbuddy(od, NULL, NULL);
 			else if (cur->type == AIM_SSI_TYPE_PERMIT || cur->type == AIM_SSI_TYPE_DENY || cur->type == AIM_SSI_TYPE_ICQDENY)
 				aim_ssi_del_from_private_list(od, NULL, cur->type);
-		} else if ((cur->type == AIM_SSI_TYPE_BUDDY) && ((cur->gid == 0x0000) || (!aim_ssi_itemlist_find(od->ssi.local, cur->gid, 0x0000)))) {
-			char *alias = aim_ssi_getalias(od->ssi.local, NULL, cur->name);
+		} else if ((cur->type == AIM_SSI_TYPE_BUDDY) && ((cur->gid == 0x0000) || (!aim_ssi_itemlist_find(&od->ssi.local, cur->gid, 0x0000)))) {
+			char *alias = aim_ssi_getalias(&od->ssi.local, NULL, cur->name);
 			aim_ssi_addbuddy(od, cur->name, "orphans", NULL, alias, NULL, NULL, FALSE);
 			aim_ssi_delbuddy(od, cur->name, NULL);
 			g_free(alias);
@@ -743,7 +771,7 @@
 	}
 
 	/* Make sure there aren't any duplicate buddies in a group, or duplicate permits or denies */
-	cur = od->ssi.local;
+	cur = od->ssi.local.data;
 	while (cur) {
 		if ((cur->type == AIM_SSI_TYPE_BUDDY) || (cur->type == AIM_SSI_TYPE_PERMIT) || (cur->type == AIM_SSI_TYPE_DENY))
 		{
@@ -784,16 +812,16 @@
 		return -EINVAL;
 
 	/* Find the parent */
-	if (!(parent = aim_ssi_itemlist_finditem(od->ssi.local, group, NULL, AIM_SSI_TYPE_GROUP))) {
+	if (!(parent = aim_ssi_itemlist_finditem(&od->ssi.local, group, NULL, AIM_SSI_TYPE_GROUP))) {
 		/* Find the parent's parent (the master group) */
-		if (aim_ssi_itemlist_find(od->ssi.local, 0x0000, 0x0000) == NULL)
+		if (aim_ssi_itemlist_find(&od->ssi.local, 0x0000, 0x0000) == NULL)
 			aim_ssi_itemlist_add(&od->ssi.local, NULL, 0x0000, 0x0000, AIM_SSI_TYPE_GROUP, NULL);
 
 		/* Add the parent */
 		parent = aim_ssi_itemlist_add(&od->ssi.local, group, 0xFFFF, 0x0000, AIM_SSI_TYPE_GROUP, NULL);
 
 		/* Modify the parent's parent (the master group) */
-		aim_ssi_itemlist_rebuildgroup(od->ssi.local, NULL);
+		aim_ssi_itemlist_rebuildgroup(&od->ssi.local, NULL);
 	}
 
 	/* Create a TLV list for the new buddy */
@@ -811,7 +839,7 @@
 	aim_tlvlist_free(data);
 
 	/* Modify the parent group */
-	aim_ssi_itemlist_rebuildgroup(od->ssi.local, group);
+	aim_ssi_itemlist_rebuildgroup(&od->ssi.local, group);
 
 	/* Sync our local list with the server list */
 	return aim_ssi_sync(od);
@@ -823,7 +851,7 @@
 	if (!od || !name || !od->ssi.received_data)
 		return -EINVAL;
 
-	if (aim_ssi_itemlist_find(od->ssi.local, 0x0000, 0x0000) == NULL)
+	if (aim_ssi_itemlist_find(&od->ssi.local, 0x0000, 0x0000) == NULL)
 		aim_ssi_itemlist_add(&od->ssi.local, NULL, 0x0000, 0x0000, AIM_SSI_TYPE_GROUP, NULL);
 
 	aim_ssi_itemlist_add(&od->ssi.local, name, 0x0000, 0xFFFF, list_type, NULL);
@@ -838,7 +866,7 @@
 	if (!od)
 		return -EINVAL;
 
-	if (!(del = aim_ssi_itemlist_finditem(od->ssi.local, NULL, name, list_type)))
+	if (!(del = aim_ssi_itemlist_finditem(&od->ssi.local, NULL, name, list_type)))
 		return -EINVAL;
 
 	aim_ssi_itemlist_del(&od->ssi.local, del);
@@ -861,14 +889,14 @@
 		return -EINVAL;
 
 	/* Find the buddy */
-	if (!(del = aim_ssi_itemlist_finditem(od->ssi.local, group, name, AIM_SSI_TYPE_BUDDY)))
+	if (!(del = aim_ssi_itemlist_finditem(&od->ssi.local, group, name, AIM_SSI_TYPE_BUDDY)))
 		return -EINVAL;
 
 	/* Remove the item from the list */
 	aim_ssi_itemlist_del(&od->ssi.local, del);
 
 	/* Modify the parent group */
-	aim_ssi_itemlist_rebuildgroup(od->ssi.local, group);
+	aim_ssi_itemlist_rebuildgroup(&od->ssi.local, group);
 
 	/* Sync our local list with the server list */
 	return aim_ssi_sync(od);
@@ -890,7 +918,7 @@
 		return -EINVAL;
 
 	/* Find the group */
-	if (!(del = aim_ssi_itemlist_finditem(od->ssi.local, group, NULL, AIM_SSI_TYPE_GROUP)))
+	if (!(del = aim_ssi_itemlist_finditem(&od->ssi.local, group, NULL, AIM_SSI_TYPE_GROUP)))
 		return -EINVAL;
 
 	/* Don't delete the group if it's not empty */
@@ -902,7 +930,7 @@
 	aim_ssi_itemlist_del(&od->ssi.local, del);
 
 	/* Modify the parent group */
-	aim_ssi_itemlist_rebuildgroup(od->ssi.local, group);
+	aim_ssi_itemlist_rebuildgroup(&od->ssi.local, group);
 
 	/* Sync our local list with the server list */
 	return aim_ssi_sync(od);
@@ -924,7 +952,7 @@
 	GSList *data;
 
 	/* Find the buddy */
-	buddy = aim_ssi_itemlist_finditem(od->ssi.local, oldgn, bn, AIM_SSI_TYPE_BUDDY);
+	buddy = aim_ssi_itemlist_finditem(&od->ssi.local, oldgn, bn, AIM_SSI_TYPE_BUDDY);
 	if (buddy == NULL)
 		return -EINVAL;
 
@@ -957,7 +985,7 @@
 	if (!od || !gn || !bn)
 		return -EINVAL;
 
-	if (!(tmp = aim_ssi_itemlist_finditem(od->ssi.local, gn, bn, AIM_SSI_TYPE_BUDDY)))
+	if (!(tmp = aim_ssi_itemlist_finditem(&od->ssi.local, gn, bn, AIM_SSI_TYPE_BUDDY)))
 		return -EINVAL;
 
 	/* Either add or remove the 0x0131 TLV from the TLV chain */
@@ -987,7 +1015,7 @@
 	if (!od || !gn || !bn)
 		return -EINVAL;
 
-	if (!(tmp = aim_ssi_itemlist_finditem(od->ssi.local, gn, bn, AIM_SSI_TYPE_BUDDY)))
+	if (!(tmp = aim_ssi_itemlist_finditem(&od->ssi.local, gn, bn, AIM_SSI_TYPE_BUDDY)))
 		return -EINVAL;
 
 	/* Either add or remove the 0x0131 TLV from the TLV chain */
@@ -1015,11 +1043,10 @@
 	if (!od || !oldgn || !newgn)
 		return -EINVAL;
 
-	if (!(group = aim_ssi_itemlist_finditem(od->ssi.local, oldgn, NULL, AIM_SSI_TYPE_GROUP)))
+	if (!(group = aim_ssi_itemlist_finditem(&od->ssi.local, oldgn, NULL, AIM_SSI_TYPE_GROUP)))
 		return -EINVAL;
 
-	g_free(group->name);
-	group->name = g_strdup(newgn);
+	aim_ssi_item_set_name(&od->ssi.local, group, newgn);
 
 	/* Sync our local list with the server list */
 	return aim_ssi_sync(od);
@@ -1046,9 +1073,9 @@
 		return -EINVAL;
 
 	/* Find the PDINFO item, or add it if it does not exist */
-	if (!(tmp = aim_ssi_itemlist_finditem(od->ssi.local, NULL, NULL, AIM_SSI_TYPE_PDINFO))) {
+	if (!(tmp = aim_ssi_itemlist_finditem(&od->ssi.local, NULL, NULL, AIM_SSI_TYPE_PDINFO))) {
 		/* Make sure the master group exists */
-		if (aim_ssi_itemlist_find(od->ssi.local, 0x0000, 0x0000) == NULL)
+		if (aim_ssi_itemlist_find(&od->ssi.local, 0x0000, 0x0000) == NULL)
 			aim_ssi_itemlist_add(&od->ssi.local, NULL, 0x0000, 0x0000, AIM_SSI_TYPE_GROUP, NULL);
 
 		tmp = aim_ssi_itemlist_add(&od->ssi.local, NULL, 0x0000, 0xFFFF, AIM_SSI_TYPE_PDINFO, NULL);
@@ -1078,9 +1105,9 @@
 		return -EINVAL;
 
 	/* Find the ICONINFO item, or add it if it does not exist */
-	if (!(tmp = aim_ssi_itemlist_finditem(od->ssi.local, NULL, "1", AIM_SSI_TYPE_ICONINFO))) {
+	if (!(tmp = aim_ssi_itemlist_finditem(&od->ssi.local, NULL, "1", AIM_SSI_TYPE_ICONINFO))) {
 		/* Make sure the master group exists */
-		if (aim_ssi_itemlist_find(od->ssi.local, 0x0000, 0x0000) == NULL)
+		if (aim_ssi_itemlist_find(&od->ssi.local, 0x0000, 0x0000) == NULL)
 			aim_ssi_itemlist_add(&od->ssi.local, NULL, 0x0000, 0x0000, AIM_SSI_TYPE_GROUP, NULL);
 
 		tmp = aim_ssi_itemlist_add(&od->ssi.local, "1", 0x0000, 0xFFFF, AIM_SSI_TYPE_ICONINFO, NULL);
@@ -1138,9 +1165,9 @@
 		return -EINVAL;
 
 	/* Find the PRESENCEPREFS item, or add it if it does not exist */
-	if (!(tmp = aim_ssi_itemlist_finditem(od->ssi.local, NULL, NULL, AIM_SSI_TYPE_PRESENCEPREFS))) {
+	if (!(tmp = aim_ssi_itemlist_finditem(&od->ssi.local, NULL, NULL, AIM_SSI_TYPE_PRESENCEPREFS))) {
 		/* Make sure the master group exists */
-		if (aim_ssi_itemlist_find(od->ssi.local, 0x0000, 0x0000) == NULL)
+		if (aim_ssi_itemlist_find(&od->ssi.local, 0x0000, 0x0000) == NULL)
 			aim_ssi_itemlist_add(&od->ssi.local, NULL, 0x0000, 0x0000, AIM_SSI_TYPE_GROUP, NULL);
 
 		tmp = aim_ssi_itemlist_add(&od->ssi.local, NULL, 0x0000, 0xFFFF, AIM_SSI_TYPE_PRESENCEPREFS, NULL);
@@ -1265,7 +1292,7 @@
 	if (!(snac->flags & 0x0001)) {
 		/* Make a copy of the list */
 		struct aim_ssi_item *cur;
-		for (cur=od->ssi.official; cur; cur=cur->next)
+		for (cur=od->ssi.official.data; cur; cur=cur->next)
 			aim_ssi_itemlist_add(&od->ssi.local, cur->name, cur->gid, cur->bid, cur->type, cur->data);
 
 		od->ssi.received_data = TRUE;
@@ -1415,18 +1442,16 @@
 			data = NULL;
 
 		/* Replace the 2 local items with the given one */
-		if ((item = aim_ssi_itemlist_find(od->ssi.local, gid, bid))) {
+		if ((item = aim_ssi_itemlist_find(&od->ssi.local, gid, bid))) {
 			item->type = type;
-			g_free(item->name);
-			item->name = g_strdup(name);
+			aim_ssi_item_set_name(&od->ssi.local, item, name);
 			aim_tlvlist_free(item->data);
 			item->data = aim_tlvlist_copy(data);
 		}
 
-		if ((item = aim_ssi_itemlist_find(od->ssi.official, gid, bid))) {
+		if ((item = aim_ssi_itemlist_find(&od->ssi.official, gid, bid))) {
 			item->type = type;
-			g_free(item->name);
-			item->name = g_strdup(name);
+			aim_ssi_item_set_name(&od->ssi.official, item, name);
 			aim_tlvlist_free(item->data);
 			item->data = aim_tlvlist_copy(data);
 		}
@@ -1460,9 +1485,9 @@
 		byte_stream_get16(bs);
 		byte_stream_advance(bs, byte_stream_get16(bs));
 
-		if ((del = aim_ssi_itemlist_find(od->ssi.local, gid, bid)))
+		if ((del = aim_ssi_itemlist_find(&od->ssi.local, gid, bid)))
 			aim_ssi_itemlist_del(&od->ssi.local, del);
-		if ((del = aim_ssi_itemlist_find(od->ssi.official, gid, bid)))
+		if ((del = aim_ssi_itemlist_find(&od->ssi.official, gid, bid)))
 			aim_ssi_itemlist_del(&od->ssi.official, del);
 
 		if ((userfunc = aim_callhandler(od, snac->family, snac->subtype)))
@@ -1503,7 +1528,8 @@
 			if (cur->action == SNAC_SUBTYPE_FEEDBAG_ADD) {
 				/* Remove the item from the local list */
 				/* Make sure cur->item is still valid memory */
-				if (aim_ssi_itemlist_valid(od->ssi.local, cur->item)) {
+				/* TODO: "Still valid memory"?  That's bad form. */
+				if (aim_ssi_itemlist_valid(&od->ssi.local, cur->item)) {
 					cur->name = g_strdup(cur->item->name);
 					aim_ssi_itemlist_del(&od->ssi.local, cur->item);
 				}
@@ -1511,11 +1537,10 @@
 
 			} else if (cur->action == SNAC_SUBTYPE_FEEDBAG_MOD) {
 				/* Replace the local item with the item from the official list */
-				if (aim_ssi_itemlist_valid(od->ssi.local, cur->item)) {
+				if (aim_ssi_itemlist_valid(&od->ssi.local, cur->item)) {
 					struct aim_ssi_item *cur1;
-					if ((cur1 = aim_ssi_itemlist_find(od->ssi.official, cur->item->gid, cur->item->bid))) {
-						g_free(cur->item->name);
-						cur->item->name = g_strdup(cur1->name);
+					if ((cur1 = aim_ssi_itemlist_find(&od->ssi.official, cur->item->gid, cur->item->bid))) {
+						aim_ssi_item_set_name(&od->ssi.official, cur->item, cur1->name);
 						aim_tlvlist_free(cur->item->data);
 						cur->item->data = aim_tlvlist_copy(cur1->data);
 					}
@@ -1524,7 +1549,7 @@
 
 			} else if (cur->action == SNAC_SUBTYPE_FEEDBAG_DEL) {
 				/* Add the item back into the local list */
-				if (aim_ssi_itemlist_valid(od->ssi.official, cur->item)) {
+				if (aim_ssi_itemlist_valid(&od->ssi.official, cur->item)) {
 					aim_ssi_itemlist_add(&od->ssi.local, cur->item->name, cur->item->gid, cur->item->bid, cur->item->type, cur->item->data);
 				} else
 					cur->item = NULL;
@@ -1534,18 +1559,17 @@
 			/* Do the exact opposite */
 			if (cur->action == SNAC_SUBTYPE_FEEDBAG_ADD) {
 			/* Add the local item to the official list */
-				if (aim_ssi_itemlist_valid(od->ssi.local, cur->item)) {
+				if (aim_ssi_itemlist_valid(&od->ssi.local, cur->item)) {
 					aim_ssi_itemlist_add(&od->ssi.official, cur->item->name, cur->item->gid, cur->item->bid, cur->item->type, cur->item->data);
 				} else
 					cur->item = NULL;
 
 			} else if (cur->action == SNAC_SUBTYPE_FEEDBAG_MOD) {
 				/* Replace the official item with the item from the local list */
-				if (aim_ssi_itemlist_valid(od->ssi.local, cur->item)) {
+				if (aim_ssi_itemlist_valid(&od->ssi.local, cur->item)) {
 					struct aim_ssi_item *cur1;
-					if ((cur1 = aim_ssi_itemlist_find(od->ssi.official, cur->item->gid, cur->item->bid))) {
-						g_free(cur1->name);
-						cur1->name = g_strdup(cur->item->name);
+					if ((cur1 = aim_ssi_itemlist_find(&od->ssi.official, cur->item->gid, cur->item->bid))) {
+						aim_ssi_item_set_name(&od->ssi.official, cur1, cur->item->name);
 						aim_tlvlist_free(cur1->data);
 						cur1->data = aim_tlvlist_copy(cur->item->data);
 					}
@@ -1554,7 +1578,7 @@
 
 			} else if (cur->action == SNAC_SUBTYPE_FEEDBAG_DEL) {
 				/* Remove the item from the official list */
-				if (aim_ssi_itemlist_valid(od->ssi.official, cur->item))
+				if (aim_ssi_itemlist_valid(&od->ssi.official, cur->item))
 					aim_ssi_itemlist_del(&od->ssi.official, cur->item);
 				cur->item = NULL;
 			}
--- a/libpurple/protocols/oscar/oscar.c	Sat Mar 19 19:38:55 2011 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Sun Mar 20 20:20:52 2011 +0000
@@ -578,7 +578,7 @@
 	gc = data;
 	od = purple_connection_get_protocol_data(gc);
 	report_idle = strcmp((const char *)value, "none") != 0;
-	presence = aim_ssi_getpresence(od->ssi.local);
+	presence = aim_ssi_getpresence(&od->ssi.local);
 
 	if (report_idle)
 		aim_ssi_setpresence(od, presence | AIM_SSI_PRESENCE_FLAG_SHOWIDLE);
@@ -600,7 +600,7 @@
 
 	gc = data;
 	od = purple_connection_get_protocol_data(gc);
-	presence = aim_ssi_getpresence(od->ssi.local);
+	presence = aim_ssi_getpresence(&od->ssi.local);
 
 	if (value)
 		aim_ssi_setpresence(od, presence & ~AIM_SSI_PRESENCE_FLAG_NORECENTBUDDIES);
@@ -3709,7 +3709,7 @@
 	}
 
 	if (od->ssi.received_data) {
-		if (!aim_ssi_itemlist_finditem(od->ssi.local, gname, bname, AIM_SSI_TYPE_BUDDY)) {
+		if (!aim_ssi_itemlist_finditem(&od->ssi.local, gname, bname, AIM_SSI_TYPE_BUDDY)) {
 			purple_debug_info("oscar",
 					   "ssi: adding buddy %s to group %s\n", bname, gname);
 			aim_ssi_addbuddy(od, bname, gname, NULL, purple_buddy_get_alias_only(buddy), NULL, NULL, 0);
@@ -3721,8 +3721,8 @@
 				purple_prpl_got_user_status(account, bname,
 						OSCAR_STATUS_ID_MOBILE, NULL);
 			}
-		} else if (aim_ssi_waitingforauth(od->ssi.local,
-		                                  aim_ssi_itemlist_findparentname(od->ssi.local, bname),
+		} else if (aim_ssi_waitingforauth(&od->ssi.local,
+		                                  aim_ssi_itemlist_findparentname(&od->ssi.local, bname),
 		                                  bname)) {
 			/* Not authorized -- Re-request authorization */
 			oscar_auth_sendrequest(gc, bname);
@@ -3760,7 +3760,7 @@
 	OscarData *od = purple_connection_get_protocol_data(gc);
 
 	if (od->ssi.received_data) {
-		char *gname = aim_ssi_itemlist_findparentname(od->ssi.local, name);
+		char *gname = aim_ssi_itemlist_findparentname(&od->ssi.local, name);
 		if (gname) {
 			purple_debug_info("oscar",
 					   "ssi: changing the alias for buddy %s to %s\n", name, alias ? alias : "(none)");
@@ -3777,7 +3777,7 @@
 
 	if (od->ssi.received_data) {
 		const char *gname = purple_group_get_name(group);
-		if (aim_ssi_itemlist_finditem(od->ssi.local, gname, NULL, AIM_SSI_TYPE_GROUP)) {
+		if (aim_ssi_itemlist_finditem(&od->ssi.local, gname, NULL, AIM_SSI_TYPE_GROUP)) {
 			GList *cur, *groups = NULL;
 			PurpleAccount *account = purple_connection_get_account(gc);
 
@@ -3928,7 +3928,7 @@
 		gname = purple_group_get_name(g);
 		bname = purple_buddy_get_name(b);
 
-		if (aim_ssi_itemlist_exists(od->ssi.local, bname)) {
+		if (aim_ssi_itemlist_exists(&od->ssi.local, bname)) {
 			/* If the buddy is an ICQ user then load his nickname */
 			const char *servernick = purple_blist_node_get_string((PurpleBlistNode*)b, "servernick");
 			char *alias;
@@ -3937,7 +3937,7 @@
 				serv_got_alias(gc, bname, servernick);
 
 			/* Store local alias on server */
-			alias = aim_ssi_getalias(od->ssi.local, gname, bname);
+			alias = aim_ssi_getalias(&od->ssi.local, gname, bname);
 			balias = purple_buddy_get_local_buddy_alias(b);
 			if (!alias && balias && *balias)
 				aim_ssi_aliasbuddy(od, gname, bname, balias);
@@ -3960,7 +3960,7 @@
 		while (next != NULL) {
 			cur = next;
 			next = next->next;
-			if (!aim_ssi_itemlist_finditem(od->ssi.local, NULL, cur->data, AIM_SSI_TYPE_PERMIT)) {
+			if (!aim_ssi_itemlist_finditem(&od->ssi.local, NULL, cur->data, AIM_SSI_TYPE_PERMIT)) {
 				purple_debug_info("oscar",
 						"ssi: removing permit %s from local list\n", (const char *)cur->data);
 				purple_privacy_permit_remove(account, cur->data, TRUE);
@@ -3973,7 +3973,7 @@
 	while (next != NULL) {
 		cur = next;
 		next = next->next;
-		if (!aim_ssi_itemlist_finditem(od->ssi.local, NULL, cur->data, deny_entry_type)) {
+		if (!aim_ssi_itemlist_finditem(&od->ssi.local, NULL, cur->data, deny_entry_type)) {
 			purple_debug_info("oscar",
 					"ssi: removing deny %s from local list\n", (const char *)cur->data);
 			purple_privacy_deny_remove(account, cur->data, TRUE);
@@ -3981,7 +3981,7 @@
 	}
 
 	/* Presence settings (idle time visibility) */
-	tmp = aim_ssi_getpresence(od->ssi.local);
+	tmp = aim_ssi_getpresence(&od->ssi.local);
 	if (tmp != 0xFFFFFFFF) {
 		const char *idle_reporting_pref;
 		gboolean report_idle;
@@ -3999,7 +3999,7 @@
 
 	/*** Begin code for adding from server list to local list ***/
 
-	for (curitem=od->ssi.local; curitem; curitem=curitem->next) {
+	for (curitem=od->ssi.local.data; curitem; curitem=curitem->next) {
 		if (curitem->name && !g_utf8_validate(curitem->name, -1, NULL)) {
 			/* Got node with invalid UTF-8 in the name.  Skip it. */
 			purple_debug_warning("oscar", "ssi: server list contains item of "
@@ -4013,7 +4013,7 @@
 					struct aim_ssi_item *groupitem;
 					char *gname, *gname_utf8, *alias, *alias_utf8;
 
-					groupitem = aim_ssi_itemlist_find(od->ssi.local, curitem->gid, 0x0000);
+					groupitem = aim_ssi_itemlist_find(&od->ssi.local, curitem->gid, 0x0000);
 					gname = groupitem ? groupitem->name : NULL;
 					gname_utf8 = oscar_utf8_try_convert(account, od, gname);
 
@@ -4023,7 +4023,7 @@
 						purple_blist_add_group(g, NULL);
 					}
 
-					alias = aim_ssi_getalias(od->ssi.local, gname, curitem->name);
+					alias = aim_ssi_getalias_from_item(curitem);
 					alias_utf8 = oscar_utf8_try_convert(account, od, alias);
 
 					b = purple_find_buddy_in_group(account, curitem->name, g);
@@ -4091,7 +4091,7 @@
 				 * a part of your status and not really related to blocking.
 				 */
 				if (!od->icq && curitem->data) {
-					guint8 perm_deny = aim_ssi_getpermdeny(od->ssi.local);
+					guint8 perm_deny = aim_ssi_getpermdeny(&od->ssi.local);
 					if (perm_deny != 0 && perm_deny != account->perm_deny)
 					{
 						purple_debug_info("oscar",
@@ -4220,10 +4220,10 @@
 	if ((type != 0x0000) || (name == NULL))
 		return 1;
 
-	gname = aim_ssi_itemlist_findparentname(od->ssi.local, name);
+	gname = aim_ssi_itemlist_findparentname(&od->ssi.local, name);
 	gname_utf8 = gname ? oscar_utf8_try_convert(account, od, gname) : NULL;
 
-	alias = aim_ssi_getalias(od->ssi.local, gname, name);
+	alias = aim_ssi_getalias(&od->ssi.local, gname, name);
 	alias_utf8 = oscar_utf8_try_convert(account, od, alias);
 	g_free(alias);
 
@@ -4261,7 +4261,7 @@
 
 	}
 
-	ssi_item = aim_ssi_itemlist_finditem(od->ssi.local,
+	ssi_item = aim_ssi_itemlist_finditem(&od->ssi.local,
 			gname, name, AIM_SSI_TYPE_BUDDY);
 	if (ssi_item == NULL)
 	{
@@ -4634,8 +4634,8 @@
 	if (purple_presence_is_online(presence) == FALSE) {
 		char *gname;
 		if ((name) && (od) && (od->ssi.received_data) &&
-			(gname = aim_ssi_itemlist_findparentname(od->ssi.local, name)) &&
-			(aim_ssi_waitingforauth(od->ssi.local, gname, name))) {
+			(gname = aim_ssi_itemlist_findparentname(&od->ssi.local, name)) &&
+			(aim_ssi_waitingforauth(&od->ssi.local, gname, name))) {
 			return "not-authorized";
 		}
 	}
@@ -4702,8 +4702,8 @@
 	if ((od != NULL) && !purple_presence_is_online(presence))
 	{
 		const char *name = purple_buddy_get_name(b);
-		char *gname = aim_ssi_itemlist_findparentname(od->ssi.local, name);
-		if (aim_ssi_waitingforauth(od->ssi.local, gname, name))
+		char *gname = aim_ssi_itemlist_findparentname(&od->ssi.local, name);
+		if (aim_ssi_waitingforauth(&od->ssi.local, gname, name))
 			ret = g_strdup(_("Not Authorized"));
 		else
 			ret = g_strdup(_("Offline"));
@@ -4946,7 +4946,7 @@
 
 	data = g_new(struct name_data, 1);
 
-	comment = aim_ssi_getcomment(od->ssi.local, purple_group_get_name(g), name);
+	comment = aim_ssi_getcomment(&od->ssi.local, purple_group_get_name(g), name);
 	comment_utf8 = comment ? oscar_utf8_try_convert(account, od, comment) : NULL;
 
 	data->gc = gc;
@@ -5161,8 +5161,8 @@
 		 * waiting for authorization.
 		 */
 		char *gname;
-		gname = aim_ssi_itemlist_findparentname(od->ssi.local, bname);
-		if (gname && aim_ssi_waitingforauth(od->ssi.local, gname, bname))
+		gname = aim_ssi_itemlist_findparentname(&od->ssi.local, bname);
+		if (gname && aim_ssi_waitingforauth(&od->ssi.local, gname, bname))
 		{
 			act = purple_menu_action_new(_("Re-request Authorization"),
 			                           PURPLE_CALLBACK(oscar_auth_sendrequest_menu),
@@ -5313,7 +5313,7 @@
 		buddy = cur->data;
 		bname = purple_buddy_get_name(buddy);
 		gname = purple_group_get_name(purple_buddy_get_group(buddy));
-		if (aim_ssi_waitingforauth(od->ssi.local, gname, bname)) {
+		if (aim_ssi_waitingforauth(&od->ssi.local, gname, bname)) {
 			filtered_buddies = g_slist_prepend(filtered_buddies, buddy);
 		}
 	}
--- a/libpurple/protocols/oscar/oscar.h	Sat Mar 19 19:38:55 2011 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Sun Mar 20 20:20:52 2011 +0000
@@ -308,9 +308,14 @@
 
 #include "peer.h"
 
-/*
- * AIM Session: The main client-data interface.
- *
+struct aim_ssi_itemlist {
+	struct aim_ssi_item *data;
+	GHashTable *idx_gid_bid;
+	GHashTable *idx_all_named_items;
+};
+
+/**
+ * The main client-data interface.
  */
 struct _OscarData
 {
@@ -387,8 +392,8 @@
 	struct {
 		gboolean received_data;
 		guint16 numitems;
-		struct aim_ssi_item *official;
-		struct aim_ssi_item *local;
+		struct aim_ssi_itemlist official;
+		struct aim_ssi_itemlist local;
 		struct aim_ssi_tmp *pending;
 		time_t timestamp;
 		gboolean waiting_for_ack;
@@ -916,15 +921,16 @@
 /* 0x001a */ int aim_ssi_sendauthreply(OscarData *od, char *bn, guint8 reply, const char *msg);
 
 /* Client functions for retrieving SSI data */
-struct aim_ssi_item *aim_ssi_itemlist_find(struct aim_ssi_item *list, guint16 gid, guint16 bid);
-struct aim_ssi_item *aim_ssi_itemlist_finditem(struct aim_ssi_item *list, const char *gn, const char *bn, guint16 type);
-struct aim_ssi_item *aim_ssi_itemlist_exists(struct aim_ssi_item *list, const char *bn);
-char *aim_ssi_itemlist_findparentname(struct aim_ssi_item *list, const char *bn);
-int aim_ssi_getpermdeny(struct aim_ssi_item *list);
-guint32 aim_ssi_getpresence(struct aim_ssi_item *list);
-char *aim_ssi_getalias(struct aim_ssi_item *list, const char *gn, const char *bn);
-char *aim_ssi_getcomment(struct aim_ssi_item *list, const char *gn, const char *bn);
-gboolean aim_ssi_waitingforauth(struct aim_ssi_item *list, const char *gn, const char *bn);
+struct aim_ssi_item *aim_ssi_itemlist_find(struct aim_ssi_itemlist *list, guint16 gid, guint16 bid);
+struct aim_ssi_item *aim_ssi_itemlist_finditem(struct aim_ssi_itemlist *list, const char *gn, const char *bn, guint16 type);
+struct aim_ssi_item *aim_ssi_itemlist_exists(struct aim_ssi_itemlist *list, const char *bn);
+char *aim_ssi_itemlist_findparentname(struct aim_ssi_itemlist *list, const char *bn);
+int aim_ssi_getpermdeny(struct aim_ssi_itemlist *list);
+guint32 aim_ssi_getpresence(struct aim_ssi_itemlist *list);
+char *aim_ssi_getalias(struct aim_ssi_itemlist *list, const char *gn, const char *bn);
+char *aim_ssi_getalias_from_item(struct aim_ssi_item *item);
+char *aim_ssi_getcomment(struct aim_ssi_itemlist *list, const char *gn, const char *bn);
+gboolean aim_ssi_waitingforauth(struct aim_ssi_itemlist *list, const char *gn, const char *bn);
 
 /* Client functions for changing SSI data */
 int aim_ssi_addbuddy(OscarData *od, const char *name, const char *group, GSList *tlvlist, const char *alias, const char *comment, const char *smsnum, gboolean needauth);
--- a/libpurple/protocols/oscar/oscar_data.c	Sat Mar 19 19:38:55 2011 +0000
+++ b/libpurple/protocols/oscar/oscar_data.c	Sun Mar 20 20:20:52 2011 +0000
@@ -47,6 +47,12 @@
 	od->buddyinfo = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
 	od->handlerlist = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
 
+	od->ssi.local.idx_gid_bid = g_hash_table_new(g_direct_hash, g_direct_equal);
+	od->ssi.local.idx_all_named_items = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
+	od->ssi.official.idx_gid_bid = g_hash_table_new(g_direct_hash, g_direct_equal);
+	od->ssi.official.idx_all_named_items = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
 	/*
 	 * Register all the modules for this session...
 	 */
@@ -126,6 +132,12 @@
 	g_hash_table_destroy(od->buddyinfo);
 	g_hash_table_destroy(od->handlerlist);
 
+	g_hash_table_destroy(od->ssi.local.idx_gid_bid);
+	g_hash_table_destroy(od->ssi.local.idx_all_named_items);
+
+	g_hash_table_destroy(od->ssi.official.idx_gid_bid);
+	g_hash_table_destroy(od->ssi.official.idx_all_named_items);
+
 	g_free(od);
 }
 
--- a/libpurple/protocols/oscar/userinfo.c	Sat Mar 19 19:38:55 2011 +0000
+++ b/libpurple/protocols/oscar/userinfo.c	Sun Mar 20 20:20:52 2011 +0000
@@ -263,8 +263,8 @@
 				message = tmp;
 			}
 
-		} else if (aim_ssi_waitingforauth(od->ssi.local,
-			aim_ssi_itemlist_findparentname(od->ssi.local, purple_buddy_get_name(b)),
+		} else if (aim_ssi_waitingforauth(&od->ssi.local,
+			aim_ssi_itemlist_findparentname(&od->ssi.local, purple_buddy_get_name(b)),
 			purple_buddy_get_name(b)))
 		{
 			/* Note if an offline buddy is not authorized */
@@ -356,7 +356,7 @@
 	}
 
 	if ((b != NULL) && (bname != NULL) && (g != NULL) && (gname != NULL)) {
-		tmp = aim_ssi_getcomment(od->ssi.local, gname, bname);
+		tmp = aim_ssi_getcomment(&od->ssi.local, gname, bname);
 		if (tmp != NULL) {
 			char *tmp2 = g_markup_escape_text(tmp, strlen(tmp));
 			g_free(tmp);
@@ -543,4 +543,4 @@
 
 	purple_notify_userinfo(gc, userinfo->bn, user_info, NULL, NULL);
 	purple_notify_user_info_destroy(user_info);
-}
\ No newline at end of file
+}
--- a/libpurple/protocols/oscar/visibility.c	Sat Mar 19 19:38:55 2011 +0000
+++ b/libpurple/protocols/oscar/visibility.c	Sun Mar 20 20:20:52 2011 +0000
@@ -51,7 +51,7 @@
 static gboolean
 is_buddy_on_list(OscarData *od, const char *bname)
 {
-	return aim_ssi_itemlist_finditem(od->ssi.local, NULL, bname, get_buddy_list_type(od)) != NULL;
+	return aim_ssi_itemlist_finditem(&od->ssi.local, NULL, bname, get_buddy_list_type(od)) != NULL;
 }
 
 static void
@@ -102,7 +102,7 @@
 
 		buddy = cur->data;
 		bname = purple_buddy_get_name(buddy);
-		if (aim_ssi_itemlist_finditem(od->ssi.local, NULL, bname, list_type)) {
+		if (aim_ssi_itemlist_finditem(&od->ssi.local, NULL, bname, list_type)) {
 			filtered_buddies = g_slist_prepend(filtered_buddies, buddy);
 		}
 	}