/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Netscape security libraries.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1994-2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#ifdef DEBUG
static const char CVS_ID[] = "@(#) $RCSfile: db.c,v $ $Revision: 1.6 $ $Date: 2006/03/02 22:48:54 $";
#endif /* DEBUG */

#include "ckdbm.h"

#define PREFIX_METADATA "0000"
#define PREFIX_OBJECT   "0001"
#define PREFIX_INDEX    "0002"

static CK_VERSION nss_dbm_db_format_version = { 1, 0 };
struct handle {
  char prefix[4];
  CK_ULONG id;
};

NSS_IMPLEMENT nss_dbm_db_t *
nss_dbm_db_open
(
  NSSArena *arena,
  NSSCKFWInstance *fwInstance,
  char *filename,
  int flags,
  CK_RV *pError
)
{
  nss_dbm_db_t *rv;
  CK_VERSION db_version;

  rv = nss_ZNEW(arena, nss_dbm_db_t);
  if( (nss_dbm_db_t *)NULL == rv ) {
    *pError = CKR_HOST_MEMORY;
    return (nss_dbm_db_t *)NULL;
  }

  rv->db = dbopen(filename, flags, 0600, DB_HASH, (const void *)NULL);
  if( (DB *)NULL == rv->db ) {
    *pError = CKR_TOKEN_NOT_PRESENT;
    return (nss_dbm_db_t *)NULL;
  }

  rv->crustylock = NSSCKFWInstance_CreateMutex(fwInstance, arena, pError);
  if( (NSSCKFWMutex *)NULL == rv->crustylock ) {
    return (nss_dbm_db_t *)NULL;
  }

  db_version = nss_dbm_db_get_format_version(rv);
  if( db_version.major != nss_dbm_db_format_version.major ) {
    nss_dbm_db_close(rv);
    *pError = CKR_TOKEN_NOT_RECOGNIZED;
    return (nss_dbm_db_t *)NULL;
  }

  return rv;
}

NSS_IMPLEMENT void
nss_dbm_db_close
(
  nss_dbm_db_t *db
)
{
  if( (NSSCKFWMutex *)NULL != db->crustylock ) {
    (void)NSSCKFWMutex_Destroy(db->crustylock);
  }

  if( (DB *)NULL != db->db ) {
    (void)db->db->close(db->db);
  }

  nss_ZFreeIf(db);
}

NSS_IMPLEMENT CK_VERSION
nss_dbm_db_get_format_version
(
  nss_dbm_db_t *db
)
{
  CK_VERSION rv;
  DBT k, v;
  int dbrv;
  char buffer[64];

  rv.major = rv.minor = 0;

  k.data = PREFIX_METADATA "FormatVersion";
  k.size = nssUTF8_Size((NSSUTF8 *)k.data, (PRStatus *)NULL);
  (void)memset(&v, 0, sizeof(v));

  /* Locked region */ 
  {
    if( CKR_OK != NSSCKFWMutex_Lock(db->crustylock) ) {
      return rv;
    }

    dbrv = db->db->get(db->db, &k, &v, 0);
    if( dbrv == 0 ) {
      CK_ULONG major = 0, minor = 0;
      (void)PR_sscanf(v.data, "%ld.%ld", &major, &minor);
      rv.major = major;
      rv.minor = minor;
    } else if( dbrv > 0 ) {
      (void)PR_snprintf(buffer, sizeof(buffer), "%ld.%ld", nss_dbm_db_format_version.major,
                        nss_dbm_db_format_version.minor);
      v.data = buffer;
      v.size = nssUTF8_Size((NSSUTF8 *)v.data, (PRStatus *)NULL);
      dbrv = db->db->put(db->db, &k, &v, 0);
      (void)db->db->sync(db->db, 0);
      rv = nss_dbm_db_format_version;
    } else {
      /* No error return.. */
      ;
    }

    (void)NSSCKFWMutex_Unlock(db->crustylock);
  }

  return rv;
}

NSS_IMPLEMENT CK_RV
nss_dbm_db_set_label
(
  nss_dbm_db_t *db,
  NSSUTF8 *label
)
{
  CK_RV rv;
  DBT k, v;
  int dbrv;

  k.data = PREFIX_METADATA "Label";
  k.size = nssUTF8_Size((NSSUTF8 *)k.data, (PRStatus *)NULL);
  v.data = label;
  v.size = nssUTF8_Size((NSSUTF8 *)v.data, (PRStatus *)NULL);

  /* Locked region */ 
  {
    if( CKR_OK != NSSCKFWMutex_Lock(db->crustylock) ) {
      return rv;
    }

    dbrv = db->db->put(db->db, &k, &v, 0);
    if( 0 != dbrv ) {
      rv = CKR_DEVICE_ERROR;
    }

    dbrv = db->db->sync(db->db, 0);
    if( 0 != dbrv ) {
      rv = CKR_DEVICE_ERROR;
    }

    (void)NSSCKFWMutex_Unlock(db->crustylock);
  }

  return rv;
}

NSS_IMPLEMENT NSSUTF8 *
nss_dbm_db_get_label
(
  nss_dbm_db_t *db,
  NSSArena *arena,
  CK_RV *pError
)
{
  NSSUTF8 *rv = (NSSUTF8 *)NULL;
  DBT k, v;
  int dbrv;

  k.data = PREFIX_METADATA "Label";
  k.size = nssUTF8_Size((NSSUTF8 *)k.data, (PRStatus *)NULL);

  /* Locked region */ 
  {
    if( CKR_OK != NSSCKFWMutex_Lock(db->crustylock) ) {
      return rv;
    }

    dbrv = db->db->get(db->db, &k, &v, 0);
    if( 0 == dbrv ) {
      rv = nssUTF8_Duplicate((NSSUTF8 *)v.data, arena);
      if( (NSSUTF8 *)NULL == rv ) {
        *pError = CKR_HOST_MEMORY;
      }
    } else if( dbrv > 0 ) {
      /* Just return null */
      ;
    } else {
      *pError = CKR_DEVICE_ERROR;
      ;
    }


    (void)NSSCKFWMutex_Unlock(db->crustylock);
  }

  return rv;
}

NSS_IMPLEMENT CK_RV
nss_dbm_db_delete_object
(
  nss_dbm_dbt_t *dbt
)
{
  CK_RV rv;
  int dbrv;

  /* Locked region */
  {
    rv = NSSCKFWMutex_Lock(dbt->my_db->crustylock);
    if( CKR_OK != rv ) {
      return rv;
    }

    dbrv = dbt->my_db->db->del(dbt->my_db->db, &dbt->dbt, 0);
    if( 0 != dbrv ) {
      rv = CKR_DEVICE_ERROR;
      goto done;
    }

    dbrv = dbt->my_db->db->sync(dbt->my_db->db, 0);
    if( 0 != dbrv ) {
      rv = CKR_DEVICE_ERROR;
      goto done;
    }

  done:
    (void)NSSCKFWMutex_Unlock(dbt->my_db->crustylock);
  }

  return rv;
}

static CK_ULONG
nss_dbm_db_new_handle
(
  nss_dbm_db_t *db,
  DBT *dbt, /* pre-allocated */
  CK_RV *pError
)
{
  CK_ULONG rv;
  DBT k, v;
  CK_ULONG align = 0, id, myid;
  struct handle *hp;

  if( sizeof(struct handle) != dbt->size ) {
    return EINVAL;
  }

  /* Locked region */
  {
    *pError = NSSCKFWMutex_Lock(db->crustylock);
    if( CKR_OK != *pError ) {
      return EINVAL;
    }

    k.data = PREFIX_METADATA "LastID";
    k.size = nssUTF8_Size((NSSUTF8 *)k.data, (PRStatus *)NULL);
    (void)memset(&v, 0, sizeof(v));

    rv = db->db->get(db->db, &k, &v, 0);
    if( 0 == rv ) {
      (void)memcpy(&align, v.data, sizeof(CK_ULONG));
      id = ntohl(align);
    } else if( rv > 0 ) {
      id = 0;
    } else {
      goto done;
    }

    myid = id;
    id++;
    align = htonl(id);
    v.data = &align;
    v.size = sizeof(CK_ULONG);

    rv = db->db->put(db->db, &k, &v, 0);
    if( 0 != rv ) {
      goto done;
    }

    rv = db->db->sync(db->db, 0);
    if( 0 != rv ) {
      goto done;
    }

  done:
    (void)NSSCKFWMutex_Unlock(db->crustylock);
  }

  if( 0 != rv ) {
    return rv;
  }

  hp = (struct handle *)dbt->data;
  (void)memcpy(&hp->prefix[0], PREFIX_OBJECT, 4);
  hp->id = myid;

  return 0;
}

/*
 * This attribute-type-dependent swapping should probably
 * be in the Framework, because it'll be a concern of just
 * about every Module.  Of course any Framework implementation
 * will have to be augmentable or overridable by a Module.
 */

enum swap_type { type_byte, type_short, type_long, type_opaque };

static enum swap_type
nss_dbm_db_swap_type
(
  CK_ATTRIBUTE_TYPE type
)
{
  switch( type ) {
  case CKA_CLASS: return type_long;
  case CKA_TOKEN: return type_byte;
  case CKA_PRIVATE: return type_byte;
  case CKA_LABEL: return type_opaque;
  case CKA_APPLICATION: return type_opaque;
  case CKA_VALUE: return type_opaque;
  case CKA_CERTIFICATE_TYPE: return type_long;
  case CKA_ISSUER: return type_opaque;
  case CKA_SERIAL_NUMBER: return type_opaque;
  case CKA_KEY_TYPE: return type_long;
  case CKA_SUBJECT: return type_opaque;
  case CKA_ID: return type_opaque;
  case CKA_SENSITIVE: return type_byte;
  case CKA_ENCRYPT: return type_byte;
  case CKA_DECRYPT: return type_byte;
  case CKA_WRAP: return type_byte;
  case CKA_UNWRAP: return type_byte;
  case CKA_SIGN: return type_byte;
  case CKA_SIGN_RECOVER: return type_byte;
  case CKA_VERIFY: return type_byte;
  case CKA_VERIFY_RECOVER: return type_byte;
  case CKA_DERIVE: return type_byte;
  case CKA_START_DATE: return type_opaque;
  case CKA_END_DATE: return type_opaque;
  case CKA_MODULUS: return type_opaque;
  case CKA_MODULUS_BITS: return type_long;
  case CKA_PUBLIC_EXPONENT: return type_opaque;
  case CKA_PRIVATE_EXPONENT: return type_opaque;
  case CKA_PRIME_1: return type_opaque;
  case CKA_PRIME_2: return type_opaque;
  case CKA_EXPONENT_1: return type_opaque;
  case CKA_EXPONENT_2: return type_opaque;
  case CKA_COEFFICIENT: return type_opaque;
  case CKA_PRIME: return type_opaque;
  case CKA_SUBPRIME: return type_opaque;
  case CKA_BASE: return type_opaque;
  case CKA_VALUE_BITS: return type_long;
  case CKA_VALUE_LEN: return type_long;
  case CKA_EXTRACTABLE: return type_byte;
  case CKA_LOCAL: return type_byte;
  case CKA_NEVER_EXTRACTABLE: return type_byte;
  case CKA_ALWAYS_SENSITIVE: return type_byte;
  case CKA_MODIFIABLE: return type_byte;
  case CKA_NETSCAPE_URL: return type_opaque;
  case CKA_NETSCAPE_EMAIL: return type_opaque;
  case CKA_NETSCAPE_SMIME_INFO: return type_opaque;
  case CKA_NETSCAPE_SMIME_TIMESTAMP: return type_opaque;
  case CKA_NETSCAPE_PKCS8_SALT: return type_opaque;
  case CKA_NETSCAPE_PASSWORD_CHECK: return type_opaque;
  case CKA_NETSCAPE_EXPIRES: return type_opaque;
  case CKA_TRUST_DIGITAL_SIGNATURE: return type_long;
  case CKA_TRUST_NON_REPUDIATION: return type_long;
  case CKA_TRUST_KEY_ENCIPHERMENT: return type_long;
  case CKA_TRUST_DATA_ENCIPHERMENT: return type_long;
  case CKA_TRUST_KEY_AGREEMENT: return type_long;
  case CKA_TRUST_KEY_CERT_SIGN: return type_long;
  case CKA_TRUST_CRL_SIGN: return type_long;
  case CKA_TRUST_SERVER_AUTH: return type_long;
  case CKA_TRUST_CLIENT_AUTH: return type_long;
  case CKA_TRUST_CODE_SIGNING: return type_long;
  case CKA_TRUST_EMAIL_PROTECTION: return type_long;
  case CKA_TRUST_IPSEC_END_SYSTEM: return type_long;
  case CKA_TRUST_IPSEC_TUNNEL: return type_long;
  case CKA_TRUST_IPSEC_USER: return type_long;
  case CKA_TRUST_TIME_STAMPING: return type_long;
  case CKA_NETSCAPE_DB: return type_opaque;
  case CKA_NETSCAPE_TRUST: return type_opaque;
  default: return type_opaque;
  }
}

static void
nss_dbm_db_swap_copy
(
  CK_ATTRIBUTE_TYPE type,
  void *dest,
  void *src,
  CK_ULONG len
)
{
  switch( nss_dbm_db_swap_type(type) ) {
  case type_byte:
  case type_opaque:
    (void)memcpy(dest, src, len);
    break;
  case type_short:
    {
      CK_USHORT s, d;
      (void)memcpy(&s, src, sizeof(CK_USHORT));
      d = htons(s);
      (void)memcpy(dest, &d, sizeof(CK_USHORT));
      break;
    }
  case type_long:
    {
      CK_ULONG s, d;
      (void)memcpy(&s, src, sizeof(CK_ULONG));
      d = htonl(s);
      (void)memcpy(dest, &d, sizeof(CK_ULONG));
      break;
    }
  }
}

static CK_RV
nss_dbm_db_wrap_object
(
  NSSArena *arena,
  CK_ATTRIBUTE_PTR pTemplate,
  CK_ULONG ulAttributeCount,
  DBT *object
)
{
  CK_ULONG object_size;
  CK_ULONG i;
  CK_ULONG *pulData;
  char *pcData;
  CK_ULONG offset;

  object_size = (1 + ulAttributeCount*3) * sizeof(CK_ULONG);
  offset = object_size;
  for( i = 0; i < ulAttributeCount; i++ ) {
    object_size += pTemplate[i].ulValueLen;
  }

  object->size = object_size;
  object->data = nss_ZAlloc(arena, object_size);
  if( (void *)NULL == object->data ) {
    return CKR_HOST_MEMORY;
  }

  pulData = (CK_ULONG *)object->data;
  pcData = (char *)object->data;

  pulData[0] = htonl(ulAttributeCount);
  for( i = 0; i < ulAttributeCount; i++ ) {
    CK_ULONG len = pTemplate[i].ulValueLen;
    pulData[1 + i*3] = htonl(pTemplate[i].type);
    pulData[2 + i*3] = htonl(len);
    pulData[3 + i*3] = htonl(offset);
    nss_dbm_db_swap_copy(pTemplate[i].type, &pcData[offset], pTemplate[i].pValue, len);
    offset += len;
  }

  return CKR_OK;
}

static CK_RV
nss_dbm_db_unwrap_object
(
  NSSArena *arena,
  DBT *object,
  CK_ATTRIBUTE_PTR *ppTemplate,
  CK_ULONG *pulAttributeCount
)
{
  CK_ULONG *pulData;
  char *pcData;
  CK_ULONG n, i;
  CK_ATTRIBUTE_PTR pTemplate;

  pulData = (CK_ULONG *)object->data;
  pcData = (char *)object->data;

  n = ntohl(pulData[0]);
  *pulAttributeCount = n;
  pTemplate = nss_ZNEWARRAY(arena, CK_ATTRIBUTE, n);
  if( (CK_ATTRIBUTE_PTR)NULL == pTemplate ) {
    return CKR_HOST_MEMORY;
  }

  for( i = 0; i < n; i++ ) {
    CK_ULONG len;
    CK_ULONG offset;
    void *p;

    pTemplate[i].type = ntohl(pulData[1 + i*3]);
    len = ntohl(pulData[2 + i*3]);
    offset = ntohl(pulData[3 +  i*3]);
    
    p = nss_ZAlloc(arena, len);
    if( (void *)NULL == p ) {
      return CKR_HOST_MEMORY;
    }
    
    nss_dbm_db_swap_copy(pTemplate[i].type, p, &pcData[offset], len);
    pTemplate[i].ulValueLen = len;
    pTemplate[i].pValue = p;
  }

  *ppTemplate = pTemplate;
  return CKR_OK;
}


NSS_IMPLEMENT nss_dbm_dbt_t *
nss_dbm_db_create_object
(
  NSSArena *arena,
  nss_dbm_db_t *db,
  CK_ATTRIBUTE_PTR pTemplate,
  CK_ULONG ulAttributeCount,
  CK_RV *pError,
  CK_ULONG *pdbrv
)
{
  NSSArena *tmparena = (NSSArena *)NULL;
  nss_dbm_dbt_t *rv = (nss_dbm_dbt_t *)NULL;
  DBT object;

  rv = nss_ZNEW(arena, nss_dbm_dbt_t);
  if( (nss_dbm_dbt_t *)NULL == rv ) {
    *pError = CKR_HOST_MEMORY;
    return (nss_dbm_dbt_t *)NULL;
  }

  rv->my_db = db;
  rv->dbt.size = sizeof(struct handle);
  rv->dbt.data = nss_ZAlloc(arena, rv->dbt.size);
  if( (void *)NULL == rv->dbt.data ) {
    *pError = CKR_HOST_MEMORY;
    return (nss_dbm_dbt_t *)NULL;
  }

  *pdbrv = nss_dbm_db_new_handle(db, &rv->dbt, pError);
  if( 0 != *pdbrv ) {
    return (nss_dbm_dbt_t *)NULL;
  }

  tmparena = NSSArena_Create();
  if( (NSSArena *)NULL == tmparena ) {
    *pError = CKR_HOST_MEMORY;
    return (nss_dbm_dbt_t *)NULL;
  }

  *pError = nss_dbm_db_wrap_object(tmparena, pTemplate, ulAttributeCount, &object);
  if( CKR_OK != *pError ) {
    return (nss_dbm_dbt_t *)NULL;
  }
  
  /* Locked region */
  {
    *pError = NSSCKFWMutex_Lock(db->crustylock);
    if( CKR_OK != *pError ) {
      goto loser;
    }

    *pdbrv = db->db->put(db->db, &rv->dbt, &object, 0);
    if( 0 != *pdbrv ) {
      *pError = CKR_DEVICE_ERROR;
    }

    (void)db->db->sync(db->db, 0);

    (void)NSSCKFWMutex_Unlock(db->crustylock);
  }

 loser:  
  if( (NSSArena *)NULL != tmparena ) {
    (void)NSSArena_Destroy(tmparena);
  }

  return rv;
}


NSS_IMPLEMENT CK_RV
nss_dbm_db_find_objects
(
  nss_dbm_find_t *find,
  nss_dbm_db_t *db,
  CK_ATTRIBUTE_PTR pTemplate,
  CK_ULONG ulAttributeCount,
  CK_ULONG *pdbrv
)
{
  CK_RV rv = CKR_OK;

  if( (nss_dbm_db_t *)NULL != db ) {
    DBT k, v;

    rv = NSSCKFWMutex_Lock(db->crustylock);
    if( CKR_OK != rv ) {
      return rv;
    }

    *pdbrv = db->db->seq(db->db, &k, &v, R_FIRST);
    while( 0 == *pdbrv ) {
      CK_ULONG i, j;
      NSSArena *tmparena = (NSSArena *)NULL;
      CK_ULONG ulac;
      CK_ATTRIBUTE_PTR pt;

      if( (k.size < 4) || (0 != memcmp(k.data, PREFIX_OBJECT, 4)) ) {
        goto nomatch;
      }

      tmparena = NSSArena_Create();

      rv = nss_dbm_db_unwrap_object(tmparena, &v, &pt, &ulac);
      if( CKR_OK != rv ) {
        goto loser;
      }

      for( i = 0; i < ulAttributeCount; i++ ) {
        for( j = 0; j < ulac; j++ ) {
          if( pTemplate[i].type == pt[j].type ) {
            if( pTemplate[i].ulValueLen != pt[j].ulValueLen ) {
              goto nomatch;
            }
            if( 0 != memcmp(pTemplate[i].pValue, pt[j].pValue, pt[j].ulValueLen) ) {
              goto nomatch;
            }
            break;
          }
        }
        if( j == ulac ) {
          goto nomatch;
        }
      }

      /* entire template matches */
      {
        struct nss_dbm_dbt_node *node;

        node = nss_ZNEW(find->arena, struct nss_dbm_dbt_node);
        if( (struct nss_dbm_dbt_node *)NULL == node ) {
          rv = CKR_HOST_MEMORY;
          goto loser;
        }

        node->dbt = nss_ZNEW(find->arena, nss_dbm_dbt_t);
        if( (nss_dbm_dbt_t *)NULL == node->dbt ) {
          rv = CKR_HOST_MEMORY;
          goto loser;
        }
        
        node->dbt->dbt.size = k.size;
        node->dbt->dbt.data = nss_ZAlloc(find->arena, k.size);
        if( (void *)NULL == node->dbt->dbt.data ) {
          rv = CKR_HOST_MEMORY;
          goto loser;
        }

        (void)memcpy(node->dbt->dbt.data, k.data, k.size);

        node->dbt->my_db = db;

        node->next = find->found;
        find->found = node;
      }

    nomatch:
      if( (NSSArena *)NULL != tmparena ) {
        (void)NSSArena_Destroy(tmparena);
      }
      *pdbrv = db->db->seq(db->db, &k, &v, R_NEXT);
    }

    if( *pdbrv < 0 ) {
      rv = CKR_DEVICE_ERROR;
      goto loser;
    }

    rv = CKR_OK;

  loser:
    (void)NSSCKFWMutex_Unlock(db->crustylock);
  }

  return rv;
}

NSS_IMPLEMENT CK_BBOOL
nss_dbm_db_object_still_exists
(
  nss_dbm_dbt_t *dbt
)
{
  CK_BBOOL rv;
  CK_RV ckrv;
  int dbrv;
  DBT object;

  ckrv = NSSCKFWMutex_Lock(dbt->my_db->crustylock);
  if( CKR_OK != ckrv ) {
    return CK_FALSE;
  }

  dbrv = dbt->my_db->db->get(dbt->my_db->db, &dbt->dbt, &object, 0);
  if( 0 == dbrv ) {
    rv = CK_TRUE;
  } else {
    rv = CK_FALSE;
  }

  (void)NSSCKFWMutex_Unlock(dbt->my_db->crustylock);

  return rv;
}

NSS_IMPLEMENT CK_ULONG
nss_dbm_db_get_object_attribute_count
(
  nss_dbm_dbt_t *dbt,
  CK_RV *pError,
  CK_ULONG *pdbrv
)
{
  CK_ULONG rv = 0;
  DBT object;
  CK_ULONG *pulData;

  /* Locked region */
  {
    *pError = NSSCKFWMutex_Lock(dbt->my_db->crustylock);
    if( CKR_OK != *pError ) {
      return rv;
    }

    *pdbrv = dbt->my_db->db->get(dbt->my_db->db, &dbt->dbt, &object, 0);
    if( 0 == *pdbrv ) {
      ;
    } else if( *pdbrv > 0 ) {
      *pError = CKR_OBJECT_HANDLE_INVALID;
      goto done;
    } else {
      *pError = CKR_DEVICE_ERROR;
      goto done;
    }

    pulData = (CK_ULONG *)object.data;
    rv = ntohl(pulData[0]);

  done:
    (void)NSSCKFWMutex_Unlock(dbt->my_db->crustylock);
  }

  return rv;
}

NSS_IMPLEMENT CK_RV
nss_dbm_db_get_object_attribute_types
(
  nss_dbm_dbt_t *dbt,
  CK_ATTRIBUTE_TYPE_PTR typeArray,
  CK_ULONG ulCount,
  CK_ULONG *pdbrv
)
{
  CK_RV rv = CKR_OK;
  DBT object;
  CK_ULONG *pulData;
  CK_ULONG n, i;

  /* Locked region */
  {
    rv = NSSCKFWMutex_Lock(dbt->my_db->crustylock);
    if( CKR_OK != rv ) {
      return rv;
    }

    *pdbrv = dbt->my_db->db->get(dbt->my_db->db, &dbt->dbt, &object, 0);
    if( 0 == *pdbrv ) {
      ;
    } else if( *pdbrv > 0 ) {
      rv = CKR_OBJECT_HANDLE_INVALID;
      goto done;
    } else {
      rv = CKR_DEVICE_ERROR;
      goto done;
    }

    pulData = (CK_ULONG *)object.data;
    n = ntohl(pulData[0]);

    if( ulCount < n ) {
      rv = CKR_BUFFER_TOO_SMALL;
      goto done;
    }

    for( i = 0; i < n; i++ ) {
      typeArray[i] = ntohl(pulData[1 + i*3]);
    }

  done:
    (void)NSSCKFWMutex_Unlock(dbt->my_db->crustylock);
  }

  return rv;
}

NSS_IMPLEMENT CK_ULONG
nss_dbm_db_get_object_attribute_size
(
  nss_dbm_dbt_t *dbt,
  CK_ATTRIBUTE_TYPE type,
  CK_RV *pError,
  CK_ULONG *pdbrv
)
{
  CK_ULONG rv = 0;
  DBT object;
  CK_ULONG *pulData;
  CK_ULONG n, i;

  /* Locked region */
  {
    *pError = NSSCKFWMutex_Lock(dbt->my_db->crustylock);
    if( CKR_OK != *pError ) {
      return rv;
    }

    *pdbrv = dbt->my_db->db->get(dbt->my_db->db, &dbt->dbt, &object, 0);
    if( 0 == *pdbrv ) {
      ;
    } else if( *pdbrv > 0 ) {
      *pError = CKR_OBJECT_HANDLE_INVALID;
      goto done;
    } else {
      *pError = CKR_DEVICE_ERROR;
      goto done;
    }

    pulData = (CK_ULONG *)object.data;
    n = ntohl(pulData[0]);

    for( i = 0; i < n; i++ ) {
      if( type == ntohl(pulData[1 + i*3]) ) {
        rv = ntohl(pulData[2 + i*3]);
      }
    }

    if( i == n ) {
      *pError = CKR_ATTRIBUTE_TYPE_INVALID;
      goto done;
    }

  done:
    (void)NSSCKFWMutex_Unlock(dbt->my_db->crustylock);
  }

  return rv;
}

NSS_IMPLEMENT NSSItem *
nss_dbm_db_get_object_attribute
(
  nss_dbm_dbt_t *dbt,
  NSSArena *arena,
  CK_ATTRIBUTE_TYPE type,
  CK_RV *pError,
  CK_ULONG *pdbrv
)
{
  NSSItem *rv = (NSSItem *)NULL;
  DBT object;
  CK_ULONG i;
  NSSArena *tmp = NSSArena_Create();
  CK_ATTRIBUTE_PTR pTemplate;
  CK_ULONG ulAttributeCount;

  /* Locked region */
  {
    *pError = NSSCKFWMutex_Lock(dbt->my_db->crustylock);
    if( CKR_OK != *pError ) {
      goto loser;
    }

    *pdbrv = dbt->my_db->db->get(dbt->my_db->db, &dbt->dbt, &object, 0);
    if( 0 == *pdbrv ) {
      ;
    } else if( *pdbrv > 0 ) {
      *pError = CKR_OBJECT_HANDLE_INVALID;
      goto done;
    } else {
      *pError = CKR_DEVICE_ERROR;
      goto done;
    }

    *pError = nss_dbm_db_unwrap_object(tmp, &object, &pTemplate, &ulAttributeCount);
    if( CKR_OK != *pError ) {
      goto done;
    }

    for( i = 0; i < ulAttributeCount; i++ ) {
      if( type == pTemplate[i].type ) {
        rv = nss_ZNEW(arena, NSSItem);
        if( (NSSItem *)NULL == rv ) {
          *pError = CKR_HOST_MEMORY;
          goto done;
        }
        rv->size = pTemplate[i].ulValueLen;
        rv->data = nss_ZAlloc(arena, rv->size);
        if( (void *)NULL == rv->data ) {
          *pError = CKR_HOST_MEMORY;
          goto done;
        }
        (void)memcpy(rv->data, pTemplate[i].pValue, rv->size);
        break;
      }
    }
    if( ulAttributeCount == i ) {
      *pError = CKR_ATTRIBUTE_TYPE_INVALID;
      goto done;
    }

  done:
    (void)NSSCKFWMutex_Unlock(dbt->my_db->crustylock);
  }

 loser:
  if( (NSSArena *)NULL != tmp ) {
    NSSArena_Destroy(tmp);
  }

  return rv;
}

NSS_IMPLEMENT CK_RV
nss_dbm_db_set_object_attribute
(
  nss_dbm_dbt_t *dbt,
  CK_ATTRIBUTE_TYPE type,
  NSSItem *value,
  CK_ULONG *pdbrv
)
{
  CK_RV rv = CKR_OK;
  DBT object;
  CK_ULONG i;
  NSSArena *tmp = NSSArena_Create();
  CK_ATTRIBUTE_PTR pTemplate;
  CK_ULONG ulAttributeCount;

  /* Locked region */
  {
    rv = NSSCKFWMutex_Lock(dbt->my_db->crustylock);
    if( CKR_OK != rv ) {
      goto loser;
    }

    *pdbrv = dbt->my_db->db->get(dbt->my_db->db, &dbt->dbt, &object, 0);
    if( 0 == *pdbrv ) {
      ;
    } else if( *pdbrv > 0 ) {
      rv = CKR_OBJECT_HANDLE_INVALID;
      goto done;
    } else {
      rv = CKR_DEVICE_ERROR;
      goto done;
    }

    rv = nss_dbm_db_unwrap_object(tmp, &object, &pTemplate, &ulAttributeCount);
    if( CKR_OK != rv ) {
      goto done;
    }

    for( i = 0; i < ulAttributeCount; i++ ) {
      if( type == pTemplate[i].type ) {
        /* Replacing an existing attribute */
        pTemplate[i].ulValueLen = value->size;
        pTemplate[i].pValue = value->data;
        break;
      }
    }

    if( i == ulAttributeCount ) {
      /* Adding a new attribute */
      CK_ATTRIBUTE_PTR npt = nss_ZNEWARRAY(tmp, CK_ATTRIBUTE, ulAttributeCount+1);
      if( (CK_ATTRIBUTE_PTR)NULL == npt ) {
        rv = CKR_DEVICE_ERROR;
        goto done;
      }

      for( i = 0; i < ulAttributeCount; i++ ) {
        npt[i] = pTemplate[i];
      }

      npt[ulAttributeCount].type = type;
      npt[ulAttributeCount].ulValueLen = value->size;
      npt[ulAttributeCount].pValue = value->data;

      pTemplate = npt;
      ulAttributeCount++;
    }

    rv = nss_dbm_db_wrap_object(tmp, pTemplate, ulAttributeCount, &object);
    if( CKR_OK != rv ) {
      goto done;
    }

    *pdbrv = dbt->my_db->db->put(dbt->my_db->db, &dbt->dbt, &object, 0);
    if( 0 != *pdbrv ) {
      rv = CKR_DEVICE_ERROR;
      goto done;
    }

    (void)dbt->my_db->db->sync(dbt->my_db->db, 0);

  done:
    (void)NSSCKFWMutex_Unlock(dbt->my_db->crustylock);
  }

 loser:
  if( (NSSArena *)NULL != tmp ) {
    NSSArena_Destroy(tmp);
  }

  return rv;
}
