/*

  silcargument.c

  Author: Pekka Riikonen <priikone@silcnet.org>

  Copyright (C) 2001 - 2007 Pekka Riikonen

  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; version 2 of the License.

  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.

*/
/* Implementation of Argument Payload routines */
/* $Id$ */

#include "silc.h"
#include "silcargument.h"

/*************************** Argument Payload *******************************/

struct SilcArgumentPayloadStruct {
  SilcUInt32 argc;
  unsigned char **argv;
  SilcUInt32 *argv_lens;
  SilcUInt32 *argv_types;
  SilcUInt32 pos;
};

/* Parses arguments and returns them into Argument Payload structure. */

SilcArgumentPayload silc_argument_payload_parse(const unsigned char *payload,
						SilcUInt32 payload_len,
						SilcUInt32 argc)
{
  SilcBufferStruct buffer;
  SilcArgumentPayload newp;
  SilcUInt16 p_len = 0;
  unsigned char arg_type = 0;
  SilcUInt32 pull_len = 0;
  int i = 0, ret;

  silc_buffer_set(&buffer, (unsigned char *)payload, payload_len);
  newp = silc_calloc(1, sizeof(*newp));
  if (!newp)
    return NULL;
  newp->argv = silc_calloc(argc, sizeof(unsigned char *));
  if (!newp->argv)
    goto err;
  newp->argv_lens = silc_calloc(argc, sizeof(SilcUInt32));
  if (!newp->argv_lens)
    goto err;
  newp->argv_types = silc_calloc(argc, sizeof(SilcUInt32));
  if (!newp->argv_types)
    goto err;

  /* Get arguments */
  for (i = 0; i < argc; i++) {
    ret = silc_buffer_unformat(&buffer,
			       SILC_STR_UI_SHORT(&p_len),
			       SILC_STR_UI_CHAR(&arg_type),
			       SILC_STR_END);
    if (ret == -1 || p_len > silc_buffer_len(&buffer) - 3) {
      SILC_LOG_DEBUG(("Malformed argument payload"));
      goto err;
    }

    newp->argv_lens[i] = p_len;
    newp->argv_types[i] = arg_type;

    /* Get argument data */
    silc_buffer_pull(&buffer, 3);
    ret = silc_buffer_unformat(&buffer,
			       SILC_STR_UI_XNSTRING_ALLOC(&newp->argv[i],
							  p_len),
			       SILC_STR_END);
    if (ret == -1) {
      SILC_LOG_DEBUG(("Malformed argument payload"));
      goto err;
    }

    silc_buffer_pull(&buffer, p_len);
    pull_len += 3 + p_len;
  }

  if (silc_buffer_len(&buffer) != 0) {
    SILC_LOG_DEBUG(("Malformed argument payload"));
    goto err;
  }

  newp->argc = argc;
  newp->pos = 0;

  silc_buffer_push(&buffer, pull_len);

  return newp;

 err:
  SILC_LOG_DEBUG(("Error parsing argument payload"));
  if (i)
    for (ret = 0; ret < i; ret++)
      silc_free(newp->argv[ret]);

  silc_free(newp->argv);
  silc_free(newp->argv_lens);
  silc_free(newp->argv_types);
  silc_free(newp);

  return NULL;
}

/* Encodes arguments in to Argument Paylods returning them to SilcBuffer. */

SilcBuffer silc_argument_payload_encode(SilcUInt32 argc,
					unsigned char **argv,
					SilcUInt32 *argv_lens,
					SilcUInt32 *argv_types)
{
  SilcBuffer buffer;
  SilcUInt32 len;
  int i;

  len = 0;
  for (i = 0; i < argc; i++)
    len += 3 + (SilcUInt16)argv_lens[i];

  buffer = silc_buffer_alloc_size(len);
  if (!buffer)
    return NULL;

  /* Put arguments */
  for (i = 0; i < argc; i++) {
    silc_buffer_format(buffer,
		       SILC_STR_UI_SHORT(argv_lens[i]),
		       SILC_STR_UI_CHAR(argv_types[i]),
		       SILC_STR_UI_XNSTRING(argv[i], (SilcUInt16)argv_lens[i]),
		       SILC_STR_END);
    silc_buffer_pull(buffer, 3 + (SilcUInt16)argv_lens[i]);
  }

  silc_buffer_push(buffer, len);

  return buffer;
}

/* Encode one argument to buffer */

SilcBuffer silc_argument_payload_encode_one(SilcBuffer args,
					    unsigned char *arg,
					    SilcUInt32 arg_len,
					    SilcUInt32 arg_type)
{
  SilcBuffer buffer = args;
  SilcUInt32 len;

  len = 3 + (SilcUInt16)arg_len;
  buffer = silc_buffer_realloc(buffer,
			       (buffer ? silc_buffer_truelen(buffer) +
				len : len));
  if (!buffer)
    return NULL;
  silc_buffer_pull(buffer, silc_buffer_len(buffer));
  silc_buffer_pull_tail(buffer, len);
  silc_buffer_format(buffer,
		     SILC_STR_UI_SHORT(arg_len),
		     SILC_STR_UI_CHAR(arg_type),
		     SILC_STR_UI_XNSTRING(arg, (SilcUInt16)arg_len),
		     SILC_STR_END);
  silc_buffer_push(buffer, buffer->data - buffer->head);

  return buffer;
}

/* Same as above but encode the buffer from SilcArgumentPayload structure
   instead of raw data. */

SilcBuffer silc_argument_payload_encode_payload(SilcArgumentPayload payload)
{
  SilcBuffer buffer;
  SilcUInt32 len;
  int i;

  len = 0;
  for (i = 0; i < payload->argc; i++)
    len += 3 + payload->argv_lens[i];

  buffer = silc_buffer_alloc_size(len);
  if (!buffer)
    return NULL;

  /* Put arguments */
  for (i = 0; i < payload->argc; i++) {
    silc_buffer_format(buffer,
		       SILC_STR_UI_SHORT(payload->argv_lens[i]),
		       SILC_STR_UI_CHAR(payload->argv_types[i]),
		       SILC_STR_UI_XNSTRING(payload->argv[i],
					    payload->argv_lens[i]),
		       SILC_STR_END);
    silc_buffer_pull(buffer, 3 + payload->argv_lens[i]);
  }

  silc_buffer_push(buffer, len);

  return buffer;
}

/* Frees Argument Payload */

void silc_argument_payload_free(SilcArgumentPayload payload)
{
  int i;

  if (payload) {
    for (i = 0; i < payload->argc; i++)
      silc_free(payload->argv[i]);

    silc_free(payload->argv);
    silc_free(payload->argv_lens);
    silc_free(payload->argv_types);
    silc_free(payload);
  }
}

/* Returns number of arguments in payload */

SilcUInt32 silc_argument_get_arg_num(SilcArgumentPayload payload)
{
  return payload ? payload->argc : 0;
}

/* Returns first argument from payload. */

unsigned char *silc_argument_get_first_arg(SilcArgumentPayload payload,
					   SilcUInt32 *type,
					   SilcUInt32 *ret_len)
{
  if (!payload)
    return NULL;

  payload->pos = 0;

  if (type)
    *type = payload->argv_types[payload->pos];
  if (ret_len)
    *ret_len = payload->argv_lens[payload->pos];

  return payload->argv[payload->pos++];
}

/* Returns next argument from payload or NULL if no more arguments. */

unsigned char *silc_argument_get_next_arg(SilcArgumentPayload payload,
					  SilcUInt32 *type,
					  SilcUInt32 *ret_len)
{
  if (!payload)
    return NULL;

  if (payload->pos >= payload->argc)
    return NULL;

  if (type)
    *type = payload->argv_types[payload->pos];
  if (ret_len)
    *ret_len = payload->argv_lens[payload->pos];

  return payload->argv[payload->pos++];
}

/* Returns argument which type is `type'. */

unsigned char *silc_argument_get_arg_type(SilcArgumentPayload payload,
					  SilcUInt32 type,
					  SilcUInt32 *ret_len)
{
  int i;

  if (!payload)
    return NULL;

  for (i = 0; i < payload->argc; i++)
    if (payload->argv_types[i] == type)
      break;

  if (i >= payload->argc)
    return NULL;

  if (ret_len)
    *ret_len = payload->argv_lens[i];

  return payload->argv[i];
}

/* Return argument already decoded */

static SilcBool silc_argument_decode(unsigned char *data,
				     SilcUInt32 data_len,
				     SilcArgumentDecodeType dec_type,
				     void *ret_arg,
				     void **ret_arg_alloc)
{
  switch (dec_type) {

  case SILC_ARGUMENT_ID:
    if (ret_arg)
      if (!silc_id_payload_parse_id(data, data_len, (SilcID *)ret_arg))
	return FALSE;

    if (ret_arg_alloc) {
      SilcID id;
      if (!silc_id_payload_parse_id(data, data_len, &id))
	return FALSE;
      *ret_arg_alloc = silc_memdup(&id, sizeof(id));
    }
    break;

  case SILC_ARGUMENT_PUBLIC_KEY:
    {
      SilcPublicKey public_key;

      if (!ret_arg_alloc)
	return FALSE;

      if (!silc_public_key_payload_decode(data, data_len, &public_key))
	return FALSE;

      *ret_arg_alloc = public_key;
    }
    break;

  case SILC_ARGUMENT_ATTRIBUTES:
    if (!ret_arg_alloc)
      return FALSE;

    *ret_arg_alloc = silc_attribute_payload_parse(data, data_len);
    break;

  case SILC_ARGUMENT_UINT32:
    if (data_len != 4)
      return FALSE;

    if (ret_arg) {
      SilcUInt32 *i = ret_arg;
      SILC_GET32_MSB(*i, data);
    }

    if (ret_arg_alloc) {
      SilcUInt32 i;
      SILC_GET32_MSB(i, data);
      *ret_arg_alloc = silc_memdup(&i, sizeof(i));
    }
    break;

  case SILC_ARGUMENT_BOOL:
    if (data_len != sizeof(SilcBool))
      return FALSE;

    if (ret_arg) {
      SilcBool *b = ret_arg;
      *b = (data[0] == 0x01 ? TRUE : FALSE);
    }

    if (ret_arg_alloc) {
      SilcBool b;
      b = (data[0] == 0x01 ? TRUE : FALSE);
      *ret_arg_alloc = silc_memdup(&b, sizeof(b));
    }
    break;

  default:
    return FALSE;
  }

  return TRUE;
}

/* Return argument already decoded */

SilcBool silc_argument_get_decoded(SilcArgumentPayload payload,
				   SilcUInt32 type,
				   SilcArgumentDecodeType dec_type,
				   void *ret_arg,
				   void **ret_arg_alloc)
{
  unsigned char *tmp;
  SilcUInt32 tmp_len;

  tmp = silc_argument_get_arg_type(payload, type, &tmp_len);
  if (!tmp)
    return FALSE;

  return silc_argument_decode(tmp, tmp_len, dec_type, ret_arg, ret_arg_alloc);
}

/************************* Argument List Payload ****************************/

/* Parses argument payload list */

SilcArgumentPayload
silc_argument_list_parse(const unsigned char *payload,
			 SilcUInt32 payload_len)
{
  SilcArgumentPayload arg;
  SilcUInt16 argc;

  if (payload_len < 5)
    return NULL;

  SILC_GET16_MSB(argc, payload);

  arg = silc_argument_payload_parse(payload + 2, payload_len - 2, argc);

  return arg;
}

/* Parses argument payload list of specific argument types */

SilcDList
silc_argument_list_parse_decoded(const unsigned char *payload,
				 SilcUInt32 payload_len,
				 SilcArgumentDecodeType dec_type)
{
  SilcArgumentPayload arg;
  SilcArgumentDecodedList dec;
  unsigned char *data;
  SilcUInt32 data_len, type;
  SilcDList list;

  arg = silc_argument_list_parse(payload, payload_len);
  if (!arg)
    return NULL;

  list = silc_dlist_init();
  if (!list) {
    silc_argument_payload_free(arg);
    return NULL;
  }

  data = silc_argument_get_first_arg(arg, &type, &data_len);
  while (data) {
    dec = silc_calloc(1, sizeof(*dec));
    if (!dec)
      continue;
    dec->arg_type = type;
    if (silc_argument_decode(data, data_len, dec_type, NULL, &dec->argument))
      silc_dlist_add(list, dec);
    else
      silc_free(dec);
    data = silc_argument_get_next_arg(arg, &type, &data_len);
  }

  silc_argument_payload_free(arg);

  silc_dlist_start(list);

  return list;
}

/* Free decoded argument payload list */

void silc_argument_list_free(SilcDList list, SilcArgumentDecodeType dec_type)
{
  SilcArgumentDecodedList dec;

  if (!list)
    return;

  silc_dlist_start(list);
  while ((dec = silc_dlist_get(list))) {
    switch (dec_type) {

    case SILC_ARGUMENT_ID:
    case SILC_ARGUMENT_UINT32:
    case SILC_ARGUMENT_BOOL:
      silc_free(dec->argument);
      break;

    case SILC_ARGUMENT_PUBLIC_KEY:
      silc_pkcs_public_key_free(dec->argument);
      break;

    case SILC_ARGUMENT_ATTRIBUTES:
      silc_attribute_payload_free(dec->argument);
      break;

    default:
      break;
    }

    silc_free(dec);
  }

  silc_dlist_uninit(list);
}
