/*
 * FUNCTION: PKIX_RevocationChecker_Check
 */
PKIX_Error *
PKIX_RevocationChecker_Check(
    PKIX_PL_Cert *cert,
    PKIX_PL_Cert *issuer,
    PKIX_RevocationChecker *revChecker,
    PKIX_ProcessingParams *procParams,
    PKIX_Boolean chainVerificationState,
    PKIX_Boolean testingLeafCert,
    PKIX_RevocationStatus *pRevStatus,
    PKIX_UInt32 *pReasonCode,
    void **pNbioContext,
    void *plContext)
{
    PKIX_RevocationStatus overallStatus = PKIX_RevStatus_NoInfo;
    PKIX_RevocationStatus methodStatus[PKIX_RevocationMethod_MAX];
    PKIX_Boolean onlyUseRemoteMethods = PKIX_FALSE;
    PKIX_UInt32 revFlags = 0;
    PKIX_List *revList = NULL;
    PKIX_PL_Date *date = NULL;
    pkix_RevocationMethod *method = NULL;
    void *nbioContext;
    int tries;
    
    PKIX_ENTER(REVOCATIONCHECKER, "PKIX_RevocationChecker_Check");
    PKIX_NULLCHECK_TWO(revChecker, procParams);

    nbioContext = *pNbioContext;
    *pNbioContext = NULL;
    
    if (testingLeafCert) {
        revList = revChecker->leafMethodList;
        revFlags = revChecker->leafMethodListFlags;        
    } else {
        revList = revChecker->chainMethodList;
        revFlags = revChecker->chainMethodListFlags;
    }
    if (!revList) {
        /* Return NoInfo status */
        goto cleanup;
    }

    PORT_Memset(methodStatus, PKIX_RevStatus_NoInfo,
                sizeof(PKIX_RevocationStatus) * PKIX_RevocationMethod_MAX);

    date = procParams->date;

    /* Need to have two loops if we testing all local info first:
     *    first we are going to test all local(cached) info
     *    second, all remote info(fetching) */
    for (tries = 0;tries < 2;tries++) {
        int methodNum = 0;
        for (;methodNum < revList->length;methodNum++) {
            PKIX_UInt32 methodFlags = 0;

            PKIX_DECREF(method);
            PKIX_CHECK(
                PKIX_List_GetItem(revList, methodNum,
                                  (PKIX_PL_Object**)&method, plContext),
                PKIX_LISTGETITEMFAILED);
            methodFlags = method->flags;
            if (!(methodFlags & PKIX_REV_M_TEST_USING_THIS_METHOD)) {
                /* Will not check with this method. Skipping... */
                continue;
            }
            if (!onlyUseRemoteMethods &&
                methodStatus[methodNum] == PKIX_RevStatus_NoInfo) {
                PKIX_RevocationStatus revStatus = PKIX_RevStatus_NoInfo;
                PKIX_CHECK_NO_GOTO(
                    (*method->localRevChecker)(cert, issuer, date,
                                               method, procParams,
                                               methodFlags, 
                                               chainVerificationState,
                                               &revStatus,
                                               pReasonCode, plContext),
                    PKIX_REVCHECKERCHECKFAILED);
                methodStatus[methodNum] = revStatus;
                if (revStatus == PKIX_RevStatus_Revoked) {
                    /* if error was generated use it as final error. */
                    overallStatus = PKIX_RevStatus_Revoked;
                    goto cleanup;
                }
                if (pkixErrorResult) {
                    /* Disregard errors. Only returned revStatus matters. */
                    PKIX_PL_Object_DecRef((PKIX_PL_Object*)pkixErrorResult,
                                          plContext);
                    pkixErrorResult = NULL;
                }
            }
            if ((!(revFlags & PKIX_REV_MI_TEST_ALL_LOCAL_INFORMATION_FIRST) ||
                 onlyUseRemoteMethods) &&
                chainVerificationState &&
                methodStatus[methodNum] == PKIX_RevStatus_NoInfo) {
                if (!(methodFlags & PKIX_REV_M_FORBID_NETWORK_FETCHING)) {
                    PKIX_RevocationStatus revStatus = PKIX_RevStatus_NoInfo;
                    PKIX_CHECK_NO_GOTO(
                        (*method->externalRevChecker)(cert, issuer, date,
                                                      method,
                                                      procParams, methodFlags,
                                                      &revStatus, pReasonCode,
                                                      &nbioContext, plContext),
                        PKIX_REVCHECKERCHECKFAILED);
                    methodStatus[methodNum] = revStatus;
                    if (revStatus == PKIX_RevStatus_Revoked) {
                        /* if error was generated use it as final error. */
                        overallStatus = PKIX_RevStatus_Revoked;
                        goto cleanup;
                    }
                    if (pkixErrorResult) {
                        /* Disregard errors. Only returned revStatus matters. */
                        PKIX_PL_Object_DecRef((PKIX_PL_Object*)pkixErrorResult,
                                              plContext);
                        pkixErrorResult = NULL;
                    }
                } else if (methodFlags &
                           PKIX_REV_M_FAIL_ON_MISSING_FRESH_INFO) {
                    /* Info is not in the local cache. Network fetching is not
                     * allowed. If need to fail on missing fresh info for the
                     * the method, then we should fail right here.*/
                    overallStatus = PKIX_RevStatus_Revoked;
                    goto cleanup;
                }
            }
            /* If success and we should not check the next method, then
             * return a success. */
            if (methodStatus[methodNum] == PKIX_RevStatus_Success &&
                !(methodFlags & PKIX_REV_M_CONTINUE_TESTING_ON_FRESH_INFO)) {
                overallStatus = PKIX_RevStatus_Success;
                goto cleanup;
            }
        } /* inner loop */
        if (!onlyUseRemoteMethods &&
            revFlags & PKIX_REV_MI_TEST_ALL_LOCAL_INFORMATION_FIRST &&
            chainVerificationState) {
            onlyUseRemoteMethods = PKIX_TRUE;
            continue;
        }
        break;
    } /* outer loop */
    
    if (overallStatus == PKIX_RevStatus_NoInfo &&
        chainVerificationState) {
        /* The following check makes sence only for chain
         * validation step, sinse we do not fetch info while
         * in the process of finding trusted anchor. 
         * For chain building step it is enough to know, that
         * the cert was not directly revoked by any of the
         * methods. */

        /* Still have no info. But one of the method could
         * have returned success status(possible if CONTINUE
         * TESTING ON FRESH INFO flag was used).
         * If any of the methods have returned Success status,
         * the overallStatus should be success. */
        int methodNum = 0;
        for (;methodNum < PKIX_RevocationMethod_MAX;methodNum++) {
            if (methodStatus[methodNum] == PKIX_RevStatus_Success) {
                overallStatus = PKIX_RevStatus_Success;
                goto cleanup;
            }
        }
        if (revFlags & PKIX_REV_MI_REQUIRE_SOME_FRESH_INFO_AVAILABLE) {
            overallStatus = PKIX_RevStatus_Revoked;
        }
    }

cleanup:
    *pRevStatus = overallStatus;
    PKIX_DECREF(method);

    PKIX_RETURN(REVOCATIONCHECKER);
}
/*
 * The OCSPChecker is created in an idle state, and remains in this state until
 * either (a) the default Responder has been set and enabled, and a Check
 * request is received with no responder specified, or (b) a Check request is
 * received with a specified responder. A request message is constructed and
 * given to the HttpClient. When a response is received it is decoded and the
 * results provided to the caller.
 *
 * During the most recent enhancement of this function, it has been found that
 * it doesn't correctly implement non-blocking I/O.
 * 
 * The nbioContext is used in two places, for "response-obtaining" and
 * for "response-verification".
 * 
 * However, if this function gets called to resume, it always
 * repeats the "request creation" and "response fetching" steps!
 * As a result, the earlier operation is never resumed.
 */
PKIX_Error *
pkix_OcspChecker_CheckExternal(
        PKIX_PL_Cert *cert,
        PKIX_PL_Cert *issuer,
        PKIX_PL_Date *date,
        pkix_RevocationMethod *checkerObject,
        PKIX_ProcessingParams *procParams,
        PKIX_UInt32 methodFlags,
        PKIX_RevocationStatus *pRevStatus,
        CERTCRLEntryReasonCode *pReasonCode,
        void **pNBIOContext,
        void *plContext)
{
        SECErrorCodes resultCode = SEC_ERROR_REVOKED_CERTIFICATE_OCSP;
        PKIX_Boolean uriFound = PKIX_FALSE;
        PKIX_Boolean passed = PKIX_TRUE;
        pkix_OcspChecker *checker = NULL;
        PKIX_PL_OcspCertID *cid = NULL;
        PKIX_PL_OcspRequest *request = NULL;
        PKIX_PL_OcspResponse *response = NULL;
        PKIX_PL_Date *validity = NULL;
        PKIX_RevocationStatus revStatus = PKIX_RevStatus_NoInfo;
        void *nbioContext = NULL;
        enum { stageGET, stagePOST } currentStage;
        PRBool retry = PR_FALSE;

        PKIX_ENTER(OCSPCHECKER, "pkix_OcspChecker_CheckExternal");

        PKIX_CHECK(
            pkix_CheckType((PKIX_PL_Object*)checkerObject,
                           PKIX_OCSPCHECKER_TYPE, plContext),
                PKIX_OBJECTNOTOCSPCHECKER);

        checker = (pkix_OcspChecker *)checkerObject;

        PKIX_CHECK(
            PKIX_PL_OcspCertID_Create(cert, NULL, &cid,
                                      plContext),
            PKIX_OCSPCERTIDCREATEFAILED);
        
        /* create request */
        PKIX_CHECK(
            pkix_pl_OcspRequest_Create(cert, cid, validity, NULL, 
                                       methodFlags, &uriFound, &request,
                                       plContext),
            PKIX_OCSPREQUESTCREATEFAILED);
        
        if (uriFound == PKIX_FALSE) {
            /* no caching for certs lacking URI */
            resultCode = 0;
            goto cleanup;
        }

        if (methodFlags & CERT_REV_M_FORCE_POST_METHOD_FOR_OCSP) {
            /* Do not try HTTP GET, only HTTP POST */
            currentStage = stagePOST;
        } else {
            /* Try HTTP GET first, falling back to POST */
            currentStage = stageGET;
        }

        do {
                const char *method;
                passed = PKIX_TRUE;

                retry = PR_FALSE;
                if (currentStage == stageGET) {
                        method = "GET";
                } else {
                        PORT_Assert(currentStage == stagePOST);
                        method = "POST";
                }

                /* send request and create a response object */
                PKIX_CHECK_NO_GOTO(
                    pkix_pl_OcspResponse_Create(request, method, NULL,
                                                checker->certVerifyFcn,
                                                &nbioContext,
                                                &response,
                                                plContext),
                    PKIX_OCSPRESPONSECREATEFAILED);

                if (pkixErrorResult) {
                    passed = PKIX_FALSE;
                }

                if (passed && nbioContext != 0) {
                        *pNBIOContext = nbioContext;
                        goto cleanup;
                }

                if (passed){
                        PKIX_CHECK_NO_GOTO(
                            pkix_pl_OcspResponse_Decode(response, &passed,
                                                        &resultCode, plContext),
                            PKIX_OCSPRESPONSEDECODEFAILED);
                        if (pkixErrorResult) {
                            passed = PKIX_FALSE;
                        }
                }
                
                if (passed){
                        PKIX_CHECK_NO_GOTO(
                            pkix_pl_OcspResponse_GetStatus(response, &passed,
                                                           &resultCode, plContext),
                            PKIX_OCSPRESPONSEGETSTATUSRETURNEDANERROR);
                        if (pkixErrorResult) {
                            passed = PKIX_FALSE;
                        }
                }

                if (passed){
                        PKIX_CHECK_NO_GOTO(
                            pkix_pl_OcspResponse_VerifySignature(response, cert,
                                                                 procParams, &passed, 
                                                                 &nbioContext, plContext),
                            PKIX_OCSPRESPONSEVERIFYSIGNATUREFAILED);
                        if (pkixErrorResult) {
                            passed = PKIX_FALSE;
                        } else {
                                if (nbioContext != 0) {
                                        *pNBIOContext = nbioContext;
                                        goto cleanup;
                                }
                        }
                }

                if (!passed && currentStage == stagePOST) {
                        /* We won't retry a POST failure, so it's final.
                         * Because the following block with its call to
                         *   pkix_pl_OcspResponse_GetStatusForCert
                         * will take care of caching good or bad state,
                         * but we only execute that next block if there hasn't
                         * been a failure yet, we must cache the POST
                         * failure now.
                         */
                         
                        if (cid && cid->certID) {
                                /* Caching MIGHT consume the cid. */
                                PKIX_Error *err;
                                err = PKIX_PL_OcspCertID_RememberOCSPProcessingFailure(
                                        cid, plContext);
                                if (err) {
                                        PKIX_PL_Object_DecRef((PKIX_PL_Object*)err, plContext);
                                }
                        }
                }

                if (passed){
                        PKIX_Boolean allowCachingOfFailures =
                                (currentStage == stagePOST) ? PKIX_TRUE : PKIX_FALSE;
                        
                        PKIX_CHECK_NO_GOTO(
                            pkix_pl_OcspResponse_GetStatusForCert(cid, response,
                                                                  allowCachingOfFailures,
                                                                  date,
                                                                  &passed, &resultCode,
                                                                  plContext),
                            PKIX_OCSPRESPONSEGETSTATUSFORCERTFAILED);
                        if (pkixErrorResult) {
                            passed = PKIX_FALSE;
                        } else if (passed == PKIX_FALSE) {
                                revStatus = pkix_OcspChecker_MapResultCodeToRevStatus(resultCode);
                        } else {
                                revStatus = PKIX_RevStatus_Success;
                        }
                }

                if (currentStage == stageGET && revStatus != PKIX_RevStatus_Success &&
                                                revStatus != PKIX_RevStatus_Revoked) {
                        /* we'll retry */
                        PKIX_DECREF(response);
                        retry = PR_TRUE;
                        currentStage = stagePOST;
                        revStatus = PKIX_RevStatus_NoInfo;
                        if (pkixErrorResult) {
                                PKIX_PL_Object_DecRef((PKIX_PL_Object*)pkixErrorResult,
                                                      plContext);
                                pkixErrorResult = NULL;
                        }
                }
        } while (retry);

cleanup:
        if (revStatus == PKIX_RevStatus_NoInfo && (uriFound || 
	    methodFlags & PKIX_REV_M_REQUIRE_INFO_ON_MISSING_SOURCE) &&
            methodFlags & PKIX_REV_M_FAIL_ON_MISSING_FRESH_INFO) {
            revStatus = PKIX_RevStatus_Revoked;
        }
        *pRevStatus = revStatus;

        /* ocsp carries only three statuses: good, bad, and unknown.
         * revStatus is used to pass them. reasonCode is always set
         * to be unknown. */
        *pReasonCode = crlEntryReasonUnspecified;

        PKIX_DECREF(cid);
        PKIX_DECREF(request);
        PKIX_DECREF(response);

        PKIX_RETURN(OCSPCHECKER);
}