/* backend-cache.c - Cache backend for keyboxd
 * Copyright (C) 2019 g10 Code GmbH
 *
 * This file is part of GnuPG.
 *
 * GnuPG 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 3 of the License, or
 * (at your option) any later version.
 *
 * GnuPG 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

/*
 * This cache backend is designed to be queried first and to deliver
 * cached items (which may also be not-found).  A set a maintenance
 * functions is used used by the frontend to fill the cache.
 * FIXME: Support x.509
 */

#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>

#include "keyboxd.h"
#include "../common/i18n.h"
#include "../common/host2net.h"
#include "backend.h"
#include "keybox-defs.h"


/* Standard values for the number of buckets and the threshold we use
 * to flush items.  */
#define NO_OF_KEY_ITEM_BUCKETS          383
#define KEY_ITEMS_PER_BUCKET_THRESHOLD  40
#define NO_OF_BLOB_BUCKETS              383
#define BLOBS_PER_BUCKET_THRESHOLD      20


/* Our definition of the backend handle.  */
struct backend_handle_s
{
  enum database_types db_type; /* Always DB_TYPE_CACHE.  */
  unsigned int backend_id;     /* Always the id the backend.  */
};


/* The object holding a blob.  */
typedef struct blob_s
{
  struct blob_s *next;
  enum pubkey_types pktype;
  unsigned int refcount;
  unsigned int usecount;
  unsigned int datalen;
  unsigned char *data;        /* The actual data of length DATALEN.  */
  unsigned char ubid[UBID_LEN];
} *blob_t;


static blob_t *blob_table;                /* Hash table with the blobs.   */
static size_t blob_table_size;            /* Number of allocated buckets. */
static unsigned int blob_table_threshold; /* Max. # of items per bucket.  */
static unsigned int blob_table_added;     /* Number of items added.       */
static unsigned int blob_table_dropped;   /* Number of items dropped.     */
static blob_t blob_attic;                 /* List of freed blobs.         */


/* A list item to blob data.  This is so that a next operation on a
 * cached key item can actually work.  Things are complicated because
 * we do not want to force caching all object before we get a next
 * request from the client.  To accomplish this we keep a flag
 * indicating that the search needs to continue instead of delivering
 * the previous item from the cache.  */
typedef struct bloblist_s
{
  struct bloblist_s *next;
  unsigned int final_kid:1;     /* The final blob for KID searches. */
  unsigned int final_fpr:1;     /* The final blob for FPR searches. */
  unsigned int ubid_valid:1;    /* The blobid below is valid.   */
  unsigned int subkey:1;        /* The entry is for a subkey.   */
  unsigned int fprlen:8;        /* The length of the fingerprint or 0.  */
  char fpr[32];                 /* The buffer for the fingerprint.  */
  unsigned char ubid[UBID_LEN]; /* The Unique-Blob-ID of the blob.  */
} *bloblist_t;

static bloblist_t bloblist_attic;  /* List of freed items.  */

/* The cache object.  For indexing we could use the fingerprint
 * directly as a hash value.  However, we use the keyid instead
 * because the keyid is used by OpenPGP in encrypted packets and older
 * signatures to identify a key.  Since v4 OpenPGP keys the keyid is
 * anyway a part of the fingerprint so it quickly extracted from a
 * fingerprint.  Note that v3 keys are not supported by gpg.
 * FIXME: Add support for X.509.
 */
typedef struct key_item_s
{
  struct key_item_s *next;
  bloblist_t  blist;       /* List of blobs or NULL for not-found.  */
  unsigned int usecount;
  unsigned int refcount;   /* Reference counter for this item.  */
  u32 kid_h;               /* Upper 4 bytes of the keyid.  */
  u32 kid_l;               /* Lower 4 bytes of the keyid.  */
} *key_item_t;

static key_item_t *key_table;            /* Hash table with the keys.    */
static size_t key_table_size;            /* Number of allocated buckets. */
static unsigned int key_table_threshold; /* Max. # of items per bucket.  */
static unsigned int key_table_added;     /* Number of items added.       */
static unsigned int key_table_dropped;   /* Number of items dropped.     */
static key_item_t key_item_attic;        /* List of freed items.         */




/* The hash function we use for the key_table.  Must not call a system
 * function.  */
static inline unsigned int
blob_table_hasher (const unsigned char *ubid)
{
  return (ubid[0] << 16 | ubid[1]) % blob_table_size;
}


/* Runtime allocation of the key table.  This allows us to eventually
 * add an option to control the size.  */
static gpg_error_t
blob_table_init (void)
{
  if (blob_table)
    return 0;
  blob_table_size = NO_OF_BLOB_BUCKETS;
  blob_table_threshold  = BLOBS_PER_BUCKET_THRESHOLD;
  blob_table = xtrycalloc (blob_table_size, sizeof *blob_table);
  if (!blob_table)
    return gpg_error_from_syserror ();
  return 0;
}

/* Free a blob.  This is done by moving it to the attic list.  */
static void
blob_unref (blob_t blob)
{
  void *p;

  if (!blob)
    return;
  log_assert (blob->refcount);
  if (!--blob->refcount)
    {
      p = blob->data;
      blob->data = NULL;
      blob->next = blob_attic;
      blob_attic = blob;
      xfree (p);
    }
}


/* Given the hash value and the ubid, find the blob in the bucket.
 * Returns NULL if not found or the blob item if found.  Always
 * returns the the number of items searched, which is in the case of a
 * not-found the length of the chain.  */
static blob_t
find_blob (unsigned int hash, const unsigned char *ubid,
           unsigned int *r_count)
{
  blob_t b;
  unsigned int count = 0;

  for (b = blob_table[hash]; b; b = b->next, count++)
    if (!memcmp (b->ubid, ubid, UBID_LEN))
      break;
  if (r_count)
    *r_count = count;
  return b;
}


/* Helper for the qsort in key_table_put.  */
static int
compare_blobs (const void *arg_a, const void *arg_b)
{
  const blob_t a = *(const blob_t *)arg_a;
  const blob_t b = *(const blob_t *)arg_b;

  /* Reverse sort on the usecount.  */
  if (a->usecount > b->usecount)
    return -1;
  else if (a->usecount == b->usecount)
    return 0;
  else
    return 1;
}


/* Put the blob (BLOBDATA, BLOBDATALEN) into the cache using UBID as
 * the index.  If it is already in the cache nothing happens.  */
static void
blob_table_put (const unsigned char *ubid, enum pubkey_types pktype,
                const void *blobdata, unsigned int blobdatalen)
{
  unsigned int hash;
  blob_t b;
  unsigned int count, n;
  void *blobdatacopy = NULL;

  hash = blob_table_hasher (ubid);
 find_again:
  b = find_blob (hash, ubid, &count);
  if (b)
    {
      xfree (blobdatacopy);
      return;  /* Already got this blob.  */
    }

  /* Create a copy of the blob if not yet done.  */
  if (!blobdatacopy)
    {
      blobdatacopy = xtrymalloc (blobdatalen);
      if (!blobdatacopy)
        {
          log_info ("Note: malloc failed while copying blob to the cache: %s\n",
                    gpg_strerror (gpg_error_from_syserror ()));
          return;  /* Out of core - ignore.  */
        }
      memcpy (blobdatacopy, blobdata, blobdatalen);
    }

  /* If the bucket is full remove a couple of items. */
  if (count >= blob_table_threshold)
    {
      blob_t list_head, *list_tailp, b_next;
      blob_t *array;
      int narray, idx;

      /* Unlink from the global list so that other threads don't
       * disturb us.  If another thread adds or removes something only
       * one will be the winner.  Bad luck for the dropped cache items
       * but after all it is just a cache.  */
      list_head = blob_table[hash];
      blob_table[hash] = NULL;

      /* Put all items into an array for sorting.  */
      array = xtrycalloc (count, sizeof *array);
      if (!array)
        {
          /* That's bad; give up all  items of the bucket.  */
          log_info ("Note: malloc failed while purging blobs from the "
                    "cache: %s\n", gpg_strerror (gpg_error_from_syserror ()));
          goto leave_drop;
        }
      narray = 0;
      for (b = list_head; b; b = b_next)
        {
          b_next = b->next;
          array[narray++] = b;
          b->next = NULL;
        }
      log_assert (narray == count);

      /* Sort the array and put half of it onto a new list.  */
      qsort (array, narray, sizeof *array, compare_blobs);
      list_head = NULL;
      list_tailp = &list_head;
      for (idx=0; idx < narray/2; idx++)
        {
          *list_tailp = array[idx];
          list_tailp = &array[idx]->next;
        }

      /* Put the new list into the bucket.  */
      b = blob_table[hash];
      blob_table[hash] = list_head;
      list_head = b;

      /* Free the remaining items and the array.  */
      for (; idx < narray; idx++)
        {
          blob_unref (array[idx]);
          blob_table_dropped++;
        }
      xfree (array);

    leave_drop:
      /* Free any items added in the meantime by other threads.  This
       * is also used in case of a malloc problem (which won't update
       * the counters, though). */
      for ( ; list_head; list_head = b_next)
        {
          b_next = list_head->next;
          blob_unref (list_head);
        }
    }

  /* Add an item to the bucket.  We allocate a whole block of items
   * for cache performance reasons.  */
  if (!blob_attic)
    {
      blob_t b_block;
      int b_blocksize = 256;

      b_block = xtrymalloc (b_blocksize * sizeof *b_block);
      if (!b_block)
        {
          log_info ("Note: malloc failed while adding blob to the cache: %s\n",
                    gpg_strerror (gpg_error_from_syserror ()));
          xfree (blobdatacopy);
          return;  /* Out of core - ignore.  */
        }
      for (n = 0; n < b_blocksize; n++)
        {
          b = b_block + n;
          b->next = blob_attic;
          blob_attic = b;
        }

      /* During the malloc another thread might have changed the
       * bucket.  Thus we need to start over.  */
      goto find_again;
    }

  /* We now know that there is an item in the attic.  Put it into the
   * chain.  Note that we may not use any system call here. */
  b = blob_attic;
  blob_attic = b->next;
  b->next = NULL;
  b->pktype = pktype;
  b->data = blobdatacopy;
  b->datalen = blobdatalen;
  memcpy (b->ubid, ubid, UBID_LEN);
  b->usecount = 1;
  b->refcount = 1;
  b->next = blob_table[hash];
  blob_table[hash] = b;
  blob_table_added++;
}


/* Given the UBID return a cached blob item.  The caller must
 * release that item using blob_unref.  */
static blob_t
blob_table_get (const unsigned char *ubid)
{
  unsigned int hash;
  blob_t b;

  hash = blob_table_hasher (ubid);
  b = find_blob (hash, ubid, NULL);
  if (b)
    {
      b->usecount++;
      b->refcount++;
      return b;  /* Found  */
    }

  return NULL;
}



/* The hash function we use for the key_table.  Must not call a system
 * function.  */
static inline unsigned int
key_table_hasher (u32 kid_l)
{
  return kid_l % key_table_size;
}


/* Runtime allocation of the key table.  This allows us to eventually
 * add an option to control the size.  */
static gpg_error_t
key_table_init (void)
{
  if (key_table)
    return 0;
  key_table_size = NO_OF_KEY_ITEM_BUCKETS;
  key_table_threshold  = KEY_ITEMS_PER_BUCKET_THRESHOLD;
  key_table = xtrycalloc (key_table_size, sizeof *key_table);
  if (!key_table)
    return gpg_error_from_syserror ();
  return 0;
}

/* Free a key_item.  This is done by moving it to the attic list.  */
static void
key_item_unref (key_item_t ki)
{
  bloblist_t bl, bl2;

  if (!ki)
    return;
  log_assert (ki->refcount);
  if (!--ki->refcount)
    {
      bl = ki->blist;
      ki->blist = NULL;
      ki->next = key_item_attic;
      key_item_attic = ki;

      if (bl)
        {
          for (bl2 = bl; bl2->next; bl2 = bl2->next)
            ;
          bl2->next = bloblist_attic;
          bloblist_attic = bl;
        }
    }
}


/* Given the hash value and the search info, find the key item in the
 * bucket.  Return NULL if not found or the key item if found.  Always
 * returns the the number of items searched, which is in the case of a
 * not-found the length of the chain.  Note that FPR may only be NULL
 * if FPRLEN is 0. */
static key_item_t
find_in_chain (unsigned int hash, u32 kid_h, u32 kid_l,
               unsigned int *r_count)
{
  key_item_t ki = key_table[hash];
  unsigned int count = 0;

  for (; ki; ki = ki->next, count++)
    if (ki->kid_h == kid_h && ki->kid_l == kid_l)
      break;
  if (r_count)
    *r_count = count;
  return ki;
}


/* Helper for the qsort in key_table_put.  */
static int
compare_key_items (const void *arg_a, const void *arg_b)
{
  const key_item_t a = *(const key_item_t *)arg_a;
  const key_item_t b = *(const key_item_t *)arg_b;

  /* Reverse sort on the usecount.  */
  if (a->usecount > b->usecount)
    return -1;
  else if (a->usecount == b->usecount)
    return 0;
  else
    return 1;
}


/* Allocate new key items.  They are put to the attic so that the
 * caller can take them from there.  On allocation failure a note
 * is printed and an error returned.  */
static gpg_error_t
alloc_more_key_items (void)
{
  gpg_error_t err;
  key_item_t kiblock, ki;
  int kiblocksize = 256;
  unsigned int n;

  kiblock = xtrymalloc (kiblocksize * sizeof *kiblock);
  if (!kiblock)
    {
      err = gpg_error_from_syserror ();
      log_info ("Note: malloc failed while adding to the cache: %s\n",
                gpg_strerror (err));
      return err;
    }
  for (n = 0; n < kiblocksize; n++)
    {
      ki = kiblock + n;
      ki->next = key_item_attic;
      key_item_attic = ki;
    }
  return 0;
}


/* Allocate new bloblist items.  They are put to the attic so that the
 * caller can take them from there.  On allocation failure a note is
 * printed and an error returned.  */
static gpg_error_t
alloc_more_bloblist_items (void)
{
  gpg_error_t err;
  bloblist_t bl;
  bloblist_t blistblock;
  int blistblocksize = 256;
  unsigned int n;

  blistblock = xtrymalloc (blistblocksize * sizeof *blistblock);
  if (!blistblock)
    {
      err = gpg_error_from_syserror ();
      log_info ("Note: malloc failed while adding to the cache: %s\n",
                gpg_strerror (err));
      return err;
    }
  for (n = 0; n < blistblocksize; n++)
    {
      bl = blistblock + n;
      bl->next = bloblist_attic;
      bloblist_attic = bl;
    }
  return 0;
}


/* Helper for key_table_put.  This function assumes that
 * bloblist_attaci is not NULL.  Returns a new bloblist item.  Be
 * aware that no system calls may be done - even not log
 * functions!  */
static bloblist_t
new_bloblist_item (const unsigned char *fpr, unsigned int fprlen,
                   const unsigned char *ubid, int subkey)
{
  bloblist_t bl;

  bl = bloblist_attic;
  bloblist_attic = bl->next;
  bl->next = NULL;

  if (ubid)
    memcpy (bl->ubid, ubid, UBID_LEN);
  else
    memset (bl->ubid, 0, UBID_LEN);
  bl->ubid_valid = 1;
  bl->final_kid = 0;
  bl->final_fpr = 0;
  bl->subkey = !!subkey;
  bl->fprlen = fprlen;
  memcpy (bl->fpr, fpr, fprlen);
  return bl;
}


/* If the list of key item in the bucken HASH is full remove a couple
 * of them.  On error a diagnostic is printed and an error code
 * return.  Note that the error code GPG_ERR_TRUE is returned if any
 * flush and thus system calls were done.
 */
static gpg_error_t
maybe_flush_some_key_buckets (unsigned int hash, unsigned int count)
{
  gpg_error_t err;
  key_item_t ki, list_head, *list_tailp, ki_next;
  key_item_t *array;
  int narray, idx;

  if (count < key_table_threshold)
    return 0;  /* Nothing to do.  */

  /* Unlink from the global list so that other threads don't disturb
   * us.  If another thread adds or removes something only one will be
   * the winner.  Bad luck for the dropped cache items but after all
   * it is just a cache.  */
  list_head = key_table[hash];
  key_table[hash] = NULL;

  /* Put all items into an array for sorting.  */
  array = xtrycalloc (count, sizeof *array);
  if (!array)
    {
      /* That's bad; give up all items of the bucket.  */
      err = gpg_error_from_syserror ();
      log_info ("Note: malloc failed while purging from the cache: %s\n",
                gpg_strerror (err));
      goto leave;
    }
  narray = 0;
  for (ki = list_head; ki; ki = ki_next)
    {
      ki_next = ki->next;
      array[narray++] = ki;
      ki->next = NULL;
    }
  log_assert (narray == count);

  /* Sort the array and put half of it onto a new list.  */
  qsort (array, narray, sizeof *array, compare_key_items);
  list_head = NULL;
  list_tailp = &list_head;
  for (idx=0; idx < narray/2; idx++)
    {
      *list_tailp = array[idx];
      list_tailp = &array[idx]->next;
    }

  /* Put the new list into the bucket.  */
  ki = key_table[hash];
  key_table[hash] = list_head;
  list_head = ki;

  /* Free the remaining items and the array.  */
  for (; idx < narray; idx++)
    {
      key_item_unref (array[idx]);
      key_table_dropped++;
    }
  xfree (array);
  err = gpg_error (GPG_ERR_TRUE);

 leave:
  /* Free any items added in the meantime by other threads.  This is
   * also used in case of a malloc problem (which won't update the
   * counters, though). */
  for ( ; list_head; list_head = ki_next)
    {
      ki_next = list_head->next;
      key_item_unref (list_head);
    }
  return err;
}


/* This is the core of
 *   key_table_put,
 *   key_table_put_no_fpr,
 *   key_table_put_no_kid.
 */
static void
do_key_table_put (u32 kid_h, u32 kid_l,
                  const unsigned char *fpr, unsigned int fprlen,
                  const unsigned char *ubid, int subkey)
{
  unsigned int hash;
  key_item_t ki;
  bloblist_t bl, bl_tail;
  unsigned int count;
  int do_find_again;
  int mark_not_found = !fpr;

  hash = key_table_hasher (kid_l);
 find_again:
  do_find_again = 0;
  ki = find_in_chain (hash, kid_h, kid_l, &count);
  if (ki)
    {
      if (mark_not_found)
        return; /* Can't put the mark because meanwhile a entry was
                 * added.  */

      for (bl = ki->blist; bl; bl = bl->next)
        if (bl->fprlen
            && bl->fprlen == fprlen
            && !memcmp (bl->fpr, fpr, fprlen))
          break;
      if (bl)
        return;  /* Already in the bloblist for the keyid  */

      /* Append to the list.  */
      if (!bloblist_attic)
        {
          if (alloc_more_bloblist_items ())
            return;  /* Out of core - ignore.  */
          goto find_again; /* Need to start over due to the malloc.  */
        }
      for (bl_tail = NULL, bl = ki->blist; bl; bl_tail = bl, bl = bl->next)
        ;
      bl = new_bloblist_item (fpr, fprlen, ubid, subkey);
      if (bl_tail)
        bl_tail->next = bl;
      else
        ki->blist = bl;

      return;
    }

  /* If the bucket is full remove a couple of items. */
  if (maybe_flush_some_key_buckets (hash, count))
    {
      /* During the function call another thread might have changed
       * the bucket.  Thus we need to start over.  */
      do_find_again = 1;
    }

  if (!key_item_attic)
    {
      if (alloc_more_key_items ())
        return;  /* Out of core - ignore.  */
      do_find_again = 1;
    }

  if (!bloblist_attic)
    {
      if (alloc_more_bloblist_items ())
        return;  /* Out of core - ignore.  */
      do_find_again = 1;
    }

  if (do_find_again)
    goto find_again;

  /* We now know that there are items in the attics.  Put them into
   * the chain.  Note that we may not use any system call here. */
  ki = key_item_attic;
  key_item_attic = ki->next;
  ki->next = NULL;

  if (mark_not_found)
    ki->blist = NULL;
  else
    ki->blist = new_bloblist_item (fpr, fprlen, ubid, subkey);

  ki->kid_h = kid_h;
  ki->kid_l = kid_l;
  ki->usecount = 1;
  ki->refcount = 1;

  ki->next = key_table[hash];
  key_table[hash] = ki;
  key_table_added++;
}


/* Given the fingerprint (FPR,FPRLEN) put the UBID into the cache.
 * SUBKEY indicates that the fingerprint is from a subkey.  */
static void
key_table_put (const unsigned char *fpr, unsigned int fprlen,
               const unsigned char *ubid, int subkey)
{
  u32 kid_h, kid_l;

  if (fprlen < 20 || fprlen > 32)
    return;  /* No support for v3 keys or unknown key versions.  */

  if (fprlen == 20)  /* v4 key */
    {
      kid_h = buf32_to_u32 (fpr+12);
      kid_l = buf32_to_u32 (fpr+16);
    }
  else  /* v5 or later key */
    {
      kid_h = buf32_to_u32 (fpr);
      kid_l = buf32_to_u32 (fpr+4);
    }
  do_key_table_put (kid_h, kid_l, fpr, fprlen, ubid, subkey);
}


/* Given the fingerprint (FPR,FPRLEN) put a flag into the cache that
 * this fingerprint was not found.  */
static void
key_table_put_no_fpr (const unsigned char *fpr, unsigned int fprlen)
{
  u32 kid_h, kid_l;

  if (fprlen < 20 || fprlen > 32)
    return;  /* No support for v3 keys or unknown key versions.  */

  if (fprlen == 20)  /* v4 key */
    {
      kid_h = buf32_to_u32 (fpr+12);
      kid_l = buf32_to_u32 (fpr+16);
    }
  else  /* v5 or later key */
    {
      kid_h = buf32_to_u32 (fpr);
      kid_l = buf32_to_u32 (fpr+4);
    }
  /* Note that our not-found chaching is only based on the keyid. */
  do_key_table_put (kid_h, kid_l, NULL, 0, NULL, 0);
}


/* Given the keyid (KID_H, KID_L) put a flag into the cache that this
 * keyid was not found. */
static void
key_table_put_no_kid (u32 kid_h, u32 kid_l)
{
  do_key_table_put (kid_h, kid_l, NULL, 0, NULL, 0);
}


/* Given the keyid or the fingerprint return the key item from the
 * cache.  The caller must release the result using key_item_unref.
 * NULL is returned if not found.  */
static key_item_t
key_table_get (u32 kid_h, u32 kid_l)
{
  unsigned int hash;
  key_item_t ki;

  hash = key_table_hasher (kid_l);
  ki = find_in_chain (hash, kid_h, kid_l, NULL);
  if (ki)
    {
      ki->usecount++;
      ki->refcount++;
      return ki;  /* Found  */
    }

  return NULL;
}


/* Return a key item by searching for the keyid.  The caller must use
 * key_item_unref on it.  */
static key_item_t
query_by_kid (u32 kid_h, u32 kid_l)
{
  return key_table_get (kid_h, kid_l);
}


/* Return a key item by searching for the fingerprint.  The caller
 * must use key_item_unref on it.  Note that the returned key item may
 * not actually carry the fingerprint; the caller needs to scan the
 * bloblist of the keyitem.  We can't do that here because the
 * reference counting is done on the keyitem s and thus this needs to
 * be returned. */
static key_item_t
query_by_fpr (const unsigned char *fpr, unsigned int fprlen)
{
  u32 kid_h, kid_l;

  if (fprlen < 20 || fprlen > 32 )
    return NULL;  /* No support for v3 keys or unknown key versions.  */

  if (fprlen == 20)  /* v4 key */
    {
      kid_h = buf32_to_u32 (fpr+12);
      kid_l = buf32_to_u32 (fpr+16);
    }
  else  /* v5 or later key */
    {
      kid_h = buf32_to_u32 (fpr);
      kid_l = buf32_to_u32 (fpr+4);
    }

  return key_table_get (kid_h, kid_l);
}





/* Make sure the tables are initialized.  */
gpg_error_t
be_cache_initialize (void)
{
  gpg_error_t err;

  err = blob_table_init ();
  if (!err)
    err = key_table_init ();
  return err;
}


/* Install a new resource and return a handle for that backend.  */
gpg_error_t
be_cache_add_resource (ctrl_t ctrl, backend_handle_t *r_hd)
{
  gpg_error_t err;
  backend_handle_t hd;

  (void)ctrl;

  *r_hd = NULL;
  hd = xtrycalloc (1, sizeof *hd);
  if (!hd)
    return gpg_error_from_syserror ();
  hd->db_type = DB_TYPE_CACHE;

  hd->backend_id = be_new_backend_id ();

  /* Just in case make sure we are initialized.  */
  err = be_cache_initialize ();
  if (err)
    goto leave;

  *r_hd = hd;
  hd = NULL;

 leave:
  xfree (hd);
  return err;
}


/* Release the backend handle HD and all its resources.  HD is not
 * valid after a call to this function.  */
void
be_cache_release_resource (ctrl_t ctrl, backend_handle_t hd)
{
  (void)ctrl;

  if (!hd)
    return;
  hd->db_type = DB_TYPE_NONE;

  /* Fixme: Free the key_table.  */

  xfree (hd);
}


/* Search for the keys described by (DESC,NDESC) and return them to
 * the caller.  BACKEND_HD is the handle for this backend and REQUEST
 * is the current database request object.  On a cache hit either 0 or
 * GPG_ERR_NOT_FOUND is returned.  The former returns the item; the
 * latter indicates that the cache has known that the item won't be
 * found in any databases.  On a cache miss GPG_ERR_EOF is
 * returned.  */
gpg_error_t
be_cache_search (ctrl_t ctrl, backend_handle_t backend_hd, db_request_t request,
                 KEYDB_SEARCH_DESC *desc, unsigned int ndesc)
{
  gpg_error_t err;
  db_request_part_t reqpart;
  unsigned int n;
  blob_t b;
  key_item_t ki;
  bloblist_t bl;
  int not_found = 0;
  int descidx = 0;
  int found_bykid = 0;

  log_assert (backend_hd && backend_hd->db_type == DB_TYPE_CACHE);
  log_assert (request);

  err = be_find_request_part (backend_hd, request, &reqpart);
  if (err)
    goto leave;

  if (!desc)
    {
      /* Reset operation.  */
      request->last_cached_valid = 0;
      request->last_cached_final = 0;
      reqpart->cache_seqno.fpr = 0;
      reqpart->cache_seqno.kid = 0;
      reqpart->cache_seqno.grip = 0;
      reqpart->cache_seqno.ubid = 0;
      err = 0;
      goto leave;
    }

  for (ki = NULL, n=0; n < ndesc && !ki; n++)
    {
      descidx = n;
      switch (desc[n].mode)
        {
        case KEYDB_SEARCH_MODE_LONG_KID:
          ki = query_by_kid (desc[n].u.kid[0], desc[n].u.kid[1]);
          if (ki && ki->blist)
            {
              not_found = 0;
              /* Note that in a bloblist all keyids are the same.  */
              for (n=0, bl = ki->blist; bl; bl = bl->next)
                if (n++ == reqpart->cache_seqno.kid)
                  break;
              if (!bl)
                {
                  key_item_unref (ki);
                  ki = NULL;
                }
              else
                {
                  found_bykid = 1;
                  reqpart->cache_seqno.kid++;
                }
            }
          else if (ki)
            not_found = 1;
          break;

        case KEYDB_SEARCH_MODE_FPR:
          ki = query_by_fpr (desc[n].u.fpr, desc[n].fprlen);
          if (ki && ki->blist)
            {
              not_found = 0;
              for (n=0, bl = ki->blist; bl; bl = bl->next)
                if (bl->fprlen
                    && bl->fprlen == desc[n].fprlen
                    && !memcmp (bl->fpr, desc[n].u.fpr, desc[n].fprlen)
                    && n++ == reqpart->cache_seqno.fpr)
                  break;
              if (!bl)
                {
                  key_item_unref (ki);
                  ki = NULL;
                }
              else
                reqpart->cache_seqno.fpr++;
            }
          else if (ki)
            not_found = 1;
          break;

        /* case KEYDB_SEARCH_MODE_KEYGRIP: */
        /*   ki = query_by_grip (desc[n].u.fpr, desc[n].fprlen); */
        /*   break; */

        case KEYDB_SEARCH_MODE_UBID:
          /* This is the quite special UBID mode: If this is
           * encountered in the search list we will return just this
           * one and obviously look only into the blob cache.  */
          if (reqpart->cache_seqno.ubid)
            err = gpg_error (GPG_ERR_NOT_FOUND);
          else
            {
              b = blob_table_get (desc[n].u.ubid);
              if (b)
                {
                  err = be_return_pubkey (ctrl, b->data, b->datalen,
                                          b->pktype, desc[n].u.ubid,
                                          0, 0, 0, 0);
                  blob_unref (b);
                  reqpart->cache_seqno.ubid++;
                }
              else
                err = gpg_error (GPG_ERR_EOF);
            }
          goto leave;

        default:
          ki = NULL;
          break;
        }
    }

  if (not_found)
    {
      err = gpg_error (GPG_ERR_NOT_FOUND);
      key_item_unref (ki);
    }
  else if (ki)
    {
      if (bl && bl->ubid_valid)
        {
          memcpy (request->last_cached_ubid, bl->ubid, UBID_LEN);
          request->last_cached_valid = 1;
          request->last_cached_fprlen = desc[descidx].fprlen;
          memcpy (request->last_cached_fpr,
                  desc[descidx].u.fpr, desc[descidx].fprlen);
          request->last_cached_kid_h = ki->kid_h;
          request->last_cached_kid_l = ki->kid_l;
          request->last_cached_valid = 1;
          if ((bl->final_kid && found_bykid)
              || (bl->final_fpr && !found_bykid))
            request->last_cached_final = 1;
          else
            request->last_cached_final = 0;

          b = blob_table_get (bl->ubid);
          if (b)
            {
              err = be_return_pubkey (ctrl, b->data, b->datalen,
                                      PUBKEY_TYPE_OPGP, bl->ubid, 0, 0, 0, 0);
              blob_unref (b);
            }
          else
            {
              /* FIXME - return a different code so that the caller
               * can lookup using the UBID.  */
              err = gpg_error (GPG_ERR_MISSING_VALUE);
            }
        }
      else if (bl)
        err = gpg_error (GPG_ERR_MISSING_VALUE);
      else
        err = gpg_error (GPG_ERR_NOT_FOUND);
      key_item_unref (ki);
    }
  else
    err = gpg_error (GPG_ERR_EOF);

 leave:
  return err;
}


/* Mark the last cached item as the final item.  This is called when
 * the actual database returned EOF in respond to a restart from the
 * last cached UBID.  */
void
be_cache_mark_final (ctrl_t ctrl, db_request_t request)
{
  key_item_t ki;
  bloblist_t bl, blfound;

  (void)ctrl;

  log_assert (request);

  if (!request->last_cached_valid)
    return;

  if (!request->last_cached_fprlen) /* Was cached via keyid.  */
    {
      ki = query_by_kid (request->last_cached_kid_h,
                         request->last_cached_kid_l);
      if (ki && (bl = ki->blist))
        {
          for (blfound=NULL; bl; bl = bl->next)
            blfound = bl;
          if (blfound)
            blfound->final_kid = 1;
        }
      key_item_unref (ki);
    }
  else /* Was cached via fingerprint.  */
    {
      ki = query_by_fpr (request->last_cached_fpr,
                         request->last_cached_fprlen);
      if (ki && (bl = ki->blist))
        {
          for (blfound=NULL; bl; bl = bl->next)
            if (bl->fprlen
                && bl->fprlen == request->last_cached_fprlen
                && !memcmp (bl->fpr, request->last_cached_fpr,
                            request->last_cached_fprlen))
              blfound = bl;
          if (blfound)
            blfound->final_fpr = 1;
        }
      key_item_unref (ki);
    }

  request->last_cached_valid = 0;
}


/* Put the key (BLOB,BLOBLEN) of PUBKEY_TYPE into the cache.  */
void
be_cache_pubkey (ctrl_t ctrl, const unsigned char *ubid,
                 const void *blob, unsigned int bloblen,
                 enum pubkey_types pubkey_type)
{
  gpg_error_t err;

  (void)ctrl;

  if (pubkey_type == PUBKEY_TYPE_OPGP)
    {
      struct _keybox_openpgp_info info;
      struct _keybox_openpgp_key_info *kinfo;

      err = _keybox_parse_openpgp (blob, bloblen, 0, NULL, &info);
      if (err)
        {
          log_info ("cache: error parsing OpenPGP blob: %s\n",
                    gpg_strerror (err));
          return;
        }

      blob_table_put (ubid, pubkey_type, blob, bloblen);

      kinfo = &info.primary;
      key_table_put (kinfo->fpr, kinfo->fprlen, ubid, 0);
      if (info.nsubkeys)
        for (kinfo = &info.subkeys; kinfo; kinfo = kinfo->next)
          key_table_put (kinfo->fpr, kinfo->fprlen, ubid, 1);

      _keybox_destroy_openpgp_info (&info);
    }

}


/* Put the a non-found mark for PUBKEY_TYPE into the cache.  The
 * indices are taken from the search descriptors (DESC,NDESC).  */
void
be_cache_not_found (ctrl_t ctrl, enum pubkey_types pubkey_type,
                    KEYDB_SEARCH_DESC *desc, unsigned int ndesc)
{
  unsigned int n;

  (void)ctrl;
  (void)pubkey_type;

  for (n=0; n < ndesc; n++)
    {
      switch (desc->mode)
        {
        case KEYDB_SEARCH_MODE_LONG_KID:
          key_table_put_no_kid (desc[n].u.kid[0], desc[n].u.kid[1]);
          break;

        case KEYDB_SEARCH_MODE_FPR:
          key_table_put_no_fpr (desc[n].u.fpr, desc[n].fprlen);
          break;

        default:
          break;
        }
    }
}
