/* 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/. */

#ifndef PKIT_H
#include "pkit.h"
#endif /* PKIT_H */

#ifndef DEVM_H
#include "devm.h"
#endif /* DEVM_H */

#include "pki3hack.h"
#include "dev3hack.h"
#include "pkim.h"

#ifndef BASE_H
#include "base.h"
#endif /* BASE_H */

#include "pk11func.h"
#include "secmodti.h"
#include "secerr.h"

NSS_IMPLEMENT nssSession *
nssSession_ImportNSS3Session(NSSArena *arenaOpt,
                             CK_SESSION_HANDLE session,
                             PZLock *lock, PRBool rw)
{
    nssSession *rvSession = NULL;
    if (session != CK_INVALID_SESSION) {
        rvSession = nss_ZNEW(arenaOpt, nssSession);
        if (rvSession) {
            rvSession->handle = session;
            rvSession->lock = lock;
            rvSession->ownLock = PR_FALSE;
            rvSession->isRW = rw;
        }
    }
    return rvSession;
}

NSS_IMPLEMENT nssSession *
nssSlot_CreateSession(
    NSSSlot *slot,
    NSSArena *arenaOpt,
    PRBool readWrite)
{
    nssSession *rvSession;

    if (!readWrite) {
        /* nss3hack version only returns rw swssions */
        return NULL;
    }
    rvSession = nss_ZNEW(arenaOpt, nssSession);
    if (!rvSession) {
        return (nssSession *)NULL;
    }

    rvSession->handle = PK11_GetRWSession(slot->pk11slot);
    if (rvSession->handle == CK_INVALID_HANDLE) {
        nss_ZFreeIf(rvSession);
        return NULL;
    }
    rvSession->isRW = PR_TRUE;
    rvSession->slot = slot;
    /*
     * The session doesn't need its own lock.  Here's why.
     * 1. If we are reusing the default RW session of the slot,
     *    the slot lock is already locked to protect the session.
     * 2. If the module is not thread safe, the slot (or rather
     *    module) lock is already locked.
     * 3. If the module is thread safe and we are using a new
     *    session, no higher-level lock has been locked and we
     *    would need a lock for the new session.  However, the
     *    current usage of the session is that it is always
     *    used and destroyed within the same function and never
     *    shared with another thread.
     * So the session is either already protected by another
     * lock or only used by one thread.
     */
    rvSession->lock = NULL;
    rvSession->ownLock = PR_FALSE;
    return rvSession;
}

NSS_IMPLEMENT PRStatus
nssSession_Destroy(nssSession *s)
{
    PRStatus rv = PR_SUCCESS;
    if (s) {
        if (s->isRW) {
            PK11_RestoreROSession(s->slot->pk11slot, s->handle);
        }
        rv = nss_ZFreeIf(s);
    }
    return rv;
}

static NSSSlot *
nssSlot_CreateFromPK11SlotInfo(NSSTrustDomain *td, PK11SlotInfo *nss3slot)
{
    NSSSlot *rvSlot;
    NSSArena *arena;
    arena = nssArena_Create();
    if (!arena) {
        return NULL;
    }
    rvSlot = nss_ZNEW(arena, NSSSlot);
    if (!rvSlot) {
        nssArena_Destroy(arena);
        return NULL;
    }
    rvSlot->base.refCount = 1;
    rvSlot->base.lock = PZ_NewLock(nssILockOther);
    rvSlot->base.arena = arena;
    rvSlot->pk11slot = PK11_ReferenceSlot(nss3slot);
    rvSlot->epv = nss3slot->functionList;
    rvSlot->slotID = nss3slot->slotID;
    /* Grab the slot name from the PKCS#11 fixed-length buffer */
    rvSlot->base.name = nssUTF8_Duplicate(nss3slot->slot_name, td->arena);
    rvSlot->lock = (nss3slot->isThreadSafe) ? NULL : nss3slot->sessionLock;
    rvSlot->isPresentLock = PZ_NewLock(nssiLockOther);
    rvSlot->isPresentCondition = PR_NewCondVar(rvSlot->isPresentLock);
    rvSlot->isPresentThread = NULL;
    rvSlot->lastTokenPingState = nssSlotLastPingState_Reset;
    return rvSlot;
}

NSSToken *
nssToken_CreateFromPK11SlotInfo(NSSTrustDomain *td, PK11SlotInfo *nss3slot)
{
    NSSToken *rvToken;
    NSSArena *arena;

    /* Don't create a token object for a disabled slot */
    if (nss3slot->disabled) {
        PORT_SetError(SEC_ERROR_NO_TOKEN);
        return NULL;
    }
    arena = nssArena_Create();
    if (!arena) {
        return NULL;
    }
    rvToken = nss_ZNEW(arena, NSSToken);
    if (!rvToken) {
        nssArena_Destroy(arena);
        return NULL;
    }
    rvToken->base.refCount = 1;
    rvToken->base.lock = PZ_NewLock(nssILockOther);
    if (!rvToken->base.lock) {
        nssArena_Destroy(arena);
        return NULL;
    }
    rvToken->base.arena = arena;
    rvToken->pk11slot = PK11_ReferenceSlot(nss3slot);
    rvToken->epv = nss3slot->functionList;
    rvToken->defaultSession = nssSession_ImportNSS3Session(td->arena,
                                                           nss3slot->session,
                                                           nss3slot->sessionLock,
                                                           nss3slot->defRWSession);
#if 0 /* we should do this instead of blindly continuing. */
    if (!rvToken->defaultSession) {
    PORT_SetError(SEC_ERROR_NO_TOKEN);
        goto loser;
    }
#endif
    if (!PK11_IsInternal(nss3slot) && PK11_IsHW(nss3slot)) {
        rvToken->cache = nssTokenObjectCache_Create(rvToken,
                                                    PR_TRUE, PR_TRUE, PR_TRUE);
        if (!rvToken->cache)
            goto loser;
    }
    rvToken->trustDomain = td;
    /* Grab the token name from the PKCS#11 fixed-length buffer */
    rvToken->base.name = nssUTF8_Duplicate(nss3slot->token_name, td->arena);
    rvToken->slot = nssSlot_CreateFromPK11SlotInfo(td, nss3slot);
    if (!rvToken->slot) {
        goto loser;
    }
    rvToken->slot->token = rvToken;
    if (rvToken->defaultSession)
        rvToken->defaultSession->slot = rvToken->slot;
    return rvToken;
loser:
    PZ_DestroyLock(rvToken->base.lock);
    nssArena_Destroy(arena);
    return NULL;
}

NSS_IMPLEMENT void
nssToken_UpdateName(NSSToken *token)
{
    if (!token) {
        return;
    }
    token->base.name = nssUTF8_Duplicate(token->pk11slot->token_name, token->base.arena);
}

NSS_IMPLEMENT PRBool
nssSlot_IsPermanent(NSSSlot *slot)
{
    return slot->pk11slot->isPerm;
}

NSS_IMPLEMENT PRBool
nssSlot_IsFriendly(NSSSlot *slot)
{
    return PK11_IsFriendly(slot->pk11slot);
}

NSS_IMPLEMENT PRStatus
nssToken_Refresh(NSSToken *token)
{
    PK11SlotInfo *nss3slot;

    if (!token) {
        return PR_SUCCESS;
    }
    nss3slot = token->pk11slot;
    token->defaultSession =
        nssSession_ImportNSS3Session(token->slot->base.arena,
                                     nss3slot->session,
                                     nss3slot->sessionLock,
                                     nss3slot->defRWSession);
    return token->defaultSession ? PR_SUCCESS : PR_FAILURE;
}

NSS_IMPLEMENT PRStatus
nssSlot_Refresh(NSSSlot *slot)
{
    PK11SlotInfo *nss3slot = slot->pk11slot;
    PRBool doit = PR_FALSE;
    if (slot->token && slot->token->base.name[0] == 0) {
        doit = PR_TRUE;
    }
    if (PK11_InitToken(nss3slot, PR_FALSE) != SECSuccess) {
        return PR_FAILURE;
    }
    if (doit) {
        nssTrustDomain_UpdateCachedTokenCerts(slot->token->trustDomain,
                                              slot->token);
    }
    return nssToken_Refresh(slot->token);
}

NSS_IMPLEMENT PRStatus
nssToken_GetTrustOrder(NSSToken *tok)
{
    PK11SlotInfo *slot;
    SECMODModule *module;
    slot = tok->pk11slot;
    module = PK11_GetModule(slot);
    return module->trustOrder;
}

NSS_IMPLEMENT PRBool
nssSlot_IsLoggedIn(NSSSlot *slot)
{
    if (!slot->pk11slot->needLogin) {
        return PR_TRUE;
    }
    return PK11_IsLoggedIn(slot->pk11slot, NULL);
}

NSSTrustDomain *
nssToken_GetTrustDomain(NSSToken *token)
{
    return token->trustDomain;
}

NSS_EXTERN PRStatus
nssTrustDomain_RemoveTokenCertsFromCache(
    NSSTrustDomain *td,
    NSSToken *token);

NSS_IMPLEMENT PRStatus
nssToken_NotifyCertsNotVisible(
    NSSToken *tok)
{
    return nssTrustDomain_RemoveTokenCertsFromCache(tok->trustDomain, tok);
}