/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#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 */
    {
        rv = NSSCKFWMutex_Lock(db->crustylock);
        if (CKR_OK != rv) {
            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;
}
