
/*
  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 <string.h>

#include "mw_common.h"


/** @todo the *_get functions should make sure to clear their
    structures in the event of failure, to prevent memory leaks */


#define MW16_PUT(b, val) \
  *(b)++ = ((val) >> 0x08) & 0xff; \
  *(b)++ = (val) & 0xff;


#define MW16_GET(b, val) \
  val = (*(b)++ & 0xff) << 8; \
  val = val | (*(b)++ & 0xff);


#define MW32_PUT(b, val) \
  *(b)++ = ((val) >> 0x18) & 0xff; \
  *(b)++ = ((val) >> 0x10) & 0xff; \
  *(b)++ = ((val) >> 0x08) & 0xff; \
  *(b)++ = (val) & 0xff;


#define MW32_GET(b, val) \
  val = (*(b)++ & 0xff) << 0x18; \
  val = val | (*(b)++ & 0xff) << 0x10; \
  val = val | (*(b)++ & 0xff) << 0x08; \
  val = val | (*(b)++ & 0xff);


struct mwPutBuffer {
  guchar *buf;  /**< head of buffer */
  gsize len;    /**< length of buffer */

  guchar *ptr;  /**< offset to first unused byte */
  gsize rem;    /**< count of unused bytes remaining */
};


struct mwGetBuffer {
  guchar *buf;  /**< head of buffer */
  gsize len;    /**< length of buffer */

  guchar *ptr;  /**< offset to first unused byte */
  gsize rem;    /**< count of unused bytes remaining */

  gboolean wrap;   /**< TRUE to indicate buf shouldn't be freed */
  gboolean error;  /**< TRUE to indicate an error */
};


#define BUFFER_USED(buffer) \
  ((buffer)->len - (buffer)->rem)


/** ensure that there's at least enough space remaining in the put
    buffer to fit needed. */
static void ensure_buffer(struct mwPutBuffer *b, gsize needed) {
  if(b->rem < needed) {
    gsize len = b->len, use = BUFFER_USED(b);
    guchar *buf;

    /* newly created buffers are empty until written to, and then they
       have 1024 available */
    if(! len) len = 1024;

    /* double len until it's large enough to fit needed */
    while( (len - use) < needed ) len = len << 1;

    /* create the new buffer. if there was anything in the old buffer,
       copy it into the new buffer and free the old copy */
    buf = g_malloc(len);
    if(b->buf) {
      memcpy(buf, b->buf, use);
      g_free(b->buf);
    }

    /* put the new buffer into b */
    b->buf = buf;
    b->len = len;
    b->ptr = buf + use;
    b->rem = len - use;
  }
}


/** determine if there are at least needed bytes available in the
    buffer. sets the error flag if there's not at least needed bytes
    left in the buffer

    @returns true if there's enough data, false if not */
static gboolean check_buffer(struct mwGetBuffer *b, gsize needed) {
  if(! b->error)  b->error = (b->rem < needed);
  return ! b->error;
}


struct mwPutBuffer *mwPutBuffer_new() {
  return g_new0(struct mwPutBuffer, 1);
}


void mwPutBuffer_write(struct mwPutBuffer *b, gpointer data, gsize len) {
  g_return_if_fail(b != NULL);
  g_return_if_fail(data != NULL);

  if(! len) return;

  ensure_buffer(b, len);
  memcpy(b->ptr, data, len);
  b->ptr += len;
  b->rem -= len;
}


void mwPutBuffer_free(struct mwPutBuffer *b) {
  if(! b) return;
  g_free(b->buf);
  g_free(b);
}


void mwPutBuffer_finalize(struct mwOpaque *to, struct mwPutBuffer *from) {
  g_return_if_fail(to != NULL);
  g_return_if_fail(from != NULL);

  to->len = BUFFER_USED(from);
  to->data = from->buf;

  g_free(from);
}


struct mwGetBuffer *mwGetBuffer_new(struct mwOpaque *o) {
  struct mwGetBuffer *b = g_new0(struct mwGetBuffer, 1);

  if(o && o->len) {
    b->buf = b->ptr = g_memdup(o->data, o->len);
    b->len = b->rem = o->len;
  }

  return b;
}


struct mwGetBuffer *mwGetBuffer_wrap(const struct mwOpaque *o) {
  struct mwGetBuffer *b = g_new0(struct mwGetBuffer, 1);

  if(o && o->len) {
    b->buf = b->ptr = o->data;
    b->len = b->rem = o->len;
  }
  b->wrap = TRUE;

  return b;
}


gsize mwGetBuffer_read(struct mwGetBuffer *b, gpointer data, gsize len) {
  g_return_val_if_fail(b != NULL, 0);
  g_return_val_if_fail(data != NULL, 0);

  if(b->error) return 0;
  if(! len) return 0;

  if(b->rem < len)
    len = b->rem;

  memcpy(data, b->ptr, len);
  b->ptr += len;
  b->rem -= len;

  return len;
}


gsize mwGetBuffer_advance(struct mwGetBuffer *b, gsize len) {
  g_return_val_if_fail(b != NULL, 0);

  if(b->error) return 0;
  if(! len) return 0;

  if(b->rem < len)
    len = b->rem;

  b->ptr += len;
  b->rem -= len;

  return len;
}


void mwGetBuffer_reset(struct mwGetBuffer *b) {
  g_return_if_fail(b != NULL);

  b->rem = b->len;
  b->ptr = b->buf;
  b->error = FALSE;
}


gsize mwGetBuffer_remaining(struct mwGetBuffer *b) {
  g_return_val_if_fail(b != NULL, 0);
  return b->rem;
}


gboolean mwGetBuffer_error(struct mwGetBuffer *b) {
  g_return_val_if_fail(b != NULL, TRUE);
  return b->error;
}


void mwGetBuffer_free(struct mwGetBuffer *b) {
  if(! b) return;
  if(! b->wrap) g_free(b->buf);
  g_free(b);
}


#define guint16_buflen()  2


void guint16_put(struct mwPutBuffer *b, guint16 val) {
  g_return_if_fail(b != NULL);

  ensure_buffer(b, guint16_buflen());
  MW16_PUT(b->ptr, val);
  b->rem -= guint16_buflen();
}


void guint16_get(struct mwGetBuffer *b, guint16 *val) {
  g_return_if_fail(b != NULL);

  if(b->error) return;
  g_return_if_fail(check_buffer(b, guint16_buflen()));

  MW16_GET(b->ptr, *val);
  b->rem -= guint16_buflen();
}


guint16 guint16_peek(struct mwGetBuffer *b) {
  guchar *buf = b->buf;
  guint16 r = 0;
  
  if(b->rem >= guint16_buflen())
    MW16_GET(buf, r);

  return r;
}


#define guint32_buflen()  4


void guint32_put(struct mwPutBuffer *b, guint32 val) {
  g_return_if_fail(b != NULL);

  ensure_buffer(b, guint32_buflen());
  MW32_PUT(b->ptr, val);
  b->rem -= guint32_buflen();
}


void guint32_get(struct mwGetBuffer *b, guint32 *val) {
  g_return_if_fail(b != NULL);

  if(b->error) return;
  g_return_if_fail(check_buffer(b, guint32_buflen()));

  MW32_GET(b->ptr, *val);
  b->rem -= guint32_buflen();
}


guint32 guint32_peek(struct mwGetBuffer *b) {
  guchar *buf = b->buf;
  guint32 r = 0;

  if(b->rem >= guint32_buflen())
    MW32_GET(buf, r);

  return r;
}


#define gboolean_buflen()  1


void gboolean_put(struct mwPutBuffer *b, gboolean val) {
  g_return_if_fail(b != NULL);

  ensure_buffer(b, gboolean_buflen());
  *(b->ptr) = !! val;
  b->ptr++;
  b->rem--;
}


void gboolean_get(struct mwGetBuffer *b, gboolean *val) {
  g_return_if_fail(b != NULL);

  if(b->error) return;
  g_return_if_fail(check_buffer(b, gboolean_buflen()));

  *val = !! *(b->ptr);
  b->ptr++;
  b->rem--;
}


gboolean gboolean_peek(struct mwGetBuffer *b) {
  gboolean v = FALSE;

  if(b->rem >= gboolean_buflen())
    v = !! *(b->ptr);

  return v;
}


static gboolean mw_streq(const char *a, const char *b) {
  return (a == b) || (a && b && !strcmp(a, b));
}


void mwString_put(struct mwPutBuffer *b, const char *val) {
  gsize len = 0;

  g_return_if_fail(b != NULL);

  if(val) len = strlen(val);

  guint16_put(b, (guint16) len);

  if(len) {
    ensure_buffer(b, len);
    memcpy(b->ptr, val, len);
    b->ptr += len;
    b->rem -= len;
  }
}


void mwString_get(struct mwGetBuffer *b, char **val) {
  guint16 len = 0;

  g_return_if_fail(b != NULL);
  g_return_if_fail(val != NULL);

  *val = NULL;

  if(b->error) return;
  guint16_get(b, &len);

  g_return_if_fail(check_buffer(b, (gsize) len));

  if(len) {
    *val = g_malloc0(len + 1);
    memcpy(*val, b->ptr, len);
    b->ptr += len;
    b->rem -= len;
  }
}


void mwOpaque_put(struct mwPutBuffer *b, const struct mwOpaque *o) {
  gsize len;

  g_return_if_fail(b != NULL);

  if(! o) {
    guint32_put(b, 0x00);
    return;
  }

  len = o->len;
  if(len)
    g_return_if_fail(o->data != NULL);
  
  guint32_put(b, (guint32) len);

  if(len) {
    ensure_buffer(b, len);
    memcpy(b->ptr, o->data, len);
    b->ptr += len;
    b->rem -= len;
  }
}


void mwOpaque_get(struct mwGetBuffer *b, struct mwOpaque *o) {
  guint32 tmp = 0;

  g_return_if_fail(b != NULL);
  g_return_if_fail(o != NULL);

  o->len = 0;
  o->data = NULL;
  
  if(b->error) return;
  guint32_get(b, &tmp);

  g_return_if_fail(check_buffer(b, (gsize) tmp));

  o->len = (gsize) tmp;
  if(tmp > 0) {
    o->data = g_memdup(b->ptr, tmp);
    b->ptr += tmp;
    b->rem -= tmp;
  }
}


void mwOpaque_clear(struct mwOpaque *o) {
  if(! o) return;
  g_free(o->data);
  o->data = NULL;
  o->len = 0;
}


void mwOpaque_free(struct mwOpaque *o) {
  if(! o) return;
  g_free(o->data);
  g_free(o);
}


void mwOpaque_clone(struct mwOpaque *to, const struct mwOpaque *from) {
  g_return_if_fail(to != NULL);

  to->len = 0;
  to->data = NULL;

  if(from) {
    to->len = from->len;
    if(to->len)
      to->data = g_memdup(from->data, to->len);
  }
}


/* 8.2 Common Structures */
/* 8.2.1 Login Info block */


void mwLoginInfo_put(struct mwPutBuffer *b, const struct mwLoginInfo *login) {
  g_return_if_fail(b != NULL);
  g_return_if_fail(login != NULL);

  mwString_put(b, login->login_id);
  guint16_put(b, login->type);
  mwString_put(b, login->user_id);
  mwString_put(b, login->user_name);
  mwString_put(b, login->community);
  gboolean_put(b, login->full);

  if(login->full) {
    mwString_put(b, login->desc);
    guint32_put(b, login->ip_addr);
    mwString_put(b, login->server_id);
  }
}


void mwLoginInfo_get(struct mwGetBuffer *b, struct mwLoginInfo *login) {
  g_return_if_fail(b != NULL);
  g_return_if_fail(login != NULL);

  if(b->error) return;

  mwString_get(b, &login->login_id);
  guint16_get(b, &login->type);
  mwString_get(b, &login->user_id);
  mwString_get(b, &login->user_name);
  mwString_get(b, &login->community);
  gboolean_get(b, &login->full);
  
  if(login->full) {
    mwString_get(b, &login->desc);
    guint32_get(b, &login->ip_addr);
    mwString_get(b, &login->server_id);
  }
}


void mwLoginInfo_clear(struct mwLoginInfo *login) {
  if(! login) return;

  g_free(login->login_id);
  g_free(login->user_id);
  g_free(login->user_name);
  g_free(login->community);
  g_free(login->desc);
  g_free(login->server_id);

  memset(login, 0x00, sizeof(struct mwLoginInfo));
}


void mwLoginInfo_clone(struct mwLoginInfo *to,
		       const struct mwLoginInfo *from) {

  g_return_if_fail(to != NULL);
  g_return_if_fail(from != NULL);

  to->login_id= g_strdup(from->login_id);
  to->type = from->type;
  to->user_id = g_strdup(from->user_id);
  to->user_name = g_strdup(from->user_name);
  to->community = g_strdup(from->community);

  if( (to->full = from->full) ) {
    to->desc = g_strdup(from->desc);
    to->ip_addr = from->ip_addr;
    to->server_id = g_strdup(from->server_id);
  }
}


/* 8.2.2 Private Info Block */


void mwUserItem_put(struct mwPutBuffer *b, const struct mwUserItem *user) {
  g_return_if_fail(b != NULL);
  g_return_if_fail(user != NULL);

  gboolean_put(b, user->full);
  mwString_put(b, user->id);
  mwString_put(b, user->community);
  
  if(user->full)
    mwString_put(b, user->name);
}


void mwUserItem_get(struct mwGetBuffer *b, struct mwUserItem *user) {
  g_return_if_fail(b != NULL);
  g_return_if_fail(user != NULL);

  if(b->error) return;

  gboolean_get(b, &user->full);
  mwString_get(b, &user->id);
  mwString_get(b, &user->community);

  if(user->full)
    mwString_get(b, &user->name);
}


void mwUserItem_clear(struct mwUserItem *user) {
  if(! user) return;

  g_free(user->id);
  g_free(user->community);
  g_free(user->name);

  memset(user, 0x00, sizeof(struct mwUserItem));
}


void mwUserItem_clone(struct mwUserItem *to,
		      const struct mwUserItem *from) {

  g_return_if_fail(to != NULL);
  g_return_if_fail(from != NULL);

  to->full = from->full;
  to->id = g_strdup(from->id);
  to->community = g_strdup(from->community);
  to->name = (to->full)? g_strdup(from->name): NULL;
}


void mwPrivacyInfo_put(struct mwPutBuffer *b,
		       const struct mwPrivacyInfo *info) {
  guint32 c;

  g_return_if_fail(b != NULL);
  g_return_if_fail(info != NULL);

  gboolean_put(b, info->deny);
  guint32_put(b, info->count);

  for(c = info->count; c--; ) mwUserItem_put(b, info->users + c);
}


void mwPrivacyInfo_get(struct mwGetBuffer *b, struct mwPrivacyInfo *info) {
  g_return_if_fail(b != NULL);
  g_return_if_fail(info != NULL);

  if(b->error) return;

  gboolean_get(b, &info->deny);
  guint32_get(b, &info->count);

  if(info->count) {
    guint32 c = info->count;
    info->users = g_new0(struct mwUserItem, c);
    while(c--) mwUserItem_get(b, info->users + c);
  }
}


void mwPrivacyInfo_clone(struct mwPrivacyInfo *to,
			 const struct mwPrivacyInfo *from) {

  guint32 c;

  g_return_if_fail(to != NULL);
  g_return_if_fail(from != NULL);

  to->deny = from->deny;
  c = to->count = from->count;

  to->users = g_new0(struct mwUserItem, c);
  while(c--) mwUserItem_clone(to->users+c, from->users+c);
}


void mwPrivacyInfo_clear(struct mwPrivacyInfo *info) {
  struct mwUserItem *u;
  guint32 c;

  g_return_if_fail(info != NULL);

  u = info->users;
  c = info->count;

  while(c--) mwUserItem_clear(u + c);
  g_free(u);

  info->count = 0;
  info->users = NULL;
}


/* 8.2.3 User Status Block */


void mwUserStatus_put(struct mwPutBuffer *b,
		      const struct mwUserStatus *stat) {

  g_return_if_fail(b != NULL);
  g_return_if_fail(stat != NULL);

  guint16_put(b, stat->status);
  guint32_put(b, stat->time);
  mwString_put(b, stat->desc);
}


void mwUserStatus_get(struct mwGetBuffer *b, struct mwUserStatus *stat) {
  g_return_if_fail(b != NULL);
  g_return_if_fail(stat != NULL);

  if(b->error) return;

  guint16_get(b, &stat->status);
  guint32_get(b, &stat->time);
  mwString_get(b, &stat->desc);

  // # User Mikael Berthe <mikael.berthe@lilotux.net>
  // # Date 1195749751 -3600
  // Fix Awareness status timestamps with recent Sametime clients

  // For some reason the status timestamps I receive from people with a recent
  // Sametime client make no sense.  This patch simply sets the timestamp to 0,
  // as I have no idea how to interpret these time values.

  // Quick'n ugly hack for recent Sametime clients
  stat->time = 0;
}


void mwUserStatus_clear(struct mwUserStatus *stat) {
  if(! stat) return;
  g_free(stat->desc);
  memset(stat, 0x00, sizeof(struct mwUserStatus));
}


void mwUserStatus_clone(struct mwUserStatus *to,
			const struct mwUserStatus *from) {

  g_return_if_fail(to != NULL);
  g_return_if_fail(from != NULL);

  to->status = from->status;
  to->time = from->time;
  to->desc = g_strdup(from->desc);
}


/* 8.2.4 ID Block */


void mwIdBlock_put(struct mwPutBuffer *b, const struct mwIdBlock *id) {
  g_return_if_fail(b != NULL);
  g_return_if_fail(id != NULL);

  mwString_put(b, id->user);
  mwString_put(b, id->community);
}


void mwIdBlock_get(struct mwGetBuffer *b, struct mwIdBlock *id) {
  g_return_if_fail(b != NULL);
  g_return_if_fail(id != NULL);

  if(b->error) return;

  mwString_get(b, &id->user);
  mwString_get(b, &id->community);
}


void mwIdBlock_clear(struct mwIdBlock *id) {
  if(! id) return;

  g_free(id->user);
  id->user = NULL;

  g_free(id->community);
  id->community = NULL;
}


void mwIdBlock_clone(struct mwIdBlock *to, const struct mwIdBlock *from) {
  g_return_if_fail(to != NULL);
  g_return_if_fail(from != NULL);

  to->user = g_strdup(from->user);
  to->community = g_strdup(from->community);
}


guint mwIdBlock_hash(const struct mwIdBlock *idb) {
  return (idb)? g_str_hash(idb->user): 0;
}


gboolean mwIdBlock_equal(const struct mwIdBlock *a,
			 const struct mwIdBlock *b) {

  g_return_val_if_fail(a != NULL, FALSE);
  g_return_val_if_fail(b != NULL, FALSE);

  return ( mw_streq(a->user, b->user) &&
	   mw_streq(a->community, b->community) );
}


/* 8.2.5 Encryption Block */

/** @todo I think this can be put into cipher */

void mwEncryptItem_put(struct mwPutBuffer *b,
		       const struct mwEncryptItem *ei) {

  g_return_if_fail(b != NULL);
  g_return_if_fail(ei != NULL);
  
  guint16_put(b, ei->id);
  mwOpaque_put(b, &ei->info);

}


void mwEncryptItem_get(struct mwGetBuffer *b, struct mwEncryptItem *ei) {
  g_return_if_fail(b != NULL);
  g_return_if_fail(ei != NULL);

  if(b->error) return;

  guint16_get(b, &ei->id);
  mwOpaque_get(b, &ei->info);
}


void mwEncryptItem_clear(struct mwEncryptItem *ei) {
  if(! ei) return;
  ei->id = 0x0000;
  mwOpaque_clear(&ei->info);
}


void mwEncryptItem_free(struct mwEncryptItem *ei) {
  mwEncryptItem_clear(ei);
  g_free(ei);
}


/* 8.4.2.1 Awareness ID Block */


/** @todo move this into srvc_aware */

void mwAwareIdBlock_put(struct mwPutBuffer *b,
			const struct mwAwareIdBlock *idb) {

  g_return_if_fail(b != NULL);
  g_return_if_fail(idb != NULL);

  guint16_put(b, idb->type);
  mwString_put(b, idb->user);
  mwString_put(b, idb->community);
}


void mwAwareIdBlock_get(struct mwGetBuffer *b, struct mwAwareIdBlock *idb) {
  g_return_if_fail(b != NULL);
  g_return_if_fail(idb != NULL);

  if(b->error) return;

  guint16_get(b, &idb->type);
  mwString_get(b, &idb->user);
  mwString_get(b, &idb->community);
}


void mwAwareIdBlock_clone(struct mwAwareIdBlock *to,
			  const struct mwAwareIdBlock *from) {

  g_return_if_fail(to != NULL);
  g_return_if_fail(from != NULL);

  to->type = from->type;
  to->user = g_strdup(from->user);
  to->community = g_strdup(from->community);
}


void mwAwareIdBlock_clear(struct mwAwareIdBlock *idb) {
  if(! idb) return;
  g_free(idb->user);
  g_free(idb->community);
  memset(idb, 0x00, sizeof(struct mwAwareIdBlock));
}


guint mwAwareIdBlock_hash(const struct mwAwareIdBlock *a) {
  return (a)? g_str_hash(a->user): 0;
}


gboolean mwAwareIdBlock_equal(const struct mwAwareIdBlock *a,
			      const struct mwAwareIdBlock *b) {

  g_return_val_if_fail(a != NULL, FALSE);
  g_return_val_if_fail(b != NULL, FALSE);
  
  return ( (a->type == b->type) &&
	   mw_streq(a->user, b->user) &&
	   mw_streq(a->community, b->community) );
}


/* 8.4.2.4 Snapshot */

void mwAwareSnapshot_get(struct mwGetBuffer *b, struct mwAwareSnapshot *idb) {
  guint32 end_of_block;

  g_return_if_fail(b != NULL);
  g_return_if_fail(idb != NULL);

  guint32_get(b, &end_of_block);
  mwAwareIdBlock_get(b, &idb->id);
  mwString_get(b, &idb->group);
  gboolean_get(b, &idb->online);

  if(idb->online) {
    mwString_get(b, &idb->alt_id);
    mwUserStatus_get(b, &idb->status);
    mwString_get(b, &idb->name);
  }

  if(b->ptr < b->buf + end_of_block) {
    mwGetBuffer_advance(b, b->buf + end_of_block - b->ptr);
  }
}


void mwAwareSnapshot_clone(struct mwAwareSnapshot *to,
			   const struct mwAwareSnapshot *from) {

  g_return_if_fail(to != NULL);
  g_return_if_fail(from != NULL);

  mwAwareIdBlock_clone(&to->id, &from->id);
  if( (to->online = from->online) ) {
    to->alt_id = g_strdup(from->alt_id);
    mwUserStatus_clone(&to->status, &from->status);
    to->name = g_strdup(from->name);
    to->group = g_strdup(from->group);
  }
}


void mwAwareSnapshot_clear(struct mwAwareSnapshot *idb) {
  if(! idb) return;
  mwAwareIdBlock_clear(&idb->id);
  mwUserStatus_clear(&idb->status);
  g_free(idb->alt_id);
  g_free(idb->name);
  g_free(idb->group);
  memset(idb, 0x00, sizeof(struct mwAwareSnapshot));
}

