/*

  silcasn1_encode.c

  Author: Pekka Riikonen <priikone@silcnet.org>

  Copyright (C) 2003 - 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.

*/

#include "silc.h"
#include "silcasn1.h"
#include "silcber.h"

#define SILC_ASN1_BUFFER_FREE(b, stack) if (!stack) silc_buffer_purge(b);

/************************** ASN.1 Encoder routines **************************/

/* Encode string from UTF-8 string to other string encodings.  Encodes
   diretly to BER blob. */
#define SILC_ASN1_ENCODE_STRING(enc)					\
  unsigned char *s, *d = va_arg(asn1->ap, unsigned char *);		\
  SilcUInt32 s_len, d_len = va_arg(asn1->ap, SilcUInt32);		\
  if (!d)								\
    break;								\
  s_len = silc_utf8_decoded_len(d, d_len, (enc));			\
  if (s_len == 0) {							\
    SILC_LOG_DEBUG(("Malformed %d string value", (enc)));		\
    goto fail;								\
  }									\
  silc_stack_push(asn1->stack2, &frame);				\
  s = silc_smalloc(stack2, s_len + 1);					\
  if (s) {								\
    silc_utf8_decode(d, d_len, (enc), s, s_len);			\
    s[s_len] = '\0';							\
  }									\
  len = silc_ber_encoded_len(tag, s_len, indef);			\
  dest = silc_buffer_srealloc_size(stack1, dest,			\
				   silc_buffer_truelen(dest) + len);	\
  ret = silc_ber_encode(dest, ber_class, SILC_BER_ENC_PRIMITIVE,	\
			tag, s, s_len, indef);				\
  silc_stack_pop(asn1->stack2);						\
  if (!ret)								\
    goto fail;

/* The internal ASN.1 encoder.  The `type', `tag' and `opts' are the
   first arguments (either very first or first for recursion) for a type.
   The `depth' includes the current depth of recursion.  The `primitive'
   is TRUE if this encoder receives one primitive type as argument.  If
   it is a constructed type it must be FALSE value. */

static SilcBool
silc_asn1_encoder(SilcAsn1 asn1, SilcStack stack1, SilcStack stack2,
		  SilcAsn1Tag type, SilcAsn1Tag tag, SilcBerClass ber_class,
		  SilcAsn1Options opts, SilcBuffer dest, SilcUInt32 depth,
		  SilcBool primitive)
{
  unsigned char *ptr = dest->data;
  SilcAsn1Tag rtype, rtag;
  SilcAsn1Options ropts;
  SilcBerClass rclass;
  SilcUInt32 len = 0;
  SilcBool ret = FALSE, indef;
  SilcBufferStruct buf;
  SilcStackFrame frame;

#ifdef SILC_DEBUG
  char sp[SILC_ASN1_RECURSION_DEPTH + 1];
  memset(sp, 0, sizeof(sp));
  if (depth)
    memset(sp, 32, depth);
#endif /* SILC_DEBUG */

  if (depth >= SILC_ASN1_RECURSION_DEPTH) {
    SILC_LOG_DEBUG(("Maximum recursion depth reached"));
    return FALSE;
  }

  while (1) {
    /* These options cannot be used in encoding */
    opts &= ~SILC_ASN1_OPTIONAL;

    /* Get length encoding */
    indef = (opts & SILC_ASN1_INDEFINITE ? TRUE : FALSE);

    /* By default UNIVERSAL is implied unless the following conditions
       are met when CONTEXT will apply.  For SILC_ASN1_TAG_ANY_PRIMITIVE
       the class is changed only if flags dictate it. */
    if (ber_class == SILC_BER_CLASS_UNIVERSAL) {
      if (type == SILC_ASN1_TAG_ANY_PRIMITIVE) {
	if (opts & SILC_ASN1_IMPLICIT ||
	    opts & SILC_ASN1_EXPLICIT)
	  ber_class = SILC_BER_CLASS_CONTEXT;
      } else {
	if (tag != type ||
	    opts & SILC_ASN1_IMPLICIT ||
	    opts & SILC_ASN1_EXPLICIT)
	  ber_class = SILC_BER_CLASS_CONTEXT;
      }
    }

#ifdef SILC_DEBUG
    SILC_LOG_DEBUG(
      ("%04d: %sEncode %s [%d] %s %s %s %s", depth, sp[0] ? sp : "",
       silc_asn1_tag_name(type), tag,
       ber_class == SILC_BER_CLASS_UNIVERSAL   ? "univ" :
       ber_class == SILC_BER_CLASS_APPLICATION ? "appl" :
       ber_class == SILC_BER_CLASS_CONTEXT     ? "cont" : "priv",
       (type != SILC_ASN1_TAG_SEQUENCE && type != SILC_ASN1_TAG_SET) ?
       opts & SILC_ASN1_EXPLICIT ? "constr" :
       type == SILC_ASN1_TAG_ANY &&
       !(opts & SILC_ASN1_EXPLICIT) ? "constr" : "primit" : "constr",
       indef ? opts & SILC_ASN1_EXPLICIT ? "defin" : "indef" : "defin",
       opts & SILC_ASN1_IMPLICIT ? "implicit" :
       opts & SILC_ASN1_EXPLICIT ? "explicit" : ""));
#endif /* SILC_DEBUG */

    /* If tagging is explicit we add constructed type before the underlaying
       types.  The underlaying types are encoded recursively with this
       encoder. */
    if (opts & SILC_ASN1_EXPLICIT) {
      memset(&buf, 0, sizeof(buf));

      primitive = (type != SILC_ASN1_TAG_SEQUENCE &&
		   type != SILC_ASN1_TAG_SET);
      opts &= ~SILC_ASN1_EXPLICIT;

      silc_stack_push(stack2, &frame);
      ret = silc_asn1_encoder(asn1, stack2, stack1, type, type,
			      SILC_BER_CLASS_UNIVERSAL, opts,
			      &buf, depth + 1, primitive);

      if (!ret) {
	SILC_LOG_DEBUG(("Error encoding explicit tag"));
	silc_stack_pop(stack2);
	goto fail;
      }

      /* Encode the explicit tag */
      len = silc_ber_encoded_len(tag, silc_buffer_len(&buf), FALSE);
      dest = silc_buffer_srealloc_size(stack1, dest,
				       silc_buffer_truelen(dest) + len);
      ret = silc_ber_encode(dest, ber_class, SILC_BER_ENC_CONSTRUCTED,
			    tag, buf.data, silc_buffer_len(&buf), FALSE);
      SILC_ASN1_BUFFER_FREE(&buf, stack2);
      silc_stack_pop(stack2);
      if (!ret)
	goto fail;
      if (primitive) {
	primitive = FALSE;
	goto cont;
      }
      goto ok;
    }

    /* Encode by the type */
    switch ((long)type) {

    case SILC_ASN1_TAG_ANY:
      {
	/* ANY is another ASN.1 node which is added to this tree */
	SilcBuffer node = va_arg(asn1->ap, SilcBuffer);
	if (!node)
	  break;

	/* Encode ASN.1 node into the tree. */
	if (opts & SILC_ASN1_IMPLICIT || type != tag) {
	  /* We are tagging implicitly so we need to change the identifier
	     of the underlaying type.  Only constructed type is allowed with
	     ANY when tagging implicitly. */
	  const unsigned char *d;
	  SilcUInt32 d_len;
	  SilcBerEncoding enc;

	  /* Get the underlaying data */
	  ret = silc_ber_decode(node, NULL, &enc, NULL, &d, &d_len,
				NULL, NULL);
	  if (!ret) {
	    SILC_LOG_DEBUG(("Error decoding underlaying node for ANY"));
	    goto fail;
	  }
	  assert(enc == SILC_BER_ENC_CONSTRUCTED);

	  /* Now encode with implicit tagging */
	  len = silc_ber_encoded_len(tag, d_len, FALSE);
	  dest = silc_buffer_srealloc_size(stack1, dest,
					   silc_buffer_truelen(dest) + len);
	  ret = silc_ber_encode(dest, ber_class, SILC_BER_ENC_CONSTRUCTED,
				tag, d, d_len, FALSE);
	  if (!ret)
	    goto fail;
	} else {
	  /* Copy the data directly into the tree. */
	  len = silc_buffer_len(node);
	  dest = silc_buffer_srealloc_size(stack1, dest,
					   silc_buffer_truelen(dest) + len);
	  if (!dest)
	    goto fail;
	  silc_buffer_put(dest, node->data, len);
	}
	break;
      }

    case SILC_ASN1_TAG_ANY_PRIMITIVE:
      {
	/* ANY_PRIMITIVE is any primitive in encoded format. */
	SilcBuffer prim = va_arg(asn1->ap, SilcBuffer);
	if (!prim)
	  break;

	/* Encode the primitive data */
	len = silc_ber_encoded_len(tag, silc_buffer_len(prim), FALSE);
	dest = silc_buffer_srealloc_size(stack1, dest,
					 silc_buffer_truelen(dest) + len);
	ret = silc_ber_encode(dest, ber_class, SILC_BER_ENC_PRIMITIVE,
			      tag, prim->data, silc_buffer_len(prim), FALSE);
	if (!ret)
	  goto fail;
	break;
      }

    case SILC_ASN1_TAG_SEQUENCE:
    case SILC_ASN1_TAG_SET:
      {
	/* SEQUENCE/SET is a sequence of types. Sequences are opened and
	   encoded recursively by calling this same encoder. */
	memset(&buf, 0, sizeof(buf));

	/* Get type, tag and options for the first argument in recursion */
	SILC_ASN1_ARGS(asn1, rtype, rtag, rclass, ropts);

	silc_stack_push(stack2, &frame);
	ret = silc_asn1_encoder(asn1, stack2, stack1, rtype, rtag, rclass,
				ropts, &buf, depth + 1, FALSE);
	if (!ret) {
	  SILC_LOG_DEBUG(("Error traversing a SEQUENCE/SET"));
	  silc_stack_pop(stack2);
	  goto fail;
	}

	/* Encode the sequence */
	len = silc_ber_encoded_len(tag, silc_buffer_len(&buf), indef);
	dest = silc_buffer_srealloc_size(stack1, dest,
					 silc_buffer_truelen(dest) + len);
	ret = silc_ber_encode(dest, ber_class, SILC_BER_ENC_CONSTRUCTED,
			      tag, buf.data, silc_buffer_len(&buf), indef);
	SILC_ASN1_BUFFER_FREE(&buf, stack2);
	silc_stack_pop(stack2);
	if (!ret)
	  goto fail;
	break;
      }

    case SILC_ASN1_TAG_INTEGER:
    case SILC_ASN1_TAG_ENUM:
      {
	/* Integer */
	SilcMPInt *mpint = va_arg(asn1->ap, SilcMPInt *);
	if (!mpint)
	  break;

	memset(&buf, 0, sizeof(buf));
	if (silc_mp_cmp_ui(mpint, 0) < 0) {
	  /* XXX TODO, negative integer.  Take 2s complement, then store
	     bytes in 1s complement */
	} else {
	  /* Positive */
	  len = silc_mp_sizeinbase(mpint, 2);
	  if (!(len & 7))
	    len = ((len + 7) / 8) + 1;
	  else
	    len = (len + 7) / 8;
	  silc_stack_push(stack2, &frame);
	  silc_buffer_srealloc_size(stack2, &buf,
				    silc_buffer_truelen(&buf) + len);
	  buf.data[0] = 0x00;
	  silc_mp_mp2bin_noalloc(mpint, buf.data, silc_buffer_len(&buf));
	}

	/* Encode the integer */
	len = silc_ber_encoded_len(tag, len, indef);
	dest = silc_buffer_srealloc_size(stack1, dest,
					 silc_buffer_truelen(dest) + len);
	ret = silc_ber_encode(dest, ber_class, SILC_BER_ENC_PRIMITIVE,
			      tag, buf.data, silc_buffer_len(&buf), FALSE);
	SILC_ASN1_BUFFER_FREE(&buf, stack2);
	silc_stack_pop(stack2);
	if (!ret)
	  goto fail;
	break;
      }

    case SILC_ASN1_TAG_SHORT_INTEGER:
      {
	/* Short Integer */
	SilcUInt32 sint = va_arg(asn1->ap, SilcUInt32);
	SilcMPInt z;

	if (tag == SILC_ASN1_TAG_SHORT_INTEGER)
	  tag = SILC_ASN1_TAG_INTEGER;

	memset(&buf, 0, sizeof(buf));

	silc_stack_push(stack2, &frame);
	silc_mp_sinit(stack2, &z);
	silc_mp_set_ui(&z, sint);

	len = silc_mp_sizeinbase(&z, 2);
	if (!(len & 7))
	  len = ((len + 7) / 8) + 1;
	else
	  len = (len + 7) / 8;
	silc_buffer_srealloc_size(stack2, &buf,
				  silc_buffer_truelen(&buf) + len);
	buf.data[0] = 0x00;
	silc_mp_mp2bin_noalloc(&z, buf.data, silc_buffer_len(&buf));
	silc_mp_uninit(&z);

	/* Encode the integer */
	len = silc_ber_encoded_len(tag, len, indef);
	dest = silc_buffer_srealloc_size(stack1, dest,
					 silc_buffer_truelen(dest) + len);
	ret = silc_ber_encode(dest, ber_class, SILC_BER_ENC_PRIMITIVE,
			      tag, buf.data, silc_buffer_len(&buf), FALSE);
	SILC_ASN1_BUFFER_FREE(&buf, stack2);
	silc_stack_pop(stack2);
	if (!ret)
	  goto fail;
	break;
      }
      break;

    case SILC_ASN1_TAG_OID:
      {
	/* Object identifier */
	char *cp, *oidstr = va_arg(asn1->ap, char *);
	SilcUInt32 words[24], oid, mask;
	int i, k, c = 0;
	if (!oidstr)
	  break;

	/* Get OID words from the string */
	cp = strchr(oidstr, '.');
	while (cp) {
	  if (sscanf(oidstr, "%u", &oid) != 1) {
	    SILC_LOG_DEBUG(("Malformed OID string"));
	    goto fail;
	  }
	  if (c + 1 > sizeof(words) / sizeof(words[0]))
	    goto fail;
	  words[c++] = oid;
	  oidstr = cp + 1;
	  cp = strchr(oidstr, '.');

	  if (!cp) {
	    if (sscanf(oidstr, "%u", &oid) != 1) {
	      SILC_LOG_DEBUG(("Malformed OID string"));
	      goto fail;
	    }
	    if (c + 1 > sizeof(words) / sizeof(words[0]))
	      goto fail;
	    words[c++] = oid;
	    break;
	  }
	}
	if (c < 2) {
	  SILC_LOG_DEBUG(("Malfromed OID string"));
	  goto fail;
	}

	/* Get OID data length */
	for (i = 2, len = 1; i < c; i++) {
	  if (words[i]) {
	    for (oid = words[i]; oid; oid >>= 7)
	      len++;
	    continue;
	  }
	  len++;
	}

	/* Encode the OID */
	memset(&buf, 0, sizeof(buf));
	silc_stack_push(stack2, &frame);
	silc_buffer_srealloc_size(stack2, &buf,
				  silc_buffer_truelen(&buf) + len);
	buf.data[0] = words[0] * 40 + words[1];
	for (i = 2, len = 1; i < c; i++) {
	  oid = words[i];
	  if (oid) {
	    k = len;
	    mask = 0;
	    while (oid) {
	      buf.data[len++] = (oid & 0x7f) | mask;
	      oid >>= 7;
	      mask |= 0x80;
	    }
	    mask = len - 1;
	    while (k < mask) {
	      oid = buf.data[k];
	      buf.data[k] = buf.data[mask];
	      buf.data[mask] = oid;
	      k++;
	      mask--;
	    }

	    continue;
	  }
	  buf.data[len++] = 0x00;
	}

	len = silc_ber_encoded_len(tag, silc_buffer_len(&buf), indef);
	dest = silc_buffer_srealloc_size(stack1, dest,
					 silc_buffer_truelen(dest) + len);
	ret = silc_ber_encode(dest, ber_class, SILC_BER_ENC_PRIMITIVE,
			      tag, buf.data, silc_buffer_len(&buf), FALSE);
	SILC_ASN1_BUFFER_FREE(&buf, stack2);
	silc_stack_pop(stack2);
	if (!ret)
	  goto fail;
	break;
      }

    case SILC_ASN1_TAG_BOOLEAN:
      {
	/* Encodes boolean (TRUE/FALSE) value */
	unsigned char val[1];
	val[0] = (va_arg(asn1->ap, SilcUInt32) ? 0xff : 0x00);

	assert(indef == FALSE);
	len = silc_ber_encoded_len(tag, 1, FALSE);
	dest = silc_buffer_srealloc_size(stack1, dest,
					 silc_buffer_truelen(dest) + len);
	ret = silc_ber_encode(dest, ber_class, SILC_BER_ENC_PRIMITIVE,
			      tag, val, 1, FALSE);
	if (!ret)
	  goto fail;
	break;
      }

    case SILC_ASN1_TAG_BIT_STRING:
      {
	/* Encode the data as is, with the bit padding. d_len is in bits. */
	unsigned char *d = va_arg(asn1->ap, unsigned char *);
	SilcUInt32 d_len = va_arg(asn1->ap, SilcUInt32);
	unsigned char pad[1];
	if (!d)
	  break;

	pad[0] = (8 - (d_len & 7)) & 7;
	d_len = ((d_len + 7) / 8) + 1;

	memset(&buf, 0, sizeof(buf));
	silc_stack_push(stack2, &frame);
	silc_buffer_srealloc_size(stack2, &buf,
				  silc_buffer_truelen(&buf) + d_len);
	silc_buffer_put(&buf, pad, 1);
	silc_buffer_pull(&buf, 1);
	silc_buffer_put(&buf, d, d_len - 1);
	silc_buffer_push(&buf, 1);

	len = silc_ber_encoded_len(tag, silc_buffer_len(&buf), indef);
	dest = silc_buffer_srealloc_size(stack1, dest,
					 silc_buffer_truelen(dest) + len);
	ret = silc_ber_encode(dest, ber_class, SILC_BER_ENC_PRIMITIVE,
			      tag, buf.data, silc_buffer_len(&buf), indef);
	SILC_ASN1_BUFFER_FREE(&buf, stack2);
	silc_stack_pop(stack2);
	if (!ret)
	  goto fail;
	break;
      }

    case SILC_ASN1_TAG_NULL:
      {
	/* Encode empty BER block */
	assert(indef == FALSE);
	len = silc_ber_encoded_len(tag, 0, FALSE);
	dest = silc_buffer_srealloc_size(stack1, dest,
					 silc_buffer_truelen(dest) + len);
	ret = silc_ber_encode(dest, ber_class, SILC_BER_ENC_PRIMITIVE,
			      tag, NULL, 0, FALSE);
	if (!ret)
	  goto fail;
	break;
      }

    case SILC_ASN1_TAG_UTC_TIME:
      {
	/* Universal encoded time string */
	SilcTime timeval = va_arg(asn1->ap, SilcTime);
	char timestr[32];
	if (!timeval)
	  break;

	if (!silc_time_universal_string(timeval, timestr, sizeof(timestr))) {
	  SILC_LOG_DEBUG(("Could not encode universal time string"));
	  goto fail;
	}

	len = silc_ber_encoded_len(tag, strlen(timestr), indef);
	dest = silc_buffer_srealloc_size(stack1, dest,
					 silc_buffer_truelen(dest) + len);
	ret = silc_ber_encode(dest, ber_class, SILC_BER_ENC_PRIMITIVE,
			      tag, timestr, strlen(timestr), indef);
	if (!ret)
	  goto fail;
	break;
      }

    case SILC_ASN1_TAG_GENERALIZED_TIME:
      {
	/* Generalized encoded time string */
	SilcTime timeval = va_arg(asn1->ap, SilcTime);
	char timestr[32];
	if (!timeval)
	  break;

	if (!silc_time_generalized_string(timeval, timestr, sizeof(timestr))) {
	  SILC_LOG_DEBUG(("Could not encode generalized time string"));
	  goto fail;
	}

	len = silc_ber_encoded_len(tag, strlen(timestr), indef);
	dest = silc_buffer_srealloc_size(stack1, dest,
					 silc_buffer_truelen(dest) + len);
	ret = silc_ber_encode(dest, ber_class, SILC_BER_ENC_PRIMITIVE,
			      tag, timestr, strlen(timestr), indef);
	if (!ret)
	  goto fail;
	break;
      }

    case SILC_ASN1_TAG_UTF8_STRING:
      {
	/* UTF-8 string */
	unsigned char *d = va_arg(asn1->ap, unsigned char *);
	SilcUInt32 d_len = va_arg(asn1->ap, SilcUInt32);
	if (!d)
	  break;

	/* By default all strings that get here should already be UTF-8 */
	if (!silc_utf8_valid(d, d_len)) {
	  SILC_LOG_DEBUG(("Malformed UTF-8 string"));
	  goto fail;
	}

	len = silc_ber_encoded_len(tag, d_len, indef);
	dest = silc_buffer_srealloc_size(stack1, dest,
					 silc_buffer_truelen(dest) + len);
	ret = silc_ber_encode(dest, ber_class, SILC_BER_ENC_PRIMITIVE,
			      tag, d, d_len, indef);
	if (!ret)
	  goto fail;
	break;
      }

    case SILC_ASN1_TAG_OCTET_STRING:
      {
	/* Octet string.  Put data as is. */
	unsigned char *d = va_arg(asn1->ap, unsigned char *);
	SilcUInt32 d_len = va_arg(asn1->ap, SilcUInt32);

	len = silc_ber_encoded_len(tag, d_len, indef);
	dest = silc_buffer_srealloc_size(stack1, dest,
					 silc_buffer_truelen(dest) + len);
	ret = silc_ber_encode(dest, ber_class, SILC_BER_ENC_PRIMITIVE,
			      tag, d, d_len, indef);
	if (!ret)
	  goto fail;
	break;
      }

    case SILC_ASN1_TAG_NUMERIC_STRING:
      {
	/* Numerical (digit) string */
	SILC_ASN1_ENCODE_STRING(SILC_STRING_NUMERICAL);
	break;
      }

    case SILC_ASN1_TAG_PRINTABLE_STRING:
      {
	/* Printable string */
	SILC_ASN1_ENCODE_STRING(SILC_STRING_PRINTABLE);
	break;
      }

    case SILC_ASN1_TAG_TELETEX_STRING:
      {
	/* Teletex (T61) string */
	SILC_ASN1_ENCODE_STRING(SILC_STRING_TELETEX);
	break;
      }

    case SILC_ASN1_TAG_IA5_STRING:
      {
	/* US ASCII string */
	SILC_ASN1_ENCODE_STRING(SILC_STRING_ASCII);
	break;
      }

    case SILC_ASN1_TAG_VISIBLE_STRING:
      {
	/* Visible string */
	SILC_ASN1_ENCODE_STRING(SILC_STRING_VISIBLE);
	break;
      }

    case SILC_ASN1_TAG_UNIVERSAL_STRING:
      {
	/* Universal (UCS-4) string */
	SILC_ASN1_ENCODE_STRING(SILC_STRING_UNIVERSAL);
	break;
      }

    case SILC_ASN1_TAG_UNRESTRICTED_STRING:
    case SILC_ASN1_TAG_GENERAL_STRING:
      {
	/* Handle now unrestricted and general as 8-bit ascii, which
	   probably isn't correct. */
	SILC_ASN1_ENCODE_STRING(SILC_STRING_ASCII);
	break;
      }

    case SILC_ASN1_TAG_BMP_STRING:
      {
	/* BMP (UCS-2) string */
	SILC_ASN1_ENCODE_STRING(SILC_STRING_UNIVERSAL);
	break;
      }

    case SILC_ASN1_TAG_ODE:
    case SILC_ASN1_TAG_ETI:
    case SILC_ASN1_TAG_REAL:
    case SILC_ASN1_TAG_EMBEDDED:
    case SILC_ASN1_TAG_ROI:
    case SILC_ASN1_TAG_VIDEOTEX_STRING:
    case SILC_ASN1_TAG_GRAPHIC_STRING:
      {
	SILC_NOT_IMPLEMENTED("Unsupported ASN.1 tag");
	ret = FALSE;
	goto fail;
	break;
      }

    default:
      SILC_LOG_DEBUG(("Invalid ASN.1 tag `%d'. Cannot encode ASN.1.", type));
      ret = FALSE;
      goto fail;
      break;
    }

  cont:
    if (len)
      silc_buffer_pull(dest, len);
    if (primitive) {
      ret = TRUE;
      goto ok;
    }

    /* Get next type, tag and options */
    SILC_ASN1_ARGS(asn1, type, tag, ber_class, opts);
    if (type == SILC_ASN1_END) {
      ret = TRUE;
      goto ok;
    }
  }

 fail:
  SILC_LOG_DEBUG(("Error encoding type %d (depth %d)", type, depth));

 ok:
  if (ptr)
    len = dest->data - ptr;
  else
    len = dest->data - dest->head;
  silc_buffer_push(dest, len);

  return ret;
}

SilcBool silc_asn1_encode(SilcAsn1 asn1, SilcBuffer dest, ...)
{
  SilcAsn1Tag type, tag;
  SilcAsn1Options opts;
  SilcBerClass ber_class;
  SilcStackFrame frame1, frame2;
  SilcStack stack1 = NULL;
  SilcBool ret;

  if (!asn1)
    return FALSE;

  va_start(asn1->ap, dest);

  /* Get the first arguments and call the encoder. */
  SILC_ASN1_ARGS(asn1, type, tag, ber_class, opts);
  if (!type) {
    va_end(asn1->ap);
    return FALSE;
  }

  /* Handle internal options for encoder. */
  if (type == SILC_ASN1_TAG_OPTS) {
    SilcUInt32 o = va_arg(asn1->ap, SilcUInt32);

    if (o & SILC_ASN1_ALLOC) {
      /* User wants to alloate everything.  Set the stack to NULL so
	 that stack aware calls revert to normal allocation routines. */
      stack1 = asn1->stack1;
      asn1->stack1 = NULL;
    }

    if (o & SILC_ASN1_ACCUMUL) {
      /* If accumul flag is not set yet, then push the stack. */
      if (!asn1->accumul) {
	silc_stack_push(asn1->stack1, NULL);
	asn1->accumul = 1;
      }
    }

    /* Take again the arguments */
    SILC_ASN1_ARGS(asn1, type, tag, ber_class, opts);
  } else {
    /* No flags set, all flags will be reset. */

    /* If accumul flag is set now pop the stack so that all accumulated
       memory becomes free again. */
    if (asn1->accumul) {
      silc_stack_pop(asn1->stack1);
      asn1->accumul = 0;
    }
  }

  /* Push the stack for normal allocation from stack. */
  if (!asn1->accumul)
    silc_stack_push(asn1->stack1, &frame1);

  /* Start encoding */
  silc_stack_push(asn1->stack2, &frame2);
  ret = silc_asn1_encoder(asn1, asn1->stack1, asn1->stack2,
			  type, tag, ber_class, opts, dest, 0, FALSE);
  silc_stack_pop(asn1->stack2);

  /* Pop the stack to free normal allocations from stack. */
  if (!asn1->accumul)
    silc_stack_pop(asn1->stack1);

  /* If SILC_ASN1_ALLOC flag was set, restore the stack. */
  if (stack1 && !asn1->stack1)
    asn1->stack1 = stack1;

  va_end(asn1->ap);

  return ret;
}
