/* packet-aim.c
 * Routines for AIM dissection
 * Copyright 2000, Adam Fritzler <mid@auk.cx>
 *
 * $Id: packet-aim.c,v 1.00 2000/05/28 17:04:23 oabad Exp $
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@unicom.net>
 * Copyright 1998 Gerald Combs
 *
 * Copied from README.developer
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>

#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif

#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif

#ifdef NEED_SNPRINTF_H
# ifdef HAVE_STDARG_H
#  include <stdarg.h>
# else
#  include <varargs.h>
# endif
# include "snprintf.h"
#endif

#include <string.h>
#include <glib.h>
#include "packet.h"

#define TCP_PORT_AIM 5190

/* Initialize the protocol and registered fields */
static int proto_aim = -1;
static int proto_aim_snac = -1;
static int hf_aim_channel = -1;
static int hf_aim_seqnum = -1;
static int hf_aim_flaplen = -1;

static int hf_aim_snac_family = -1;
static int hf_aim_snac_subtype = -1;
static int hf_aim_snac_flags0 = -1;
static int hf_aim_snac_flags1 = -1;
static int hf_aim_snac_reqid = -1;
static int hf_aim_snac_name = -1;

static int hf_aim_userinfo_sn = -1;
static int hf_aim_userinfo_warnlevel = -1;
static int hf_aim_userinfo_idletime = -1;
static int hf_aim_userinfo_class = -1;
static int hf_aim_userinfo_membersince = -1;
static int hf_aim_userinfo_onlinesince = -1;
static int hf_aim_userinfo_sessionlen = -1;
static int hf_aim_userinfo_capabilities = -1;
static int hf_aim_userinfo_unknown = -1;

static int hf_aim_icbmcookie = -1;
static int hf_aim_icbm_channel = -1;
static int hf_aim_icbm_unknown = -1;
static int hf_aim_icbm_ack = -1;
static int hf_aim_icbm_away = -1;
static int hf_aim_icbm_msgblock = -1;
static int hf_aim_icbm_msg = -1;
static int hf_aim_icbm_encflag1 = -1, hf_aim_icbm_encflag2 = -1;

/* Initialize the subtree pointers */
static gint ett_aim = -1;
static gint ett_aim_snac = -1;
static gint ett_aim_data = -1;
static gint ett_aim_icbmmsgblock = -1;

/*
 * The aim_tlv* and aim_info* functions are directly from 
 * libfaim CVS as of the start of July 2000.  
 * 
 * Its okay, really.  I'm the author.
 */
#define MAXSNLEN 32
#define aimutil_get16(buf) ((((*(buf))<<8)&0xff00) + ((*((buf)+1)) & 0xff))
#define aimutil_get32(buf) ((((*(buf))<<24)&0xff000000) + \
                            (((*((buf)+1))<<16)&0x00ff0000) + \
                            (((*((buf)+2))<< 8)&0x0000ff00) + \
                            (((*((buf)+3)    )&0x000000ff)))
struct aim_tlv_t {
  u_short type;
  u_short length;
  u_char *value;
};

/* List of above. */
struct aim_tlvlist_t {
  struct aim_tlv_t *tlv;
  struct aim_tlvlist_t *next;
};

#define AIM_CLASS_TRIAL         0x0001
#define AIM_CLASS_UNKNOWN2      0x0002
#define AIM_CLASS_AOL           0x0004
#define AIM_CLASS_UNKNOWN4      0x0008
#define AIM_CLASS_FREE          0x0010
#define AIM_CLASS_AWAY          0x0020
#define AIM_CLASS_UNKNOWN40     0x0040
#define AIM_CLASS_UNKNOWN80     0x0080

#define AIM_CAPS_BUDDYICON 0x01
#define AIM_CAPS_VOICE 0x02
#define AIM_CAPS_IMIMAGE 0x04
#define AIM_CAPS_CHAT 0x08
#define AIM_CAPS_GETFILE 0x10
#define AIM_CAPS_SENDFILE 0x20

static struct aim_tlv_t *aim_createtlv(void)
{
  struct aim_tlv_t *newtlv = NULL;
  newtlv = (struct aim_tlv_t *)malloc(sizeof(struct aim_tlv_t));
  memset(newtlv, 0, sizeof(struct aim_tlv_t));
  return newtlv;
}

static int aim_freetlv(struct aim_tlv_t **oldtlv)
{
  if (!oldtlv)
    return -1;
  if (!*oldtlv)
    return -1;
  if ((*oldtlv)->value)
    free((*oldtlv)->value);
  free(*(oldtlv));
  (*oldtlv) = NULL;

  return 0;
}

static struct aim_tlvlist_t *aim_readtlvchain(tvbuff_t *tvb, int pos)
{
  struct aim_tlvlist_t *list;
  struct aim_tlvlist_t *cur;
  
  u_short type;
  u_short length;

  int maxlen = tvb_length(tvb);

  list = NULL;
  
  while (pos < maxlen)
    {
      type = tvb_get_ntohs(tvb, pos);
      pos += 2;

      if (pos < maxlen)
	{
	  length = tvb_get_ntohs(tvb, pos);
	  pos += 2;
	  
	  if ((pos+length) <= maxlen)
	    {
	      /*
	       * Okay, so now AOL has decided that any TLV of
	       * type 0x0013 can only be two bytes, despite
	       * what the actual given length is.  So here 
	       * we dump any invalid TLVs of that sort.  Hopefully
	       * theres no special cases to this special case.
	       *   - mid (30jun2000)
	       */
	      if ((type == 0x0013) && (length != 0x0002)) {
		printf("faim: skipping TLV t(0013) with invalid length (0x%04x)\n", length);
		length = 0x0002;
	      } else {
		cur = (struct aim_tlvlist_t *)malloc(sizeof(struct aim_tlvlist_t));
		memset(cur, 0x00, sizeof(struct aim_tlvlist_t));

		cur->tlv = aim_createtlv();	
		cur->tlv->type = type;
		cur->tlv->length = length;
		cur->tlv->value = (u_char *)malloc(length*sizeof(u_char));
		tvb_memcpy(tvb, cur->tlv->value, pos, length);
	      
		cur->next = list;
		list = cur;
	      }
	      pos += length;
	    }
	}
    }

  return list;
}

static void aim_freetlvchain(struct aim_tlvlist_t **list)
{
  struct aim_tlvlist_t *cur, *cur2;

  if (!list || !(*list))
    return;

  cur = *list;
  while (cur)
    {
      aim_freetlv(&cur->tlv);
      cur2 = cur->next;
      free(cur);
      cur = cur2;
    }
  list = NULL;
  return;
}

static int aim_counttlvchain(struct aim_tlvlist_t **list)
{
  struct aim_tlvlist_t *cur;
  int count = 0;

  if (!list || !(*list))
    return 0;

  for (cur = *list; cur; cur = cur->next)
    count++;
 
  return count;
}

/*
 * Grab the Nth TLV of type type in the TLV list list.
 */
static struct aim_tlv_t *aim_gettlv(struct aim_tlvlist_t *list, u_short type, int nth)
{
  int i;
  struct aim_tlvlist_t *cur;
  
  i = 0;
  for (cur = list; cur != NULL; cur = cur->next)
    {
      if (cur && cur->tlv)
	{
	  if (cur->tlv->type == type)
	    i++;
	  if (i >= nth)
	    return cur->tlv;
	}
    }
  return NULL;
}

static char *aim_gettlv_str(struct aim_tlvlist_t *list, u_short type, int nth)
{
  struct aim_tlv_t *tlv;
  char *newstr;

  if (!(tlv = aim_gettlv(list, type, nth)))
    return NULL;
  
  newstr = (char *) malloc(tlv->length + 1);
  memcpy(newstr, tlv->value, tlv->length);
  *(newstr + tlv->length) = '\0';

  return newstr;
}

/*
 * Capability blocks.  
 */
static u_char aim_caps[6][16] = {
  
  /* Buddy icon */
  {0x09, 0x46, 0x13, 0x46, 0x4c, 0x7f, 0x11, 0xd1, 
   0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00},
  
  /* Voice */
  {0x09, 0x46, 0x13, 0x41, 0x4c, 0x7f, 0x11, 0xd1, 
   0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00},
  
  /* IM image */
  {0x09, 0x46, 0x13, 0x45, 0x4c, 0x7f, 0x11, 0xd1, 
   0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00},
  
  /* Chat */
  {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 
   0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00},
  
  /* Get file */
  {0x09, 0x46, 0x13, 0x48, 0x4c, 0x7f, 0x11, 0xd1,
   0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00},
  
  /* Send file */
  {0x09, 0x46, 0x13, 0x43, 0x4c, 0x7f, 0x11, 0xd1, 
   0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}
};

/* XXX convert to use tvbuffs */
static u_short aim_getcap(unsigned char *capblock, int buflen)
{
  u_short ret = 0;
  int y;
  int offset = 0;

  while (offset < buflen) {
    for(y=0; y < (sizeof(aim_caps)/0x10); y++) {
      if (memcmp(&aim_caps[y], capblock+offset, 0x10) == 0) {
	switch(y) {
	case 0: ret |= AIM_CAPS_BUDDYICON; break;
	case 1: ret |= AIM_CAPS_VOICE; break;
	case 2: ret |= AIM_CAPS_IMIMAGE; break;
	case 3: ret |= AIM_CAPS_CHAT; break;
	case 4: ret |= AIM_CAPS_GETFILE; break;
	case 5: ret |= AIM_CAPS_SENDFILE; break;
	default: ret |= 0xff00; break;
	}
      }
    }
    offset += 0x10;
  } 
  return ret;
}

static char *aim_getsnacname(unsigned short family, unsigned short subtype)
{
  unsigned short maxf;
  unsigned short maxs;

  char *literals[14][25] = {
    {"Invalid", 
     NULL
    },
    {"General", 
     "Invalid",
     "Error",
     "Client Ready",
     "Server Ready",
     "Service Request",
     "Redirect",
     "Rate Information Request",
     "Rate Information",
     "Rate Information Ack",
     NULL,
     "Rate Information Change",
     "Server Pause",
     NULL,
     "Server Resume",
     "Request Personal User Information",
     "Personal User Information",
     "Evil Notification",
     NULL,
     "Migration notice",
     "Message of the Day",
     "Set Privacy Flags",
     "Well Known URL",
     "NOP"
    },
    {"Location", 
      "Invalid",
      "Error",
      "Request Rights",
      "Rights Information", 
      "Set user information", 
      "Request User Information", 
      "User Information", 
      "Watcher Sub Request",
      "Watcher Notification"
    },
    {"Buddy List Management", 
      "Invalid", 
      "Error", 
      "Request Rights",
      "Rights Information",
      "Add Buddy", 
      "Remove Buddy", 
      "Watcher List Query", 
      "Watcher List Response", 
      "Watcher SubRequest", 
      "Watcher Notification", 
      "Reject Notification", 
      "Oncoming Buddy", 
      "Offgoing Buddy"
    },
    {"Messeging", 
      "Invalid",
      "Error", 
      "Add ICBM Parameter",
      "Remove ICBM Parameter", 
      "Request Parameter Information",
      "Parameter Information",
      "Outgoing Message", 
      "Incoming Message",
      "Evil Request",
      "Evil Reply", 
      "Missed Calls",
      "Message Error", 
      "Host Ack"
    },
    {"Advertisements", 
      "Invalid", 
      "Error", 
      "Request Ad",
      "Ad Data (GIFs)"
    },
    {"Invitation / Client-to-Client", 
     "Invalid",
     "Error",
     "Invite a Friend",
     "Invitation Ack"
    },
    {"Administrative", 
      "Invalid",
      "Error",
      "Information Request",
      "Information Reply",
      "Information Change Request",
      "Information Chat Reply",
      "Account Confirm Request",
      "Account Confirm Reply",
      "Account Delete Request",
      "Account Delete Reply"
    },
    {"Popups", 
      "Invalid",
      "Error",
      "Display Popup"
    },
    {"BOS", 
      "Invalid",
      "Error",
      "Request Rights",
      "Rights Response",
      "Set group permission mask",
      "Add permission list entries",
      "Delete permission list entries",
      "Add deny list entries",
      "Delete deny list entries",
      "Server Error"
    },
    {"User Lookup", 
      "Invalid",
      "Error",
      "Search Request",
      "Search Response"
    },
    {"Stats", 
      "Invalid",
      "Error",
      "Set minimum report interval",
      "Report Events"
    },
    {"Translate", 
      "Invalid",
      "Error",
      "Translate Request",
      "Translate Reply",
    },
    {"Chat Navigation", 
      "Invalid",
      "Error",
      "Request rights",
      "Request Exchange Information",
      "Request Room Information",
      "Request Occupant List",
      "Search for Room",
      "Outgoing Message", 
      "Incoming Message",
      "Evil Request", 
      "Evil Reply", 
      "Chat Error",
    }
  };

  maxf = sizeof(literals) / sizeof(literals[0]);
  maxs = sizeof(literals[0]) / sizeof(literals[0][0]);

  if((family < maxf) && (subtype+1 < maxs) && (literals[family][subtype] != NULL))
    return literals[family][subtype+1];
  return NULL;
}

/*
 * This is basically aim_extractuserinfo() from libfaim, converted to use
 * tvbuffs and add widgets occasionally.
 */
static int dissect_aim_userinfo(tvbuff_t *tvb, int offset, proto_tree *tree)
{
  unsigned short tempshort;
  char sn[MAXSNLEN+1];
  proto_item *tv;
  int offsetsave,lastvalid=1;
  unsigned short tlvcnt,curtlv=0;

  offsetsave = offset;
  tempshort = tvb_get_guint8(tvb, offset);
  offset++;
  tvb_memcpy(tvb, sn, offset, tempshort);
  sn[tempshort] = '\0';
  offset += tempshort;
  tv = proto_tree_add_string_format(tree, hf_aim_userinfo_sn, tvb, offsetsave, offset-offsetsave, sn, "Screen Name: %s", sn);

  tempshort = tvb_get_ntohs(tvb, offset);
  /* XXX if theres'a add_float, we should use that and give in a nice way */
  tv = proto_tree_add_uint_format(tree, hf_aim_userinfo_warnlevel, tvb, offset, 2, tempshort, "Warning Level: 0x%04x", tempshort); 
  offset += 2;
  
  tlvcnt = tvb_get_ntohs(tvb, offset);
  offset += 2;

  while (curtlv < tlvcnt) {
    unsigned short curtype;

    lastvalid = 1;
    curtype = tvb_get_ntohs(tvb, offset);

    switch (curtype) {
      /*
       * Type = 0x0000: Invalid
       *
       * AOL has been trying to throw these in just to break us.
       * They're real nice guys over there at AOL.  
       *
       * Just skip the two zero bytes and continue on. (This doesn't
       * count towards tlvcnt!)
       */
    case 0x0000:
      lastvalid = 0;
      offset += 2;
      break;

      /*
       * Type = 0x0001: Member Class.   
       * 
       * Specified as any of the following bitwise ORed together:
       *      0x0001  Trial (user less than 60days)
       *      0x0002  Unknown bit 2
       *      0x0004  AOL Main Service user
       *      0x0008  Unknown bit 4
       *      0x0010  Free (AIM) user 
       *      0x0020  Away
       *
       */
    case 0x0001: {
      unsigned short class;
      char classstr[512];
      int stroff = 0;

      class = tvb_get_ntohs(tvb, offset+4);

      stroff += sprintf(classstr+stroff, "Class: 0x%04x (", class);
      if (class & AIM_CLASS_TRIAL)
	stroff += sprintf(classstr+stroff, "Trial ");
      if (class & AIM_CLASS_UNKNOWN2) 
	stroff += sprintf(classstr+stroff, "Unknown2 ");
      if (class & AIM_CLASS_AOL)
	stroff += sprintf(classstr+stroff, "AOL ");
      if (class & AIM_CLASS_UNKNOWN4)
	stroff += sprintf(classstr+stroff, "Unknown4 ");
      if (class & AIM_CLASS_FREE)
	stroff += sprintf(classstr+stroff, "Free ");
      if (class & AIM_CLASS_AWAY)
	stroff += sprintf(classstr+stroff, "Away ");
      if (class & AIM_CLASS_UNKNOWN40)
	stroff += sprintf(classstr+stroff, "Unknown40 ");
      if (class & AIM_CLASS_UNKNOWN80)
	stroff += sprintf(classstr+stroff, "Unknown80 ");
      stroff += sprintf(classstr+stroff-1, ")");

      proto_tree_add_string_format(tree, hf_aim_userinfo_class, tvb, offset, 6, classstr, classstr);
      break;
    }

      /*
       * Type = 0x0002: Member-Since date. 
       *
       * The time/date that the user originally
       * registered for the service, stored in 
       * time_t format
       */
    case 0x0002: {
      unsigned long membersince;
      
      membersince = tvb_get_ntohl(tvb, offset+4);
      proto_tree_add_uint_format(tree, hf_aim_userinfo_membersince, tvb, offset, 8, membersince, "Member since: 0x%08lx (time_t)", membersince);
      break;
    }

      /*
       * Type = 0x0003: On-Since date.
       *
       * The time/date that the user started 
       * their current session, stored in time_t
       * format.
       */
    case 0x0003: {
      unsigned long onlinesince;

      onlinesince = tvb_get_ntohl(tvb, offset+4);
      proto_tree_add_uint_format(tree, hf_aim_userinfo_onlinesince, tvb, offset, 8, onlinesince, "Online since: 0x%08lx (time_t)", onlinesince);
      break;
    }

      /*
       * Type = 0x0004: Idle time.
       *
       * Number of seconds since the user
       * actively used the service.
       */
    case 0x0004: {
      unsigned short idletime;
      idletime = tvb_get_ntohs(tvb, offset+4);
      proto_tree_add_uint_format(tree, hf_aim_userinfo_idletime, tvb, offset, 6, idletime, "Idle time: 0x%04x seconds", idletime);
      break;
    } 

      /*
       * Type = 0x000d
       *
       * Capability information.  Not real sure of
       * actual decoding.  See comment on aim_bos_setprofile()
       * in aim_misc.c about the capability block, its the same.
       *
       */
    case 0x000d: {
	int len;
	unsigned short caps;
	unsigned char *buf;
	char capstr[512];
	int stroff = 0;

	len = tvb_get_ntohs(tvb, offset+2);
	if (!len)
	  break;
	
	buf = tvb_get_ptr(tvb, offset+4, len);
	caps = aim_getcap(buf, len);
	
	/* XXX do we have to free() the pointer from tvb_get_ptr? */

	stroff += sprintf(capstr+stroff, "Capabilities: 0x%04x (", caps);
	if (caps & AIM_CAPS_BUDDYICON)
	  stroff += sprintf(capstr+stroff, "Buddyicon ");
	if (caps & AIM_CAPS_VOICE)
	  stroff += sprintf(capstr+stroff, "Voice ");
	if (caps & AIM_CAPS_IMIMAGE)
	  stroff += sprintf(capstr+stroff, "IMImage ");
	if (caps & AIM_CAPS_CHAT)
	  stroff += sprintf(capstr+stroff, "Chat ");
	if (caps & AIM_CAPS_GETFILE)
	  stroff += sprintf(capstr+stroff, "GetFile ");
	if (caps & AIM_CAPS_SENDFILE)
	  stroff += sprintf(capstr+stroff, "SendFile ");
	stroff += sprintf(capstr+stroff-1, ")");

	proto_tree_add_string_format(tree, hf_aim_userinfo_capabilities, tvb, offset, 2+2+len, capstr, capstr);
      }
      break;
      
      /*
       * Type = 0x000e
       *
       * Unknown.  Always of zero length, and always only
       * on AOL users.
       *
       * Ignore.
       *
       */
    case 0x000e:
      break;
      
      /*
       * Type = 0x000f: Session Length. (AIM)
       * Type = 0x0010: Session Length. (AOL)
       *
       * The duration, in seconds, of the user's
       * current session.
       *
       * Which TLV type this comes in depends
       * on the service the user is using (AIM or AOL).
       *
       */
    case 0x000f:
    case 0x0010: {
      unsigned long sessionlen;

      sessionlen = tvb_get_ntohl(tvb, offset+4);

      proto_tree_add_uint_format(tree, hf_aim_userinfo_sessionlen, tvb, offset, 8, sessionlen, "Session length: 0x%08lx seconds", sessionlen);
      break;
    }
      
      /*
       * Reaching here indicates that either AOL has
       * added yet another TLV for us to deal with, 
       * or the parsing has gone Terribly Wrong.
       *
       * Either way, inform the owner and attempt
       * recovery.
       *
       */
    default:
      {
	unsigned short type, length;
	type = tvb_get_ntohs(tvb, offset);
	length = tvb_get_ntohs(tvb, offset+2);
	/* XXX this may occur more than once -- is there any hazard to using hf_aim_userinfo_unknown over again? */
	proto_tree_add_string_format(tree, hf_aim_userinfo_unknown, tvb, offset, 2+2+length, NULL, "Unknown Userinfo TLV t(%04x) l(%04x)", type, length);
      }
#if 0
      {
	int len,z = 0, y = 0, x = 0;
	char tmpstr[80];
	printf("faim: userinfo: **warning: unexpected TLV:\n");
	printf("faim: userinfo:   sn    =%s\n", outinfo->sn);
	printf("faim: userinfo:   curtlv=0x%04x\n", curtlv);
	printf("faim: userinfo:   type  =0x%04x\n",aimutil_get16(&buf[i]));
	printf("faim: userinfo:   length=0x%04x\n", len = aimutil_get16(&buf[i+2]));
	printf("faim: userinfo:   data: \n");
	while (z<len)
	  {
	    x = sprintf(tmpstr, "faim: userinfo:      ");
	    for (y = 0; y < 8; y++)
	      {
		if (z<len)
		  {
		    sprintf(tmpstr+x, "%02x ", buf[i+4+z]);
		    z++;
		    x += 3;
		  }
		else
		  break;
	      }
	    printf("%s\n", tmpstr);
	  }
      }
#endif
      break;
    }

    /*
     * No matter what, TLV triplets should always look like this:
     *
     *   u_short type;
     *   u_short length;
     *   u_char  data[length];
     *
     */
    if (lastvalid) {
      offset += (2 + 2 + tvb_get_ntohs(tvb, offset+2));
      curtlv++;
    }
  }

  return offset;
}

/*
 * Since incoming messages are the same as outgoing once except for the 
 * way userinfo is handled, we combine the handlers.
 */
static int dissect_aim_msg(tvbuff_t *tvb, int offset, proto_tree *tree, unsigned short subtype)
{
  char tmpstr[512];
  int tmpoff = 0;
  int i,offsetsave;
  unsigned short channel;
  struct aim_tlvlist_t *tlvlist;

  offsetsave = offset;
  tmpoff = sprintf(tmpstr, "Message cookie: ");
  for (i = 0; i < 8; i++)
    tmpoff += sprintf(tmpstr+tmpoff, "0x%02x ", tvb_get_guint8(tvb, offset++));
  proto_tree_add_string_format(tree, hf_aim_icbmcookie, tvb, offsetsave, 8, NULL, tmpstr);
  
  channel = tvb_get_ntohs(tvb, offset);
  proto_tree_add_uint_format(tree, hf_aim_icbm_channel, tvb, offset, 2, channel, "ICBM Channel: 0x%04x", channel);
  offset += 2;

  /* XXX implement channel 2 icbms */
  if (channel != 0x0001)
    return offset;
  
  if (subtype == 0x0007) /* incoming message -- full userinfo block */
    offset = dissect_aim_userinfo(tvb, offset, tree);
  else if (subtype == 0x0006) { /* outgoing message -- just SN */
    unsigned short tempshort;
    int offsetsave;
    char sn[MAXSNLEN];

    offsetsave = offset;
    tempshort = tvb_get_guint8(tvb, offset);
    offset++;
    tvb_memcpy(tvb, sn, offset, tempshort);
    sn[tempshort] = '\0';
    offset += tempshort;
    proto_tree_add_string_format(tree, hf_aim_userinfo_sn, tvb, offsetsave, offset-offsetsave, sn, "Screen Name: %s", sn);
  }

  {
    /*
     * We can't use the neat libfaim TLV parsing routines here for a variety
     * reasons.  The most apparent and noticable reason is that using them
     * would make it impossible to highlight the correct parts of the packet
     * when a specific TLV is selected.  (Theres more technical reasons too.)
     */
    unsigned short type;
    unsigned short length;

    while (offset < tvb_length(tvb)) {
      type = tvb_get_ntohs(tvb, offset);
      length = tvb_get_ntohs(tvb, offset+2);
      
      switch (type) {
      case 0x0003:
	proto_tree_add_uint_format(tree, hf_aim_icbm_ack, tvb, offset, 2+2+length, 1, "Ack requested");
	break;
      case 0x0004: {
	proto_tree_add_uint_format(tree, hf_aim_icbm_away, tvb, offset, 2+2+length, 1, "Away message");
	break;
      }
      case 0x0002: {
	int j = 0, y,z;
	unsigned short flag1, flag2;
	proto_tree *msgblock_tree;
	proto_item *tv;

	tv = proto_tree_add_string_format(tree, hf_aim_icbm_msgblock, tvb, offset, 2+2+length, NULL, "Message block");
	msgblock_tree = proto_item_add_subtree(tv, ett_aim_icbmmsgblock);

	j = 4; /* skip tlv header */

	j += 2;
	y = tvb_get_ntohs(tvb, offset+j);
	j += 2 + y + 2;

	z = tvb_get_ntohs(tvb, offset+j); /* message length, including flags */
	j += 2;

	flag1 = tvb_get_ntohs(tvb, offset+j);
	proto_tree_add_uint_format(msgblock_tree, hf_aim_icbm_encflag1, tvb, offset+j, 2, flag1, "Encoding Flag 1: 0x%04x", flag1);
	j += 2;
	flag2 = tvb_get_ntohs(tvb, offset+j);
	proto_tree_add_uint_format(msgblock_tree, hf_aim_icbm_encflag1, tvb, offset+j, 2, flag2, "Encoding Flag 2: 0x%04x", flag2);
	j += 2;

	z -= 4; /* exclude flag words */
	/* message is now (unterminated) at offset+j */
	/* XXX should we display the message? */
	proto_tree_add_string_format(msgblock_tree, hf_aim_icbm_msg, tvb, offset+j, z, NULL, "Message (%d bytes)", z);

	break;
      }
      default: {
	/* XXX this may occur more than once -- is there any hazard to using hf_aim_icbm_unknown over again? */
	proto_tree_add_string_format(tree, hf_aim_icbm_unknown, tvb, offset, 2+2+length, NULL, "Unknown ICBM TLV t(%04x) l(%04x)", type, length);
	break;
      }
      }
      offset += 2 + 2 + length;
    }
  }
  
  return offset;
}

static int dissect_aim_snac(tvbuff_t *tvb, proto_tree *tree, unsigned short *familyret, unsigned short *subtyperet)
{
  unsigned short family, subtype;
  unsigned char flags0, flags1;
  unsigned long reqid;
  proto_tree *snac_tree;
  proto_item *snac_item;
  int offset = 6; /* skip FLAP */
  proto_item *tv;

  if (!tree) {
    *familyret = tvb_get_ntohs(tvb, offset);
    *subtyperet = tvb_get_ntohs(tvb, offset+2);
    return 4;
  }

  /* SNAC is kinda a sub-protocol, so we'll declare it as such */
  proto_aim_snac = proto_register_protocol("SNAC", "aim.snac");
  snac_item = proto_tree_add_item(tree, proto_aim_snac, tvb, 6, 10, FALSE);
  snac_tree = proto_item_add_subtree(snac_item, ett_aim_snac);
		
  family = tvb_get_ntohs(tvb, offset);
  tv = proto_tree_add_uint_format(snac_tree, hf_aim_snac_family, tvb, offset, 2, family, "Family: 0x%04x", family);
  offset += 2;

  subtype = tvb_get_ntohs(tvb, offset);
  tv = proto_tree_add_uint_format(snac_tree, hf_aim_snac_subtype, tvb, offset, 2, subtype, "Subtype: 0x%04x", subtype);
  offset += 2;

  flags0 = tvb_get_guint8(tvb, offset);
  tv = proto_tree_add_uint_format(snac_tree, hf_aim_snac_flags0, tvb, offset, 1, flags0, "Flags0: 0x%02x", flags0);
  offset++;

  flags1 = tvb_get_guint8(tvb, offset);
  tv = proto_tree_add_uint_format(snac_tree, hf_aim_snac_flags1, tvb, offset, 1, flags1, "Flags1: 0x%02x", flags1);
  offset++;

  reqid = tvb_get_ntohl(tvb, offset);
  tv = proto_tree_add_uint_format(snac_tree, hf_aim_snac_reqid, tvb, offset, 4, reqid, "Request ID: 0x%08lx", reqid);
  offset += 4;

  *familyret = family;
  *subtyperet = subtype;

  return offset;
}

static void dissect_aim_chan2(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, frame_data *fd)
{
  int offset;
  proto_item *tv;
  unsigned short family, subtype;
  char *snacname = NULL;
  proto_tree *data_tree;
  
  offset = dissect_aim_snac(tvb, tree, &family, &subtype);

  snacname = aim_getsnacname(family, subtype);

  if (check_col(fd, COL_INFO))
    col_add_str(fd, COL_INFO, snacname);

  if (!tree)
    return;

  if ((snacname = aim_getsnacname(family, subtype)))
    tv = proto_tree_add_string_format(tree, hf_aim_snac_name, tvb, offset, tvb_length(tvb), snacname, "%s", snacname);
  else
    tv = proto_tree_add_string_format(tree, hf_aim_snac_name, tvb, offset, tvb_length(tvb), NULL, "Unknown SNAC");

  data_tree = proto_item_add_subtree(tv, ett_aim_data);

  switch (family) {
  case 0x0003:
    switch (subtype) {
    case 0x000b: { /* 0003/000b -- Oncoming buddy -- contains only userinfo */
      dissect_aim_userinfo(tvb, offset, data_tree);
      break;
    }
    default:
      printf("aim: unsupported subtype 0x%04x in family 0x00003\n", subtype);
      break;
    }
    break;
  case 0x0004:
    switch (subtype) {
    case 0x0006:
    case 0x0007:
      dissect_aim_msg(tvb, offset, data_tree, subtype);
      break;
    default:
      printf("aim: unsupported subtype 0x%04x in family 0x00004\n", subtype);
      break;
    }
    break;
  default:
    printf("aim: unsupported family 0x%04x\n", family);
  }

  return;
}

/* Code to actually dissect the packets */
#if 0
static gboolean
dissect_aim(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
#else
static gboolean
dissect_aim(const u_char *pd, int offset, frame_data *fd, proto_tree *tree)
{
        tvbuff_t *tvb = tvb_create_from_top(offset);
        packet_info *pinfo = &pi;
#endif

	proto_item *ti,*tv;
	proto_tree *aim_tree;

	unsigned char channel = 0;
	unsigned short seqnum = 0;
	unsigned short flaplen = 0;

	if (pi.srcport != TCP_PORT_AIM && pi.destport != TCP_PORT_AIM) 
	  return FALSE;

	if (tvb_get_guint8(tvb, 0) != 0x2a) 
	  return FALSE;
	
	channel = tvb_get_guint8(tvb, 1);
	if (channel != 0x01 && channel != 0x02 && channel != 0x04)
	  return FALSE;

	if (check_col(fd, COL_PROTOCOL)) 
		col_add_str(fd, COL_PROTOCOL, "AIM");

	seqnum = tvb_get_ntohs(tvb, 2);
	flaplen = tvb_get_ntohs(tvb, 4);

	if (tree) {
	  ti = proto_tree_add_item(tree, proto_aim, tvb, 0, tvb_length(tvb), FALSE);
	  aim_tree = proto_item_add_subtree(ti, ett_aim);
	  
	  tv = proto_tree_add_uint_format(aim_tree, hf_aim_channel, tvb, 1, 1, channel, "Channel: 0x%02x", channel);
	  tv = proto_tree_add_uint_format(aim_tree, hf_aim_seqnum, tvb, 2, 2, seqnum, "Sequence number: 0x%04x", seqnum);
	  tv = proto_tree_add_uint_format(aim_tree, hf_aim_flaplen, tvb, 4, 2, flaplen, "Payload Length: 0x%04x", flaplen);
	} else
	  aim_tree = NULL;

	switch (channel) {
	case 0x02: {
	  dissect_aim_chan2(tvb, pinfo, aim_tree, fd);
	  break;
	}
	case 0x04:
	case 0x01:
	default:
	  printf("aim: unsupported channel 0x%02x\n", channel);
	  break;
	}

	return TRUE;
}

/* Register the protocol with Ethereal */
void proto_register_aim(void)
{                 

/* Setup list of header fields */
	static hf_register_info hf[] = {

	        /* FLAP only */
		{ &hf_aim_channel,
		  { "channel", "aim.channel",
		    FT_UINT8, BASE_HEX, NULL, 0, "Channel"}},

		{ &hf_aim_seqnum,
		  { "seqnum", "aim.seqnum",
		    FT_UINT16, BASE_HEX, NULL, 0, "Sequence Number"}},

		{ &hf_aim_flaplen,
		  { "flaplen", "aim.flaplen",
		    FT_UINT16, BASE_HEX, NULL, 0, "Payload Length"}},


		/* SNAC header */
		{ &hf_aim_snac_family,
		  { "snac_family", "aim.snac.family",
		    FT_UINT16, BASE_HEX, NULL, 0, "Family"}},

		{ &hf_aim_snac_subtype,
		  { "snac_subtype", "aim.snac.subtype",
		    FT_UINT16, BASE_HEX, NULL, 0, "Subtype"}},

		{ &hf_aim_snac_flags0,
		  { "snac_flags0", "aim.snac.flags0",
		    FT_UINT8, BASE_HEX, NULL, 0, "Flags0"}},

		{ &hf_aim_snac_flags1,
		  { "snac_flags1", "aim.snac.flags1",
		    FT_UINT8, BASE_HEX, NULL, 0, "Flags1"}},

		{ &hf_aim_snac_reqid,
		  { "snac_reqid", "aim.snac.reqid",
		    FT_UINT32, BASE_HEX, NULL, 0, "Request ID"}},

		{ &hf_aim_snac_name,
		  { "snac_name", "aim.snac.name",
		    FT_STRING, BASE_HEX, NULL, 0, "Name"}},

		{ &hf_aim_userinfo_sn,
		  { "userinfo_sn", "aim.userinfo.sn",
		    FT_STRING, 0, NULL, 0, "Screen Name"}},
		{ &hf_aim_userinfo_warnlevel,
		  { "userinfo_warnlevel", "aim.userinfo.warnlevel",
		    FT_UINT16, 0, NULL, 0, "Warning Level"}},
		{ &hf_aim_userinfo_idletime,
		  { "userinfo_idletime", "aim.userinfo.idletime",
		    FT_UINT16, 0, NULL, 0, "Idle Time (seconds)"}},
		{ &hf_aim_userinfo_class,
		  { "userinfo_class", "aim.userinfo.class",
		    FT_STRING, 0, NULL, 0, "Class"}},
		{ &hf_aim_userinfo_membersince,
		  { "userinfo_membersince", "aim.userinfo.membersince",
		    FT_UINT32, 0, NULL, 0, "Member Since (time_t)"}},
		{ &hf_aim_userinfo_onlinesince,
		  { "userinfo_onlinesince", "aim.userinfo.onlinesince",
		    FT_UINT32, 0, NULL, 0, "Online Since (time_t)"}},
		{ &hf_aim_userinfo_sessionlen,
		  { "userinfo_sessionlen", "aim.userinfo.sessionlen",
		    FT_UINT32, 0, NULL, 0, "Session length (seconds)"}},
		{ &hf_aim_userinfo_capabilities,
		  { "userinfo_capabilities", "aim.userinfo.capabilities",
		    FT_STRING, 0, NULL, 0, "Capabilities"}},
		{ &hf_aim_userinfo_unknown,
		  { "userinfo_unknown", "aim.userinfo.unknown",
		    FT_STRING, 0, NULL, 0, "Unknown Userinfo TLV"}},
		
		{ &hf_aim_icbmcookie,
		  { "icbmcookie", "aim.icbmcookie",
		    FT_STRING, 0, NULL, 0, "ICBM Cookie"}},
		{ &hf_aim_icbm_channel,
		  { "icbm_channel", "aim.icbm_channel",
		    FT_UINT16, 0, NULL, 0, "ICBM Channel"}},
		{ &hf_aim_icbm_unknown,
		  { "icbm_unknown", "aim.icbm_unknown",
		    FT_STRING, 0, NULL, 0, "Unknown ICBM TLV"}},
		{ &hf_aim_icbm_ack,
		  { "icbm_ack", "aim.icbm_ack",
		    FT_UINT16, 0, NULL, 0, "ICBM Ack Requested"}},
		{ &hf_aim_icbm_away,
		  { "icbm_away", "aim.icbm_away",
		    FT_UINT16, 0, NULL, 0, "ICBM Away message"}},
		{ &hf_aim_icbm_msgblock,
		  { "icbm_msgblock", "aim.icbm_msgblock",
		    FT_STRING, 0, NULL, 0, "ICBM Message Block"}},
		{ &hf_aim_icbm_msg,
		  { "icbm_msg", "aim.icbm_msg",
		    FT_STRING, 0, NULL, 0, "Message"}},
		{ &hf_aim_icbm_encflag1,
		  { "icbm_encflag1", "aim.icbm_encflag1",
		    FT_UINT8, 0, NULL, 0, "Encoding Flag 1"}},
		{ &hf_aim_icbm_encflag2,
		  { "icbm_encflag2", "aim.icbm_encflag2",
		    FT_UINT8, 0, NULL, 0, "Encoding Flag 2"}},
	};

/* Setup protocol subtree array */
	static gint *ett[] = {
		&ett_aim,
		&ett_aim_snac,
		&ett_aim_data,
		&ett_aim_icbmmsgblock,
	};

/* Register the protocol name and description */
	proto_aim = proto_register_protocol("OSCAR/FLAP (AOL Instant Messenger)", "aim");

/* Required function calls to register the header fields and subtrees used */
	proto_register_field_array(proto_aim, hf, array_length(hf));
	proto_register_subtree_array(ett, array_length(ett));
};

void
proto_reg_handoff_aim(void)
{
        heur_dissector_add("tcp", dissect_aim);
}
