/*
 * Copyright (C) 2000-2022 Free Software Foundation, Inc.
 *
 * This file is part of LIBTASN1.
 *
 * The LIBTASN1 library is free software; you can redistribute it
 * and/or modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA
 */

#include <limits.h>		/* WORD_BIT */

#include "int.h"
#include "parser_aux.h"
#include "gstr.h"
#include "structure.h"
#include "element.h"
#include "c-ctype.h"

char _asn1_identifierMissing[ASN1_MAX_NAME_SIZE + 1];	/* identifier name not found */

/* Return a hash of the N bytes of X using the method described by
   Bruno Haible in https://www.haible.de/bruno/hashfunc.html.
   Note that while many hash functions reduce their result via modulo
   to a 0..table_size-1 range, this function does not do that.

   This implementation has been changed from size_t -> unsigned int. */

#ifdef __clang__
__attribute__((no_sanitize ("integer")))
#endif
     _GL_ATTRIBUTE_PURE static unsigned int _asn1_hash_name (const char *x)
{
  const unsigned char *s = (unsigned char *) x;
  unsigned h = 0;

  while (*s)
    h = (*s++) + ((h << 9) | (h >> (WORD_BIT - 9)));

  return h;
}

/******************************************************/
/* Function : _asn1_add_static_node                   */
/* Description: creates a new NODE_ASN element and    */
/* puts it in the list pointed by e_list.       */
/* Parameters:                                        */
/*   e_list: of type list_type; must be NULL initially */
/*   type: type of the new element (see ASN1_ETYPE_   */
/*         and CONST_ constants).                     */
/* Return: pointer to the new element.                */
/******************************************************/
asn1_node
_asn1_add_static_node (list_type ** e_list, unsigned int type)
{
  list_type *p;
  asn1_node punt;

  punt = calloc (1, sizeof (struct asn1_node_st));
  if (punt == NULL)
    return NULL;

  p = malloc (sizeof (list_type));
  if (p == NULL)
    {
      free (punt);
      return NULL;
    }

  p->node = punt;
  p->next = *e_list;
  *e_list = p;

  punt->type = type;

  return punt;
}

static int
_asn1_add_static_node2 (list_type ** e_list, asn1_node node)
{
  list_type *p;

  p = malloc (sizeof (list_type));
  if (p == NULL)
    {
      return -1;
    }

  p->node = node;
  p->next = *e_list;
  *e_list = p;

  return 0;
}

/**
 * asn1_find_node:
 * @pointer: NODE_ASN element pointer.
 * @name: null terminated string with the element's name to find.
 *
 * Searches for an element called @name starting from @pointer.  The
 * name is composed by different identifiers separated by dots.  When
 * *@pointer has a name, the first identifier must be the name of
 * *@pointer, otherwise it must be the name of one child of *@pointer.
 *
 * Returns: the search result, or %NULL if not found.
 **/
asn1_node
asn1_find_node (asn1_node_const pointer, const char *name)
{
  asn1_node_const p;
  char *n_end, n[ASN1_MAX_NAME_SIZE + 1];
  const char *n_start;
  unsigned int nsize;
  unsigned int nhash;

  if (pointer == NULL)
    return NULL;

  if (name == NULL)
    return NULL;

  p = pointer;
  n_start = name;

  if (name[0] == '?' && name[1] == 'C' && p->name[0] == '?')
    {				/* ?CURRENT */
      n_start = strchr (n_start, '.');
      if (n_start)
	n_start++;
    }
  else if (p->name[0] != 0)
    {				/* has *pointer got a name ? */
      n_end = strchr (n_start, '.');	/* search the first dot */
      if (n_end)
	{
	  nsize = n_end - n_start;
	  if (nsize >= sizeof (n))
	    return NULL;

	  memcpy (n, n_start, nsize);
	  n[nsize] = 0;
	  n_start = n_end;
	  n_start++;

	  nhash = _asn1_hash_name (n);
	}
      else
	{
	  _asn1_str_cpy (n, sizeof (n), n_start);
	  nhash = _asn1_hash_name (n);

	  n_start = NULL;
	}

      while (p)
	{
	  if (nhash == p->name_hash && (!strcmp (p->name, n)))
	    break;
	  else
	    p = p->right;
	}			/* while */

      if (p == NULL)
	return NULL;
    }
  else
    {				/* *pointer doesn't have a name */
      if (n_start[0] == 0)
	return (asn1_node) p;
    }

  while (n_start)
    {				/* Has the end of NAME been reached? */
      n_end = strchr (n_start, '.');	/* search the next dot */
      if (n_end)
	{
	  nsize = n_end - n_start;
	  if (nsize >= sizeof (n))
	    return NULL;

	  memcpy (n, n_start, nsize);
	  n[nsize] = 0;
	  n_start = n_end;
	  n_start++;

	  nhash = _asn1_hash_name (n);
	}
      else
	{
	  _asn1_str_cpy (n, sizeof (n), n_start);
	  nhash = _asn1_hash_name (n);
	  n_start = NULL;
	}

      if (p->down == NULL)
	return NULL;

      p = p->down;
      if (p == NULL)
	return NULL;

      /* The identifier "?LAST" indicates the last element
         in the right chain. */
      if (n[0] == '?' && n[1] == 'L')	/* ?LAST */
	{
	  while (p->right)
	    p = p->right;
	}
      else
	{			/* no "?LAST" */
	  while (p)
	    {
	      if (p->name_hash == nhash && !strcmp (p->name, n))
		break;
	      else
		p = p->right;
	    }
	}
      if (p == NULL)
	return NULL;
    }				/* while */

  return (asn1_node) p;
}


/******************************************************************/
/* Function : _asn1_set_value                                     */
/* Description: sets the field VALUE in a NODE_ASN element. The   */
/*              previous value (if exist) will be lost            */
/* Parameters:                                                    */
/*   node: element pointer.                                       */
/*   value: pointer to the value that you want to set.            */
/*   len: character number of value.                              */
/* Return: pointer to the NODE_ASN element.                       */
/******************************************************************/
asn1_node
_asn1_set_value (asn1_node node, const void *value, unsigned int len)
{
  if (node == NULL)
    return node;
  if (node->value)
    {
      if (node->value != node->small_value)
	free (node->value);
      node->value = NULL;
      node->value_len = 0;
    }

  if (!len)
    return node;

  if (len < sizeof (node->small_value))
    {
      node->value = node->small_value;
    }
  else
    {
      node->value = malloc (len);
      if (node->value == NULL)
	return NULL;
    }
  node->value_len = len;

  memcpy (node->value, value, len);
  return node;
}

/******************************************************************/
/* Function : _asn1_set_value_lv                                  */
/* Description: sets the field VALUE in a NODE_ASN element. The   */
/*              previous value (if exist) will be lost. The value */
/*		given is stored as an length-value format (LV     */
/* Parameters:                                                    */
/*   node: element pointer.                                       */
/*   value: pointer to the value that you want to set.            */
/*   len: character number of value.                              */
/* Return: pointer to the NODE_ASN element.                       */
/******************************************************************/
asn1_node
_asn1_set_value_lv (asn1_node node, const void *value, unsigned int len)
{
  int len2;
  void *temp;

  if (node == NULL)
    return node;

  asn1_length_der (len, NULL, &len2);
  temp = malloc (len + len2);
  if (temp == NULL)
    return NULL;

  asn1_octet_der (value, len, temp, &len2);
  return _asn1_set_value_m (node, temp, len2);
}

/* the same as _asn1_set_value except that it sets an already malloc'ed
 * value.
 */
asn1_node
_asn1_set_value_m (asn1_node node, void *value, unsigned int len)
{
  if (node == NULL)
    return node;

  if (node->value)
    {
      if (node->value != node->small_value)
	free (node->value);
      node->value = NULL;
      node->value_len = 0;
    }

  if (!len)
    return node;

  node->value = value;
  node->value_len = len;

  return node;
}

/******************************************************************/
/* Function : _asn1_append_value                                  */
/* Description: appends to the field VALUE in a NODE_ASN element. */
/*							          */
/* Parameters:                                                    */
/*   node: element pointer.                                       */
/*   value: pointer to the value that you want to be appended.    */
/*   len: character number of value.                              */
/* Return: pointer to the NODE_ASN element.                       */
/******************************************************************/
asn1_node
_asn1_append_value (asn1_node node, const void *value, unsigned int len)
{
  if (node == NULL)
    return node;

  if (node->value == NULL)
    return _asn1_set_value (node, value, len);

  if (len == 0)
    return node;

  if (node->value == node->small_value)
    {
      /* value is in node */
      int prev_len = node->value_len;
      node->value_len += len;
      node->value = malloc (node->value_len);
      if (node->value == NULL)
	{
	  node->value_len = 0;
	  return NULL;
	}

      if (prev_len > 0)
	memcpy (node->value, node->small_value, prev_len);

      memcpy (&node->value[prev_len], value, len);

      return node;
    }
  else				/* if (node->value != NULL && node->value != node->small_value) */
    {
      /* value is allocated */
      int prev_len = node->value_len;
      node->value_len += len;

      node->value = _asn1_realloc (node->value, node->value_len);
      if (node->value == NULL)
	{
	  node->value_len = 0;
	  return NULL;
	}

      memcpy (&node->value[prev_len], value, len);

      return node;
    }
}

/******************************************************************/
/* Function : _asn1_set_name                                      */
/* Description: sets the field NAME in a NODE_ASN element. The    */
/*              previous value (if exist) will be lost            */
/* Parameters:                                                    */
/*   node: element pointer.                                       */
/*   name: a null terminated string with the name that you want   */
/*         to set.                                                */
/* Return: pointer to the NODE_ASN element.                       */
/******************************************************************/
asn1_node
_asn1_set_name (asn1_node node, const char *name)
{
  if (node == NULL)
    return node;

  _asn1_str_cpy (node->name, sizeof (node->name), name ? name : "");
  node->name_hash = _asn1_hash_name (node->name);

  return node;
}

/******************************************************************/
/* Function : _asn1_cpy_name                                      */
/* Description: copies the field NAME in a NODE_ASN element.      */
/* Parameters:                                                    */
/*   dst: a dest element pointer.                                 */
/*   src: a source element pointer.                               */
/* Return: pointer to the NODE_ASN element.                       */
/******************************************************************/
asn1_node
_asn1_cpy_name (asn1_node dst, asn1_node_const src)
{
  if (dst == NULL)
    return dst;

  if (src == NULL)
    {
      dst->name[0] = 0;
      dst->name_hash = _asn1_hash_name (dst->name);
      return dst;
    }

  _asn1_str_cpy (dst->name, sizeof (dst->name), src->name);
  dst->name_hash = src->name_hash;

  return dst;
}

/******************************************************************/
/* Function : _asn1_set_right                                     */
/* Description: sets the field RIGHT in a NODE_ASN element.       */
/* Parameters:                                                    */
/*   node: element pointer.                                       */
/*   right: pointer to a NODE_ASN element that you want be pointed*/
/*          by NODE.                                              */
/* Return: pointer to *NODE.                                      */
/******************************************************************/
asn1_node
_asn1_set_right (asn1_node node, asn1_node right)
{
  if (node == NULL)
    return node;
  node->right = right;
  if (right)
    right->left = node;
  return node;
}


/******************************************************************/
/* Function : _asn1_get_last_right                                */
/* Description: return the last element along the right chain.    */
/* Parameters:                                                    */
/*   node: starting element pointer.                              */
/* Return: pointer to the last element along the right chain.     */
/******************************************************************/
asn1_node
_asn1_get_last_right (asn1_node_const node)
{
  asn1_node_const p;

  if (node == NULL)
    return NULL;
  p = node;
  while (p->right)
    p = p->right;
  return (asn1_node) p;
}

/******************************************************************/
/* Function : _asn1_remove_node                                   */
/* Description: gets free the memory allocated for an NODE_ASN    */
/*              element (not the elements pointed by it).         */
/* Parameters:                                                    */
/*   node: NODE_ASN element pointer.                              */
/*   flags: ASN1_DELETE_FLAG_*                                    */
/******************************************************************/
void
_asn1_remove_node (asn1_node node, unsigned int flags)
{
  if (node == NULL)
    return;

  if (node->value != NULL)
    {
      if (flags & ASN1_DELETE_FLAG_ZEROIZE)
	{
	  safe_memset (node->value, 0, node->value_len);
	}

      if (node->value != node->small_value)
	free (node->value);
    }
  free (node);
}

/******************************************************************/
/* Function : _asn1_find_up                                       */
/* Description: return the father of the NODE_ASN element.        */
/* Parameters:                                                    */
/*   node: NODE_ASN element pointer.                              */
/* Return: Null if not found.                                     */
/******************************************************************/
asn1_node
_asn1_find_up (asn1_node_const node)
{
  asn1_node_const p;

  if (node == NULL)
    return NULL;

  p = node;

  while ((p->left != NULL) && (p->left->right == p))
    p = p->left;

  return p->left;
}

static unsigned
_asn1_is_up (asn1_node_const up_cand, asn1_node_const down)
{
  asn1_node_const d, u;

  if (up_cand == NULL || down == NULL)
    return 0;

  d = down;

  while ((u = _asn1_find_up (d)) != NULL && u != d)
    {
      if (u == up_cand)
	return 1;
      d = u;
    }

  return 0;
}

/******************************************************************/
/* Function : _asn1_delete_node_from_list                         */
/* Description: deletes the list element given                    */
/******************************************************************/
void
_asn1_delete_node_from_list (list_type * list, asn1_node node)
{
  list_type *p = list;

  while (p)
    {
      if (p->node == node)
	p->node = NULL;
      p = p->next;
    }
}

/******************************************************************/
/* Function : _asn1_delete_list                                   */
/* Description: deletes the list elements (not the elements       */
/*  pointed by them).                                             */
/******************************************************************/
void
_asn1_delete_list (list_type * e_list)
{
  list_type *p;

  while (e_list)
    {
      p = e_list;
      e_list = e_list->next;
      free (p);
    }
}

/******************************************************************/
/* Function : _asn1_delete_list_and nodes                         */
/* Description: deletes the list elements and the elements        */
/*  pointed by them.                                              */
/******************************************************************/
void
_asn1_delete_list_and_nodes (list_type * e_list)
{
  list_type *p;

  while (e_list)
    {
      p = e_list;
      e_list = e_list->next;
      _asn1_remove_node (p->node, 0);
      free (p);
    }
}


char *
_asn1_ltostr (int64_t v, char str[LTOSTR_MAX_SIZE])
{
  uint64_t d, r;
  char temp[LTOSTR_MAX_SIZE];
  int count, k, start;
  uint64_t val;

  if (v < 0)
    {
      str[0] = '-';
      start = 1;
      val = -((uint64_t) v);
    }
  else
    {
      val = v;
      start = 0;
    }

  count = 0;
  do
    {
      d = val / 10;
      r = val - d * 10;
      temp[start + count] = '0' + (char) r;
      count++;
      val = d;
    }
  while (val && ((start + count) < LTOSTR_MAX_SIZE - 1));

  for (k = 0; k < count; k++)
    str[k + start] = temp[start + count - k - 1];
  str[count + start] = 0;
  return str;
}


/******************************************************************/
/* Function : _asn1_change_integer_value                          */
/* Description: converts into DER coding the value assign to an   */
/*   INTEGER constant.                                            */
/* Parameters:                                                    */
/*   node: root of an ASN1element.                                */
/* Return:                                                        */
/*   ASN1_ELEMENT_NOT_FOUND if NODE is NULL,                       */
/*   otherwise ASN1_SUCCESS                                             */
/******************************************************************/
int
_asn1_change_integer_value (asn1_node node)
{
  asn1_node p;
  unsigned char val[SIZEOF_UNSIGNED_LONG_INT];
  unsigned char val2[SIZEOF_UNSIGNED_LONG_INT + 1];
  int len;

  if (node == NULL)
    return ASN1_ELEMENT_NOT_FOUND;

  p = node;
  while (p)
    {
      if ((type_field (p->type) == ASN1_ETYPE_INTEGER)
	  && (p->type & CONST_ASSIGN))
	{
	  if (p->value)
	    {
	      _asn1_convert_integer (p->value, val, sizeof (val), &len);
	      asn1_octet_der (val, len, val2, &len);
	      _asn1_set_value (p, val2, len);
	    }
	}

      if (p->down)
	{
	  p = p->down;
	}
      else
	{
	  if (p == node)
	    p = NULL;
	  else if (p->right)
	    p = p->right;
	  else
	    {
	      while (1)
		{
		  p = _asn1_find_up (p);
		  if (p == node)
		    {
		      p = NULL;
		      break;
		    }
		  if (p && p->right)
		    {
		      p = p->right;
		      break;
		    }
		}
	    }
	}
    }

  return ASN1_SUCCESS;
}

#define MAX_CONSTANTS 1024
/******************************************************************/
/* Function : _asn1_expand_object_id                              */
/* Description: expand the IDs of an OBJECT IDENTIFIER constant.  */
/* Parameters:                                                    */
/*   list: root of an object list                                 */
/*   node: root of an ASN1 element.                               */
/* Return:                                                        */
/*   ASN1_ELEMENT_NOT_FOUND if NODE is NULL,                      */
/*   otherwise ASN1_SUCCESS                                       */
/******************************************************************/
int
_asn1_expand_object_id (list_type ** list, asn1_node node)
{
  asn1_node p, p2, p3, p4, p5;
  char name_root[ASN1_MAX_NAME_SIZE], name2[2 * ASN1_MAX_NAME_SIZE + 1];
  int move, tlen, tries;
  unsigned max_constants;

  if (node == NULL)
    return ASN1_ELEMENT_NOT_FOUND;

  _asn1_str_cpy (name_root, sizeof (name_root), node->name);

  p = node;
  move = DOWN;
  tries = 0;

  while (!((p == node) && (move == UP)))
    {
      if (move != UP)
	{
	  if ((type_field (p->type) == ASN1_ETYPE_OBJECT_ID)
	      && (p->type & CONST_ASSIGN))
	    {
	      p2 = p->down;
	      if (p2 && (type_field (p2->type) == ASN1_ETYPE_CONSTANT))
		{
		  if (p2->value && !c_isdigit (p2->value[0]))
		    {
		      _asn1_str_cpy (name2, sizeof (name2), name_root);
		      _asn1_str_cat (name2, sizeof (name2), ".");
		      _asn1_str_cat (name2, sizeof (name2),
				     (char *) p2->value);
		      p3 = asn1_find_node (node, name2);
		      if (!p3 || _asn1_is_up (p2, p3) ||
			  (type_field (p3->type) != ASN1_ETYPE_OBJECT_ID) ||
			  !(p3->type & CONST_ASSIGN))
			return ASN1_ELEMENT_NOT_FOUND;

		      _asn1_set_down (p, p2->right);
		      if (p2->down)
			_asn1_delete_structure (*list, &p2->down, 0);
		      _asn1_delete_node_from_list (*list, p2);
		      _asn1_remove_node (p2, 0);
		      p2 = p;
		      p4 = p3->down;
		      max_constants = 0;
		      while (p4)
			{
			  if (type_field (p4->type) == ASN1_ETYPE_CONSTANT)
			    {
			      max_constants++;
			      if (max_constants == MAX_CONSTANTS)
				return ASN1_RECURSION;

			      p5 =
				_asn1_add_single_node (ASN1_ETYPE_CONSTANT);
			      _asn1_set_name (p5, p4->name);
			      if (p4->value)
				{
				  tlen = _asn1_strlen (p4->value);
				  if (tlen > 0)
				    _asn1_set_value (p5, p4->value, tlen + 1);
				}
			      _asn1_add_static_node2 (list, p5);

			      if (p2 == p)
				{
				  _asn1_set_right (p5, p->down);
				  _asn1_set_down (p, p5);
				}
			      else
				{
				  _asn1_set_right (p5, p2->right);
				  _asn1_set_right (p2, p5);
				}
			      p2 = p5;
			    }
			  p4 = p4->right;
			}
		      move = DOWN;

		      tries++;
		      if (tries >= EXPAND_OBJECT_ID_MAX_RECURSION)
			return ASN1_RECURSION;

		      continue;
		    }
		}
	    }
	  move = DOWN;
	}
      else
	move = RIGHT;

      tries = 0;
      if (move == DOWN)
	{
	  if (p->down)
	    p = p->down;
	  else
	    move = RIGHT;
	}

      if (p == node)
	{
	  move = UP;
	  continue;
	}

      if (move == RIGHT)
	{
	  if (p && p->right)
	    p = p->right;
	  else
	    move = UP;
	}
      if (move == UP)
	p = _asn1_find_up (p);
    }

  /*******************************/
  /*       expand DEFAULT        */
  /*******************************/
  p = node;
  move = DOWN;

  while (!((p == node) && (move == UP)))
    {
      if (move != UP)
	{
	  if ((type_field (p->type) == ASN1_ETYPE_OBJECT_ID) &&
	      (p->type & CONST_DEFAULT))
	    {
	      p2 = p->down;
	      if (p2 && (type_field (p2->type) == ASN1_ETYPE_DEFAULT))
		{
		  _asn1_str_cpy (name2, sizeof (name2), name_root);
		  _asn1_str_cat (name2, sizeof (name2), ".");
		  if (p2->value)
		    _asn1_str_cat (name2, sizeof (name2), (char *) p2->value);
		  p3 = asn1_find_node (node, name2);
		  if (!p3 || (type_field (p3->type) != ASN1_ETYPE_OBJECT_ID)
		      || !(p3->type & CONST_ASSIGN))
		    return ASN1_ELEMENT_NOT_FOUND;
		  p4 = p3->down;
		  name2[0] = 0;
		  while (p4)
		    {
		      if (type_field (p4->type) == ASN1_ETYPE_CONSTANT)
			{
			  if (p4->value == NULL)
			    return ASN1_VALUE_NOT_FOUND;

			  if (name2[0])
			    _asn1_str_cat (name2, sizeof (name2), ".");
			  _asn1_str_cat (name2, sizeof (name2),
					 (char *) p4->value);
			}
		      p4 = p4->right;
		    }
		  tlen = strlen (name2);
		  if (tlen > 0)
		    _asn1_set_value (p2, name2, tlen + 1);
		}
	    }
	  move = DOWN;
	}
      else
	move = RIGHT;

      if (move == DOWN)
	{
	  if (p->down)
	    p = p->down;
	  else
	    move = RIGHT;
	}

      if (p == node)
	{
	  move = UP;
	  continue;
	}

      if (move == RIGHT)
	{
	  if (p && p->right)
	    p = p->right;
	  else
	    move = UP;
	}
      if (move == UP)
	p = _asn1_find_up (p);
    }

  return ASN1_SUCCESS;
}


/******************************************************************/
/* Function : _asn1_type_set_config                               */
/* Description: sets the CONST_SET and CONST_NOT_USED properties  */
/*   in the fields of the SET elements.                           */
/* Parameters:                                                    */
/*   node: root of an ASN1 element.                               */
/* Return:                                                        */
/*   ASN1_ELEMENT_NOT_FOUND if NODE is NULL,                       */
/*   otherwise ASN1_SUCCESS                                             */
/******************************************************************/
int
_asn1_type_set_config (asn1_node node)
{
  asn1_node p, p2;
  int move;

  if (node == NULL)
    return ASN1_ELEMENT_NOT_FOUND;

  p = node;
  move = DOWN;

  while (!((p == node) && (move == UP)))
    {
      if (move != UP)
	{
	  if (type_field (p->type) == ASN1_ETYPE_SET)
	    {
	      p2 = p->down;
	      while (p2)
		{
		  if (type_field (p2->type) != ASN1_ETYPE_TAG)
		    p2->type |= CONST_SET | CONST_NOT_USED;
		  p2 = p2->right;
		}
	    }
	  move = DOWN;
	}
      else
	move = RIGHT;

      if (move == DOWN)
	{
	  if (p->down)
	    p = p->down;
	  else
	    move = RIGHT;
	}

      if (p == node)
	{
	  move = UP;
	  continue;
	}

      if (move == RIGHT)
	{
	  if (p && p->right)
	    p = p->right;
	  else
	    move = UP;
	}
      if (move == UP)
	p = _asn1_find_up (p);
    }

  return ASN1_SUCCESS;
}


/******************************************************************/
/* Function : _asn1_check_identifier                              */
/* Description: checks the definitions of all the identifiers     */
/*   and the first element of an OBJECT_ID (e.g. {pkix 0 4}).     */
/*   The _asn1_identifierMissing global variable is filled if     */
/*   necessary.                                                   */
/* Parameters:                                                    */
/*   node: root of an ASN1 element.                               */
/* Return:                                                        */
/*   ASN1_ELEMENT_NOT_FOUND      if NODE is NULL,                 */
/*   ASN1_IDENTIFIER_NOT_FOUND   if an identifier is not defined, */
/*   otherwise ASN1_SUCCESS                                       */
/******************************************************************/
int
_asn1_check_identifier (asn1_node_const node)
{
  asn1_node_const p, p2;
  char name2[ASN1_MAX_NAME_SIZE * 2 + 2];

  if (node == NULL)
    return ASN1_ELEMENT_NOT_FOUND;

  p = node;
  while (p)
    {
      if (p->value && type_field (p->type) == ASN1_ETYPE_IDENTIFIER)
	{
	  _asn1_str_cpy (name2, sizeof (name2), node->name);
	  _asn1_str_cat (name2, sizeof (name2), ".");
	  _asn1_str_cat (name2, sizeof (name2), (char *) p->value);
	  p2 = asn1_find_node (node, name2);
	  if (p2 == NULL)
	    {
	      if (p->value)
		_asn1_str_cpy (_asn1_identifierMissing,
			       sizeof (_asn1_identifierMissing),
			       (char *) p->value);
	      else
		_asn1_strcpy (_asn1_identifierMissing, "(null)");
	      return ASN1_IDENTIFIER_NOT_FOUND;
	    }
	}
      else if ((type_field (p->type) == ASN1_ETYPE_OBJECT_ID) &&
	       (p->type & CONST_DEFAULT))
	{
	  p2 = p->down;
	  if (p2 && (type_field (p2->type) == ASN1_ETYPE_DEFAULT))
	    {
	      _asn1_str_cpy (name2, sizeof (name2), node->name);
	      if (p2->value)
		{
		  _asn1_str_cat (name2, sizeof (name2), ".");
		  _asn1_str_cat (name2, sizeof (name2), (char *) p2->value);
		  _asn1_str_cpy (_asn1_identifierMissing,
				 sizeof (_asn1_identifierMissing),
				 (char *) p2->value);
		}
	      else
		_asn1_strcpy (_asn1_identifierMissing, "(null)");

	      p2 = asn1_find_node (node, name2);
	      if (!p2 || (type_field (p2->type) != ASN1_ETYPE_OBJECT_ID) ||
		  !(p2->type & CONST_ASSIGN))
		return ASN1_IDENTIFIER_NOT_FOUND;
	      else
		_asn1_identifierMissing[0] = 0;
	    }
	}
      else if ((type_field (p->type) == ASN1_ETYPE_OBJECT_ID) &&
	       (p->type & CONST_ASSIGN))
	{
	  p2 = p->down;
	  if (p2 && (type_field (p2->type) == ASN1_ETYPE_CONSTANT))
	    {
	      if (p2->value && !c_isdigit (p2->value[0]))
		{
		  _asn1_str_cpy (name2, sizeof (name2), node->name);
		  _asn1_str_cat (name2, sizeof (name2), ".");
		  _asn1_str_cat (name2, sizeof (name2), (char *) p2->value);
		  _asn1_str_cpy (_asn1_identifierMissing,
				 sizeof (_asn1_identifierMissing),
				 (char *) p2->value);

		  p2 = asn1_find_node (node, name2);
		  if (!p2 || (type_field (p2->type) != ASN1_ETYPE_OBJECT_ID)
		      || !(p2->type & CONST_ASSIGN))
		    return ASN1_IDENTIFIER_NOT_FOUND;
		  else
		    _asn1_identifierMissing[0] = 0;
		}
	    }
	}

      if (p->down)
	{
	  p = p->down;
	}
      else if (p->right)
	p = p->right;
      else
	{
	  while (p)
	    {
	      p = _asn1_find_up (p);
	      if (p == node)
		{
		  p = NULL;
		  break;
		}
	      if (p && p->right)
		{
		  p = p->right;
		  break;
		}
	    }
	}
    }

  return ASN1_SUCCESS;
}


/******************************************************************/
/* Function : _asn1_set_default_tag                               */
/* Description: sets the default IMPLICIT or EXPLICIT property in */
/*   the tagged elements that don't have this declaration.        */
/* Parameters:                                                    */
/*   node: pointer to a DEFINITIONS element.                      */
/* Return:                                                        */
/*   ASN1_ELEMENT_NOT_FOUND if NODE is NULL or not a pointer to   */
/*     a DEFINITIONS element,                                     */
/*   otherwise ASN1_SUCCESS                                       */
/******************************************************************/
int
_asn1_set_default_tag (asn1_node node)
{
  asn1_node p;

  if ((node == NULL) || (type_field (node->type) != ASN1_ETYPE_DEFINITIONS))
    return ASN1_ELEMENT_NOT_FOUND;

  p = node;
  while (p)
    {
      if ((type_field (p->type) == ASN1_ETYPE_TAG) &&
	  !(p->type & CONST_EXPLICIT) && !(p->type & CONST_IMPLICIT))
	{
	  if (node->type & CONST_EXPLICIT)
	    p->type |= CONST_EXPLICIT;
	  else
	    p->type |= CONST_IMPLICIT;
	}

      if (p->down)
	{
	  p = p->down;
	}
      else if (p->right)
	p = p->right;
      else
	{
	  while (1)
	    {
	      p = _asn1_find_up (p);
	      if (p == node)
		{
		  p = NULL;
		  break;
		}
	      if (p && p->right)
		{
		  p = p->right;
		  break;
		}
	    }
	}
    }

  return ASN1_SUCCESS;
}
