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

#include <drmcommon.h>
#include <drmutilities.h>
#include <drmcontextsizes.h>
#include <drmlicenseparser.h>
#include <drmxmlbuilder.h>
#include <drmxmlparser.h>
#include <drmblackbox.h>
#include <drmlicacq.h>
#include <drmexpreval.h>
#include <drmsha1.h>
#include <drmcrt.h>
#if DRM_SUPPORT_APP_REVOCATION || DRM_SUPPORT_DEVICE_REVOCATION || DRM_SUPPORT_REVOCATION
#include <drmmanager.h>
#include <drmrevocation.h>
#endif
#include <drmsyncstore.h>
#include <oemimpl.h>
#include <drmlicreason.h>

/*
**
*/
static DRM_RESULT DRM_API _EvaluateLicense(   
    IN     DRM_LICEVAL_CONTEXT *f_pcontextLEVL,
    IN     DRM_HDS_CONTEXT     *f_pcontextHDS,
    OUT    DRM_CONST_STRING    *f_pdstrLIData )
{
    DRM_BYTE         rgbLicensePassword [__CB_DECL(SHA_DIGEST_LEN)] = { 0x00 };
    DRM_CONST_STRING dstrLID   = EMPTY_DRM_STRING;
    DRM_BOOL         fOK       = FALSE;
    DRM_RESULT       dr        = DRM_SUCCESS;
    DRM_LONG         lReasonForFailure  = 0;
    DRM_BOOL         fUpdatedRevocationList = FALSE;
    
    ChkArg (f_pcontextLEVL != NULL
        &&  f_pcontextHDS  != NULL);

    ChkDRMString( &f_pcontextLEVL->dstrContentLicense );

    /*
    **  Cache the reason for failure in case we fail before any of the
    **  DRM_LEVL_PerformOperations
    */
    lReasonForFailure = f_pcontextLEVL->lReasonForFail;
    
    /*
    **  Clear the output
    */
    f_pdstrLIData->cchString = 0;
    f_pdstrLIData->pwszString = NULL;

#if DRM_SUPPORT_APP_REVOCATION
    fUpdatedRevocationList = f_pcontextLEVL->fUpdatedRevocationList;

    ChkDR(DRM_RVK_PerformAppRevocationCheck(f_pcontextLEVL,
                                            f_pcontextHDS));

    f_pcontextLEVL->fUpdatedRevocationList = (fUpdatedRevocationList || f_pcontextLEVL->fUpdatedRevocationList);                                           
#endif /* DRM_SUPPORT_APP_REVOCATION */

    /* construct lic password */
    ChkDR(DRM_SST_CreateLicenseStatePassword(&f_pcontextLEVL->LID, rgbLicensePassword, (DRM_BYTE *) f_pcontextLEVL->pcontextBBX));
    ChkDR(DRM_SST_OpenKeyTokens( f_pcontextLEVL->pcontextSSTLicense, 
                                &f_pcontextLEVL->LID, 
                                 NULL,
                                 rgbLicensePassword, 
                                 DRM_SECURE_STORE_CREATE_IF_NOT_EXISTS, 
                                 SECURE_STORE_LICENSE_DATA,
                                 f_pcontextHDS));

    /* call License Eval */
    f_pcontextLEVL->cPlaylistBurnIncrement = 0;
    f_pcontextLEVL->fReserved              = FALSE;
    f_pcontextLEVL->dwFlags                = LICEVAL_STORE_CHECK_SETTINGS;
    
    dr = DRM_LEVL_PerformOperations( f_pcontextLEVL, 
                                     DRM_LICENSE_EVAL_STORE, 
                                     DRM_LICENSE_EVAL_CAN_DO_OPERATION, 
                                     NULL,
                                    &fOK, 
                                     NULL, 
                                     f_pcontextHDS );
    /*
    **  Cache the reason for failure. We want to return this, and not the reason
    **  for failure we get from ONSELECT
    */
    lReasonForFailure = f_pcontextLEVL->lReasonForFail;
    ChkDR( dr );
    
    /* check to see if the license is selectable */
    if ( fOK )
    {
        /* save the content header */
        DRM_CONST_STRING dstrTemp = f_pcontextLEVL->dstrContentHeader;

        /* set content header NULL to avoid KID matching */
        f_pcontextLEVL->dstrContentHeader.pwszString = NULL;
        f_pcontextLEVL->dstrContentHeader.cchString = 0;
        dr = DRM_LEVL_PerformOperations( f_pcontextLEVL, 
                                         DRM_LICENSE_EVAL_SELECT, 
                                         DRM_LICENSE_EVAL_CAN_DO_OPERATION, 
                                         NULL, 
                                         &fOK, 
                                         NULL, 
                                         f_pcontextHDS);

        /* restore the content header */
        f_pcontextLEVL->dstrContentHeader = dstrTemp;
        ChkDR(dr);
    }

    if ( !fOK )
    {
        /* Note, WMRM does not set drmreason to LR_LICENSE_EXPIRED currently. So 
        ** even if a license expires, the reason code will still be zero
        */
        
        /* for the following cases, do not save the license to store. */
        if (   f_pcontextLEVL->lReasonForFail == LR_LICENSE_EXPIRED 
            || f_pcontextLEVL->lReasonForFail == LR_LICENSE_STORE_NOT_ALLOWED 
            || f_pcontextLEVL->lReasonForFail == LR_LICENSE_CERT_EXPIRED)
        {
            dr = DRM_E_FAILED_TO_STORE_LICENSE; /* License store not allowed  */
        }
    }

    /* ignore the error code here */
    (void)DRM_SST_CloseKey(f_pcontextLEVL->pcontextSSTLicense, f_pcontextHDS);

ErrorExit:
    if ( f_pcontextLEVL != NULL )
    {
        f_pcontextLEVL->lReasonForFail = lReasonForFailure;
    }
    
    return dr;
}

#if DRM_SUPPORT_SYMMETRIC_OPTIMIZATIONS

#define CCH_SYMSIG_TAG  ( 2  + g_dstrTagSymSig.cchString               /* <SYMSIGNATURE> */                   \
                             + CCH_BASE64_EQUIV( SHA_DIGEST_LEN )      /* B64-encoded symmetric signature */  \
                         + 3 + g_dstrTagSymSig.cchString)              /* </SYMSIGNATURE> */                  \

#define CCH_SYMVAL_TAG  ( 2  + g_dstrTagSymValue.cchString             /* <SYMVALUE> */                                               \
                             + CCH_BASE64_EQUIV( 20 )                  /* B64-encoded symmetrically encrypted content key.        */  \
                                                                       /* This value will be updated during processing            */  \
                                                                       /* as it could be different sizes depending on the license */  \
                         + 3 + g_dstrTagSymValue.cchString)            /* </SYMVALUE> */                                              \


static DRM_RESULT DRM_API _UpdateLicenseWithSymmetricSignature(
    IN OUT   DRM_STRING          *f_pdstrLicense,
    IN const DRM_CONST_STRING    *f_pdstrData,
    IN       DRM_LICEVAL_CONTEXT *f_pcontextLEVL,
    IN const DRM_SLK             *f_pslk )
{    
    DRM_BYTE          rgbSymmSignature[__CB_DECL(SHA_DIGEST_LEN)];
    DRM_CONST_STRING *pdstrLicense       = (DRM_CONST_STRING*) f_pdstrLicense;
    DRM_CONST_STRING  dstrString         = EMPTY_DRM_STRING;
    DRM_RESULT        dr                 = DRM_SUCCESS; 
    DRM_WCHAR        *pwszInsertionPoint = NULL;
    DRM_DWORD         cch                = 0;


    /*   1.  If a sym sig already exists try to verify it with the current slk
    **   2.  If sym sig doesn't exist, Verify the signature assymetrically
    **   3.  Then create new symsig
    */

    if( f_pslk != NULL
     && DRM_SUCCEEDED( DRM_XML_GetSubNodeByPath( pdstrLicense, 
                                                &g_dstrXPathSymSig, 
                                                 NULL, 
                                                 NULL, 
                                                 NULL,
                                                &dstrString, 
                                                 g_wchForwardSlash ) ) )
    {
        cch = SIZEOF( rgbSymmSignature );
        ChkDR( DRM_B64_DecodeW( &dstrString, &cch, rgbSymmSignature, 0 ) );

        if( cch != SHA_DIGEST_LEN
         || DRM_FAILED( DRM_BBX_SymmetricVerify( f_pcontextLEVL->pcontextBBX,
                                                 PB_DSTR( f_pdstrData ),
                                                 CB_DSTR( f_pdstrData ),
                                                 f_pslk,
                                                 rgbSymmSignature ) ) )
        {
            ChkDR( DRM_E_INVALIDLICENSE );
        }

        /* Set the insertion point to just at the begining of the <SYMSIG tag */
        pwszInsertionPoint = (DRM_WCHAR*) (dstrString.pwszString - ( 2 + g_dstrTagSymSig.cchString ));
    }
    else
    {
        DRM_LONG lResult = 0;

        ChkDR( DRM_LIC_VerifySignature( pdstrLicense, f_pcontextLEVL->pcontextBBX, &lResult ) );
        if( lResult != 1 )
        {
            ChkDR( DRM_E_INVALIDLICENSE );
        }
        ChkDR( DRM_LIC_VerifyCertChain( pdstrLicense, TRUE, f_pcontextLEVL, &lResult ) );
        if( lResult != 1 )
        {
            ChkDR( DRM_E_INVALIDLICENSE );
        }

        /* We are inserting immediatly after the closing tag of the DATA node */
        pwszInsertionPoint  = (DRM_WCHAR*) (f_pdstrData->pwszString + f_pdstrData->cchString);
        ChkDR( DRM_UTL_StringInsertBlankSubString( f_pdstrLicense, 
                                       (DRM_DWORD)(f_pdstrData->pwszString - f_pdstrLicense->pwszString ) + f_pdstrData->cchString, 
                                                   CCH_SYMSIG_TAG ) );

        
    }

    *pwszInsertionPoint  = g_wchLessThan;
     pwszInsertionPoint += 1;
    DRM_wcsncpy( pwszInsertionPoint, g_dstrTagSymSig.pwszString, g_dstrTagSymSig.cchString );
     pwszInsertionPoint += g_dstrTagSymSig.cchString;
    *pwszInsertionPoint  = g_wchGreaterThan;
     pwszInsertionPoint += 1;

    ChkDR( DRM_BBX_SymmetricSign( f_pcontextLEVL->pcontextBBX, 
                                  PB_DSTR( f_pdstrData ),
                                  CB_DSTR( f_pdstrData ),
                      (DRM_BYTE*) pwszInsertionPoint) );
    
    cch = CCH_BASE64_EQUIV( SHA_DIGEST_LEN );
    ChkDR( DRM_B64_EncodeW( (DRM_BYTE*)pwszInsertionPoint,
                            SHA_DIGEST_LEN,
                            pwszInsertionPoint,
                           &cch,
                            0 ) );

     pwszInsertionPoint += cch;
    *pwszInsertionPoint  = g_wchLessThan;
     pwszInsertionPoint += 1;
    *pwszInsertionPoint  = g_wchForwardSlash;
     pwszInsertionPoint += 1;
    DRM_wcsncpy( pwszInsertionPoint, g_dstrTagSymSig.pwszString, g_dstrTagSymSig.cchString );
     pwszInsertionPoint += g_dstrTagSymSig.cchString;
    *pwszInsertionPoint  = g_wchGreaterThan;
     pwszInsertionPoint += 1;

ErrorExit:
     return dr;
}

static DRM_RESULT DRM_API _UpdateLicenseWithSymmetricKey(
    IN OUT   DRM_STRING          *f_pdstrLicense,
    IN const DRM_CONST_STRING    *f_pdstrData,
    IN const DRM_CONST_STRING    *f_pdstrEnablingBits,
    IN       DRM_LICEVAL_CONTEXT *f_pcontextLEVL,
    IN const DRM_SLK             *f_pslk )
{
    DRM_RESULT       dr                 = DRM_SUCCESS;
    DRM_CONST_STRING dstrString         = EMPTY_DRM_STRING;
    DRM_CONST_STRING *pdstrLicense      = (DRM_CONST_STRING*) f_pdstrLicense;
    DRM_DWORD        cch                = 0;
    DRM_DWORD        cbSymmKey          = 0;
    DRM_WCHAR       *pwszInsertionPoint = NULL;    
    DRM_BINDING_INFO bindinfo;    

    /*
    **   Using the blackbox create a symmerticly decryptable content key for the license
    **   1.  if a symvalue exists extract it with the current slk and rebind
    **   2.  else asymmetrically extract and rebind.
    */
        
    if( f_pslk != NULL
     && DRM_SUCCEEDED( DRM_XML_GetSubNodeByPath( pdstrLicense, 
                                                &g_dstrXPathSymValue, 
                                                 NULL, 
                                                 NULL, 
                                                 NULL,
                                                &dstrString, 
                                                 g_wchForwardSlash ) ) )
    {
        cbSymmKey = SIZEOF( bindinfo.m_rgbContentKey );
        ChkDR( DRM_B64_DecodeW( &dstrString, &cbSymmKey, bindinfo.m_rgbContentKey, 0 ) );

        ChkDR( DRM_BBX_RebindSymmetricKey( f_pcontextLEVL->pcontextBBX, f_pslk, bindinfo.m_rgbContentKey, cbSymmKey ) );
        
        /* Set the insertion point to just at the begining of the <SYMVALUE tag */
        pwszInsertionPoint = (DRM_WCHAR*) (dstrString.pwszString - ( 2 + g_dstrTagSymValue.cchString ));
    }
    else
    {
        DRM_DWORD cbKey = SIZEOF( bindinfo.m_rgbContentKey );

        /* Get the PUBKEY this license is bound to */
        ChkDR( DRM_LIC_GetEnablingBits( (DRM_CONST_STRING*) f_pdstrLicense,
                                        0,
                                       &bindinfo.m_dwAlgorithm,
                                       &bindinfo.m_oPublKey,
                                        bindinfo.m_rgbContentKey,
                                       &cbKey,
                                       &bindinfo.m_oLsPublKey,
                                        bindinfo.m_rgbSignature,
                                        NULL,
                                       &f_pcontextLEVL->pcontextBBX->CryptoContext ) );


        /* Convert the asymmertic key to a symmetric key.  This happens in place. */
        ChkDR( DRM_BBX_AsymmetricToSymmetricKey( f_pcontextLEVL->pcontextBBX, 
                                                &bindinfo,
                                                &cbSymmKey ) );

        cch = (CCH_SYMVAL_TAG - CCH_BASE64_EQUIV( 20 )) + CCH_BASE64_EQUIV(cbSymmKey);

        /* We are inserting immediatly before the closing tag of the LICENSORINFO tag */
        pwszInsertionPoint  = (DRM_WCHAR*) (f_pdstrData->pwszString + f_pdstrData->cchString);
        ChkDR( DRM_UTL_StringInsertBlankSubString( f_pdstrLicense, 
                                       (DRM_DWORD)(f_pdstrData->pwszString - f_pdstrLicense->pwszString ) + f_pdstrData->cchString, 
                                                   cch ) );
    }    
    
    /* Create the symmetric encrypted content key node and data */
    *pwszInsertionPoint = g_wchLessThan;
     pwszInsertionPoint++;
    DRM_wcsncpy( pwszInsertionPoint, g_dstrTagSymValue.pwszString, g_dstrTagSymValue.cchString );
     pwszInsertionPoint += g_dstrTagSymValue.cchString;
    *pwszInsertionPoint = g_wchGreaterThan;
     pwszInsertionPoint++;

    cch = CCH_BASE64_EQUIV( cbSymmKey );
    ChkDR( DRM_B64_EncodeW( bindinfo.m_rgbContentKey,
                            cbSymmKey,
                            pwszInsertionPoint,
                           &cch,
                            0 ) );
    
     pwszInsertionPoint += cch;
    *pwszInsertionPoint  = g_wchLessThan;
     pwszInsertionPoint += 1;
    *pwszInsertionPoint  = g_wchForwardSlash;
     pwszInsertionPoint += 1;
    
    DRM_wcsncpy( pwszInsertionPoint, g_dstrTagSymValue.pwszString, g_dstrTagSymValue.cchString );
     pwszInsertionPoint += g_dstrTagSymValue.cchString;    
    *pwszInsertionPoint  = g_wchGreaterThan;
     pwszInsertionPoint += 1;

ErrorExit:
     return dr;
}

/*********************************************************************
**
**  Function:  _UpdateLicenseWithSymmetricData
**
**  Synopsis:  Given a license update it with a symmetrical signature and symmetrically encrypted content key.
**             This drastically improves binding and playback performance by offloading those assymetricall
**             operations to a one time hit here.
**
**  Arguments:  
**     [f_pdstrLicense]  -- License to add symmetrical data to
**     [f_cchLicenseMax] -- Maximum license size that can fix in the f_pdstrLicense buffer
**     [f_pcontextLEVL]  -- Pointer to a license evaluator context.
*********************************************************************/

static DRM_RESULT DRM_API _UpdateLicenseWithSymmetricData( 
    IN OUT   DRM_STRING          *f_pdstrLicense,
    IN       DRM_DWORD            f_cchLicenseMax,
    IN       DRM_LICEVAL_CONTEXT *f_pcontextLEVL,
    IN const DRM_SLK             *f_pslk )
{
    DRM_RESULT       dr               = DRM_SUCCESS;
    DRM_CONST_STRING dstrEnablingBits = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrData         = EMPTY_DRM_STRING;
    DRM_BOOL         fChainedLicense  = FALSE;

    if( f_pdstrLicense->cchString + CCH_SYMSIG_TAG + CCH_SYMVAL_TAG > f_cchLicenseMax )
    {
        ChkDR( DRM_E_BUFFERTOOSMALL );
    }

    /*
    ** Steps:
    **   1.  Find the enabling bits node for this license.
    **   2.  Update with symmetric signature    
    **   3.  Update the license with this information
    */

    /*
    ** The following XML will be added(or updated) to the license enabling bits section
    ** <SYMVALUE>B64-encoded symmetrically encrypted content key</SYMVALUE>
    ** <SYMSIGNATURE>B64-encoded symmetric signature</SYMSIGNATURE>
    */
    
    /* If there is an uplink kid node we don't rebind the content key */
    dr = DRM_LIC_GetAttribute( (DRM_CONST_STRING*)f_pdstrLicense, 
                                NULL,
                                DRM_LICENSE_ATTRIB_CHAINEDKID,                                
                               &dstrData, 
                               &dstrEnablingBits,
                                0);
    if( DRM_SUCCEEDED( dr ) )
    {
        fChainedLicense = TRUE;
    }

    /* dstrData points at the LICENSEORINFO node in the license -- populated by the previous call to GetAttribute */

    if( fChainedLicense )
    {
        ChkDR( DRM_XML_GetSubNode( &dstrData, 
                                   &g_dstrChainedEnablingBits, 
                                    NULL, 
                                    NULL, 
                                    0, 
                                   &dstrEnablingBits, 
                                    NULL, 
                                    1) );
    }
    else
    {
        ChkDR( DRM_XML_GetSubNode( &dstrData, 
                                   &g_dstrTagEnablingbits, 
                                    NULL, 
                                    NULL, 
                                    0, 
                                   &dstrEnablingBits, 
                                    NULL, 
                                    1) );
    }

    if( !fChainedLicense
     || f_pslk != NULL )
    {
        ChkDR( _UpdateLicenseWithSymmetricKey( f_pdstrLicense,
                                              &dstrData,
                                              &dstrEnablingBits,
                                               f_pcontextLEVL,
                                               f_pslk ) );
    }

    ChkDR( _UpdateLicenseWithSymmetricSignature( f_pdstrLicense,
                                                &dstrData,
                                                 f_pcontextLEVL,
                                                 f_pslk ) );

ErrorExit:
    return dr;
}

#endif

#if DRM_SUPPORT_SYMMETRIC_OPTIMIZATIONS
DRM_ID g_idSLKSST = { TWO_BYTES( 'I', '\0'), TWO_BYTES( 'D', '\0'), TWO_BYTES( 'F', '\0'), TWO_BYTES( 'O', '\0'), 
                      TWO_BYTES( 'R', '\0'), TWO_BYTES( 'S', '\0'), TWO_BYTES( 'L', '\0'), TWO_BYTES( 'K', '\0') };

typedef struct _tagSLKDATA
{
    DRM_SLK slk;
    DRM_ID  idSLK;
} SLKDATA;

#endif

DRM_RESULT DRM_API DRM_LA_ProcessResponse(
    IN     DRM_BYTE                *f_pbResponse,
    IN     DRM_DWORD                f_cbResponse,
    IN     DRM_LICEVAL_CONTEXT     *f_pcontextLEVL,
    IN     DRM_LICSTORE_CONTEXT    *f_pcontextLST,
    IN     pfnStoreLicenseCallback  f_pfnCallback,
    IN     DRM_VOID                *f_pvCallbackContext,
    IN     DRM_HDS_CONTEXT         *f_pcontextHDS,
    IN     DRM_VIEW_RIGHTS_CONTEXT *f_pcontextASD,
    IN     DRM_BYTE                 f_rgbLicenseBuffer[__CB_DECL(DRM_MAX_LICENSESIZE)],
    IN     DRM_SYNC_CONTEXT        *f_pcontextSync,
       OUT DRM_LONG                *f_plReason)
{
    DRM_RESULT dr       = DRM_SUCCESS;
    DRM_RESULT drReason = DRM_SUCCESS;
    DRM_DWORD  iNode    = 0;
    DRM_DWORD  cStored  = 0;
    DRM_SUBSTRING         dasstrLicenseList = { 0 };
    DRM_SUBSTRING         dasstrLicenseData = { 0 };
    DRM_SUBSTRING         dasstrNodeData    = { 0 };
    DRM_SUBSTRING         dasstrTagData     = { 0 };
    DRM_ANSI_CONST_STRING dastrLicenseData  = EMPTY_DRM_STRING;
    DRM_CONST_STRING      dstrLIData        = EMPTY_DRM_STRING;

#if DRM_SUPPORT_APP_REVOCATION
    DRM_BYTE              bZero             = 0x00;
#endif
#if DRM_SUPPORT_APP_REVOCATION || DRM_SUPPORT_SYMMETRIC_OPTIMIZATIONS
    DRM_BYTE              rgbPasswordSST [SHA_DIGEST_LEN] = { 0 };
#endif
#if DRM_SUPPORT_SYMMETRIC_OPTIMIZATIONS
    SLKDATA               slkdata           = { 0 };
    DRM_BOOL              fSLKValid         = FALSE;
#endif

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

    dastrLicenseData.cchString =              f_cbResponse;
    dastrLicenseData.pszString = (DRM_CHAR *) f_pbResponse;
                
    ChkArg (f_pcontextLEVL     != NULL
         && f_pcontextLST      != NULL
         && f_pbResponse       != NULL
         && f_pcontextHDS      != NULL
         && f_rgbLicenseBuffer != NULL
         && f_cbResponse       != 0);

#if DRM_SUPPORT_LICENSE_SYNC
    ChkArg( f_pcontextASD  != NULL
         && f_pcontextSync != NULL );
#endif

#if DRM_SUPPORT_SYMMETRIC_OPTIMIZATIONS
    /* Extract the current SLK from the secure store */

    if( DRM_SUCCEEDED( DRM_SST_CreateLicenseStatePassword( &g_idSLKSST, 
                                                            rgbPasswordSST, 
                                               (DRM_BYTE *) f_pcontextLEVL->pcontextBBX ) ) )
    {
        DRM_DWORD cbSLK = SIZEOF( slkdata );

        if( DRM_SUCCEEDED( DRM_SST_GetData( f_pcontextLEVL->pcontextSSTLicense, 
                                           &g_idSLKSST, 
                                            NULL,
                                            rgbPasswordSST,
                                            SECURE_STORE_GLOBAL_DATA,
                                            f_pcontextHDS,
                                (DRM_BYTE*)&slkdata,
                                           &cbSLK ) ) )
        {
            if( cbSLK == SIZEOF( slkdata ) )
            {
                fSLKValid = TRUE;
            }
        }
    }
#endif

    /* extract the license list */
    dasstrLicenseData.m_ich = 0;
    dasstrLicenseData.m_cch = f_cbResponse;

    ChkDR(DRM_XML_GetNodeA((DRM_CHAR*) f_pbResponse,
                            &dasstrLicenseData, 
                            &g_adstrLicenseRespTag, 
                             NULL, 
                             NULL, 
                             0, 
                             NULL, 
                            &dasstrLicenseList));

#if DRM_SUPPORT_SYMMETRIC_OPTIMIZATIONS
    dasstrLicenseData.m_ich = 0;
    dasstrLicenseData.m_cch = f_cbResponse;

    dr = DRM_XML_GetNodeA((DRM_CHAR*) f_pbResponse,
                          &dasstrLicenseList, 
                          &g_dastrTagSLK, 
                           NULL, 
                           NULL, 
                           0, 
                           NULL,
                          &dasstrTagData );
    if( DRM_SUCCEEDED( dr ) )
    {
        DRM_BOOL fNewSLKValid = FALSE;
        DRM_BOOL fPersist     = TRUE;

        /* BUGBUG:  Look for a persist flag */

        /* Extract the SLK ID from the response */
        dr = DRM_XML_GetNodeA( (DRM_CHAR*) f_pbResponse,
                              &dasstrTagData,
                              &g_dastrTagSLKID,
                               NULL,
                               NULL,
                               0,
                               NULL,
                              &dasstrNodeData );
        if( DRM_SUCCEEDED( dr ) )
        {
            DRM_ID    idSLKNew = { 0 };
            DRM_DWORD cbidSLK  = SIZEOF( idSLKNew );

            dr = DRM_B64_DecodeA((DRM_CHAR *) f_pbResponse, 
                                 &dasstrNodeData, 
                                 &cbidSLK, 
                                 (DRM_BYTE*)&idSLKNew, 
                                  0);
            if( DRM_SUCCEEDED( dr ) )
            {
                if( fSLKValid
                 && MEMCMP( &slkdata.idSLK, &idSLKNew, SIZEOF( idSLKNew ) ) == 0 )
                {
                    /* If same as current slk id skip */
                    fNewSLKValid = TRUE;
                }
                else
                {
                    DRM_BYTE rgbSLKKey[__CB_DECL(PK_ENC_CIPHERTEXT_LEN)];

                    /* New SLK.  Extract the SLK and decrypt */
                    dr = DRM_XML_GetNodeA( (DRM_CHAR*) f_pbResponse,
                                           &dasstrTagData,
                                           &g_dastrTagSLKDATA,
                                            NULL,
                                            NULL,
                                            0,
                                            NULL,
                                           &dasstrNodeData );

                    cbidSLK = SIZEOF( rgbSLKKey );
                    dr = DRM_B64_DecodeA((DRM_CHAR *) f_pbResponse, 
                                         &dasstrNodeData, 
                                         &cbidSLK, 
                                          rgbSLKKey,
                                          0);

                    if( cbidSLK == SIZEOF( rgbSLKKey )
                     && DRM_SUCCEEDED( dr ) )
                    {
                        dr = DRM_BBX_RebindSLK( f_pcontextLEVL->pcontextBBX, 
                                                rgbSLKKey,
                                               &slkdata.slk );
                        if( DRM_SUCCEEDED( dr ) )
                        {
                            fNewSLKValid = TRUE;
                            MEMCPY( &slkdata.idSLK, &idSLKNew, SIZEOF( slkdata.idSLK ) );
                            
                            if( fPersist )
                            {
                                if( DRM_SUCCEEDED( DRM_SST_CreateLicenseStatePassword( &g_idSLKSST, 
                                                                                        rgbPasswordSST, 
                                                                           (DRM_BYTE *) f_pcontextLEVL->pcontextBBX) ) )
                                {
                                    (void)DRM_SST_SetData( f_pcontextLEVL->pcontextSSTLicense, 
                                                          &g_idSLKSST, 
                                                           NULL,
                                                           rgbPasswordSST,
                                                           SECURE_STORE_GLOBAL_DATA,
                                                           f_pcontextHDS,
                                               (DRM_BYTE*)&slkdata,
                                                   SIZEOF( slkdata ) );
                                }
                            }
                        }
                    }
                }
            }
        }

        fSLKValid = fNewSLKValid;
    }
    else
    {
        fSLKValid = FALSE;
    }
#endif

                                           
    /* for each license in dastrLicenseList, Add license to license store */
    for (iNode = 0; ; iNode++)
    {
        DRM_CONST_STRING dstrKID         = EMPTY_DRM_STRING;
        DRM_BYTE        *pbLicense       = NULL;
        DRM_DWORD        dwPriority      = 0;
#if SIXTEEN_BIT_ADDRESSING
        DRM_BYTE         bHead           = 0;
        DRM_BOOL         fDecryptOK      = FALSE;
#endif
        dr = DRM_XML_GetNodeA((DRM_CHAR*) f_pbResponse,
                             &dasstrLicenseList, 
                             &g_dastrTagLicense, 
                              NULL, 
                              NULL, 
                              iNode, 
                             &dasstrTagData, 
                             &dasstrNodeData);

        /* A <LICENSE> node was found. */
        
        if (DRM_SUCCEEDED(dr))
        {
            DRM_DWORD     cbVersionLicense     = 0;
            DRM_SUBSTRING dasstrVersionLicense = { 0 };

            /* Check the version information */
            dr = DRM_XML_GetNodeAttributeA((DRM_CHAR*) f_pbResponse,
                                           &dasstrTagData, 
                                           &g_adstrAttributeVersion, 
                                           &dasstrVersionLicense);

            if (DRM_FAILED(dr))
            {
                continue;
            }
            else if (DRM_UTL_DASSTRStringsEqual((DRM_CHAR *) f_pbResponse, 
                                               &dasstrVersionLicense, 
                                               &g_dastrPMLicenseVersionString))
            {
#if DRM_SUPPORT_V1_LICENSES
                /* It is a V1 license.  Callback to store it */
                if (f_pfnCallback)
                {
                    DRM_ANSI_CONST_STRING dastrV1License = EMPTY_DRM_STRING;
                    
                    /* NOTE:  This code will not work on 16 bit addressable systems
                    **        which is OK because the refcode doesn't natively support
                    **        V1 licenses anyway
                    */
                    dastrV1License.pszString = (DRM_CHAR*)f_pbResponse + dasstrNodeData.m_ich;
                    dastrV1License.cchString = dasstrNodeData.m_cch;

                    drReason = f_pfnCallback((DRM_VOID*)&dastrV1License, DRM_STORE_LICENSE_STORE_V1_LICENSE, f_pvCallbackContext);

                    if (DRM_SUCCEEDED(drReason))
                    {
                        cStored++;
                    }
                }
#endif
                continue;
            }
            else            
            {
#if DRM_SUPPORT_APP_REVOCATION
                DRM_DWORD cbCRLAppCurrent = SIZEOF (f_pcontextLEVL->contextEXPR);
#endif            
                /*
                **  Check to see if there is an "encrypted" attribute.
                **  If this value is 'false' then the license is directly inline.
                **  If 'true' or non-existent then the license is B64 encoded and
                **  privacy encrypted.
                */

                dr = DRM_XML_GetNodeAttributeA((DRM_CHAR*) f_pbResponse,
                                               &dasstrTagData, 
                                               &g_adstrAttributeEncrypted, 
                                               &dasstrVersionLicense);

                drReason = DRM_B64_DecodeA((DRM_CHAR *) f_pbResponse, 
                                            &dasstrNodeData, 
                                            &cbVersionLicense, 
                                             NULL, 
                                             DRM_BASE64_DECODE_IN_PLACE);

                if (DRM_FAILED(drReason))
                {
                    drReason = DRM_E_INVALIDLICENSE;
                    continue;
                }            

                if(  DRM_FAILED( dr )
                 || !DRM_UTL_DASSTRStringsEqual((DRM_CHAR *) f_pbResponse, 
                                                &dasstrVersionLicense, 
                                                &g_dastrAttributeFALSE))
                {
                    /* Attribute doesn't exist or it isn't set to FALSE */

                    /* License is base64 decoded.  Now it should be decrypted. */

#if SIXTEEN_BIT_ADDRESSING
                    /* DecryptLicense expects an aligned CHAR* pointer, not a packed one; rather than
                    change its interface this code temporarily moves a packed buffer into alignment */

                    if ((dasstrNodeData.m_ich % CB_NATIVE_BYTE) != 0)
                    {
                        pbLicense = f_pbResponse + (dasstrNodeData.m_ich & ~CB_NATIVE_BYTE);
                        
                        DRM_16B_IncrementPackedPointer(pbLicense, dasstrNodeData.m_cch, &bHead);
                    }
                    else
                    {
                        pbLicense = f_pbResponse + __CB_DECL(dasstrNodeData.m_ich);
                    }
                    
                    if( cbVersionLicense                         > PK_ENC_CIPHERTEXT_LEN
                     && cbVersionLicense - PK_ENC_CIPHERTEXT_LEN > DRM_MAX_LICENSESIZE )
                    {
                        drReason = DRM_E_BUFFERTOOSMALL;
                        continue;
                    }
                    
                    fDecryptOK = DRM_BBX_DecryptLicense(pbLicense, cbVersionLicense, f_rgbLicenseBuffer + __CB_DECL( SIZEOF( DRM_DWORD) ), f_pcontextLEVL->pcontextBBX);

                    if ((dasstrNodeData.m_ich % CB_NATIVE_BYTE) != 0)
                    {
                        DRM_16B_DecrementPackedPointer(pbLicense, dasstrNodeData.m_cch, bHead);
                    }

                    if (! fDecryptOK)
                    {
                        drReason = DRM_E_INVALIDLICENSE;
                        continue;
                    }
#else                
                    /* must always add this offset when ANSI B64 decoding in place */
                    /* TODO: add in other call points, even if index is zero */
                    
                    pbLicense = f_pbResponse + dasstrNodeData.m_ich;
                    if( cbVersionLicense                         > PK_ENC_CIPHERTEXT_LEN
                     && cbVersionLicense - PK_ENC_CIPHERTEXT_LEN > DRM_MAX_LICENSESIZE )
                    {
                        drReason = DRM_E_BUFFERTOOSMALL;
                        continue;
                    }

                    if(! DRM_BBX_DecryptLicense(pbLicense, cbVersionLicense, f_rgbLicenseBuffer + __CB_DECL( SIZEOF( DRM_DWORD ) ), f_pcontextLEVL->pcontextBBX))
                    {
                        drReason = DRM_E_INVALIDLICENSE;
                        continue;
                    }
#endif
                    cbVersionLicense -= PK_ENC_CIPHERTEXT_LEN;
                }
                else
                {
                    if( cbVersionLicense > DRM_MAX_LICENSESIZE )
                    {
                        drReason = DRM_E_BUFFERTOOSMALL;
                        continue;
                    }

                    /* License is inline.  Copy it to the license buffer */
                    DRM_BYT_CopyBytes( f_rgbLicenseBuffer + __CB_DECL( SIZEOF( DRM_DWORD ) ), 0, f_pbResponse, dasstrNodeData.m_ich, cbVersionLicense );
                }

#if DEBUG__SAVE_PROTOCOL_INFORMATION
                {
	                /* save the lic to a file */
	                OEM_FILEHDL fp=OEM_INVALID_HANDLE_VALUE;
	                DRM_DWORD dwNumberOfBytesWritten=0;

	                fp = OEM_OpenFile(
	                        L"c:\\licresp.xml", 
	                        OEM_GENERIC_READ|OEM_GENERIC_WRITE,
	                        OEM_FILE_SHARE_READ|OEM_FILE_SHARE_WRITE, 
	                        OEM_CREATE_NEW, 
	                        OEM_ATTRIBUTE_NORMAL);
	                if ( fp != OEM_INVALID_HANDLE_VALUE )
	                {
	                    OEM_WriteFile(fp, f_rgbLicenseBuffer + __CB_DECL( SIZEOF( DRM_DWORD ) ), cbVersionLicense, &dwNumberOfBytesWritten);
	                    OEM_CloseFile(fp);
	                }
                }
#endif

#if DRM_SUPPORT_SYMMETRIC_OPTIMIZATIONS
                DSTR_FROM_PB( &dstrKID, f_rgbLicenseBuffer + __CB_DECL( SIZEOF( DRM_DWORD ) ), cbVersionLicense );

                ChkDR( _UpdateLicenseWithSymmetricData( (DRM_STRING*)&dstrKID, 
                                                         DRM_MAX_LICENSESIZE, 
                                                         f_pcontextLEVL, 
                                                         fSLKValid ? &slkdata.slk : NULL ) );
                cbVersionLicense = CB_DSTR( &dstrKID );
#endif
                DSTR_FROM_PB( &f_pcontextLEVL->dstrContentLicense,
                              f_rgbLicenseBuffer + __CB_DECL( SIZEOF( DRM_DWORD ) ),
                              cbVersionLicense );
                f_pcontextLEVL->fUseCachedAttribs = FALSE;
                
                {
                    DRM_CONST_STRING dstrLIData = EMPTY_DRM_STRING;
                    
                    ChkDRContinue( DRM_LIC_GetAttribute( &f_pcontextLEVL->dstrContentLicense, 
                                                          NULL, 
                                                          DRM_LICENSE_ATTRIB_PRIORITY, 
                                                         &dstrLIData, 
                                                         &dstrKID, 
                                                          0 ) );    

                    ChkDRContinue( wcsntol( dstrKID.pwszString, dstrKID.cchString, (DRM_LONG *) &dwPriority ) );

                    ChkDRContinue( DRM_LIC_GetAttribute( &f_pcontextLEVL->dstrContentLicense, 
                                                          NULL, 
                                                          DRM_LICENSE_ATTRIB_LID, 
                                                         &dstrLIData, 
                                                         &dstrKID, 
                                                          0 ) );                    
                    ChkDRContinue( DRM_UTL_StringToGuid( &dstrKID, (DRM_GUID*)f_pcontextLEVL->LID.rgb ) );

                    ChkDRContinue( DRM_LIC_GetAttribute( &f_pcontextLEVL->dstrContentLicense, 
                                                          NULL, 
                                                          DRM_LICENSE_ATTRIB_KID, 
                                                         &dstrLIData, 
                                                         &dstrKID, 
                                                          0 ) );
                    ChkDRContinue( DRM_UTL_DecodeKID( &dstrKID, &f_pcontextLEVL->KID ) );
                    /* Do not change dstrKID after this call.  Later code depends on it really pointing to the KID */
                    
                    f_pcontextLEVL->fUseCachedAttribs = TRUE;
                }
                

                drReason = _EvaluateLicense( f_pcontextLEVL, 
                                             f_pcontextHDS,
                                             &dstrLIData );

                if ( f_plReason != NULL )
                {
                    *f_plReason = f_pcontextLEVL->lReasonForFail;
                }

                /* store the license if the license evaluated ok */
                if (DRM_SUCCEEDED(drReason) )
                {
#if DRM_SUPPORT_PLAYLIST_BURN
                    /* 
                    **  Save this data off early.  If SYNC is supported the lic eval context could get changed before
                    **  we notify for playlist burn 
                    */
                    DRM_DWORD cPlaylistBurnIncrement = f_pcontextLEVL->cPlaylistBurnIncrement;
#endif

                    drReason = DRM_LST_AddLicense(f_pcontextLST, 
                                                  CB_DSTR( &f_pcontextLEVL->dstrContentLicense ),
                                                  f_rgbLicenseBuffer,
                                                 &f_pcontextLEVL->KID,
                                                 &f_pcontextLEVL->LID,
                                                  dwPriority);
                    
                    if (DRM_SUCCEEDED(drReason))
                    {
                        cStored++;

                        if (f_pfnCallback != NULL)
                        {
#if DRM_SUPPORT_PLAYLIST_BURN
                            if (cPlaylistBurnIncrement > 0)
                            {
                                DRM_PLAYLIST_NOTIFICATION_STRUCT burnUpdate;

                                burnUpdate.pdstrKID = &dstrKID;
                                burnUpdate.cPlaylistBurnIncrement = cPlaylistBurnIncrement;
                                f_pfnCallback( (DRM_VOID*) &burnUpdate, DRM_STORE_LICENSE_NOTIFY_PLAYLIST_BURN_UPDATE, f_pvCallbackContext);
                            }
#endif
                            f_pfnCallback( (DRM_VOID*) &dstrKID, DRM_STORE_LICENSE_NOTIFY_KID, f_pvCallbackContext);
                        }
                    }
                } /* end if EvaluateLicense () succeeded */

#if DRM_SUPPORT_LICENSE_SYNC
                /* don't stop the license processing loop on a synclist failure */
                ChkDRContinue( DRM_SNC_UpdateKID(f_pcontextSync, f_pcontextASD, &f_pcontextLEVL->KID) );
#endif

                /* Fill the data back in with what could be valid base64.  If not the decoded license may
                    cause further calls into the xml parser to fail */
                
                DRM_BYT_SetBytes(f_pbResponse, 
                                 dasstrNodeData.m_ich, 
                                 dasstrNodeData.m_cch, 
                                 'a');
            }
        } /* end if got license tag */
        else
        {
            if (dr == DRM_E_XMLNOTFOUND)
            {
                /* no more license found */
                dr = DRM_SUCCESS;
                break;
            }

            ChkDR(dr);  /* other errors */
        }
    } /* end for license */

#if DRM_SUPPORT_ANTIROLLBACK_CLOCK
    /* Explicitly make the global secure store writeabele */
    f_pcontextLEVL->fGlobalSecStoreWritable = TRUE;
    ChkDR(DRM_LIC_CheckClockRollback( f_pcontextLEVL,
                                      f_pcontextLST,
                                      f_pcontextLEVL->pLicStoreEnumContext,
                                      f_pbResponse,
                                      f_cbResponse,
                                      f_pcontextHDS));
#endif

ErrorExit:

    DRM_PROFILING_LEAVE_SCOPE(L"DRM_LA_ProcessResponse", g_pwszLeavingFunction);

    if (DRM_SUCCEEDED(dr))
    {   
        if (iNode == 1 && cStored == 0)
        {
            if (DRM_FAILED(drReason))
            {
                /* Only one license in response. No license was stored. */
                dr = drReason;
            }
            else
            {
                dr = DRM_E_FAILED_TO_STORE_LICENSE;
            }
        }
        else if (iNode > 1 && cStored == 0)
        {
            /* None were stored. Return usual error as done in past. */
            dr = DRM_E_FAILED_TO_STORE_LICENSE;
        }
        else if (iNode > 1 && cStored < iNode)
        {
            /* Multiple licenses in response. One or more licenses were not stored. */
            dr = DRM_E_NOT_ALL_STORED;
        }
    }
    return dr;
}

