/* new-packet.c - packet handling (freeing, copying, ...)
 *       Copyright (C) 2001, 2002, 2003, 2007 Timo Schulz
 *
 * This file is part of OpenCDK.
 *
 * OpenCDK 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. 
 *  
 * OpenCDK 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. 
 */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <string.h>
#include <stdio.h>
#include <assert.h>

#include "opencdk.h"
#include "main.h"
#include "packet.h"


/* Release an array of MPI values. */
void
_cdk_free_mpibuf (size_t n, gcry_mpi_t *array)
{
  while (n--) 
    {
      gcry_mpi_release (array[n]);
      array[n] = NULL;
    }
}


/**
 * cdk_pkt_new:
 * @r_pkt: the new packet
 * 
 * Allocate a new packet.
 **/
cdk_error_t
cdk_pkt_new (cdk_packet_t *r_pkt)
{
  cdk_packet_t pkt;
  
  if (!r_pkt)
    return CDK_Inv_Value;
  pkt = cdk_calloc (1, sizeof *pkt);
  if (!pkt)
    return CDK_Out_Of_Core;
  *r_pkt = pkt;
  return 0;
}


static void
free_symkey_enc (cdk_pkt_symkey_enc_t enc)
{
  if (!enc)
    return;
  cdk_s2k_free (enc->s2k);
  cdk_free (enc);
}


static void
free_pubkey_enc (cdk_pkt_pubkey_enc_t enc)
{
  size_t nenc;
  
  if (!enc)
    return;
  
  nenc = cdk_pk_get_nenc (enc->pubkey_algo);
  _cdk_free_mpibuf (nenc, enc->mpi);
  cdk_free (enc);
}


static void
free_literal (cdk_pkt_literal_t pt)
{
  if (!pt)
    return;
  /* The buffer which is referenced in this packet is closed
     elsewhere. To close it here would cause a double close. */
  cdk_free (pt);
}


void
_cdk_free_userid (cdk_pkt_userid_t uid)
{
  if (!uid)
    return;
  
  cdk_free (uid->prefs);
  uid->prefs = NULL;
  cdk_free (uid->attrib_img);
  uid->attrib_img = NULL;      
  cdk_free (uid);
}


void
_cdk_free_signature (cdk_pkt_signature_t sig)
{
  cdk_desig_revoker_t r;
  size_t nsig;
  
  if (!sig)
    return;
  
  nsig = cdk_pk_get_nsig (sig->pubkey_algo);
  _cdk_free_mpibuf (nsig, sig->mpi);

  cdk_subpkt_free (sig->hashed);
  sig->hashed = NULL;
  cdk_subpkt_free (sig->unhashed);
  sig->unhashed = NULL;
  while (sig->revkeys)
    {
      r = sig->revkeys->next;
      cdk_free (sig->revkeys);
      sig->revkeys = r;
    }
  cdk_free (sig);
}


void
cdk_pk_release (cdk_pubkey_t pk)
{
  size_t npkey;
  
  if (!pk)
    return;
  
  npkey = cdk_pk_get_npkey (pk->pubkey_algo);
  _cdk_free_userid (pk->uid);
  pk->uid = NULL;
  cdk_free (pk->prefs);
  pk->prefs = NULL;
  _cdk_free_mpibuf (npkey, pk->mpi);
  cdk_free (pk);
}


void
cdk_sk_release (cdk_seckey_t sk)
{
  size_t nskey;
  
  if (!sk)
    return;
  
  nskey = cdk_pk_get_nskey (sk->pubkey_algo);
  _cdk_free_mpibuf (nskey, sk->mpi);
  cdk_free (sk->encdata);
  sk->encdata = NULL;
  cdk_pk_release (sk->pk);
  sk->pk = NULL;
  cdk_s2k_free (sk->protect.s2k);
  sk->protect.s2k = NULL;
  cdk_free (sk);
}


static void
free_encrypted (cdk_pkt_encrypted_t enc)
{
  if (!enc)
    return;
  
  /* This is just a reference for the filters to know where
     the encrypted data starts and to read from the sream. It
     us closed elsewhere and to close it here would double close it. */
  /*cdk_stream_close (enc->buf);*/
  enc->buf = NULL;
  cdk_free (enc);
}


/* Detach the openpgp packet from the packet structure
   and release the packet structure itself. */
void
_cdk_pkt_detach_free (cdk_packet_t pkt, int *r_pkttype, void **ctx)
{
  /* For now we just allow this for keys. */
  switch (pkt->pkttype)
    {
    case CDK_PKT_PUBLIC_KEY:
    case CDK_PKT_PUBLIC_SUBKEY:
      *ctx = pkt->pkt.public_key;
      break;
      
    case CDK_PKT_SECRET_KEY:
    case CDK_PKT_SECRET_SUBKEY:
      *ctx = pkt->pkt.secret_key;
      break;
      
    default:
      *r_pkttype = 0;
      return;
    }
  
  /* The caller might expect a specific packet type and
     is not interested to store it for later use. */
  if (r_pkttype)
    *r_pkttype = pkt->pkttype;
  
  cdk_free (pkt);
}


void
cdk_pkt_free (cdk_packet_t pkt)
{
  if (!pkt)
    return;
  
  switch (pkt->pkttype) 
    {
    case CDK_PKT_ATTRIBUTE    :
    case CDK_PKT_USER_ID      : _cdk_free_userid (pkt->pkt.user_id); break;
    case CDK_PKT_PUBLIC_KEY   :
    case CDK_PKT_PUBLIC_SUBKEY: cdk_pk_release (pkt->pkt.public_key); break;
    case CDK_PKT_SECRET_KEY   :
    case CDK_PKT_SECRET_SUBKEY: cdk_sk_release (pkt->pkt.secret_key); break;
    case CDK_PKT_SIGNATURE    : _cdk_free_signature (pkt->pkt.signature);break;
    case CDK_PKT_PUBKEY_ENC   : free_pubkey_enc (pkt->pkt.pubkey_enc); break;
    case CDK_PKT_SYMKEY_ENC   : free_symkey_enc (pkt->pkt.symkey_enc); break;
    case CDK_PKT_MDC          : cdk_free (pkt->pkt.mdc); break;
    case CDK_PKT_ENCRYPTED    :
    case CDK_PKT_ENCRYPTED_MDC: free_encrypted (pkt->pkt.encrypted); break;
    case CDK_PKT_ONEPASS_SIG  : cdk_free (pkt->pkt.onepass_sig); break;
    case CDK_PKT_LITERAL      : free_literal (pkt->pkt.literal); break;
    case CDK_PKT_COMPRESSED   : cdk_free (pkt->pkt.compressed); break;
    default                   : break;
    }
  
  /* Reset the packet type to avoid, when cdk_pkt_release() will be
     used, that the second cdk_pkt_free() call will double free the data. */
  pkt->pkttype = 0;
}


/**
 * cdk_pkt_release:
 * @pkt: the packet
 * 
 * Free the contents of the given package and
 * release the memory of the structure.
 **/
void
cdk_pkt_release (cdk_packet_t pkt)
{
  if (!pkt)
    return;
  cdk_pkt_free (pkt);
  cdk_free (pkt);
}


/**
 * cdk_pkt_alloc:
 * @r_pkt: output is the new packet
 * @pkttype: the requested packet type
 * 
 * Allocate a new packet structure with the given packet type.
 **/
cdk_error_t
cdk_pkt_alloc (cdk_packet_t *r_pkt, int pkttype)
{
  cdk_packet_t pkt;
  int rc;

  if (!r_pkt)
    return CDK_Inv_Value;
  
  rc = cdk_pkt_new (&pkt);
  if (rc)
    return rc;

  switch (pkttype) 
    {
    case CDK_PKT_USER_ID:
      pkt->pkt.user_id = cdk_calloc (1, sizeof pkt->pkt.user_id);
      if (!pkt->pkt.user_id)
	return CDK_Out_Of_Core;
      break;
      
    case CDK_PKT_PUBLIC_KEY:
    case CDK_PKT_PUBLIC_SUBKEY:
      pkt->pkt.public_key = cdk_calloc (1, sizeof *pkt->pkt.public_key);
      if (!pkt->pkt.public_key)
	return CDK_Out_Of_Core;
      break;
      
    case CDK_PKT_SECRET_KEY:
    case CDK_PKT_SECRET_SUBKEY:
      pkt->pkt.secret_key = cdk_calloc (1, sizeof *pkt->pkt.secret_key);
      pkt->pkt.secret_key->pk =
	cdk_calloc (1, sizeof *pkt->pkt.secret_key->pk);
      if (!pkt->pkt.secret_key || !pkt->pkt.secret_key->pk)
	return CDK_Out_Of_Core;
      break;
      
    case CDK_PKT_SIGNATURE:
      pkt->pkt.signature = cdk_calloc (1, sizeof *pkt->pkt.signature);
      if (!pkt->pkt.signature)
	return CDK_Out_Of_Core;
      break;
      
    case CDK_PKT_SYMKEY_ENC:
      pkt->pkt.symkey_enc = cdk_calloc (1, sizeof *pkt->pkt.symkey_enc);
      if (!pkt->pkt.symkey_enc)
	return CDK_Out_Of_Core;
      break;
      
    case CDK_PKT_PUBKEY_ENC:
      pkt->pkt.pubkey_enc = cdk_calloc (1, sizeof *pkt->pkt.pubkey_enc);
      if (!pkt->pkt.pubkey_enc)
	return CDK_Out_Of_Core;
      break;
      
    case CDK_PKT_MDC:
      pkt->pkt.mdc = cdk_calloc (1, sizeof *pkt->pkt.mdc);
      if (!pkt->pkt.mdc)
	return CDK_Out_Of_Core;
      break;
      
    case CDK_PKT_ENCRYPTED_MDC:
    case CDK_PKT_ENCRYPTED:
      pkt->pkt.symkey_enc = cdk_calloc (1, sizeof *pkt->pkt.symkey_enc);
      if (!pkt->pkt.symkey_enc)
	return CDK_Out_Of_Core;
      break;
      
    case CDK_PKT_ONEPASS_SIG:
      pkt->pkt.onepass_sig = cdk_calloc (1, sizeof *pkt->pkt.onepass_sig);
      if (!pkt->pkt.onepass_sig)
	return CDK_Out_Of_Core;
      break;
      
    case CDK_PKT_LITERAL:
      /* FIXME: We would need the size of the file name to allocate extra
                bytes, otherwise the result would be useless. */
      pkt->pkt.literal = cdk_calloc (1, sizeof *pkt->pkt.literal);
      if (!pkt->pkt.literal)
	return CDK_Out_Of_Core;
      break;
    }
  pkt->pkttype = pkttype;
  *r_pkt = pkt;
  return 0;
}


cdk_prefitem_t
_cdk_copy_prefs (const cdk_prefitem_t prefs)
{
  size_t n = 0;
  struct cdk_prefitem_s *new_prefs;
  
  if (!prefs)
    return NULL;
  
  for (n = 0; prefs[n].type; n++)
    ;
  new_prefs = cdk_calloc (1, sizeof *new_prefs * (n + 1));
  if (!new_prefs)
    return NULL;
  for (n = 0; prefs[n].type; n++) 
    {    
      new_prefs[n].type = prefs[n].type;
      new_prefs[n].value = prefs[n].value;
    }
  new_prefs[n].type = CDK_PREFTYPE_NONE;
  new_prefs[n].value = 0;  
  return new_prefs;
}


cdk_error_t
_cdk_copy_userid (cdk_pkt_userid_t* dst, cdk_pkt_userid_t src)
{
  cdk_pkt_userid_t u;
  
  if (!dst || !src)
    return CDK_Inv_Value;
  
  *dst = NULL;
  u = cdk_calloc (1, sizeof *u + strlen (src->name) + 1);
  if (!u)
    return CDK_Out_Of_Core;
  memcpy (u, src, sizeof *u);
  memcpy (u->name, src->name, strlen (src->name));
  u->prefs = _cdk_copy_prefs (src->prefs);
  if (src->selfsig)
    _cdk_copy_signature (&u->selfsig, src->selfsig);
  *dst = u;
  
  return 0;
}


cdk_error_t
_cdk_copy_pubkey (cdk_pkt_pubkey_t* dst, cdk_pkt_pubkey_t src)
{
  cdk_pkt_pubkey_t k;
  int i;
  
  if (!dst || !src)
    return CDK_Inv_Value;
  
  *dst = NULL;
  k = cdk_calloc (1, sizeof *k);
  if (!k)
    return CDK_Out_Of_Core;
  memcpy (k, src, sizeof *k);
  if (src->uid)
    _cdk_copy_userid (&k->uid, src->uid);
  if (src->prefs)
    k->prefs = _cdk_copy_prefs (src->prefs);
  for (i = 0; i < cdk_pk_get_npkey (src->pubkey_algo); i++)
    k->mpi[i] = gcry_mpi_copy (src->mpi[i]);
  *dst = k;
  
  return 0;
}


cdk_error_t
_cdk_copy_seckey (cdk_pkt_seckey_t* dst, cdk_pkt_seckey_t src)
{
  cdk_pkt_seckey_t k;
  int i;
  
  if (!dst || !src)
    return CDK_Inv_Value;
  
  *dst = NULL;
  k = cdk_calloc (1, sizeof *k);
  if (!k)
    return CDK_Out_Of_Core;
  memcpy (k, src, sizeof *k);
  _cdk_copy_pubkey (&k->pk, src->pk);
  
  if (src->encdata) 
    {
      k->encdata = cdk_calloc (1, src->enclen + 1);
      if (!k->encdata)
	return CDK_Out_Of_Core;
      memcpy (k->encdata, src->encdata, src->enclen);
    }
  
  _cdk_s2k_copy (&k->protect.s2k, src->protect.s2k);
  
  for (i = 0; i < cdk_pk_get_nskey (src->pubkey_algo); i++) 
    {
      k->mpi[i] = gcry_mpi_copy (src->mpi[i]);
      gcry_mpi_set_flag (k->mpi[i], GCRYMPI_FLAG_SECURE);
    }
  
  *dst = k;  
  return 0;
}


cdk_error_t
_cdk_copy_pk_to_sk (cdk_pkt_pubkey_t pk, cdk_pkt_seckey_t sk)
{
  if (!pk || !sk)
    return CDK_Inv_Value;
  
  sk->version = pk->version;
  sk->expiredate = pk->expiredate;
  sk->pubkey_algo = pk->pubkey_algo;
  sk->has_expired = pk->has_expired;
  sk->is_revoked = pk->is_revoked;
  sk->main_keyid[0] = pk->main_keyid[0];
  sk->main_keyid[1] = pk->main_keyid[1];
  sk->keyid[0] = pk->keyid[0];
  sk->keyid[1] = pk->keyid[1];
  
  return 0;
}


cdk_error_t
_cdk_copy_signature (cdk_pkt_signature_t * dst, cdk_pkt_signature_t src)
{
  cdk_pkt_signature_t s;
  
  if (!dst || !src)
    return CDK_Inv_Value;

  *dst = NULL;
  s = cdk_calloc (1, sizeof *s);
  if (!s)
    return CDK_Out_Of_Core;
  memcpy (s, src, sizeof *src);
  _cdk_subpkt_copy (&s->hashed, src->hashed);
  _cdk_subpkt_copy (&s->unhashed, src->unhashed);
  /* FIXME: Copy MPI parts */
  *dst = s;
  
  return 0;
}


cdk_error_t
_cdk_pubkey_compare (cdk_pkt_pubkey_t a, cdk_pkt_pubkey_t b)
{
  int na, nb, i;
  
  if (a->timestamp != b->timestamp || a->pubkey_algo != b->pubkey_algo)
    return -1;
  if (a->version < 4 && a->expiredate != b->expiredate)
    return -1;
  na = cdk_pk_get_npkey (a->pubkey_algo);
  nb = cdk_pk_get_npkey (b->pubkey_algo);
  if (na != nb)
    return -1;
  
  for (i = 0; i < na; i++) 
    {
      if (gcry_mpi_cmp (a->mpi[i], b->mpi[i]))
	return -1;
    }
  
  return 0;
}


/**
 * cdk_subpkt_free:
 * @ctx: the sub packet node to free
 *
 * Release the context.
 **/
void
cdk_subpkt_free (cdk_subpkt_t ctx)
{
  cdk_subpkt_t s;
  
  while (ctx)
    {
      s = ctx->next;
      cdk_free (ctx);
      ctx = s;
    }
}


/**
 * cdk_subpkt_find:
 * @ctx: the sub packet node
 * @type: the packet type to find
 *
 * Find the given packet type in the node. If no packet with this
 * type was found, return null otherwise pointer to the node.
 **/
cdk_subpkt_t
cdk_subpkt_find (cdk_subpkt_t ctx, size_t type)
{
  return cdk_subpkt_find_nth (ctx, type, 0);
}

/**
 * cdk_subpkt_type_count:
 * @ctx: The sub packet context
 * @type: The sub packet type.
 * 
 * Return the amount of sub packets with this type.
 **/
size_t
cdk_subpkt_type_count (cdk_subpkt_t ctx, size_t type)
{
  cdk_subpkt_t s;
  size_t count;
  
  count = 0;
  for (s = ctx; s; s = s->next)
    {
      if (s->type == type)
	count++;
    }
  
  return count;
}


/**
 * cdk_subpkt_find_nth:
 * @ctx: The sub packet context
 * @type: The sub packet type
 * @index: The nth packet to retrieve, 0 means the first
 * 
 * Return the nth sub packet of the given type.
 **/
cdk_subpkt_t
cdk_subpkt_find_nth (cdk_subpkt_t ctx, size_t type, size_t idx)
{
  cdk_subpkt_t s;
  size_t pos;
  
  pos = 0;
  for (s = ctx; s; s = s->next)
    {
      if (s->type == type && pos++ == idx)
	return s;
    }
  
  return NULL;
}


/**
 * cdk_subpkt_new:
 * @size: the size of the new context
 *
 * Create a new sub packet node with the size of @size.
 **/
cdk_subpkt_t
cdk_subpkt_new (size_t size)
{
  cdk_subpkt_t s;
  
  if (!size)
    return NULL;
  s = cdk_calloc (1, sizeof *s + size + 1);
  if (!s)
    return NULL;
  return s;
}


/**
 * cdk_subpkt_get_data:
 * @ctx: the sub packet node
 * @r_type: pointer store the packet type
 * @r_nbytes: pointer to store the packet size
 *
 * Extract the data from the given sub packet. The type is returned
 * in @r_type and the size in @r_nbytes.
 **/
const byte*
cdk_subpkt_get_data (cdk_subpkt_t ctx, size_t *r_type, size_t *r_nbytes)
{
  if (!ctx || !r_nbytes)
    return NULL;
  if (r_type)
    *r_type = ctx->type;
  *r_nbytes = ctx->size;
  return ctx->d;
}


/**
 * cdk_subpkt_add:
 * @root: the root node
 * @node: the node to add
 *
 * Add the node in @node to the root node @root.
 **/
cdk_error_t
cdk_subpkt_add (cdk_subpkt_t root, cdk_subpkt_t node)
{
  cdk_subpkt_t n1;

  if (!root)
    return CDK_Inv_Value;
  for (n1 = root; n1->next; n1 = n1->next)
    ;
  n1->next = node;
  return 0;
}


byte*
_cdk_subpkt_get_array (cdk_subpkt_t s, int count, size_t *r_nbytes)
{
  cdk_subpkt_t list;
  byte *buf;
  size_t n, nbytes;
  
  if (!s) 
    {
      if (r_nbytes)
	*r_nbytes = 0;
      return NULL;
    }
  
  for (n = 0, list = s; list; list = list->next)
    {
      n++; /* type */
      n += list->size;
      if (list->size < 192)
	n++;
      else if (list->size < 8384)
	n += 2;
      else 
	n += 5;
    }
  buf = cdk_calloc (1, n+1);
  if (!buf)
    return NULL;

  n = 0;
  for (list = s; list; list = list->next)
    {
      nbytes = 1 + list->size; /* type */
      if (nbytes < 192)
	buf[n++] = nbytes;
      else if (nbytes < 8384)
	{
	  buf[n++] = nbytes / 256 + 192;
	  buf[n++] = nbytes % 256;
        }
      else
	{
	  buf[n++] = 0xFF;
	  buf[n++] = nbytes >> 24;
	  buf[n++] = nbytes >> 16;
	  buf[n++] = nbytes >>  8;
	  buf[n++] = nbytes;
        }
      buf[n++] = list->type;
      memcpy (buf + n, list->d, list->size);
      n += list->size;
    }
  
  if (count)
    {
      cdk_free (buf);
      buf = NULL;
    }
  if (r_nbytes)
    *r_nbytes = n;
  return buf;
}


cdk_error_t
_cdk_subpkt_copy (cdk_subpkt_t *r_dst, cdk_subpkt_t src)
{
  cdk_subpkt_t root, p, node;
  
  if (!src || !r_dst)
    return CDK_Inv_Value;
  
  root = NULL;
  for (p = src; p; p = p->next) 
    {
      node = cdk_subpkt_new (p->size);
      if (node) 
	{
	  memcpy (node->d, p->d, p->size);
	  node->type = p->type;
	  node->size = p->size;
        }
      if (!root)
	root = node;
      else
	cdk_subpkt_add (root, node);
    }
  *r_dst = root;
  return 0;
}


/**
 * cdk_subpkt_init:
 * @node: the sub packet node
 * @type: type of the packet which data should be initialized
 * @buf: the buffer with the actual data
 * @buflen: the size of the data
 *
 * Set the packet data of the given root and set the type of it.
 **/
void
cdk_subpkt_init (cdk_subpkt_t node, size_t type, 
		 const void *buf, size_t buflen)
{
  if (!node)
    return;  
  node->type = type;
  node->size = buflen;
  memcpy (node->d, buf, buflen);
}


/* FIXME: We need to think of a public interface for it. */
const byte*
cdk_key_desig_revoker_walk (cdk_desig_revoker_t root,
                            cdk_desig_revoker_t * ctx,
                            int *r_class, int *r_algid)
{
  cdk_desig_revoker_t n;
  
  if (! *ctx)
    {
      *ctx = root;
      n = root;
    }
  else 
    {
      n = (*ctx)->next;
      *ctx = n;
    }
  
  if (n && r_class && r_algid)
    {
      *r_class = n->r_class;
      *r_algid = n->algid;
    }
  
  return n? n->fpr : NULL;
}


/**
 * cdk_subpkt_find_next:
 * @root: the base where to begin the iteration
 * @type: the type to find or 0 for the next node.
 * 
 * Try to find the next node after @root with type.
 * If type is 0, the next node will be returned.
 **/
cdk_subpkt_t
cdk_subpkt_find_next (cdk_subpkt_t root, size_t type)
{
  cdk_subpkt_t node;
  
  for (node = root->next; node; node = node->next)
    {
      if (!type)
	return node;
      else if (node->type == type)
	return node;
    }
  
  return NULL;
}
