/**@@@+++@@@@******************************************************************
**
** Microsoft Windows Media
** Copyright (C) Microsoft Corporation. All rights reserved.
**
***@@@---@@@@******************************************************************
*/

#include <drmcommon.h>
#include <drmutilities.h>
#include <drmcrt.h>
#include <drmcontextsizes.h>
#include <drmlicenseparser.h>
#if DRM_SUPPORT_APP_REVOCATION || DRM_SUPPORT_DEVICE_REVOCATION || DRM_SUPPORT_REVOCATION
#include <drmrevocation.h>
#endif
#include <drmchain.h>
#include <oemimpl.h>
#include <drmsecureclock.h>
#include <drmxmlparser.h>
#include <drmlicreason.h>

/******************************************************************************
** 
** Function :   _ParseLicenseAttributes
** 
** Synopsis :   Parse the license for frequently used attributes
** 
** Arguments :  f_pdstrLicense  - License string
**              f_rgpdstrEvents - Array of events to parse
**              f_cEvents       - Count of events to parse
**              f_rgdstrCachedAttribs - Cached attribs
**              f_rgCachedEvents- Cached events
** 
** Returns :    
** 
** Notes :      We can parse the license more optimally if needed. 
** 
******************************************************************************/
static DRM_RESULT _ParseLicenseAttributes(
    IN          DRM_CONST_STRING    *f_pdstrLicense,
    IN  const   DRM_CONST_STRING    *f_rgpdstrEvents[],
    IN          DRM_DWORD            f_cEvents,
        OUT     DRM_CONST_STRING     f_rgdstrCachedAttribs[eDRM_LIC_CACHED_ATTRIB_LAST],
        OUT     DRM_CACHED_EVENT     f_rgCachedEvents[DRM_MAX_ACTIONS] )
{
    DRM_RESULT          dr          = DRM_SUCCESS;
    DRM_CONST_STRING    dstrLIData  = EMPTY_DRM_STRING;
    DRM_DWORD           iEvents     = 0;
    
    /*
    **  If need be we can try to do better here by doing a single scan of the XML
    */
    ChkDR( DRM_LIC_GetAttribute( f_pdstrLicense, 
                                 NULL, 
                                 DRM_LICENSE_ATTRIB_KID, 
                                 &dstrLIData,
                                 &f_rgdstrCachedAttribs[eDRM_LIC_CACHED_ATTRIB_KID], 
                                 0 ) );
    
    ChkDR( DRM_LIC_GetAttribute( f_pdstrLicense, 
                                 NULL, 
                                 DRM_LICENSE_ATTRIB_LID, 
                                 &dstrLIData,
                                 &f_rgdstrCachedAttribs[eDRM_LIC_CACHED_ATTRIB_LID], 
                                 0 ) );
    
    dr = DRM_LIC_GetAttribute( f_pdstrLicense, 
                               NULL, 
                               DRM_LICENSE_ATTRIB_CONTENTPUBKEY, 
                               &dstrLIData,
                               &f_rgdstrCachedAttribs[eDRM_LIC_CACHED_ATTRIB_CONTENTPUBKEY], 
                               0 );
    if ( DRM_FAILED(dr) )
    {
        INIT_DRM_STRING(f_rgdstrCachedAttribs[eDRM_LIC_CACHED_ATTRIB_CONTENTPUBKEY]);
        dr = DRM_SUCCESS;
    }
    
    dr = DRM_LIC_GetAttribute( f_pdstrLicense, 
                               NULL, 
                               DRM_LICENSE_ATTRIB_METERING_ID, 
                               &dstrLIData,
                               &f_rgdstrCachedAttribs[eDRM_LIC_CACHED_ATTRIB_METERING_ID], 
                               0 );
    if ( DRM_FAILED(dr) )
    {
        INIT_DRM_STRING(f_rgdstrCachedAttribs[eDRM_LIC_CACHED_ATTRIB_METERING_ID]);
        dr = DRM_SUCCESS;
    }
    
    dr = DRM_LIC_GetAttribute( f_pdstrLicense, 
                               NULL, 
                               DRM_LICENSE_ATTRIB_CHAINEDKID, 
                               &dstrLIData,
                               &f_rgdstrCachedAttribs[eDRM_LIC_CACHED_ATTRIB_CHAINEDKID], 
                               0 );  
    if ( DRM_FAILED(dr) )
    {
        INIT_DRM_STRING(f_rgdstrCachedAttribs[eDRM_LIC_CACHED_ATTRIB_CHAINEDKID]);
        dr = DRM_SUCCESS;        
    }

    for ( iEvents = 0; (iEvents < f_cEvents) && (iEvents < DRM_MAX_ACTIONS); iEvents++)
    {
        ASSIGN_DRM_STRING( f_rgCachedEvents[iEvents].dstrType, *(f_rgpdstrEvents[iEvents]) );
        dr = DRM_LIC_GetEvent( f_pdstrLicense, 
                               &g_dstrLicEvalOnAction,
                               f_rgpdstrEvents[iEvents], 
                               &dstrLIData,
                               &f_rgCachedEvents[iEvents].dstrCondition,
                               &f_rgCachedEvents[iEvents].dstrAction,
                               &f_rgCachedEvents[iEvents].dstrRestriction);
        if ( DRM_FAILED(dr) )
        {
            INIT_DRM_STRING(f_rgCachedEvents[iEvents].dstrCondition);
            INIT_DRM_STRING(f_rgCachedEvents[iEvents].dstrAction);
            INIT_DRM_STRING(f_rgCachedEvents[iEvents].dstrRestriction);
            dr = DRM_SUCCESS;
        }
    }
    
    
ErrorExit:
    return dr;
}

/******************************************************************************
** 
** Function :   _LoadLicenseAttributes
** 
** Synopsis :   Load some of those attributes from a license which are
**              frequently used.
** 
** Arguments :  rgpdstrRights : Array of pointers to rights requested
**              f_pLicEval    : License eval context
** 
** Returns :    
** 
** Notes :      
** 
******************************************************************************/
static DRM_RESULT _LoadLicenseAttributes(
    IN  const   DRM_CONST_STRING    *f_rgpdstrEvents[], 
    IN          DRM_DWORD            f_cRights,
    IN  OUT     DRM_LICEVAL_CONTEXT *f_pLicEval )
{
    DRM_RESULT          dr          = DRM_SUCCESS;
    DRM_CONST_STRING    dstrLIData  = EMPTY_DRM_STRING;
    DRM_CONST_STRING    dstrTemp    = EMPTY_DRM_STRING;
    
    
    ChkDR( _ParseLicenseAttributes( &f_pLicEval->dstrContentLicense,
                                    f_rgpdstrEvents,
                                    f_cRights,
                                    f_pLicEval->rgdstrCachedAttribs, 
                                    f_pLicEval->rgCachedEvents ) ); 
    f_pLicEval->cCachedEvents = f_cRights;

    /*
    **  Cache the binary LID
    */
    ChkDR( DRM_UTL_StringToGuid( &f_pLicEval->rgdstrCachedAttribs[eDRM_LIC_CACHED_ATTRIB_LID], 
                                 (DRM_GUID*) &(f_pLicEval->LID) ) );

    /*
    **  Cache the binary KID
    */
    ChkDR( DRM_UTL_DecodeKID( &f_pLicEval->rgdstrCachedAttribs[eDRM_LIC_CACHED_ATTRIB_KID],
                              &f_pLicEval->KID ) );
    
    f_pLicEval->fUseCachedAttribs = TRUE;
    
ErrorExit:
    return dr;  
}



/******************************************************************************
** 
** Function :   _PerformActions
** 
** Synopsis :   The license is selectable -- Can I perform the actions on it?
**              Loop through all the request actions and ask if we can do them
**              If we can do them all the report them all             
** 
** Arguments :  
**              
** 
** Returns :    
** 
** Notes :      
** 
******************************************************************************/
static DRM_RESULT _PerformActions(
    IN OUT   DRM_FFLICENSE               *f_pFFLicense,
    IN OUT   DRM_STACK_ALLOCATOR_CONTEXT *f_pstack,
    IN const DRM_CONST_STRING            *f_rgpdstrRights[], /* Array of DRM_CONST_STRING pointers */
    IN       DRM_DWORD                    f_cRights,
    IN       DRM_HDS_CONTEXT             *f_pcontextHDS,
    IN       DRMPFNOUTPUTLEVELSCALLBACK   f_pfnOutputLevelsCallback)
{
    DRM_RESULT       dr     = DRM_SUCCESS;
    DRM_DWORD        iRight = f_cRights;
    DRM_CONST_STRING dstrLIData  = EMPTY_DRM_STRING;
    
    DRM_PROFILING_ENTER_SCOPE(L"_PerformActions", g_pwszEnteringFunction, DRM_PROFILING_DONT_START);
    
    /* internal function. no need to validate arguments 
    */

    for( ; iRight > 0; )
    {
        DRM_BOOL fExisted    = FALSE;
        DRM_BOOL fEvalResult = FALSE;        

        iRight--;
        ChkDR( DRM_LEVL_PerformOperations( f_pFFLicense->pLicEval,
                                           DRM_LICENSE_EVAL_ACTION,
                                           DRM_LICENSE_EVAL_CAN_DO_OPERATION,
                                           f_rgpdstrRights[iRight],
                                           &fEvalResult, 
                                           &fExisted,
                                           f_pcontextHDS) );
        if ( !fEvalResult )
        {
            dr = DRM_E_RIGHTSNOTAVAILABLE;
            goto ErrorExit;
        }

        if( fExisted )
        {
            /* Check if there are any output levels on this right */
            if ( f_pFFLicense->pLicEval->rgCachedEvents[iRight].dstrRestriction.cchString == 0 )
            {
                continue;
            }

            if ( f_pfnOutputLevelsCallback == NULL)  /* There were output restrictions and no callback function was supplied */
            {
                ChkDR(DRM_E_RIGHTSNOTAVAILABLE);
            }

            /* Reset the stack context */
            ZEROMEM( f_pstack, SIZEOF( DRM_STACK_ALLOCATOR_CONTEXT ) );                        

            if( DRM_UTL_DSTRStringsEqual( f_rgpdstrRights[iRight], &g_dstrWMDRM_RIGHT_PLAYBACK ) )
            {
                f_pstack->pbStack  =         f_pFFLicense->rgPlayOpl[f_pFFLicense->dwChainDepth].rgbPlayOPLBuffer;
                f_pstack->cbStack  = SIZEOF( f_pFFLicense->rgPlayOpl[f_pFFLicense->dwChainDepth].rgbPlayOPLBuffer );

                ChkDR( DRM_OPL_ProcessPlayOutputLevelData( &f_pFFLicense->pLicEval->rgCachedEvents[iRight].dstrRestriction,
                                                           &f_pFFLicense->rgPlayOpl[f_pFFLicense->dwChainDepth].oplPlay,
                                                            f_pstack ) );
                f_pFFLicense->rgfPlayOplValid[f_pFFLicense->dwChainDepth] = TRUE;
            }

            else if( DRM_UTL_DSTRStringsEqual( f_rgpdstrRights[iRight], &g_dstrWMDRM_RIGHT_COPY ) )
            {
#if DRM_SUPPORT_COPY_OPL
                f_pstack->pbStack  =         f_pFFLicense->rgCopyOpl[f_pFFLicense->dwChainDepth].rgbCopyOPLBuffer;
                f_pstack->cbStack  = SIZEOF( f_pFFLicense->rgCopyOpl[f_pFFLicense->dwChainDepth].rgbCopyOPLBuffer );

                ChkDR( DRM_OPL_ProcessCopyOutputLevelData( &f_pFFLicense->pLicEval->rgCachedEvents[iRight].dstrRestriction,
                                                           &f_pFFLicense->rgCopyOpl[f_pFFLicense->dwChainDepth].oplCopy,
                                                            f_pstack ) );
                f_pFFLicense->rgfCopyOplValid[f_pFFLicense->dwChainDepth] = TRUE;
#else
                ChkDR( DRM_E_RIGHTSNOTAVAILABLE );
#endif
            }

        }

    } /* end for rights */

ErrorExit:
    
    DRM_PROFILING_LEAVE_SCOPE(L"_PerformActions", g_pwszLeavingFunction);
    
    return dr;
} /* unconditional brace to create a scope */


#if DRM_SUPPORT_REVOCATION

/******************************************************************************
** 
** Function :   _ValidateRevocationVersions
** 
** Synopsis :   This will check the revocation list versions in the license versus the current 
**              list versions to ensure they are adequate.  The current version numbers should 
**              be pre-cached for this check
**              
** 
******************************************************************************/
static DRM_RESULT _ValidateRevocationVersions( 
    IN OUT DRM_FFLICENSE    *f_pFFLicense,
    IN     DRM_HDS_CONTEXT  *f_pcontextHDS)
{
    DRM_RESULT          dr                    = DRM_SUCCESS;
    DRM_DWORD           idCRLAppRequired      = 0;
    DRM_DWORD           idCRLDeviceRequired   = 0;
    DRM_DWORD           idCRLWMDRMNETRequired = 0;
    DRM_CONST_STRING    dstrValue             = EMPTY_DRM_STRING;
    DRM_LONG            lValue                = 0;

    ChkArg( f_pFFLicense != NULL 
         && f_pcontextHDS != NULL );


    /* Extract required versions from an XML license */
#if DRM_SUPPORT_APP_REVOCATION
    idCRLAppRequired = f_pFFLicense->idCRLsCurrent.app;
    f_pFFLicense->pLicEval->idCRLsCurrent.app = f_pFFLicense->idCRLsCurrent.app;

    ChkDR(DRM_RVK_PerformAppRevocationCheck(f_pFFLicense->pLicEval, f_pcontextHDS));     
#endif

#if DRM_SUPPORT_DEVICE_REVOCATION
    dr = DRM_LIC_GetAttribute( &f_pFFLicense->pLicEval->dstrContentLicense,
                               &g_dstrDeviceRevocationLicenseTag, 
                                DRM_LICENSE_ATTRIB_REVOCATION,
                                NULL,
                               &dstrValue,
                                0 );
    if( DRM_SUCCEEDED( dr ) )
    {
        ChkDR( wcsntol( dstrValue.pwszString, dstrValue.cchString, &lValue ) );
        idCRLDeviceRequired = (DRM_DWORD)lValue;
    }
#endif

#if DRM_SUPPORT_WMDRMNET
    dr = DRM_LIC_GetAttribute( &f_pFFLicense->pLicEval->dstrContentLicense,
                               &g_dstrWMDRMNETRevocationLicenseTag, 
                                DRM_LICENSE_ATTRIB_REVOCATION,
                                NULL,
                               &dstrValue,
                                0 );
    if( DRM_SUCCEEDED( dr ) )
    {
        ChkDR( wcsntol( dstrValue.pwszString, dstrValue.cchString, &lValue ) );
        idCRLWMDRMNETRequired = (DRM_DWORD)lValue;
    }
#endif


    /* Compare the required values to the cached values */
    if( idCRLAppRequired      > f_pFFLicense->idCRLsCurrent.app
     || idCRLDeviceRequired   > f_pFFLicense->idCRLsCurrent.device
     || idCRLWMDRMNETRequired > f_pFFLicense->idCRLsCurrent.wmdrmnet )
    {        
        /* Although we're failing here, the caller should update
        *  the revocation version cache and try again. This is in
        *  case there have been any updates to the revocation lists.
        */
        ChkDR( DRM_E_LICEVAL_REQUIRED_REVOCATION_LIST_NOT_AVAILABLE );
    }


    /*
    **Compare license RIV with HDS RIV if higher return DRM_E_RIV_TOO_SMALL error
    */
    dr = DRM_LIC_GetAttribute(  &f_pFFLicense->pLicEval->dstrContentLicense, 
                                NULL, 
                                DRM_LICENSE_ATTRIB_REVINFO, 
                                NULL, 
                                &dstrValue, 
                                0 );
    if( DRM_SUCCEEDED( dr ) )
    {
        ChkDR( wcsntol( dstrValue.pwszString, dstrValue.cchString, (DRM_LONG *) &lValue) );
        if( (DRM_DWORD)lValue > f_pFFLicense->dwLicRevInfoVer )
        {
            f_pFFLicense->dwLicRevInfoVer = lValue;
        }
        if( (DRM_DWORD)lValue > f_pFFLicense->idCRLsCurrent.riv )
        {
            /* Although we're failing here, the caller should update
            *  the revocation version cache and try again. This is in
            *  case there have been any updates to the revocation lists.
            */
            ChkDR( DRM_E_RIV_TOO_SMALL );
        }
    }

    dr = DRM_SUCCESS;

ErrorExit:
    
    return dr;
}

#endif /*DRM_SUPPORT_REVOCATION*/


/******************************************************************************
** 
** Function :   _GetLicenseInfoAndSetup
** 
** Synopsis :   Load the license in memory and setup
**              
** 
** Arguments :  
**              
** 
** Returns :    
** 
** Notes :      
** 
******************************************************************************/
static DRM_RESULT _GetLicenseInfoAndSetup(
    IN OUT   DRM_FFLICENSE    *f_pFFLicense,
    IN const DRM_CONST_STRING *f_rgpdstrRights[], /* Array of DRM_CONST_STRING pointers */
    IN       DRM_DWORD         f_cRights,
    IN       DRM_HDS_CONTEXT  *f_pcontextHDS)
{
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_DWORD  cbLicSize  = 0;
    DRM_BYTE  *pbChecksum = NULL;

    /* internal function. no need to validate arguments 
    */
    
    cbLicSize = f_pFFLicense->cbBuffer;
    ChkDR( DRM_LST_GetLicense( f_pFFLicense->pLicStore, 
                              &f_pFFLicense->rgkid      [f_pFFLicense->dwChainDepth], 
                              &f_pFFLicense->rglid      [f_pFFLicense->dwChainDepth],
                              &(f_pFFLicense->rgslotHint[f_pFFLicense->dwChainDepth]),
                               f_pFFLicense->pbBuffer,
                              &cbLicSize) );

    DSTR_FROM_PB( &(f_pFFLicense->pLicEval->dstrContentLicense), f_pFFLicense->pbBuffer, cbLicSize );
    f_pFFLicense->pLicEval->fUseCachedAttribs = FALSE;
    
    /*
    **  Load the necessary License Attribs
    */
    ChkDR( _LoadLicenseAttributes( f_rgpdstrRights, f_cRights, f_pFFLicense->pLicEval ) );
                 
    /* see if we have a global policy forbidding metering */
    
    if ((f_pFFLicense->dwPolicyFlags & DRM_POLICY_FLAG_METERING_DISABLED_ENTERPRISE) != 0)
    {
        if ( f_pFFLicense->pLicEval->rgdstrCachedAttribs[eDRM_LIC_CACHED_ATTRIB_METERING_ID].cchString != 0 )
        {
            /* we have a MID */
            ChkDR(DRM_E_POLICY_METERING_DISABLED);
        }
    }
#if DRM_SUPPORT_METERING
    else
    {
        if ( f_pFFLicense->pLicEval->rgdstrCachedAttribs[eDRM_LIC_CACHED_ATTRIB_METERING_ID].cchString != 0 )
        {
            DRM_DWORD cbMID = SIZEOF(DRM_MID);
            
            f_pFFLicense->rgfHasMetering [f_pFFLicense->dwChainDepth] = TRUE;
   
            ChkDR(DRM_B64_DecodeW( &f_pFFLicense->pLicEval->rgdstrCachedAttribs[eDRM_LIC_CACHED_ATTRIB_METERING_ID], 
                                   &cbMID, 
                                   (DRM_BYTE *) &f_pFFLicense->rgmid[f_pFFLicense->dwChainDepth], 
                                   0 ) );
        }                               
    }
    
#endif  

                                       
    cbLicSize = SIZEOF( f_pFFLicense->pBindingInfo[f_pFFLicense->dwChainDepth].m_rgbContentKey );
    if( f_pFFLicense->dwChainDepth == DRM_MAX_LICENSE_CHAIN_DEPTH-1 )
    {
        /* If we are on the last level that a chain could be pass in NULL */
        pbChecksum = NULL;
    }
    else
    {
        /* Otherwise get the checksum for the next license up the chain */
        pbChecksum = f_pFFLicense->pBindingInfo[f_pFFLicense->dwChainDepth+1].m_rgbChecksum;
    }

    ChkDR( DRM_LIC_GetEnablingBits( &(f_pFFLicense->pLicEval->dstrContentLicense),
                                     0, /* Get the first enabling bit */
                                    &f_pFFLicense->pBindingInfo[f_pFFLicense->dwChainDepth].m_dwAlgorithm,
                                    &f_pFFLicense->pBindingInfo[f_pFFLicense->dwChainDepth].m_oPublKey,
                                     f_pFFLicense->pBindingInfo[f_pFFLicense->dwChainDepth].m_rgbContentKey, 
                                    &cbLicSize, 
                                    &f_pFFLicense->pBindingInfo[f_pFFLicense->dwChainDepth].m_oLsPublKey,
                                     f_pFFLicense->pBindingInfo[f_pFFLicense->dwChainDepth].m_rgbSignature,
                                     pbChecksum,
                                    &f_pFFLicense->pLicEval->pcontextBBX->CryptoContext ) );

    if( f_pFFLicense->fSecStoreOpen[f_pFFLicense->dwChainDepth] )
    {
        f_pFFLicense->fSecStoreOpen[f_pFFLicense->dwChainDepth] = FALSE;
        ZEROMEM(&(f_pFFLicense->pSecStoreLicense[f_pFFLicense->dwChainDepth]), 
                SIZEOF( &f_pFFLicense->pSecStoreLicense[f_pFFLicense->dwChainDepth] ) );
    }

    f_pFFLicense->pLicEval->fSecureTimeDataValid = FALSE;      /* Must be done */
    f_pFFLicense->pLicEval->fInGracePeriod       = FALSE;      /* Must be done */
    f_pFFLicense->pLicEval->u64GPStartTime       = DRM_UI64( 0 );  /* Must be done */

#if DRM_SUPPORT_SECURE_CLOCK
    /* Never fail here for secure clock stuff */

    dr = DRM_SST_CreateGlobalStorePassword( f_pFFLicense->rgbLicensePassword, 
                                (DRM_BYTE *)f_pFFLicense->pLicEval->pcontextBBX ); 
    if( DRM_SUCCESS == dr )
    {
        DRM_DWORD dwSecureData = 0;
        DRM_SECSTORE_CLOCK_DATA oClkData;

        ZEROMEM(&oClkData, SIZEOF(oClkData));
        dwSecureData = SIZEOF(oClkData);

        dr = DRM_SST_GetData( &(f_pFFLicense->pSecStoreLicense[f_pFFLicense->dwChainDepth]), 
                              &g_idSData, 
                              &g_idSDataPrev, 
                               f_pFFLicense->rgbLicensePassword, 
                               SECURE_STORE_GLOBAL_DATA,
                               f_pcontextHDS,
                              (DRM_BYTE *) &oClkData, 
                              &dwSecureData );

        if( DRM_SUCCESS == dr )
        {
            _PrepareSecureClockDataForReading( &oClkData );
            /* Now Test the Values */
            if( ( oClkData.flag&CLK_LK_GP_VALID ) && ( oClkData.flag&CLK_IN_GP_VALID ) )
            {
                f_pFFLicense->pLicEval->fSecureTimeDataValid = TRUE;
                if( oClkData.fInGracePeriod )
                {
                    f_pFFLicense->pLicEval->fInGracePeriod = TRUE;
                }
                f_pFFLicense->pLicEval->u64GPStartTime = oClkData.LastKnownGracePeriodStartTime;
            }
        }
    }
    dr = DRM_SUCCESS;
#endif
        
ErrorExit:
    
    return dr;
}



/******************************************************************************
** 
** Function :   _ProcessEndOfChain
** 
** Synopsis :   Process end of the liense chain and return
**              
** 
** Arguments :  
**              
** 
** Returns :    
** 
** Notes :      
** 
******************************************************************************/
static DRM_RESULT _ProcessEndOfChain(
    IN OUT   DRM_FFLICENSE               *f_pFFLicense,
    IN       DRMPFNOUTPUTLEVELSCALLBACK   f_pfnOutputLevelsCallback,
    IN const DRM_VOID                    *f_pv)
{
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_PROFILING_ENTER_SCOPE(L"_ProcessEndOfChain", g_pwszEnteringFunction, DRM_PROFILING_DONT_START);

    ChkArg( f_pFFLicense != NULL );

    /* internal function. no need to validate arguments 
    */

    if ( f_pFFLicense->pV1Header )
    {
        /* use legacy (v1) header */
        if( SIZEOF( DRM_CIPHER_CONTEXT ) > f_pFFLicense->cbBuffer )
        {
            ChkDR( DRM_E_BUFFERTOOSMALL );
        }
        if( DRM_SUCCEEDED( DRM_BBX_LegacyCipherKeySetup( f_pFFLicense->pBindingInfo,
                                                         f_pFFLicense->pV1Header->pbKeyID,
                                                         f_pFFLicense->pV1Header->cbKeyID,
                                                         f_pFFLicense->pV1Header->pbSecretData,
                                                         f_pFFLicense->pV1Header->cbSecretData, 
                                                         (DRM_CIPHER_CONTEXT*)f_pFFLicense->pbBuffer,
                                                         f_pFFLicense->pLicEval->pcontextBBX ) ) )
        {
            dr = DRM_SUCCESS;
        }
        else
        {
            dr = DRM_E_LIC_KEY_DECODE_FAILURE;
        }            
    }
    else
    {
        if( DRM_BBX_CanBind( f_pFFLicense->pBindingInfo,
                             f_pFFLicense->dwChainDepth + 1,
                             f_pFFLicense->pLicEval->pcontextBBX ) )
        {
            dr = DRM_SUCCESS;
        }
        else
        {
            dr = DRM_E_LIC_KEY_DECODE_FAILURE;
        }
    }

    if( DRM_SUCCEEDED( dr ) )
    {
        if( f_pfnOutputLevelsCallback != NULL )
        {
            DRM_DWORD i = 0;

            /*
            **  Perform an inclusion list callback
            */
            f_pFFLicense->inclusionListCallback.dwChainDepth = f_pFFLicense->dwChainDepth;
            ChkDR( f_pfnOutputLevelsCallback( &f_pFFLicense->inclusionListCallback, DRM_INCLUSION_LIST_CALLBACK, f_pv ) );

            for( i = f_pFFLicense->dwChainDepth + 1; i > 0; )
            {
                /* Find and return the root most OPL for the license chain. */
                i--;
                if( f_pFFLicense->rgfPlayOplValid[i] )
                {
                    ChkDR( f_pfnOutputLevelsCallback( &f_pFFLicense->rgPlayOpl[i].oplPlay, DRM_PLAY_OPL_CALLBACK, f_pv ) );
                    break;
                }
            }
            
#if DRM_SUPPORT_COPY_OPL
            for( i = f_pFFLicense->dwChainDepth + 1; i > 0; )
            {
                i--;
                /* Find and return the root most OPL for the license chain. */                
                if( f_pFFLicense->rgfCopyOplValid[i] )
                {
                    ChkDR( f_pfnOutputLevelsCallback( &f_pFFLicense->rgCopyOpl[i].oplCopy, DRM_COPY_OPL_CALLBACK, f_pv ) );
                    break;
                }
            }
#endif
        }
    }

ErrorExit:

    DRM_PROFILING_LEAVE_SCOPE(L"_ProcessEndOfChain", g_pwszLeavingFunction);
    return dr;
}


/*****************************************************************************
** Function: _GetSourceIDFromLicense
**
** Synopsis: Checks of the <SOURCEID> tag in the XML license and updates the
**           the dwSourceid in DRM_FFLICENSE if found.
**           Source ID should be specified only in leaf license. We will ignore 
**           the sourceID if specified in the root license.
** Arguments:
**           IN f_pFFLicense   -- DRM_FFLICENSE the license
** Returns:
**           DRM_SUCCESS           -- On success
*****************************************************************************/
DRM_RESULT _GetSourceIDFromLicense ( 
    IN DRM_FFLICENSE *f_pFFLicense )
{
    DRM_RESULT       dr        = DRM_SUCCESS;
    DRM_CONST_STRING dstrValue = EMPTY_DRM_STRING;
    
    
    ChkArg( f_pFFLicense != NULL 
         && f_pFFLicense->pLicEval != NULL 
         && f_pFFLicense->pLicEval->dstrContentLicense.pwszString != NULL );

    f_pFFLicense->dwSourceid = 0;
    
    dr = DRM_LIC_GetAttribute(  &f_pFFLicense->pLicEval->dstrContentLicense, 
                                NULL, 
                                DRM_LICENSE_ATTRIB_SOURCEID, 
                                NULL, 
                                &dstrValue, 
                                0 );
    if( DRM_SUCCEEDED( dr ) )
    {
        ChkDR( wcsntol( dstrValue.pwszString, 
                        dstrValue.cchString, 
             (DRM_LONG*)&f_pFFLicense->dwSourceid ) );        
    }

    dr = DRM_SUCCESS;
ErrorExit:

    return dr;
}


DRM_RESULT DRM_API DRM_LIC_CompleteLicenseChain(
    IN OUT   DRM_FFLICENSE               *pFFLicense,
    IN const DRM_CONST_STRING            *rgpdstrRights[], /* Array of DRM_CONST_STRING pointers */
    IN       DRM_DWORD                    cRights,
       OUT   DRM_DWORD                   *pdwChainDepth,
    IN       DRM_HDS_CONTEXT             *pcontextHDS,
    IN       DRMPFNOUTPUTLEVELSCALLBACK   pfnOutputLevelsCallback,
    IN const DRM_VOID                    *pv )
{
    DRM_RESULT       dr             = DRM_SUCCESS;
    DRM_CONST_STRING dstrMID        = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrLID        = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrLIData     = EMPTY_DRM_STRING;
    DRM_BYTE        *pbChecksum     = NULL;
    DRM_BOOL         fEvalResult    = FALSE;
    DRM_STACK_ALLOCATOR_CONTEXT stack = { 0, 0, NULL };

    DRM_PROFILING_ENTER_SCOPE(L"DRM_LIC_CompleteLicenseChain", g_pwszEnteringFunction, DRM_PROFILING_DONT_START);
    
    DRMCASSERT( DRM_MAX_LICENSE_CHAIN_DEPTH > 0 );

    ChkArg( pFFLicense                != NULL
         && pFFLicense->pLicStore     != NULL
         && pFFLicense->pbBuffer      != NULL
         && pFFLicense->cbBuffer       > 0 
         && pFFLicense->pBindingInfo  != NULL
         && pFFLicense->pLicStoreEnum != NULL
         && pdwChainDepth             != NULL
         && pcontextHDS               != NULL 
         && cRights                   <= DRM_MAX_ACTIONS
         && pFFLicense->pLicEval      != NULL
         && pFFLicense->pLicEval->pcontextBBX    != NULL);
    pFFLicense->pLicEval->fDeleteLicense = FALSE;

    *pdwChainDepth = 0;
    if( pFFLicense->dwChainDepth >= DRM_MAX_LICENSE_CHAIN_DEPTH )
    {
        ChkDR ( DRM_E_LICENSENOTFOUND );
    }
    else if( pFFLicense->dwChainDepth == 0 )
    {
        DRM_DWORD i;
        for( i = 0; i < DRM_MAX_LICENSE_CHAIN_DEPTH; i++ )
        {
#if DRM_SUPPORT_COPY_OPL
            pFFLicense->rgfCopyOplValid[i] = FALSE;
#endif
            pFFLicense->rgfPlayOplValid[i] = FALSE;
        }
#if DRM_SUPPORT_WMDRMNET
        pFFLicense->dwMinimumRequiredAppSec = 0;
#endif
    }

#if DRM_SUPPORT_APP_REVOCATION
    pFFLicense->fUpdatedRevocationList = FALSE;
#endif

#if DRM_SUPPORT_METERING
    pFFLicense->rgfHasMetering [pFFLicense->dwChainDepth] = FALSE;
#endif

    ChkDR(_GetLicenseInfoAndSetup(pFFLicense, rgpdstrRights, cRights, pcontextHDS));

#if DRM_SUPPORT_REVOCATION
    /*
    *  Compare license RIV and CRL versions, 
    *  then return DRM_E_RIV_TOO_SMALL 
    *  or DRM_E_LICEVAL_REQUIRED_REVOCATION_LIST_NOT_AVAILABLE error
    *
    */
    ChkDR( _ValidateRevocationVersions( pFFLicense, pcontextHDS ) );
#endif /*DRM_SUPPORT_REVOCATION*/

    /* 
    ** Check if the license has source id >0 and update the dwSourceid in
    ** DRM_FFLICENSE. The source ID should be specified only in leaf license.
    */
    if( pFFLicense->dwChainDepth == 0 )
    {
        ChkDR( _GetSourceIDFromLicense( pFFLicense ) );  
    }

    /* Talk to blackbox to get the secure store password for this license */
    ChkDR( DRM_BBX_HashValue( pFFLicense->pLicEval->LID.rgb, 
                              SIZEOF( pFFLicense->pLicEval->LID.rgb ),
                              pFFLicense->rgbLicensePassword, 
                              pFFLicense->pLicEval->pcontextBBX ) );

    ChkDR( DRM_SST_OpenKeyTokens( &(pFFLicense->pSecStoreLicense[pFFLicense->dwChainDepth]),
                                  &pFFLicense->pLicEval->LID,
                                  NULL,
                                  pFFLicense->rgbLicensePassword, 
                                  0, 
                                  SECURE_STORE_LICENSE_DATA, 
                                  pcontextHDS ) );

    pFFLicense->fSecStoreOpen[pFFLicense->dwChainDepth] = TRUE;

    pFFLicense->pLicEval->pcontextSSTLicense       = &(pFFLicense->pSecStoreLicense[pFFLicense->dwChainDepth]);
    pFFLicense->pLicEval->fGlobalSecStoreWritable  = FALSE;

    pFFLicense->pLicEval->dwFlags              = LICEVAL_OPEN_CHECK_SETTINGS;
    pFFLicense->pLicEval->fReserved            = 0;
    pFFLicense->pLicEval->pLicStoreEnumContext = &(pFFLicense->pLicStoreEnum[pFFLicense->dwChainDepth]);
    pFFLicense->pLicEval->dwChainDepth         = pFFLicense->dwChainDepth;
    pFFLicense->pLicEval->pcontextHDS          = pcontextHDS;
    pFFLicense->pLicEval->eTimeBasedState      = LICEVAL_NOREFERENECE;
    
    ChkDR( DRM_LEVL_PerformOperations( pFFLicense->pLicEval,
                                       DRM_LICENSE_EVAL_SELECT, 
                                       DRM_LICENSE_EVAL_CAN_DO_OPERATION, 
                                       NULL,
                                       &fEvalResult,
                                       NULL,
                                       pcontextHDS) );
    if( !fEvalResult )
    {
        /*
        **  We need to make sure that the reason for failure is set correctly.
        **  This is a workaround to compensate for the fact that v9
        **  licenses do not set the reason correctly sometimes.
        */
        if ( pFFLicense->pLicEval->lReasonForFail == 0 )
        {
            /* No reason given by license. Try parsing to get the reason, if we can. */
            (void) DRM_LEVL_GetLicenseReasonForUnusable( pFFLicense->pLicEval,
                                                        &pFFLicense->pLicEval->lReasonForFail );
        }
        dr = DRM_E_RIGHTSNOTAVAILABLE;
        /*
        ** If the reason for license not usable is because license expired
        ** OR if the license is "deleting" itself, then delete the license
        */
        if ( pFFLicense->pLicEval->lReasonForFail == LR_LICENSE_EXPIRED
          || pFFLicense->pLicEval->fDeleteLicense )
        {
            /* License has expired, mark it for deletion on Commit */
            pFFLicense->rgfDeleteLicense[pFFLicense->dwChainDepth] = TRUE;

            if( pFFLicense->pLicEval->lReasonForFail == LR_LICENSE_EXPIRED )
            {
                dr = DRM_E_LICENSEEXPIRED;
            }
        }
        goto ErrorExit;
    }

#if DRM_SUPPORT_SECURE_CLOCK
    if (pFFLicense->pLicEval->eTimeBasedState == LICEVAL_MACHINEDATETIMEREFERENCED 
     && pFFLicense->pLicEval->fInGracePeriod )
    {
        pFFLicense->pLicEval->lReasonForFail = LR_LICENSE_CLOCK_NOT_SET;
        dr = DRM_E_RIGHTSNOTAVAILABLE;
        goto ErrorExit;
    }

#endif

    DRM_LIC_GetInclusionList( &pFFLicense->pLicEval->dstrContentLicense,
                               pFFLicense->inclusionListCallback.rgInclusionList,
                               pFFLicense->inclusionListCallback.rgfInclusionListValid,
                               pFFLicense->dwChainDepth );

    ChkDR( _PerformActions( pFFLicense,
                           &stack,
                            rgpdstrRights,
                            cRights,
                            pcontextHDS,
                            pfnOutputLevelsCallback ) );

#if DRM_SUPPORT_WMDRMNET
    {
        DRM_DWORD dwMinimumRequiredAppSec = 0;
        
        ChkDR( DRM_ASD_ParseV2License(&pFFLicense->pLicEval->dstrContentLicense, 
                                      &g_dstrDRM_LS_APPSEC_ATTR, 
                                      &dwMinimumRequiredAppSec,
                                       NULL));
        
        if( dwMinimumRequiredAppSec > pFFLicense->dwMinimumRequiredAppSec )
        {
            pFFLicense->dwMinimumRequiredAppSec = dwMinimumRequiredAppSec;
        }
    }
#endif

    /* Get the KID for the chained license */
    if( (pFFLicense->pLicEval->rgdstrCachedAttribs[eDRM_LIC_CACHED_ATTRIB_CHAINEDKID]).cchString != 0 )
    {
        DRM_KID oChainKID;

        if( pFFLicense->pV1Header )
        {
            /* We don't support chained licenses from V1 content */
            ChkDR( DRM_E_V1_LICENSE_CHAIN_NOT_SUPPORTED );
        }

        if( pFFLicense->dwChainDepth + 1 >= DRM_MAX_LICENSE_CHAIN_DEPTH )
        {
            /* We're at the maximum chain depth that we support */
            ChkDR ( DRM_E_LICENSENOTFOUND );
        }

        ChkDR( DRM_UTL_DecodeKID( &(pFFLicense->pLicEval->rgdstrCachedAttribs[eDRM_LIC_CACHED_ATTRIB_CHAINEDKID]),
                                  &oChainKID) );

        /* Open an enumerator for the KID, increment the chain depth and recurse */                    
        /* Don't recurse if the dependent kid is a kid already in the chain   */
        ChkDR( DRM_LST_InitEnum( pFFLicense->pLicStore, 
                                &oChainKID, 
                                 TRUE, 
                                &(pFFLicense->pLicStoreEnum[pFFLicense->dwChainDepth+1]) ) );
        while( TRUE )
        {
            dr = DRM_LST_EnumNext(&(pFFLicense->pLicStoreEnum [pFFLicense->dwChainDepth + 1]), 
                                  &pFFLicense->rgkid          [pFFLicense->dwChainDepth + 1], 
                                  &pFFLicense->rglid          [pFFLicense->dwChainDepth + 1], 
                                  &(pFFLicense->rgslotHint    [pFFLicense->dwChainDepth + 1]),
                                  NULL);
            if( dr == DRM_E_NOMORE )
            {
                dr = DRM_E_LICENSENOTFOUND;
                goto ErrorExit;
            }
            ChkDR( dr );
            pFFLicense->dwChainDepth++;
            dr = DRM_LIC_CompleteLicenseChain( pFFLicense, 
                                               rgpdstrRights, 
                                               cRights, 
                                               pdwChainDepth, 
                                               pcontextHDS, 
                                               pfnOutputLevelsCallback, 
                                               pv );
            pFFLicense->dwChainDepth--;
            if( DRM_SUCCEEDED( dr ) )
            {
                goto ErrorExit;
            }
        }
    }
    else
    {
        /* 
        Yeah!  We can use this license.  Now try to set up a decryptor. 
        Only setup a decryptor is this is either a simple license ( no chain ),
        or the last link in the chain
        */

        if (!pFFLicense->fCanBind )
        {
            ChkDR(_ProcessEndOfChain(pFFLicense, pfnOutputLevelsCallback, pv));
        }
    }

    *pdwChainDepth = pFFLicense->dwChainDepth + 1;

ErrorExit:
    DRM_PROFILING_LEAVE_SCOPE(L"DRM_LIC_CompleteLicenseChain", g_pwszLeavingFunction);
    return dr;
}



DRM_RESULT DRM_API DRM_LIC_ReportActions(
    IN OUT   DRM_FFLICENSE         *pFFLicense,
    IN const DRM_CONST_STRING      *rgpdstrRights[], /* Array of DRM_CONST_STRING pointers */
    IN       DRM_DWORD              cRights,
    IN       DRM_DWORD              dwChainDepth,
    IN       DRM_HDS_CONTEXT       *pcontextHDS)
{
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_DWORD cbLicSize = 0;
    DRM_DWORD iCount = 0;
    DRM_DWORD iRights = 0;

    DRM_PROFILING_ENTER_SCOPE(L"DRM_LIC_ReportActions", g_pwszEnteringFunction, DRM_PROFILING_DONT_START);

    /* Report actions and update metering info */        
    for( ; iCount < dwChainDepth ; iCount++ )
    {
        pFFLicense->rgfDeleteLicense[iCount] = FALSE;
        /*
        ** Read the licenses from the license store again.  It will need 
        **  refreshing if it was a chained license 
        */
        {
            cbLicSize = pFFLicense->cbBuffer;
            ChkDR( DRM_LST_GetLicense( pFFLicense->pLicStore, 
                                      &pFFLicense->rgkid[iCount],
                                      &pFFLicense->rglid[iCount], 
                                      &(pFFLicense->rgslotHint[iCount]),
                                       pFFLicense->pbBuffer, &cbLicSize ) );

            DSTR_FROM_PB( &(pFFLicense->pLicEval->dstrContentLicense), 
                            pFFLicense->pbBuffer, 
                            cbLicSize );
            pFFLicense->pLicEval->fUseCachedAttribs = FALSE;
            
            /*
            **  Load the necessary License Attribs
            */
            ChkDR( _LoadLicenseAttributes( rgpdstrRights, cRights, pFFLicense->pLicEval ) );
            
        }

        pFFLicense->pLicEval->pcontextSSTLicense      = &(pFFLicense->pSecStoreLicense[iCount]);
        pFFLicense->pLicEval->fGlobalSecStoreWritable = FALSE;
        pFFLicense->pLicEval->fReserved               = 0;
        pFFLicense->pLicEval->pLicStoreEnumContext    = &(pFFLicense->pLicStoreEnum[iCount]);
        pFFLicense->pLicEval->dwChainDepth            = iCount;

        pFFLicense->pLicEval->fDeleteLicense = FALSE;
        iRights = cRights;
        for( ; iRights > 0; )
        {
            DRM_BOOL fEvalResult = FALSE,
                     fExisted    = FALSE;

            iRights--;
            ChkDR( DRM_LEVL_PerformOperations( pFFLicense->pLicEval, 
                                               DRM_LICENSE_EVAL_ACTION,
                                               DRM_LICENSE_EVAL_DONE_WITH_OPERATION, 
                                               rgpdstrRights[iRights],
                                              &fEvalResult, 
                                              &fExisted,
                                               pcontextHDS) );
            if( fExisted && !fEvalResult )
            {
                /* This Right is not allowed.  Move to the next license */
                ChkDR( DRM_E_RIGHTSNOTAVAILABLE );
            }                        
        }
        pFFLicense->rgfDeleteLicense[iCount] = pFFLicense->pLicEval->fDeleteLicense;
    }
ErrorExit:

    DRM_PROFILING_LEAVE_SCOPE(L"DRM_LIC_ReportActions", g_pwszLeavingFunction);
    return dr;
}


DRM_RESULT DRM_API DRM_LIC_GetInclusionList(
    IN const DRM_CONST_STRING  *pdstrLicense, 
    IN OUT   DRM_GUID           rgInclusionList[DRM_MAX_INCLUSION_GUIDS],
    IN OUT   DRM_BOOL           rgfGuidValid[DRM_MAX_INCLUSION_GUIDS][DRM_MAX_LICENSE_CHAIN_DEPTH],
    IN       DRM_DWORD          dwChainDepth )
{
    DRM_RESULT       dr       = DRM_SUCCESS;
    DRM_CONST_STRING dstrList = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrTag  = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrGuid = EMPTY_DRM_STRING;
    DRM_DWORD        dwGuid    = 0;
    DRM_BOOL         fIsLeaf  = FALSE;
    
    /*
    ** Set all the valid flags at this chain depth to FALSE for all GUIDs
    */
    for( ; dwGuid < DRM_MAX_INCLUSION_GUIDS; dwGuid++ )
    {
        rgfGuidValid[dwGuid][dwChainDepth] = FALSE;
    }

    dwGuid = 0;

    ChkDR( DRM_XML_GetSubNodeByPath( pdstrLicense, &g_dstrTagDataInclusionList, NULL, NULL, &dstrList, NULL, g_wchForwardSlash ) );
    
    while( DRM_SUCCEEDED( DRM_XML_GetSubNode( &dstrList, &g_dstrGUID, NULL, NULL, dwGuid, NULL, &dstrGuid, 1 ) ) )
    {
        DRM_GUID guid;

        if( dwChainDepth == 0 
         && dwGuid        >= DRM_MAX_INCLUSION_GUIDS )
        {
            /*
            **  If this is a leaf license we have to stop at the Maximum number of guids
            */
            ChkDR( DRM_E_TOO_MANY_INCLUSION_GUIDS );
        }

        /*
        **  Convert the value to a GUID
        */

        if( DRM_SUCCEEDED( DRM_UTL_StringToGuid(  &dstrGuid, &guid ) ) )
        {
            
            if( dwChainDepth == 0 )
            {
                /*
                **  For the leaf license we need to populate the output array of GUIDs
                **  and valid list as appropriate
                */
                MEMCPY( &rgInclusionList[dwGuid], &guid, SIZEOF( guid ) );
                rgfGuidValid[dwGuid][0] = TRUE;
            }
            else
            {
                DRM_DWORD i;

                /*
                **  For non-leaf licenses we should check to see if this guid is in the list and if so is it valid
                **  at the previous chain level.  If it is then it is still valid at the current level.
                */
                for( i = 0; i < DRM_MAX_INCLUSION_GUIDS; i++ )
                {
                    if( MEMCMP( &rgInclusionList[i], &guid, SIZEOF( guid ) ) == 0 
                     && rgfGuidValid[i][dwChainDepth - 1] )
                    {
                        rgfGuidValid[i][dwChainDepth] = TRUE;
                    }
                }
            }            
        }
        dwGuid++;
    }


ErrorExit:

    if( dr != DRM_E_TOO_MANY_INCLUSION_GUIDS )
    {
        /* Don't fail for any reason other than too many GUIDs */
        dr = DRM_SUCCESS;
    }

    return dr;
}


#if DRM_SUPPORT_ANTIROLLBACK_CLOCK

DRM_RESULT DRM_API DRM_LIC_CheckClockRollback(  
    IN DRM_LICEVAL_CONTEXT      *pLicEval,
    IN DRM_LICSTORE_CONTEXT     *pLicStore,
    IN DRM_LICSTOREENUM_CONTEXT *pLicEnum,
    IN DRM_BYTE                 *pbBuffer,
    IN DRM_DWORD                 cbBuffer,
    IN DRM_HDS_CONTEXT          *pcontextHDS)
{
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_BOOL fEvalResult = TRUE;
    DRM_BYTE rgbPassword [__CB_DECL(SHA_DIGEST_LEN)];
    const DRM_CONST_STRING *pdstrSetClockString = &g_dstrSetSavedDateTime;

    if( DRM_FAILED( DRM_LEVL_EvaluateExpression( pLicEval,
                                                &g_dstrCheckClockRollback,
                                                &fEvalResult ) ) 
     || fEvalResult )
    {
        DRM_KID oKID;
        DRM_LID oLID;

        /* The clock set string in this case will set the saved date time to the current time */
        /* If this code path isn't executed the expression logic will only set the clock to a time forward from
           what the current saved date time is */
        pdstrSetClockString = &g_dstrResetRollbackedClock;

        /* There was a failure or the clock was roll back.  In either case we report that as clock rollback */
        ChkDR( DRM_LST_InitEnum( pLicStore, NULL, FALSE, pLicEnum ) );    

        while( TRUE ) /* Loop will break when DRM_E_NOMORE is hit */
        {
            DRM_DWORD cbLicSize=0;
            DRM_CONST_STRING dstrLID = EMPTY_DRM_STRING;
            DRM_HDS_SLOT_HINT slotHint = {0};

            cbLicSize = cbBuffer;
            dr = DRM_LST_EnumNext( pLicEnum, &oKID, &oLID, &slotHint, &cbLicSize);
            if ( dr == DRM_E_NOMORE )
            {
                dr = DRM_SUCCESS;
                break;
            }
            ChkDR( dr );

            if ( cbLicSize > cbBuffer )  /* We could fail because the buffer isn't big enough. */
            {                
                ChkDR(DRM_E_OUTOFMEMORY);       
            }

            ChkDR(DRM_LST_GetLicense(pLicStore, &oKID, &oLID, &slotHint, pbBuffer, &cbLicSize ) );

            DSTR_FROM_PB( &(pLicEval->dstrContentLicense), pbBuffer, cbLicSize );
            pLicEval->fUseCachedAttribs = FALSE;
            
            /* Get the LID for the license */
            ChkDRContinue( DRM_LIC_GetAttribute( &pLicEval->dstrContentLicense, 
                                                  NULL, 
                                                  DRM_LICENSE_ATTRIB_LID, 
                                                  NULL,
                                                  &dstrLID,
                                                  0 ) );
            ChkDRContinue( DRM_UTL_StringToGuid( &dstrLID, (DRM_GUID*) &pLicEval->LID ) );

            ChkDRContinue( DRM_BBX_HashValue( pLicEval->LID.rgb, 
                                              SIZEOF( pLicEval->LID.rgb ),
                                              rgbPassword, 
                                              pLicEval->pcontextBBX ) );

            ChkDRContinue( DRM_SST_OpenKeyTokens( pLicEval->pcontextSSTLicense, 
                                                 &pLicEval->LID, 
                                                  NULL,
                                                  rgbPassword, 
                                                  0,
                                                  SECURE_STORE_LICENSE_DATA,
                                                  pcontextHDS ) );

            pLicEval->fGlobalSecStoreWritable = FALSE;
            pLicEval->dwFlags                 = LICEVAL_OPEN_CHECK_SETTINGS;
            pLicEval->fReserved               = 0;
            pLicEval->pLicStoreEnumContext    = pLicEnum;
            dr = DRM_LEVL_PerformOperations( pLicEval, 
                                             DRM_LICENSE_EVAL_REPORTCLOCK_ROLLBACK, 
                                             DRM_LICENSE_EVAL_DONE_WITH_OPERATION, 
                                             NULL,
                                             &fEvalResult, 
                                             NULL,
                                             pcontextHDS);

            if( pLicEval->fDeleteLicense )
            {
                (void) DRM_LST_EnumDelete( pLicEnum );
            }
            
            DRM_SST_CloseKey( pLicEval->pcontextSSTLicense, pcontextHDS );
        }
    }

    /* update the saveddatetime -- ignore errors here */
    pLicEval->fGlobalSecStoreWritable = TRUE;
    DRM_LEVL_EvaluateExpression( pLicEval, pdstrSetClockString, &fEvalResult );
ErrorExit:
    return dr;
}


#endif  /* DRM_SUPPORT_ANTIROLLBACK_CLOCK  */

