/*
 *  Functions converting HTSMSGs to/from a simple binary format
 *  Copyright (C) 2007 Andreas Öman
 *
 *  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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include <assert.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#include "htsmsg_binary.h"

/*
 *
 */
static int
htsmsg_binary_des0(htsmsg_t *msg, const uint8_t *buf, size_t len)
{
  unsigned type, namelen, datalen;
  htsmsg_field_t *f;
  htsmsg_t *sub;
  char *n;
  uint64_t u64;
  int i;

  while(len > 5) {

    type    =  buf[0];
    namelen =  buf[1];
    datalen = (buf[2] << 24) |
              (buf[3] << 16) |
              (buf[4] << 8 ) |
              (buf[5]      );
    
    buf += 6;
    len -= 6;
    
    if(len < namelen + datalen)
      return -1;

    f = malloc(sizeof(htsmsg_field_t));
    f->hmf_type  = type;

    if(namelen > 0) {
      n = malloc(namelen + 1);
      memcpy(n, buf, namelen);
      n[namelen] = 0;

      buf += namelen;
      len -= namelen;
      f->hmf_flags = HMF_NAME_ALLOCED;

    } else {
      n = NULL;
      f->hmf_flags = 0;
    }

    f->hmf_name  = n;

    switch(type) {
    case HMF_STR:
      f->hmf_str = n = malloc(datalen + 1);
      memcpy(n, buf, datalen);
      n[datalen] = 0;
      f->hmf_flags |= HMF_ALLOCED;
      break;

    case HMF_BIN:
      f->hmf_bin = (const void *)buf;
      f->hmf_binsize = datalen;
      break;

    case HMF_S64:
      u64 = 0;
      for(i = datalen - 1; i >= 0; i--)
	  u64 = (u64 << 8) | buf[i];
      f->hmf_s64 = u64;
      break;

    case HMF_MAP:
    case HMF_LIST:
      sub = &f->hmf_msg;
      TAILQ_INIT(&sub->hm_fields);
      sub->hm_data = NULL;
      if(htsmsg_binary_des0(sub, buf, datalen) < 0) {
        free(n);
        free(f);
        return -1;
      }
      break;

    default:
      free(n);
      free(f);
      return -1;
    }

    TAILQ_INSERT_TAIL(&msg->hm_fields, f, hmf_link);
    buf += datalen;
    len -= datalen;
  }
  return 0;
}



/*
 *
 */
htsmsg_t *
htsmsg_binary_deserialize(const void *data, size_t len, const void *buf)
{
  htsmsg_t *msg = htsmsg_create_map();
  msg->hm_data = buf;

  if(htsmsg_binary_des0(msg, data, len) < 0) {
    htsmsg_destroy(msg);
    return NULL;
  }
  return msg;
}



/*
 *
 */
static size_t
htsmsg_binary_count(htsmsg_t *msg)
{
  htsmsg_field_t *f;
  size_t len = 0;
  uint64_t u64;

  TAILQ_FOREACH(f, &msg->hm_fields, hmf_link) {

    len += 6;
    len += f->hmf_name ? strlen(f->hmf_name) : 0;

    switch(f->hmf_type) {
    case HMF_MAP:
    case HMF_LIST:
      len += htsmsg_binary_count(&f->hmf_msg);
      break;

    case HMF_STR:
      len += strlen(f->hmf_str);
      break;

    case HMF_BIN:
      len += f->hmf_binsize;
      break;

    case HMF_S64:
      u64 = f->hmf_s64;
      while(u64 != 0) {
	len++;
	u64 = u64 >> 8;
      }
      break;
    }
  }
  return len;
}


/*
 *
 */
static void
htsmsg_binary_write(htsmsg_t *msg, uint8_t *ptr)
{
  htsmsg_field_t *f;
  uint64_t u64;
  int l, i, namelen;

  TAILQ_FOREACH(f, &msg->hm_fields, hmf_link) {
    namelen = f->hmf_name ? strlen(f->hmf_name) : 0;
    *ptr++ = f->hmf_type;
    *ptr++ = namelen;

    switch(f->hmf_type) {
    case HMF_MAP:
    case HMF_LIST:
      l = htsmsg_binary_count(&f->hmf_msg);
      break;

    case HMF_STR:
      l = strlen(f->hmf_str);
      break;

    case HMF_BIN:
      l = f->hmf_binsize;
      break;

    case HMF_S64:
      u64 = f->hmf_s64;
      l = 0;
      while(u64 != 0) {
	l++;
	u64 = u64 >> 8;
      }
      break;
    default:
      abort();
    }


    *ptr++ = l >> 24;
    *ptr++ = l >> 16;
    *ptr++ = l >> 8;
    *ptr++ = l;

    if(namelen > 0) {
      memcpy(ptr, f->hmf_name, namelen);
      ptr += namelen;
    }

    switch(f->hmf_type) {
    case HMF_MAP:
    case HMF_LIST:
      htsmsg_binary_write(&f->hmf_msg, ptr);
      break;

    case HMF_STR:
      memcpy(ptr, f->hmf_str, l);
      break;

    case HMF_BIN:
      memcpy(ptr, f->hmf_bin, l);
      break;

    case HMF_S64:
      u64 = f->hmf_s64;
      for(i = 0; i < l; i++) {
	ptr[i] = u64;
	u64 = u64 >> 8;
      }
      break;
    }
    ptr += l;
  }
}


/*
 *
 */
int
htsmsg_binary_serialize(htsmsg_t *msg, void **datap, size_t *lenp, int maxlen)
{
  size_t len;
  uint8_t *data;

  len = htsmsg_binary_count(msg);
  if(len + 4 > maxlen)
    return -1;

  data = malloc(len + 4);

  data[0] = len >> 24;
  data[1] = len >> 16;
  data[2] = len >> 8;
  data[3] = len;

  htsmsg_binary_write(msg, data + 4);
  *datap = data;
  *lenp  = len + 4;
  return 0;
}
