/* module.c - Module management for libgcrypt.
 * Copyright (C) 2003, 2008 Free Software Foundation, Inc.
 *
 * This file is part of Libgcrypt.
 *
 * Libgcrypt 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.
 *
 * Libgcrypt 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 program; if not, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>
#include <errno.h>
#include "g10lib.h"

/* Please match these numbers with the allocated algorithm
   numbers.  */
#define MODULE_ID_MIN 600
#define MODULE_ID_LAST 65500
#define MODULE_ID_USER GCRY_MODULE_ID_USER
#define MODULE_ID_USER_LAST GCRY_MODULE_ID_USER_LAST

#if MODULE_ID_MIN >= MODULE_ID_USER
#error Need to implement a different search strategy
#endif

/* Internal function.  Generate a new, unique module ID for a module
   that should be inserted into the module chain starting at
   MODULES.  */
static gcry_err_code_t
_gcry_module_id_new (gcry_module_t modules, unsigned int *id_new)
{
  unsigned int mod_id;
  gcry_err_code_t err = GPG_ERR_NO_ERROR;
  gcry_module_t module;

  /* Search for unused ID.  */
  for (mod_id = MODULE_ID_MIN; mod_id < MODULE_ID_LAST; mod_id++)
    {
      if (mod_id == MODULE_ID_USER)
        {
          mod_id = MODULE_ID_USER_LAST;
          continue;
        }

      /* Search for a module with the current ID.  */
      for (module = modules; module; module = module->next)
	if (mod_id == module->mod_id)
	  break;

      if (! module)
	/* None found -> the ID is available for use.  */
	break;
    }

  if (mod_id < MODULE_ID_LAST)
    /* Done.  */
    *id_new = mod_id;
  else
    /* No free ID found.  */
    err = GPG_ERR_INTERNAL;

  return err;
}

/* Add a module specification to the list ENTRIES.  The new module has
   it's use-counter set to one.  */
gcry_err_code_t
_gcry_module_add (gcry_module_t *entries, unsigned int mod_id,
		  void *spec, void *extraspec, gcry_module_t *module)
{
  gcry_err_code_t err = 0;
  gcry_module_t entry;

  if (! mod_id)
    err = _gcry_module_id_new (*entries, &mod_id);

  if (! err)
    {
      entry = gcry_malloc (sizeof (struct gcry_module));
      if (! entry)
	err = gpg_err_code_from_errno (errno);
    }

  if (! err)
    {
      /* Fill new module entry.  */
      entry->flags = 0;
      entry->counter = 1;
      entry->spec = spec;
      entry->extraspec = extraspec;
      entry->mod_id = mod_id;

      /* Link it into the list.  */
      entry->next = *entries;
      entry->prevp = entries;
      if (*entries)
	(*entries)->prevp = &entry->next;
      *entries = entry;

      /* And give it to the caller.  */
      if (module)
	*module = entry;
    }
  return err;
}

/* Internal function.  Unlink CIPHER_ENTRY from the list of registered
   ciphers and destroy it.  */
static void
_gcry_module_drop (gcry_module_t entry)
{
  *entry->prevp = entry->next;
  if (entry->next)
    entry->next->prevp = entry->prevp;

  gcry_free (entry);
}

/* Lookup a module specification by it's ID.  After a successful
   lookup, the module has it's resource counter incremented.  */
gcry_module_t
_gcry_module_lookup_id (gcry_module_t entries, unsigned int mod_id)
{
  gcry_module_t entry;

  for (entry = entries; entry; entry = entry->next)
    if (entry->mod_id == mod_id)
      {
	entry->counter++;
	break;
      }

  return entry;
}

/* Lookup a module specification.  After a successful lookup, the
   module has it's resource counter incremented.  FUNC is a function
   provided by the caller, which is responsible for identifying the
   wanted module.  */
gcry_module_t
_gcry_module_lookup (gcry_module_t entries, void *data,
		     gcry_module_lookup_t func)
{
  gcry_module_t entry;

  for (entry = entries; entry; entry = entry->next)
    if ((*func) (entry->spec, data))
      {
	entry->counter++;
	break;
      }

  return entry;
}

/* Release a module.  In case the use-counter reaches zero, destroy
   the module.  Passing MODULE as NULL is a dummy operation (similar
   to free()). */
void
_gcry_module_release (gcry_module_t module)
{
  if (module && ! --module->counter)
    _gcry_module_drop (module);
}

/* Add a reference to a module.  */
void
_gcry_module_use (gcry_module_t module)
{
  ++module->counter;
}

/* If LIST is zero, write the number of modules identified by MODULES
   to LIST_LENGTH and return.  If LIST is non-zero, the first
   *LIST_LENGTH algorithm IDs are stored in LIST, which must be of
   according size.  In case there are less cipher modules than
   *LIST_LENGTH, *LIST_LENGTH is updated to the correct number.  */
gcry_err_code_t
_gcry_module_list (gcry_module_t modules,
		   int *list, int *list_length)
{
  gcry_err_code_t err = GPG_ERR_NO_ERROR;
  gcry_module_t module;
  int length, i;

  for (module = modules, length = 0; module; module = module->next, length++);

  if (list)
    {
      if (length > *list_length)
	length = *list_length;

      for (module = modules, i = 0; i < length; module = module->next, i++)
	list[i] = module->mod_id;

      if (length < *list_length)
	*list_length = length;
    }
  else
    *list_length = length;

  return err;
}
