/* ***** 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 PKIX-C library.
 *
 * The Initial Developer of the Original Code is
 * Sun Microsystems, Inc.
 * Portions created by the Initial Developer are
 * Copyright 2004-2007 Sun Microsystems, Inc.  All Rights Reserved.
 *
 * Contributor(s):
 *   Sun Microsystems, Inc.
 *
 * 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 ***** */
/*
 * pkix_pl_crlentry.c
 *
 * CRLENTRY Function Definitions
 *
 */

#include "pkix_pl_crlentry.h"

/* --Private-CRLEntry-Functions------------------------------------- */

/*
 * FUNCTION: pkix_pl_CRLEntry_Destroy
 * (see comments for PKIX_PL_DestructorCallback in pkix_pl_system.h)
 */
static PKIX_Error *
pkix_pl_CRLEntry_Destroy(
        PKIX_PL_Object *object,
        void *plContext)
{
        PKIX_PL_CRLEntry *crlEntry = NULL;

        PKIX_ENTER(CRLENTRY, "pkix_pl_CRLEntry_Destroy");
        PKIX_NULLCHECK_ONE(object);

        PKIX_CHECK(pkix_CheckType(object, PKIX_CRLENTRY_TYPE, plContext),
                    PKIX_OBJECTNOTCRLENTRY);

        crlEntry = (PKIX_PL_CRLEntry*)object;

        /* crlEntry->nssCrlEntry is freed by NSS when freeing CRL */
        crlEntry->userReasonCode = 0;
        crlEntry->userReasonCodeAbsent = PKIX_FALSE;
        crlEntry->nssCrlEntry = NULL;
        PKIX_DECREF(crlEntry->serialNumber);
        PKIX_DECREF(crlEntry->critExtOids);

cleanup:

        PKIX_RETURN(CRLENTRY);
}

/*
 * FUNCTION: pkix_pl_CRLEntry_ToString_Helper
 *
 * DESCRIPTION:
 *  Helper function that creates a string representation of the CRLEntry
 *  pointed to by "crlEntry" and stores it at "pString".
 *
 * PARAMETERS
 *  "crlEntry"
 *      Address of CRLEntry whose string representation is desired.
 *      Must be non-NULL.
 *  "pString"
 *      Address where object pointer will be stored. Must be non-NULL.
 *  "plContext"
 *      Platform-specific context pointer.
 * THREAD SAFETY:
 *  Thread Safe (see Thread Safety Definitions in Programmer's Guide)
 * RETURNS:
 *  Returns NULL if the function succeeds.
 *  Returns a CRLEntry Error if the function fails in a non-fatal way.
 *  Returns a Fatal Error if the function fails in an unrecoverable way.
 */
PKIX_Error *
pkix_pl_CRLEntry_ToString_Helper(
        PKIX_PL_CRLEntry *crlEntry,
        PKIX_PL_String **pString,
        void *plContext)
{
        char *asciiFormat = NULL;
        PKIX_List *critExtOIDs = NULL;
        PKIX_PL_String *crlEntryString = NULL;
        PKIX_PL_String *formatString = NULL;
        PKIX_PL_String *crlSerialNumberString = NULL;
        PKIX_PL_String *crlRevocationDateString = NULL;
        PKIX_PL_String *critExtOIDsString = NULL;
        PKIX_Int32 reasonCode = 0;

        PKIX_ENTER(CRLENTRY, "pkix_pl_CRLEntry_ToString_Helper");
        PKIX_NULLCHECK_FOUR
                (crlEntry,
                crlEntry->serialNumber,
                crlEntry->nssCrlEntry,
                pString);

        asciiFormat =
                "\n\t[\n"
                "\tSerialNumber:    %s\n"
                "\tReasonCode:      %d\n"
                "\tRevocationDate:  %s\n"
                "\tCritExtOIDs:     %s\n"
                "\t]\n\t";

        PKIX_CHECK(PKIX_PL_String_Create
                    (PKIX_ESCASCII,
                    asciiFormat,
                    0,
                    &formatString,
                    plContext),
                    PKIX_STRINGCREATEFAILED);

        /* SerialNumber */
        PKIX_CHECK(PKIX_PL_Object_ToString
                    ((PKIX_PL_Object *)crlEntry->serialNumber,
                    &crlSerialNumberString,
                    plContext),
                    PKIX_BIGINTTOSTRINGHELPERFAILED);

        /* RevocationDate - No Date object created, use nss data directly */
        PKIX_CHECK(pkix_pl_Date_ToString_Helper
                    (&(crlEntry->nssCrlEntry->revocationDate),
                    &crlRevocationDateString,
                    plContext),
                    PKIX_DATETOSTRINGHELPERFAILED);

        /* CriticalExtensionOIDs */
        PKIX_CHECK(PKIX_PL_CRLEntry_GetCriticalExtensionOIDs
                    (crlEntry, &critExtOIDs, plContext),
                    PKIX_CRLENTRYGETCRITICALEXTENSIONOIDSFAILED);

        PKIX_TOSTRING(critExtOIDs, &critExtOIDsString, plContext,
                    PKIX_LISTTOSTRINGFAILED);

        /* Revocation Reason Code */
        PKIX_CHECK(PKIX_PL_CRLEntry_GetCRLEntryReasonCode
                            (crlEntry, &reasonCode, plContext),
                            PKIX_CRLENTRYGETCRLENTRYREASONCODEFAILED);

        PKIX_CHECK(PKIX_PL_Sprintf
                    (&crlEntryString,
                    plContext,
                    formatString,
                    crlSerialNumberString,
                    reasonCode,
                    crlRevocationDateString,
                    critExtOIDsString),
                    PKIX_SPRINTFFAILED);

        *pString = crlEntryString;

cleanup:

        PKIX_DECREF(critExtOIDs);
        PKIX_DECREF(crlSerialNumberString);
        PKIX_DECREF(crlRevocationDateString);
        PKIX_DECREF(critExtOIDsString);
        PKIX_DECREF(formatString);

        PKIX_RETURN(CRLENTRY);
}

/*
 * FUNCTION: pkix_pl_CRLEntry_ToString
 * (see comments for PKIX_PL_ToStringCallback in pkix_pl_system.h)
 */
static PKIX_Error *
pkix_pl_CRLEntry_ToString(
        PKIX_PL_Object *object,
        PKIX_PL_String **pString,
        void *plContext)
{
        PKIX_PL_String *crlEntryString = NULL;
        PKIX_PL_CRLEntry *crlEntry = NULL;

        PKIX_ENTER(CRLENTRY, "pkix_pl_CRLEntry_ToString");
        PKIX_NULLCHECK_TWO(object, pString);

        PKIX_CHECK(pkix_CheckType(object, PKIX_CRLENTRY_TYPE, plContext),
                    PKIX_OBJECTNOTCRLENTRY);

        crlEntry = (PKIX_PL_CRLEntry *) object;

        PKIX_CHECK(pkix_pl_CRLEntry_ToString_Helper
                    (crlEntry, &crlEntryString, plContext),
                    PKIX_CRLENTRYTOSTRINGHELPERFAILED);

        *pString = crlEntryString;

cleanup:

        PKIX_RETURN(CRLENTRY);
}

/*
 * FUNCTION: pkix_pl_CRLEntry_Extensions_Hashcode
 * DESCRIPTION:
 *
 *  For each CRL Entry extension stored at NSS structure CERTCertExtension,
 *  get its derbyte data and do the hash.
 *
 * PARAMETERS
 *  "extensions"
 *      Address of arrray of CERTCertExtension whose hash value is desired.
 *      Must be non-NULL.
 *  "pHashValue"
 *      Address where the final hash value is returned. Must be non-NULL.
 *  "plContext"
 *      Platform-specific context pointer.
 * THREAD SAFETY:
 *  Conditional Thread Safe
 *  Though the value of extensions once created is not supposed to change,
 *  it may be de-allocated while we are accessing it. But since we are
 *  validating the object, it is unlikely we or someone is de-allocating
 *  at the moment.
 * RETURNS:
 *  Returns NULL if the function succeeds.
 *  Returns an OID Error if the function fails in a non-fatal way.
 *  Returns a Fatal Error if the function fails in an unrecoverable way.
 */
static PKIX_Error *
pkix_pl_CRLEntry_Extensions_Hashcode(
        CERTCertExtension **extensions,
        PKIX_UInt32 *pHashValue,
        void *plContext)
{
        CERTCertExtension *extension = NULL;
        PRArenaPool *arena = NULL;
        PKIX_UInt32 extHash = 0;
        PKIX_UInt32 hashValue = 0;
        SECItem *derBytes = NULL;
        SECItem *resultSecItem = NULL;

        PKIX_ENTER(CRLENTRY, "pkix_pl_CRLEntry_Extensions_Hashcode");
        PKIX_NULLCHECK_TWO(extensions, pHashValue);

        if (extensions) {

                PKIX_CRLENTRY_DEBUG("\t\tCalling PORT_NewArena\n");
                arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
                if (arena == NULL) {
                        PKIX_ERROR(PKIX_OUTOFMEMORY);
                }

                while (*extensions) {

                        extension = *extensions++;

                        PKIX_NULLCHECK_ONE(extension);

                        PKIX_CRLENTRY_DEBUG("\t\tCalling PORT_ArenaZNew\n");
                        derBytes = PORT_ArenaZNew(arena, SECItem);
                        if (derBytes == NULL) {
                                PKIX_ERROR(PKIX_PORTARENAALLOCFAILED);
                        }

                        PKIX_CRLENTRY_DEBUG
                                ("\t\tCalling SEC_ASN1EncodeItem\n");
                        resultSecItem = SEC_ASN1EncodeItem
                                (arena,
                                derBytes,
                                extension,
                                CERT_CertExtensionTemplate);

                        if (resultSecItem == NULL){
                                PKIX_ERROR(PKIX_SECASN1ENCODEITEMFAILED);
                        }

                        PKIX_CHECK(pkix_hash
                                (derBytes->data,
                                derBytes->len,
                                &extHash,
                                plContext),
                                PKIX_HASHFAILED);

                        hashValue += (extHash << 7);

                }
        }

        *pHashValue = hashValue;

cleanup:

        if (arena){
                /* Note that freeing the arena also frees derBytes */
                PKIX_CRLENTRY_DEBUG("\t\tCalling PORT_FreeArena\n");
                PORT_FreeArena(arena, PR_FALSE);
                arena = NULL;
        }
        PKIX_RETURN(CRLENTRY);
}

/*
 * FUNCTION: pkix_pl_CRLEntry_Hashcode
 * (see comments for PKIX_PL_HashcodeCallback in pkix_pl_system.h)
 */
static PKIX_Error *
pkix_pl_CRLEntry_Hashcode(
        PKIX_PL_Object *object,
        PKIX_UInt32 *pHashcode,
        void *plContext)
{
        SECItem *nssDate = NULL;
        PKIX_PL_CRLEntry *crlEntry = NULL;
        PKIX_UInt32 crlEntryHash;
        PKIX_UInt32 hashValue;
        PKIX_Int32 reasonCode = 0;

        PKIX_ENTER(CRLENTRY, "pkix_pl_CRLEntry_Hashcode");
        PKIX_NULLCHECK_TWO(object, pHashcode);

        PKIX_CHECK(pkix_CheckType(object, PKIX_CRLENTRY_TYPE, plContext),
                    PKIX_OBJECTNOTCRLENTRY);

        crlEntry = (PKIX_PL_CRLEntry *)object;

        PKIX_NULLCHECK_ONE(crlEntry->nssCrlEntry);
        nssDate = &(crlEntry->nssCrlEntry->revocationDate);

        PKIX_NULLCHECK_ONE(nssDate->data);

        PKIX_CHECK(pkix_hash
                ((const unsigned char *)nssDate->data,
                nssDate->len,
                &crlEntryHash,
                plContext),
                PKIX_ERRORGETTINGHASHCODE);

        PKIX_CHECK(PKIX_PL_Object_Hashcode
                ((PKIX_PL_Object *)crlEntry->serialNumber,
                &hashValue,
                plContext),
                PKIX_OBJECTHASHCODEFAILED);

        crlEntryHash += (hashValue << 7);

        hashValue = 0;

        if (crlEntry->nssCrlEntry->extensions) {

                PKIX_CHECK(pkix_pl_CRLEntry_Extensions_Hashcode
                    (crlEntry->nssCrlEntry->extensions, &hashValue, plContext),
                    PKIX_CRLENTRYEXTENSIONSHASHCODEFAILED);
        }

        crlEntryHash += (hashValue << 7);

        PKIX_CHECK(PKIX_PL_CRLEntry_GetCRLEntryReasonCode
                (crlEntry, &reasonCode, plContext),
                PKIX_CRLENTRYGETCRLENTRYREASONCODEFAILED);

        crlEntryHash += (reasonCode + 777) << 3;

        *pHashcode = crlEntryHash;

cleanup:

        PKIX_RETURN(CRLENTRY);
}

/*
 * FUNCTION: pkix_pl_CRLENTRY_Extensions_Equals
 * DESCRIPTION:
 *
 *  Compare each extension's DERbyte data in "firstExtensions" with extension
 *  in "secondExtensions" in sequential order and store the result in
 *  "pResult".
 *
 * PARAMETERS
 *  "firstExtensions"
 *      Address of first NSS structure CERTCertExtension to be compared.
 *      Must be non-NULL.
 *  "secondExtensions"
 *      Address of second NSS structure CERTCertExtension to be compared.
 *      Must be non-NULL.
 *  "pResult"
 *      Address where the comparison result is returned. Must be non-NULL.
 *  "plContext"
 *      Platform-specific context pointer.
 * THREAD SAFETY:
 *  Conditionally Thread Safe
 *  Though the value of extensions once created is not supposed to change,
 *  it may be de-allocated while we are accessing it. But since we are
 *  validating the object, it is unlikely we or someone is de-allocating
 *  at the moment.
 * RETURNS:
 *  Returns NULL if the function succeeds.
 *  Returns an OID Error if the function fails in a non-fatal way.
 *  Returns a Fatal Error if the function fails in an unrecoverable way.
 */
static PKIX_Error *
pkix_pl_CRLEntry_Extensions_Equals(
        CERTCertExtension **extensions1,
        CERTCertExtension **extensions2,
        PKIX_Boolean *pResult,
        void *plContext)
{
        CERTCertExtension **firstExtensions;
        CERTCertExtension **secondExtensions;
        CERTCertExtension *firstExtension = NULL;
        CERTCertExtension *secondExtension = NULL;
        PRArenaPool *arena = NULL;
        PKIX_Boolean cmpResult = PKIX_FALSE;
        SECItem *firstDerBytes = NULL;
        SECItem *secondDerBytes = NULL;
        SECItem *firstResultSecItem = NULL;
        SECItem *secondResultSecItem = NULL;
        PKIX_UInt32 firstNumExt = 0;
        PKIX_UInt32 secondNumExt = 0;
        SECComparison secResult;

        PKIX_ENTER(CRLENTRY, "pkix_pl_CRLEntry_Extensions_Equals");
        PKIX_NULLCHECK_THREE(extensions1, extensions2, pResult);

        firstExtensions = extensions1;
        secondExtensions = extensions2;

        if (firstExtensions) {
                while (*firstExtensions) {
                        firstExtension = *firstExtensions++;
                        firstNumExt++;
                }
        }

        if (secondExtensions) {
                while (*secondExtensions) {
                        secondExtension = *secondExtensions++;
                        secondNumExt++;
                }
        }

        if (firstNumExt != secondNumExt) {
                *pResult = PKIX_FALSE;
                goto cleanup;
        }

        if (firstNumExt == 0 && secondNumExt == 0) {
                *pResult = PKIX_TRUE;
                goto cleanup;
        }

        /* now have equal number, but non-zero extension items to compare */

        firstExtensions = extensions1;
        secondExtensions = extensions2;

        cmpResult = PKIX_TRUE;

        PKIX_CRLENTRY_DEBUG("\t\tCalling PORT_NewArena\n");
        arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE*2);
        if (arena == NULL) {
                PKIX_ERROR(PKIX_OUTOFMEMORY);
        }

        while (firstNumExt--) {

                firstExtension = *firstExtensions++;
                secondExtension = *secondExtensions++;

                PKIX_NULLCHECK_TWO(firstExtension, secondExtension);

                PKIX_CRLENTRY_DEBUG("\t\tCalling PORT_ArenaZNew\n");
                firstDerBytes = PORT_ArenaZNew(arena, SECItem);
                if (firstDerBytes == NULL) {
                        PKIX_ERROR(PKIX_PORTARENAALLOCFAILED);
                }

                PKIX_CRLENTRY_DEBUG("\t\tCalling PORT_ArenaZNew\n");
                secondDerBytes = PORT_ArenaZNew(arena, SECItem);
                if (secondDerBytes == NULL) {
                        PKIX_ERROR(PKIX_PORTARENAALLOCFAILED);
                }

                PKIX_CRLENTRY_DEBUG
                        ("\t\tCalling SEC_ASN1EncodeItem\n");
                firstResultSecItem = SEC_ASN1EncodeItem
                        (arena,
                        firstDerBytes,
                        firstExtension,
                        CERT_CertExtensionTemplate);

                if (firstResultSecItem == NULL){
                        PKIX_ERROR(PKIX_SECASN1ENCODEITEMFAILED);
                }

                PKIX_CRLENTRY_DEBUG
                        ("\t\tCalling SEC_ASN1EncodeItem\n");
                secondResultSecItem = SEC_ASN1EncodeItem
                        (arena,
                        secondDerBytes,
                        secondExtension,
                        CERT_CertExtensionTemplate);

                if (secondResultSecItem == NULL){
                        PKIX_ERROR(PKIX_SECASN1ENCODEITEMFAILED);
                }

                PKIX_CRLENTRY_DEBUG("\t\tCalling SECITEM_CompareItem\n");
                secResult = SECITEM_CompareItem
                        (firstResultSecItem, secondResultSecItem);

                if (secResult != SECEqual) {
                        cmpResult = PKIX_FALSE;
                        break;
                }

        }

        *pResult = cmpResult;

cleanup:

        if (arena){
                /* Note that freeing the arena also frees derBytes */
                PKIX_CRLENTRY_DEBUG("\t\tCalling PORT_FreeArena\n");
                PORT_FreeArena(arena, PR_FALSE);
                arena = NULL;
        }

        PKIX_RETURN(CRLENTRY);
}

/*
 * FUNCTION: pkix_pl_CRLEntry_Equals
 * (see comments for PKIX_PL_Equals_Callback in pkix_pl_system.h)
 */
static PKIX_Error *
pkix_pl_CRLEntry_Equals(
        PKIX_PL_Object *firstObject,
        PKIX_PL_Object *secondObject,
        PKIX_Boolean *pResult,
        void *plContext)
{
        PKIX_PL_CRLEntry *firstCrlEntry = NULL;
        PKIX_PL_CRLEntry *secondCrlEntry = NULL;
        PKIX_UInt32 secondType;
        PKIX_Boolean cmpResult = PKIX_FALSE;

        PKIX_ENTER(CRLENTRY, "pkix_pl_CRLEntry_Equals");
        PKIX_NULLCHECK_THREE(firstObject, secondObject, pResult);

        /* test that firstObject is a CRLEntry */
        PKIX_CHECK(pkix_CheckType(firstObject, PKIX_CRLENTRY_TYPE, plContext),
                PKIX_FIRSTOBJECTNOTCRLENTRY);

        firstCrlEntry = (PKIX_PL_CRLEntry *)firstObject;
        secondCrlEntry = (PKIX_PL_CRLEntry *)secondObject;

        PKIX_NULLCHECK_TWO
                (firstCrlEntry->nssCrlEntry, secondCrlEntry->nssCrlEntry);

        /*
         * Since we know firstObject is a CRLEntry, if both references are
         * identical, they must be equal
         */
        if (firstCrlEntry == secondCrlEntry){
                *pResult = PKIX_TRUE;
                goto cleanup;
        }

        /*
         * If secondCrlEntry isn't a CRL Entry, we don't throw an error.
         * We simply return a Boolean result of FALSE
         */
        *pResult = PKIX_FALSE;
        PKIX_CHECK(PKIX_PL_Object_GetType
                    ((PKIX_PL_Object *)secondCrlEntry, &secondType, plContext),
                    PKIX_COULDNOTGETTYPEOFSECONDARGUMENT);
        if (secondType != PKIX_CRLENTRY_TYPE) goto cleanup;

        /* Compare userSerialNumber */
        PKIX_CRLENTRY_DEBUG("\t\tCalling SECITEM_CompareItem\n");
        if (SECITEM_CompareItem(
            &(((PKIX_PL_CRLEntry *)firstCrlEntry)->nssCrlEntry->serialNumber),
            &(((PKIX_PL_CRLEntry *)secondCrlEntry)->nssCrlEntry->serialNumber))
            != SECEqual) {
                *pResult = PKIX_FALSE;
                goto cleanup;
        }

        /* Compare revocationDate */
        PKIX_CRLENTRY_DEBUG("\t\tCalling SECITEM_CompareItem\n");
        if (SECITEM_CompareItem
            (&(((PKIX_PL_CRLEntry *)firstCrlEntry)->nssCrlEntry->
                revocationDate),
            &(((PKIX_PL_CRLEntry *)secondCrlEntry)->nssCrlEntry->
                revocationDate))
            != SECEqual) {
                *pResult = PKIX_FALSE;
                goto cleanup;
        }

        /* Compare Critical Extension List */
        PKIX_CHECK(pkix_pl_CRLEntry_Extensions_Equals
                    (firstCrlEntry->nssCrlEntry->extensions,
                    secondCrlEntry->nssCrlEntry->extensions,
                    &cmpResult,
                    plContext),
                    PKIX_CRLENTRYEXTENSIONSEQUALSFAILED);

        if (cmpResult != PKIX_TRUE){
                *pResult = PKIX_FALSE;
                goto cleanup;
        }

        cmpResult = (firstCrlEntry->userReasonCode ==
                    secondCrlEntry->userReasonCode);

        *pResult = cmpResult;

cleanup:

        PKIX_RETURN(CRLENTRY);
}

/*
 * FUNCTION: pkix_pl_CRLEntry_RegisterSelf
 * DESCRIPTION:
 *  Registers PKIX_CRLEntry_TYPE and its related functions with systemClasses[]
 * THREAD SAFETY:
 *  Not Thread Safe - for performance and complexity reasons
 *
 *  Since this function is only called by PKIX_PL_Initialize, which should
 *  only be called once, it is acceptable that this function is not
 *  thread-safe.
 */
PKIX_Error *
pkix_pl_CRLEntry_RegisterSelf(void *plContext)
{

        extern pkix_ClassTable_Entry systemClasses[PKIX_NUMTYPES];
        pkix_ClassTable_Entry entry;

        PKIX_ENTER(CRLENTRY, "pkix_pl_CRLEntry_RegisterSelf");

        entry.description = "CRLEntry";
        entry.objCounter = 0;
        entry.typeObjectSize = sizeof(PKIX_PL_CRLEntry);
        entry.destructor = pkix_pl_CRLEntry_Destroy;
        entry.equalsFunction = pkix_pl_CRLEntry_Equals;
        entry.hashcodeFunction = pkix_pl_CRLEntry_Hashcode;
        entry.toStringFunction = pkix_pl_CRLEntry_ToString;
        entry.comparator = NULL;
        entry.duplicateFunction = pkix_duplicateImmutable;

        systemClasses[PKIX_CRLENTRY_TYPE] = entry;

        PKIX_RETURN(CRLENTRY);
}

/*
 * FUNCTION: pkix_pl_CRLEntry_CreateEntry
 * DESCRIPTION:
 *
 *  Creates a new CRLEntry using the CertCrlEntry pointed to by "nssCrlEntry"
 *  and stores it at "pCrlEntry". Once created, a CRLEntry is immutable.
 *
 *  revokedCertificates SEQUENCE OF SEQUENCE  {
 *              userCertificate         CertificateSerialNumber,
 *              revocationDate          Time,
 *              crlEntryExtensions      Extensions OPTIONAL
 *                                      -- if present, MUST be v2
 *
 * PARAMETERS:
 *  "nssCrlEntry"
 *      Address of CERTCrlEntry representing an NSS CRL entry.
 *      Must be non-NULL.
 *  "pCrlEntry"
 *      Address where object pointer will be stored. Must be non-NULL.
 *  "plContext"
 *      Platform-specific context pointer.
 * THREAD SAFETY:
 *  Thread Safe (see Thread Safety Definitions in Programmer's Guide)
 * RETURNS:
 *  Returns NULL if the function succeeds.
 *  Returns a CRLEntry Error if the function fails in a non-fatal way.
 *  Returns a Fatal Error if the function fails in an unrecoverable way.
 */
static PKIX_Error *
pkix_pl_CRLEntry_CreateEntry(
        CERTCrlEntry *nssCrlEntry, /* entry data to be created from */
        PKIX_PL_CRLEntry **pCrlEntry,
        void *plContext)
{
        PKIX_PL_CRLEntry *crlEntry = NULL;

        PKIX_ENTER(CRLENTRY, "pkix_pl_CRLEntry_CreateEntry");
        PKIX_NULLCHECK_TWO(nssCrlEntry, pCrlEntry);

        PKIX_CHECK(PKIX_PL_Object_Alloc
                    (PKIX_CRLENTRY_TYPE,
                    sizeof (PKIX_PL_CRLEntry),
                    (PKIX_PL_Object **)&crlEntry,
                    plContext),
                    PKIX_COULDNOTCREATECRLENTRYOBJECT);

        crlEntry->nssCrlEntry = nssCrlEntry;
        crlEntry->serialNumber = NULL;
        crlEntry->critExtOids = NULL;
        crlEntry->userReasonCode = 0;
        crlEntry->userReasonCodeAbsent = PKIX_FALSE;

        *pCrlEntry = crlEntry;

cleanup:

        PKIX_RETURN(CRLENTRY);
}

/*
 * FUNCTION: pkix_pl_CRLEntry_Create
 * DESCRIPTION:
 *
 *  Creates a List of CRLEntries using the array of CERTCrlEntries pointed to
 *  by "nssCrlEntries" and stores it at "pCrlEntryList". If "nssCrlEntries" is
 *  NULL, this function stores an empty List at "pCrlEntryList".
 *                              }
 * PARAMETERS:
 *  "nssCrlEntries"
 *      Address of array of CERTCrlEntries representing NSS CRL entries.
 *      Can be NULL if CRL has no NSS CRL entries.
 *  "pCrlEntryList"
 *      Address where object pointer will be stored. Must be non-NULL.
 *  "plContext"
 *      Platform-specific context pointer.
 * THREAD SAFETY:
 *  Thread Safe (see Thread Safety Definitions in Programmer's Guide)
 * RETURNS:
 *  Returns NULL if the function succeeds.
 *  Returns a CRLEntry Error if the function fails in a non-fatal way.
 *  Returns a Fatal Error if the function fails in an unrecoverable way.
 */
PKIX_Error *
pkix_pl_CRLEntry_Create(
        CERTCrlEntry **nssCrlEntries, /* head of entry list */
        PKIX_List **pCrlEntryList,
        void *plContext)
{
        PKIX_List *entryList = NULL;
        PKIX_PL_CRLEntry *crlEntry = NULL;
        CERTCrlEntry **entries = NULL;
        SECItem serialNumberItem;
        PKIX_PL_BigInt *serialNumber;
        char *bytes = NULL;
        PKIX_UInt32 length;

        PKIX_ENTER(CRLENTRY, "pkix_pl_CRLEntry_Create");
        PKIX_NULLCHECK_ONE(pCrlEntryList);

        entries = nssCrlEntries;

        PKIX_CHECK(PKIX_List_Create(&entryList, plContext),
                    PKIX_LISTCREATEFAILED);

        if (entries) {
            while (*entries){
                PKIX_CHECK(pkix_pl_CRLEntry_CreateEntry
                            (*entries, &crlEntry, plContext),
                            PKIX_COULDNOTCREATECRLENTRYOBJECT);

                /* Get Serial Number */
                serialNumberItem = (*entries)->serialNumber;
                length = serialNumberItem.len;
                bytes = (char *)serialNumberItem.data;

                PKIX_CHECK(pkix_pl_BigInt_CreateWithBytes
                            (bytes, length, &serialNumber, plContext),
                            PKIX_BIGINTCREATEWITHBYTESFAILED);

                crlEntry->serialNumber = serialNumber;
                crlEntry->nssCrlEntry = *entries;

                PKIX_CHECK(PKIX_List_AppendItem
                            (entryList, (PKIX_PL_Object *)crlEntry, plContext),
                            PKIX_LISTAPPENDITEMFAILED);

                PKIX_DECREF(crlEntry);

                entries++;
            }
        }

        *pCrlEntryList = entryList;

cleanup:
        PKIX_DECREF(crlEntry);

        if (PKIX_ERROR_RECEIVED){
                PKIX_DECREF(entryList);
        }

        PKIX_RETURN(CRLENTRY);
}

/* --Public-CRLENTRY-Functions------------------------------------- */

/*
 * FUNCTION: PKIX_PL_CRLEntry_GetCRLEntryReasonCode
 * (see comments in pkix_pl_pki.h)
 */
PKIX_Error *
PKIX_PL_CRLEntry_GetCRLEntryReasonCode (
        PKIX_PL_CRLEntry *crlEntry,
        PKIX_Int32 *pReason,
        void *plContext)
{
        SECStatus status;
        CERTCRLEntryReasonCode nssReasonCode;

        PKIX_ENTER(CRLENTRY, "PKIX_PL_CRLEntry_GetCRLEntryReasonCode");
        PKIX_NULLCHECK_TWO(crlEntry, pReason);

        if (!crlEntry->userReasonCodeAbsent && crlEntry->userReasonCode == 0) {

            PKIX_OBJECT_LOCK(crlEntry);

            if (!crlEntry->userReasonCodeAbsent &&
                crlEntry->userReasonCode == 0) {

                /* reason code has not been cached in */
                PKIX_CRLENTRY_DEBUG("\t\tCERT_FindCRLEntryReasonExten.\n");
                status = CERT_FindCRLEntryReasonExten
                        (crlEntry->nssCrlEntry, &nssReasonCode);

                if (status == SECSuccess) {
                        crlEntry->userReasonCode = (PKIX_Int32) nssReasonCode;
                } else {
                        crlEntry->userReasonCodeAbsent = PKIX_TRUE;
                }
            }

            PKIX_OBJECT_UNLOCK(crlEntry);

        }

        *pReason = crlEntry->userReasonCode;

cleanup:

        PKIX_RETURN(CRLENTRY);
}

/*
 * FUNCTION: PKIX_PL_CRLEntry_GetCriticalExtensionOIDs
 * (see comments in pkix_pl_pki.h)
 */
PKIX_Error *
PKIX_PL_CRLEntry_GetCriticalExtensionOIDs (
        PKIX_PL_CRLEntry *crlEntry,
        PKIX_List **pList,  /* list of PKIX_PL_OID */
        void *plContext)
{
        PKIX_List *oidsList = NULL;
        CERTCertExtension **extensions;

        PKIX_ENTER(CRLENTRY, "PKIX_PL_CRLEntry_GetCriticalExtensionOIDs");
        PKIX_NULLCHECK_THREE(crlEntry, crlEntry->nssCrlEntry, pList);

        /* if we don't have a cached copy from before, we create one */
        if (crlEntry->critExtOids == NULL) {

                PKIX_OBJECT_LOCK(crlEntry);

                if (crlEntry->critExtOids == NULL) {

                        extensions = crlEntry->nssCrlEntry->extensions;

                        PKIX_CHECK(pkix_pl_OID_GetCriticalExtensionOIDs
                                    (extensions, &oidsList, plContext),
                                    PKIX_GETCRITICALEXTENSIONOIDSFAILED);

                        crlEntry->critExtOids = oidsList;
                }

                PKIX_OBJECT_UNLOCK(crlEntry);

        }

        /* We should return a copy of the List since this list changes */
        PKIX_DUPLICATE(crlEntry->critExtOids, pList, plContext,
                PKIX_OBJECTDUPLICATELISTFAILED);

cleanup:

        PKIX_RETURN(CRLENTRY);
}
