/**@@@+++@@@@******************************************************************
**
** 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 <oemimpl.h>
#include <drmsecureclock.h>
#include <drmcrt.h>

#define ONE_MIN_IN_FILETIME 10000000*60

/******************************************************************************/

DRM_RESULT DRM_API DRM_CLK_ProcessResponse(
    IN const PUBKEY                *ppubkeySecureClockServer,
    IN       DRM_BYTE              *pbResponse,
    IN       DRM_DWORD              cbResponse,
    IN const DRM_BYTE               rgbSecStoreInPswd [__CB_DECL(SHA_DIGEST_LEN)],
    OUT      DRM_RESULT            *pResult,
    IN       DRM_CRYPTO_CONTEXT    *pContext,
    IN       DRM_HDS_CONTEXT       *poHdsContext,
    IN       DRM_SECSTORE_CONTEXT  *poSecureStoreContext,
    IN       DRM_LICEVAL_CONTEXT   *poLicEvalContext )
{
    DRM_RESULT       dr          = DRM_SUCCESS;
    DRM_DWORD        cbDecoded   = 0;    
    DRM_DWORD        cbLGPUBKEY  = SIZEOF(PUBKEY);
    DRM_LONG         lResult     = 0;
    DRM_DWORD        cbSecureData= 0;
    DRM_BOOL         fEvalResult = FALSE;
    TOKEN            Token;
    DRM_UINT64       u64DRMTime;
    DRMSYSTEMTIME    oSystemTime;
    DRMFILETIME      filetime;
    DRM_TID          tid;

    DRM_CONST_STRING dstrResponse          = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrData              = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrSign              = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrCert              = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrValue             = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrErrorNodes        = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrSecureClockPubKey = EMPTY_DRM_STRING;
    DRM_SUBSTRING    dasstrEncoded         = { 0 };
    DRM_SECSTORE_CLOCK_DATA oClkData       = { 0 };
    DRM_BYTE *pbAligned                     = NULL;
    DRM_DWORD cbAligned                     = 0;
    

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

    ChkArg( pbResponse           != NULL
         && pContext             != NULL
         && cbResponse            > 0 
         && pResult              != NULL
         && rgbSecStoreInPswd    != NULL 
         && poHdsContext         != NULL
         && poSecureStoreContext != NULL
         && poLicEvalContext     != NULL );
    
    *pResult = DRM_SUCCESS;
    dasstrEncoded.m_ich = 0;
    dasstrEncoded.m_cch = cbResponse;
    cbDecoded           = cbResponse;

    ChkDR(DRM_B64_DecodeA((DRM_CHAR *) pbResponse,
                          &dasstrEncoded, 
                          &cbDecoded, 
                           NULL, 
                           DRM_BASE64_DECODE_IN_PLACE ) );

    ChkDR( DRM_UTL_EnsureDataAlignment( pbResponse, cbResponse, &pbAligned, &cbAligned, SIZEOF( DRM_DWORD_PTR ) / CB_NATIVE_BYTE, NULL ) );

    if( cbAligned < cbDecoded )
    {
        /* Shouldn't ever happen since the B64 decode gives us lots of extra slack */
        ChkDR( DRM_E_BUFFERTOOSMALL );
    }

    if( pbAligned != pbResponse )
    {
        DRM_BYT_MoveBytes( pbAligned, 0, pbResponse, 0, cbDecoded );
        pbResponse = pbAligned;
    }
    

    /* Decode this ANSI encoded blob in place */

    DSTR_FROM_PB(&dstrResponse, pbResponse + dasstrEncoded.m_ich, cbDecoded);

    ChkDR( DRM_XML_GetNode( 
            &dstrResponse, 
            &g_dstrTagDRMReqRoot, 
            NULL, 
            NULL, 
            0, 
            NULL, 
            &dstrValue ) );



    if ( DRM_SUCCEEDED( DRM_XML_GetNode( &dstrValue, &g_dstrTagError,  NULL, NULL, 0, NULL, &dstrErrorNodes )  )  )
    {
        /* We have error code */
        ChkDR( wcsntol( dstrErrorNodes.pwszString, dstrErrorNodes.cchString, pResult ) );
        goto ErrorExit;    
    }
    else
    {
        if (dr == DRM_E_XMLNOTFOUND)
        {
            dr = DRM_SUCCESS; 
        }
        else
        {
            ChkDR(dr);  /* other errors */
        }
     }

#if DRM_SECURE_CLOCK_RESPONSE_TEST_MODE == 0
    /*Here verify the response*/
    /*
    **  Extract the signature, data and cert chain nodes from the response
    */
    ChkDR( DRM_XML_GetNode( &dstrValue, &g_dstrTagSignature,        NULL, NULL, 0, &dstrSign, NULL  ) );
    ChkDR( DRM_XML_GetNode( &dstrValue, &g_dstrTagCertificateChain, NULL, NULL, 0, &dstrCert, NULL ) );
    ChkDR( DRM_XML_GetNode( &dstrValue, &g_dstrTagData,             NULL, NULL, 0, &dstrData, NULL  ) );

    /*
    **  Extract the public key from the devcert
    */    

    MEMCPY( pContext->pubKey.y, ppubkeySecureClockServer, SIZEOF( PUBKEY ) );
    ChkDR( DRM_UTL_VerifyXMLSignature( &dstrData, &dstrSign, &dstrCert, TRUE, TRUE, WM_DRM_ROOTPUBKEY_CLK, poLicEvalContext ) );  
#endif

    /* Open secure stote and Read the Values If it cant be opened, FAIL
       as we dont have any TID to verify with */
    ZEROMEM(&oClkData, SIZEOF(oClkData));
    cbSecureData = SIZEOF(oClkData);
  
    ChkDR( DRM_SST_GetData(
            poSecureStoreContext, 
            &g_idSData, 
            &g_idSDataPrev, 
            rgbSecStoreInPswd, 
            SECURE_STORE_GLOBAL_DATA,
            poHdsContext,
            (DRM_BYTE *) &oClkData, 
            &cbSecureData) );
    _PrepareSecureClockDataForReading( &oClkData );
    
    ChkDR( DRM_XML_GetNode( 
            &dstrValue, 
            &g_dstrTagData, 
            NULL, 
            NULL, 
            0, 
            NULL, 
            &dstrData ) );

#if DRM_SECURE_CLOCK_RESPONSE_TEST_MODE == 0
    /*Get TID, Decode it and compare it with stored TID*/
    ChkDR( DRM_XML_GetNode( 
            &dstrData, 
            &g_dstrTagTID, 
            NULL, 
            NULL, 
            0, 
            NULL, 
            &dstrValue ) );

    cbDecoded = dstrValue.cchString;

    ChkDR(DRM_B64_DecodeW(
            (const DRM_CONST_STRING *)&dstrValue, 
                                      &cbDecoded, 
                          (DRM_BYTE *)&tid, 
                                       0));

    if (cbDecoded != SIZEOF(DRM_TID))
    {
        ChkDR( DRM_E_CLK_INVALID_RESPONSE );
    }

    if (MEMCMP(&tid, &oClkData.tid, SIZEOF(DRM_TID)) != 0 )
    {
        ChkDR( DRM_E_CLK_INVALID_RESPONSE );
    }

    /*Check whether the response is received within 5 minutes */

    /*Get Time and store it in secure store. Response should not come after 5 min of this time*/
    OEM_GetDeviceDateTime( &oSystemTime );
    if ( !OEM_SystemTimeToFileTime( &oSystemTime, &filetime ) )
    {
        ChkDR( DRM_E_CLK_INVALID_DATE );
    }

    FILETIME_TO_UI64( filetime, u64DRMTime );

    if ( !( oClkData.flag & CLK_CHALLENGETIME_VALID )
      || DRM_UI64Les( u64DRMTime, oClkData.ChallengeTime )
      || DRM_UI64Les( DRM_UI64Add( oClkData.ChallengeTime, DRM_UI64Mul(DRM_UI64(5),DRM_UI64(ONE_MIN_IN_FILETIME))), u64DRMTime ) )
    {
        ChkDR( DRM_E_CLK_INVALID_RESPONSE );
    }
#endif /* ! DRM_SECURE_CLOCK_RESPONSE_TEST_MODE */

    ChkDR( DRM_XML_GetNode( 
            &dstrData, 
            &g_dstrTagGmtTime, 
            NULL, 
            NULL, 
            0, 
            NULL, 
            &dstrValue ) );

    ChkDR( ExtractDateToken( &dstrValue, &Token ) );
    MEMCPY(&oClkData.LastKnownGoodSecureClock, &Token.val.u64DateTime, SIZEOF(DRM_UINT64));
    oClkData.flag |= CLK_LK_GSC_VALID;

    MEMCPY(&oClkData.LastKnownRealtimeSecureClock, &Token.val.u64DateTime, SIZEOF(DRM_UINT64));
    oClkData.flag |= CLK_LK_RSC_VALID;

    oClkData.fInGracePeriod = FALSE;
    oClkData.flag |= CLK_IN_GP_VALID;
   
    ZEROMEM(&oClkData.LastKnownGracePeriodStartTime, SIZEOF( oClkData.LastKnownGracePeriodStartTime ) );
    oClkData.flag |= CLK_LK_GP_VALID;

    ChkDR( DRM_XML_GetNode( 
            &dstrData, 
            &g_dstrTagRefreshDate, 
            NULL, 
            NULL, 
            0, 
            NULL, 
            &dstrValue ) );

    ChkDR( ExtractDateToken( &dstrValue, &Token ) );
    MEMCPY(&oClkData.RefreshDate, &Token.val.u64DateTime, SIZEOF(DRM_UINT64));
    
    oClkData.flag |= CLK_REFRESHDATE_VALID;
    oClkData.flag |= CLK_EVER_SET;


    /*Write it to secure store*/
    cbSecureData = SIZEOF( oClkData );
    
    _PrepareSecureClockDataForWriting( &oClkData );
    ChkDR( DRM_SST_SetData( 
            poSecureStoreContext,
            &g_idSData,
            &g_idSDataPrev,
            rgbSecStoreInPswd,
            SECURE_STORE_GLOBAL_DATA,
            poHdsContext,
            (DRM_BYTE *) &oClkData, 
            cbSecureData ) );

    _PrepareSecureClockDataForReading( &oClkData );

    ChkDR( OEM_SetClockResetState( FALSE ) );
    UI64_TO_FILETIME( oClkData.LastKnownGoodSecureClock, filetime );
    if (! OEM_FileTimeToSystemTime( &filetime, &oSystemTime ) )
    {
        ChkDR( DRM_E_CLK_INVALID_RESPONSE );
    }

    OEM_SetSystemTime( &oSystemTime );

#if DRM_SUPPORT_ANTIROLLBACK_CLOCK    
    /* Write it in Anti Rollback Store. It must already be in writable state */
    poLicEvalContext->fGlobalSecStoreWritable = TRUE;
    DRM_LEVL_EvaluateExpression( poLicEvalContext, &g_dstrResetRollbackedClock, &fEvalResult );
#endif

ErrorExit:

    DRM_PROFILING_LEAVE_SCOPE(L"DRM_CLK_ProcessResponse", g_pwszLeavingFunction);

    if( DRM_FAILED( dr ) )
    {
        dr = DRM_E_CLK_INVALID_RESPONSE;
    }
    return( dr );
}


