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

#include <drmcommon.h>
#include <drmutilities.h>
#include <drmcrt.h>
#include <drmcontextsizes.h>
#include <drmxmlparser.h>
#include <drmrc4.h>
#include <oemimpl.h>
#if DRM_SUPPORT_CERTIFICATE_CACHING
#include <drmblackbox.h>
#endif

#ifndef WMDRM_ON_SEP
/*****************************************************************************
** Function: DRM_UTL_IsCertDateOK
**
** Synopsis: test whether a given date is before todays date.
**
** Arguments:
**   [f_pbDate]     -- date as a byte array
**   f_pbData [0] -- century, e.g. 20
**   f_pbData [1] -- year mod 100 e.g. 4
**   f_pbData [2] -- month, 1-based
**   f_pbData [3] -- day-of-month, 1-based
**
** Example: March 21, 2004 = |20|4|3|21|
**                            0  1 2 3
** Returns TRUE if the date is reasonable
*****************************************************************************/

DRM_BOOL DRM_API DRM_UTL_IsCertDateOK(const DRM_BYTE *f_pbDate)
{                 
    DRMSYSTEMTIME systimeCurrent;
    DRM_BOOL      fOK = FALSE;
    DRM_BYTE      rgbDate[4]; /* Don't use __CB_DECL here */

    OEM_GetDeviceDateTime(&systimeCurrent);

    rgbDate[0] = GET_BYTE( f_pbDate, 0 );
    rgbDate[1] = GET_BYTE( f_pbDate, 1 );
    rgbDate[2] = GET_BYTE( f_pbDate, 2 );
    rgbDate[3] = GET_BYTE( f_pbDate, 3 );

    if ((rgbDate [0] * 100 + rgbDate [1]) > systimeCurrent.wYear)
    {
        fOK = TRUE;
    }
    else if ((rgbDate [0] * 100 + rgbDate [1]) < systimeCurrent.wYear)
    {
        fOK = FALSE;
    }
    else if (rgbDate [2] > systimeCurrent.wMonth)
    {
        fOK = TRUE;
    }
    else if (rgbDate [2] < systimeCurrent.wMonth)
    {
        fOK = FALSE;
    }
    else 
    {
        fOK = (rgbDate [3] >= systimeCurrent.wDay);
    }
        
    return fOK;
}


DRM_BOOL DRM_API DRM_UTL_DateLessThan(
    IN const DRMFILETIME* f_pFileTime1, 
    IN const DRMFILETIME* f_pFileTime2)
{
    DRM_UINT64 u641;
    DRM_UINT64 u642;

    FILETIME_TO_UI64( *f_pFileTime1, u641 );
    FILETIME_TO_UI64( *f_pFileTime2, u642 );
    return DRM_UI64Les( u641, u642);
}

#if DRM_SUPPORT_CERTIFICATE_CACHING

DRM_ID g_idCachedCert = { TWO_BYTES( 'C', 0 ), TWO_BYTES( 'E', 0 ), TWO_BYTES( 'R', 0 ), TWO_BYTES( 'T', 0 ), 
                          TWO_BYTES( 'C', 0 ), TWO_BYTES( 'A', 0 ), TWO_BYTES( 'C', 0 ), TWO_BYTES( 'H', 0 ) };

static DRM_BOOL _VerifyCachedCertificate (
    IN       DRM_LICEVAL_CONTEXT *f_pcontextLEVL,
    IN const DRM_BYTE            *f_pbData,
    IN       DRM_DWORD            f_cbData )
{
    DRM_RESULT  dr        = DRM_SUCCESS;
    DRM_BOOL    fVerified = FALSE;
    DRM_MD5_CTX contextMD5;
    DRM_BYTE    rgbHash[__CB_DECL(SHA_DIGEST_LEN)];
    DRM_BYTE    rgbSigDigest[__CB_DECL(MD5DIGESTLEN)];
    DRM_DWORD   cbSigDigest = SIZEOF( rgbSigDigest );

    ChkArg( f_pcontextLEVL                        != NULL
         && f_pcontextLEVL->pcontextBBX           != NULL
         && f_pcontextLEVL->pcontextSSTRevocation != NULL
         && f_pcontextLEVL->pcontextHDS           != NULL
         && f_pbData                              != NULL
         && f_cbData                               > 0 );

    ChkDR( DRM_BBX_HashValue( f_pbData, f_cbData, rgbHash, f_pcontextLEVL->pcontextBBX ) );
    DRM_MD5_Init  ( &contextMD5 );
    DRM_MD5_Update( &contextMD5, f_pbData, f_cbData );
    DRM_MD5_Final ( &contextMD5 );

    DRMCASSERT( SIZEOF( contextMD5.digest ) == SIZEOF( DRM_ID ) );

    dr = DRM_SST_GetData( f_pcontextLEVL->pcontextSSTRevocation,
                 (DRM_ID*)contextMD5.digest,
                         &g_idCachedCert,
                          rgbHash,
                          SECURE_STORE_CACHED_CERTIFICATE_DATA,
                          f_pcontextLEVL->pcontextHDS,
                          rgbSigDigest,
                         &cbSigDigest );
    if( DRM_SUCCEEDED( dr ) )
    {
        if( cbSigDigest == SIZEOF( rgbSigDigest )
         && 0 == MEMCMP( rgbSigDigest, contextMD5.digest, cbSigDigest ) )
        {
            fVerified = TRUE;
        }
    }

ErrorExit:
    return fVerified;
}

static DRM_RESULT _AddCachedCertificate (
    IN       DRM_LICEVAL_CONTEXT *f_pcontextLEVL,
    IN const DRM_BYTE            *f_pbData,
    IN       DRM_DWORD            f_cbData )
{
    DRM_RESULT  dr        = DRM_SUCCESS;
    DRM_MD5_CTX contextMD5;
    DRM_BYTE    rgbHash[__CB_DECL(SHA_DIGEST_LEN)];

    ChkArg( f_pcontextLEVL                        != NULL
         && f_pcontextLEVL->pcontextBBX           != NULL
         && f_pcontextLEVL->pcontextHDS           != NULL
         && f_pcontextLEVL->pcontextSSTRevocation != NULL
         && f_pbData                              != NULL
         && f_cbData                               > 0 );

    ChkDR( DRM_BBX_HashValue( f_pbData, f_cbData, rgbHash, f_pcontextLEVL->pcontextBBX ) );
    DRM_MD5_Init  ( &contextMD5 );
    DRM_MD5_Update( &contextMD5, f_pbData, f_cbData );
    DRM_MD5_Final ( &contextMD5 );

    DRMCASSERT( SIZEOF( contextMD5.digest ) == SIZEOF( DRM_ID ) );

    dr = DRM_SST_SetData( f_pcontextLEVL->pcontextSSTRevocation,
                 (DRM_ID*)contextMD5.digest,
                         &g_idCachedCert,
                          rgbHash,
                          SECURE_STORE_CACHED_CERTIFICATE_DATA,
                          f_pcontextLEVL->pcontextHDS,
                          (DRM_BYTE*)contextMD5.digest,
                   SIZEOF(contextMD5.digest) );
ErrorExit:
    return dr;
}

#endif


/******************************************************************************
** 
** Function :   _UTL_VerifySignature
** 
** Synopsis :   Verify the xml signature over some data
** 
** Arguments :  f_pdstrSignedData   -   The entire data that is signed
**              f_pdstrSignatureValue -   Sig value 
**              f_pdstrCertChainXML -   <CERTIFICATECHAIN>...</CERTIFICATECHAIN>
**              f_fCheckExpiry      -   Check for cert expiration
**              f_fCheckCertChain   -   Verify cert chain. If this is false,
**                                      f_pcontextCrypto->pubKey must contain
**                                      the public key which will be used to
**                                      verify the signature.
**              f_eRootPubkey       -   Root Pubkey to use for cert verification
**              f_pcontextLEVL      -   License eval context.  The HDS, pcontextSSTRevocation, 
**                                      and BBX pointers must be valid 
**
** Returns :    DRM_SUCCESS -   Signature verified successfully
**              DRM_E_INVALID_SIGNATURE -   Verification failed
**              some other problem
** 
** Notes :      
** 
******************************************************************************/
static DRM_RESULT _UTL_VerifySignature(
    IN  const   DRM_CONST_STRING        *f_pdstrSignedData,
    IN  const   DRM_CONST_STRING        *f_pdstrSignatureValue,
    IN  const   DRM_CONST_STRING        *f_pdstrCertChainXML,
    IN          DRM_BOOL                 f_fCheckExpiry,
    IN          DRM_BOOL                 f_fCheckCertChain,
    IN          DRM_ROOTPUBKEY_CATEGORY  f_eRootPubkey,
    IN          DRM_LICEVAL_CONTEXT     *f_pcontextLEVL)
{
    DRM_BOOL         fFirstCertCheck =   TRUE;
    DRM_CONST_STRING dstrCert        =   EMPTY_DRM_STRING;
    DRM_DWORD        cbSignature     =   0;
    DRM_DWORD        iCert           =   0;
    DRM_RESULT       dr              =   DRM_SUCCESS;    
    
    /*
    **  No need to verify input in internal function
    */    
    
#if DRM_SUPPORT_CERTIFICATE_CACHING
    if( _VerifyCachedCertificate( f_pcontextLEVL, 
                                  PB_DSTR( f_pdstrSignedData ), 
                                  CB_DSTR( f_pdstrSignedData ) ) )
    {
        dr = DRM_SUCCESS;
        goto ErrorExit;
    }
#endif

    if (f_fCheckCertChain)
    {
        /*
        **  Verify cert chain 
        */  
        while( TRUE ) /*    Loop will break when DRM_E_XMLNOTFOUND is hit */
        {
            DRM_DWORD cbCert = SIZEOF(CERT);

            dr  =   DRM_XML_GetSubNode( f_pdstrCertChainXML, 
                                       &g_dstrTagCertificate, 
                                        NULL, 
                                        NULL, 
                                        iCert, 
                                        NULL, 
                                       &dstrCert, 
                                        1);
            iCert++;

            if (dr == DRM_E_XMLNOTFOUND)
            {
                break;
            }
            ChkDR(dr);
            
            ChkDR(DRM_B64_DecodeW( &dstrCert, 
                                   &cbCert, 
                       (DRM_BYTE*) &f_pcontextLEVL->pcontextBBX->CryptoContext.union_cert.cert,
                                    0) );

            ChkDR(  DRM_UTL_CheckCertificate( &f_pcontextLEVL->pcontextBBX->CryptoContext.union_cert.cert, 
                                               fFirstCertCheck ? NULL : &f_pcontextLEVL->pcontextBBX->CryptoContext.pubKey, 
                                               f_fCheckExpiry, 
                                               f_pcontextLEVL,
                                               f_eRootPubkey) );
            
            MEMCPY( &(f_pcontextLEVL->pcontextBBX->CryptoContext.pubKey), 
                    &(f_pcontextLEVL->pcontextBBX->CryptoContext.union_cert.cert.cd.pk), 
                      SIZEOF(PUBKEY) );
            
            fFirstCertCheck = FALSE;        
        }
        
        if( iCert == 1 )
        {
            dr = DRM_E_INVALID_SIGNATURE;   /*  No certificates */
            goto ErrorExit;
        }
        /*
        **  f_pcontextCrypto->pubKey now contains the pubkey needed for verification
        */        
    }
        
    cbSignature = SIZEOF(f_pcontextLEVL->pcontextBBX->CryptoContext.signature);
    ChkDR(DRM_B64_DecodeW( f_pdstrSignatureValue, 
                          &cbSignature, 
                           f_pcontextLEVL->pcontextBBX->CryptoContext.signature, 
                           0) );
    
    if( !DRM_PK_Verify( f_pcontextLEVL->pcontextBBX->CryptoContext.rgbCryptoContext, 
                       &f_pcontextLEVL->pcontextBBX->CryptoContext.pubKey,
                        PB_DSTR(f_pdstrSignedData), 
                        CB_DSTR(f_pdstrSignedData),
                        f_pcontextLEVL->pcontextBBX->CryptoContext.signature) )
    {       
        dr = DRM_E_INVALID_SIGNATURE;
    }
#if DRM_SUPPORT_CERTIFICATE_CACHING
    else
    {
        (void)_AddCachedCertificate( f_pcontextLEVL, 
                                     PB_DSTR( f_pdstrSignedData ), 
                                     CB_DSTR( f_pdstrSignedData ) );
    }
#endif

    
ErrorExit:
    return dr;
}

static DRM_RESULT _CheckCertificate(
    const CERT                *f_pcert,
    const PUBKEY              *f_ppubkey,
          DRM_BOOL             f_fCheckDate,
          DRM_LICEVAL_CONTEXT *f_pcontextLEVL )
{
    DRM_DWORD  cbData = 0;
    DRM_RESULT dr     = DRM_SUCCESS;

    ChkArg(f_pcert        != NULL
        && f_ppubkey      != NULL
        && f_pcontextLEVL != NULL
        && f_pcontextLEVL->pcontextBBX != NULL );

    BYTES_TO_DWORD(cbData, f_pcert->datalen);

    if (MEMCMP(f_pcert->certVersion, (DRM_BYTE *)CERT_VER, VERSION_LEN) != 0)
    {
        ChkDR(DRMUTIL_UNSUPPORTED_VERSION);
    }

    if (f_fCheckDate && !DRM_UTL_IsCertDateOK(f_pcert->cd.expiryDate))
    {
        ChkDR(DRMUTIL_EXPIRED_CERT);
    }

#if DRM_SUPPORT_CERTIFICATE_CACHING
    /*
    ** First check to see if the certificate signature is cached.  
    ** If it is then we don't need to call DRM_PK_Verify 
    */
    if( !_VerifyCachedCertificate( f_pcontextLEVL, (const DRM_BYTE*) f_pcert, sizeof( *f_pcert ) ) )
#endif
    {
        if (! DRM_PK_Verify( f_pcontextLEVL->pcontextBBX->CryptoContext.rgbCryptoContext, 
                             f_ppubkey, 
               (DRM_BYTE *) &f_pcert->cd, 
                             cbData, 
                             f_pcert->sign))
        {
            ChkDR(DRMUTIL_INVALID_CERT);
        }
#if DRM_SUPPORT_CERTIFICATE_CACHING
        (void)_AddCachedCertificate( f_pcontextLEVL, (const DRM_BYTE*) f_pcert, sizeof( *f_pcert ) );
#endif
    }

ErrorExit:
    return dr;
}


/******************************************************************************
** 
** Function :   DRM_UTL_CheckCertificate
** 
** Synopsis :   Verifies the signature on a certificate using either the 
**              supplied public key, or the default for the supplied
**              DRM_ROOTPUBKEY_CATEGORY
** 
** Arguments :  f_pcert     -   Cert to be checked
**              f_ppubkey   -   Pubkey to be used for verification - If it is 
**                              NULL, use appropriate hard-coded key
**              f_fCheckCertDate    -   Check for expiration
**              f_pcontextCrypto   -   Crypto context
**              f_eRootPubkey   -   The root pubkey to use if f_ppubkey is NULL.
**                                  if it is WM_DRM_ROOTPUBKEY_CLK
**                                  f_pcontextCrypto->pubKey will hold the root
**                                  public key to use to verify the cert chain.
**              
** Returns :    
** 
** Notes :      
** 
******************************************************************************/
DRM_RESULT DRM_API DRM_UTL_CheckCertificate(
    IN  const   CERT                    *f_pcert, 
    IN  const   PUBKEY                  *f_ppubkey, 
    IN          DRM_BOOL                 f_fCheckCertDate,   
    IN          DRM_LICEVAL_CONTEXT     *f_pcontextLEVL,
    IN          DRM_ROOTPUBKEY_CATEGORY  f_eRootPubkey)
{
    DRM_RESULT  dr          =   DRM_SUCCESS;
    PUBKEY      *ppubkey    =   NULL;
    
    static PUBKEY pubkeyMSCERT = { {
            TWO_BYTES(0x4D, 0xBF), TWO_BYTES(0xD9, 0x0D), TWO_BYTES(0xD9, 0x6E), TWO_BYTES(0x8C, 0x9E),
            TWO_BYTES(0x32, 0x5F), TWO_BYTES(0x4F, 0x3D), TWO_BYTES(0xEC, 0xA9), TWO_BYTES(0x84, 0x59),
            TWO_BYTES(0x6B, 0x5E), TWO_BYTES(0x06, 0x86), TWO_BYTES(0xE7, 0xE2), TWO_BYTES(0xC2, 0x8B),
            TWO_BYTES(0xDE, 0x14), TWO_BYTES(0x4B, 0x29), TWO_BYTES(0x2C, 0xEC), TWO_BYTES(0x4D, 0x1D),
            TWO_BYTES(0x76, 0xFD), TWO_BYTES(0x5A, 0x14), TWO_BYTES(0x90, 0x3A), TWO_BYTES(0x10, 0x77)
    } };

    static PUBKEY pubkeyMSSecureClock = { {
            TWO_BYTES(0xF8, 0x23), TWO_BYTES(0x61, 0xBE), TWO_BYTES(0xFC, 0xF5), TWO_BYTES(0x59, 0xA3), 
            TWO_BYTES(0x55, 0xD4), TWO_BYTES(0x01, 0x54), TWO_BYTES(0x99, 0xEB), TWO_BYTES(0x71, 0x92), 
            TWO_BYTES(0xF9, 0xBD), TWO_BYTES(0x88, 0x3E), TWO_BYTES(0x14, 0xAC), TWO_BYTES(0x4F, 0x5C), 
            TWO_BYTES(0x9C, 0x65), TWO_BYTES(0x22, 0xFA), TWO_BYTES(0xA7, 0x2A), TWO_BYTES(0x77, 0x13), 
            TWO_BYTES(0x22, 0x5E), TWO_BYTES(0x60, 0xF5), TWO_BYTES(0xFE, 0x29), TWO_BYTES(0x18, 0x18)
    } }; 
    static PUBKEY pubkeyRootMeteringCert = 
    {
        TWO_BYTES(0x45, 0xB1), TWO_BYTES(0xA7, 0xE1), TWO_BYTES(0x90, 0x81), TWO_BYTES(0x98, 0x37), 
        TWO_BYTES(0x00, 0xCC), TWO_BYTES(0x89, 0xA7), TWO_BYTES(0x57, 0x24), TWO_BYTES(0x72, 0xB9), 
        TWO_BYTES(0xC1, 0x29), TWO_BYTES(0xA3, 0x62), TWO_BYTES(0xD9, 0x55), TWO_BYTES(0x74, 0x04), 
        TWO_BYTES(0x02, 0x7D), TWO_BYTES(0x6E, 0x69), TWO_BYTES(0x79, 0xE9), TWO_BYTES(0x6A, 0xD9), 
        TWO_BYTES(0x7A, 0x92), TWO_BYTES(0xE4, 0xF3), TWO_BYTES(0x4B, 0x6B), TWO_BYTES(0x42, 0x6C)
    };

    if (f_ppubkey != NULL)
    {
        ppubkey = (PUBKEY *)f_ppubkey;
    }
    else
    {
        switch(f_eRootPubkey)
        {
        case WM_DRM_ROOTPUBKEY_LRB:
        case WM_DRM_ROOTPUBKEY_LICENSES:
            ppubkey = &pubkeyMSCERT;
            break;
            
        case WM_DRM_ROOTPUBKEY_CLK:
            ppubkey = &(f_pcontextLEVL->pcontextBBX->CryptoContext.pubKey);
            break;

        case WM_DRM_ROOTPUBKEY_MTR:
            ppubkey = &pubkeyRootMeteringCert;
            break;
        
        default :
            dr = DRM_E_INVALIDARG;
        } 
    }

    ChkDR( _CheckCertificate( f_pcert, 
                              ppubkey, 
                              f_fCheckCertDate, 
                              f_pcontextLEVL) );

ErrorExit:
    return dr;
}

static PUBKEY pubkeyMS = 
{ 
    {
        TWO_BYTES(0x4D, 0xBF), TWO_BYTES(0xD9, 0x0D), TWO_BYTES(0xD9, 0x6E), TWO_BYTES(0x8C, 0x9E),
        TWO_BYTES(0x32, 0x5F), TWO_BYTES(0x4F, 0x3D), TWO_BYTES(0xEC, 0xA9), TWO_BYTES(0x84, 0x59),
        TWO_BYTES(0x6B, 0x5E), TWO_BYTES(0x06, 0x86), TWO_BYTES(0xE7, 0xE2), TWO_BYTES(0xC2, 0x8B),
        TWO_BYTES(0xDE, 0x14), TWO_BYTES(0x4B, 0x29), TWO_BYTES(0x2C, 0xEC), TWO_BYTES(0x4D, 0x1D),
        TWO_BYTES(0x76, 0xFD), TWO_BYTES(0x5A, 0x14), TWO_BYTES(0x90, 0x3A), TWO_BYTES(0x10, 0x77)
    } 
};

DRM_RESULT DRM_API DRM_UTL_CheckCert(
    IN const CERT                *f_pcert, 
    IN const PUBKEY              *f_ppubkey, 
    IN       DRM_BOOL             f_fCheckDate, 
    IN       DRM_LICEVAL_CONTEXT *f_pcontextLEVL)
{
    DRM_RESULT dr = DRM_SUCCESS;

    ChkDR(_CheckCertificate( f_pcert, 
                            (f_ppubkey != NULL) ? f_ppubkey : &pubkeyMS, 
                             f_fCheckDate, 
                             f_pcontextLEVL) );

ErrorExit:
    return (dr);
}

DRM_RESULT DRM_API DRM_UTL_CheckCertNoCache(
    IN const CERT               *f_pcert, 
    IN const PUBKEY             *f_ppubkey, 
    IN       DRM_BOOL            f_fCheckDate, 
    IN       DRM_CRYPTO_CONTEXT *f_pcontextCRYP)
{
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_LICEVAL_CONTEXT contextLEVL = {0};

    /* 
    ** This is a safe cast.  When the underlying CheckCert functions are called
    ** the will not use the BBX as a BBX, they will just use it as a crypto context.
    */
    contextLEVL.pcontextBBX = (DRM_BB_CONTEXT*)f_pcontextCRYP;

    ChkDR(_CheckCertificate( f_pcert, 
                            (f_ppubkey != NULL) ? f_ppubkey : &pubkeyMS, 
                             f_fCheckDate, 
                            &contextLEVL) );

ErrorExit:
    return (dr);
}

DRM_RESULT DRM_API DRM_UTL_CheckDACCert(
    IN const CERT                *f_pcert, 
    IN const PUBKEY              *f_ppubkey, 
    IN       DRM_BOOL             f_fCheckDate, 
    IN       DRM_LICEVAL_CONTEXT *f_pcontextLEVL)
{
    DRM_RESULT    dr = DRM_SUCCESS;

    static PUBKEY pubkeyDeviceAuthorizationCertificate = 
    { 
        {
            TWO_BYTES(0x4D, 0xBF), TWO_BYTES(0xD9, 0x0D), TWO_BYTES(0xD9, 0x6E), TWO_BYTES(0x8C, 0x9E),
            TWO_BYTES(0x32, 0x5F), TWO_BYTES(0x4F, 0x3D), TWO_BYTES(0xEC, 0xA9), TWO_BYTES(0x84, 0x59),
            TWO_BYTES(0x6B, 0x5E), TWO_BYTES(0x06, 0x86), TWO_BYTES(0xE7, 0xE2), TWO_BYTES(0xC2, 0x8B),
            TWO_BYTES(0xDE, 0x14), TWO_BYTES(0x4B, 0x29), TWO_BYTES(0x2C, 0xEC), TWO_BYTES(0x4D, 0x1D),
            TWO_BYTES(0x76, 0xFD), TWO_BYTES(0x5A, 0x14), TWO_BYTES(0x90, 0x3A), TWO_BYTES(0x10, 0x77)
        } 
    };

    ChkDR(_CheckCertificate( f_pcert, 
                            (f_ppubkey != NULL) ? f_ppubkey : &pubkeyDeviceAuthorizationCertificate,
                             f_fCheckDate, 
                             f_pcontextLEVL) );

ErrorExit:
    return (dr);
}

static DRM_BOOL HexStringToDword(
    IN const DRM_WCHAR *f_pwsz,
       OUT   DRM_DWORD *f_pdwValue,
    IN       DRM_INT    f_cDigits)
{
    DRM_INT  iDigit;    
    DRM_BOOL fOK = TRUE;
    DRM_DWORD dwValue = 0;
    
    *f_pdwValue = 0;
    
    for (iDigit = 0; 
         iDigit < f_cDigits; 
         iDigit++)
    {
        dwValue <<= 4;
        if( NATIVE_WCHAR(f_pwsz[iDigit]) >= NATIVE_WCHAR(g_wch0) 
         && NATIVE_WCHAR(f_pwsz[iDigit]) <= NATIVE_WCHAR(g_wch9))
        {
            dwValue += NATIVE_WCHAR(f_pwsz[iDigit]) - NATIVE_WCHAR(g_wch0);
        }
        else if( NATIVE_WCHAR(f_pwsz[iDigit]) >= NATIVE_WCHAR(g_wchA)
              && NATIVE_WCHAR(f_pwsz[iDigit]) <= NATIVE_WCHAR(g_wchF) )
        {
            
            dwValue += NATIVE_WCHAR(f_pwsz[iDigit]) - NATIVE_WCHAR(g_wchA) + 10;            
        }
        else if( NATIVE_WCHAR(f_pwsz[iDigit]) >= NATIVE_WCHAR(g_wcha) 
              && NATIVE_WCHAR(f_pwsz[iDigit]) <= NATIVE_WCHAR(g_wchf) )
        {
            dwValue += NATIVE_WCHAR(f_pwsz[iDigit]) - NATIVE_WCHAR(g_wcha) + 10;
        }
        else
        {
            /* Reverse the shift operation if all the range checks failed */
        	dwValue >>= 4;
            fOK = FALSE;
        }
    }
    
    MEMCPY( f_pdwValue, &dwValue, SIZEOF( dwValue ) );

    return fOK;
}

/*****************************************************************************
** Function: DRM_UTL_NumberToString
**
** Synopsis: express an unsigned long as a base10 UNICODE string.  
**
** Arguments:
** [f_dwValue]   -- value to translate
** [f_pszBuffer] -- output buffer
** [f_cchBuffer] -- output buffer size
**
** Returns string length
**
** Notes:    Output is not NUL-terminated 
*****************************************************************************/

DRM_DWORD DRM_API DRM_UTL_NumberToString(
    IN  DRM_DWORD  f_dwValue, 
    OUT DRM_WCHAR *f_pszBuffer, 
    IN  DRM_DWORD  f_cchBuffer)
{
    DRM_WCHAR *pszBuffer = f_pszBuffer + (f_cchBuffer  - 1);     /* last digit at end of buffer */
    DRM_DWORD  cchOut    = 0;

    if (f_dwValue == 0)
    {
        *f_pszBuffer = g_wch0;
        cchOut = 1;
    }
    else 
    {
        while (f_dwValue != 0 
           &&  pszBuffer >= f_pszBuffer) 
        {
            cchOut++;
            *pszBuffer-- = WCHAR_CAST(NATIVE_WCHAR(g_wch0) + (f_dwValue % 10)); /* store the digit */
            f_dwValue /= 10;            /* reduce number */        
        }

        /* Shift string to begin offset */
        DRM_BYT_MoveBytes(f_pszBuffer, 0, pszBuffer, 1, cchOut * SIZEOF (DRM_WCHAR)); 
    }    
    
    return cchOut; /* compute length of number & return */
}

/*****************************************************************************
** Function: DRM_UTL_GetVersionAsString
**
** Synopsis: express n version binary as a base10 dotted UNICODE string.  
**
** Arguments:
** [f_rgbVersion]   -- version as binary
** [f_wszVerString] -- output buffer
**
** Returns string length
**
** Notes:    Output is not NUL-terminated 
*****************************************************************************/

DRM_RESULT DRM_API DRM_UTL_GetVersionAsString(
    IN  DRM_BYTE  f_rgbVersion   [__CB_DECL(VERSION_LEN)],
    OUT DRM_WCHAR f_wszVerString [VER_STRING_MAX_LEN])
{
    DRM_INT ich = 0;
    DRM_INT ib  = 0;
    
    /* 
    ** (VER_STRING_MAX_LEN - 4) / 4 :
    ** This is 4 characters for the 3 periods and the NULL terminator.
    ** We divide by 4 because we have to perform the operation 4 times and give an equal buffer size to each operation
    */
       
    for (ib = 0; ib < VERSION_LEN; ib++)
    {
        ich += DRM_UTL_NumberToString( GET_BYTE(f_rgbVersion, ib), 
                                      &f_wszVerString[ich], 
                                      (VER_STRING_MAX_LEN - 4) / 4);
        
        f_wszVerString [ich++] = g_wchPeriod;
    }

    /* Overwrite that last g_wchPeriod with the NULL terminator */
    f_wszVerString [ich-1] = g_wchNull;

    return DRM_SUCCESS;
}

DRM_RESULT DRM_API DRM_UTL_GetVersionFromString(
   IN const DRM_WCHAR  *pwszVersion,
   IN       DRM_DWORD   cchVersion,
      OUT   DRM_WORD    rgwVersion[VERSION_LEN] )
{
    DRM_UINT uVer[VERSION_LEN];
	DRM_DWORD iCount = 0;
    DRM_DWORD ich = 0;
    
    for( iCount = 0; iCount < VERSION_LEN - 1; iCount++ )
    {
        ich = 0;
        while( ich < cchVersion )
        {
            if( pwszVersion[ich] == g_wchPeriod )
            {
                break;
            }
            ich++;
        }
        if( pwszVersion[ich] != g_wchPeriod                                        /* Didn't find a . seperator */
         || DRM_FAILED( wcsntol( pwszVersion, ich, (DRM_LONG *) &uVer [iCount] ) ) /* Couldn't extract a number */
         || uVer[iCount]     != (DRM_WORD) uVer[iCount] )                          /* Not in a valid range      */
        {
            return CPRMEXP_INVALID_ARGUMENT;
        }

        cchVersion -= (ich+1);
        pwszVersion = pwszVersion + (ich + 1);
    }

    if( DRM_FAILED( wcsntol( pwszVersion, cchVersion, (DRM_LONG *) &uVer [iCount] ) ) /* Couldn't extract a number */
     || uVer[iCount] != (DRM_WORD) uVer[iCount] )                                     /* Not in a valid range      */
    {
        return CPRMEXP_INVALID_ARGUMENT;
    }

    for( iCount = 0; iCount < VERSION_LEN; iCount++ )
    {
        rgwVersion[iCount] = (DRM_WORD)uVer[iCount];
    }
    return DRM_SUCCESS;

}

DRM_RESULT DRM_API DRM_UTL_StringToGuid(
    IN const DRM_CONST_STRING *pdstrString,
    OUT      DRM_GUID         *pGuid)
{
    DRM_RESULT dr      = DRM_SUCCESS;
    DRM_DWORD  dwValue = 0;
    DRM_INT ib;

    ChkArg(pdstrString != NULL 
        && pGuid       != NULL 
        && pdstrString->pwszString != NULL 
        && pdstrString->cchString  == 38); /* 38 characters in a proper Guid String - including { } */

    ChkArg(pdstrString->pwszString [0]  == g_wchOpenCurly 
        && pdstrString->pwszString [37] == g_wchCloseCurly); /* First and last char must be { & } */

    /* Validate the delimitar characters */

    if (pdstrString->pwszString [9]  != g_wchMinus 
     || pdstrString->pwszString [14] != g_wchMinus 
     || pdstrString->pwszString [19] != g_wchMinus 
     || pdstrString->pwszString [24] != g_wchMinus)
    {
        ChkDR(DRM_E_INVALIDARG);
    }

    /* Convert pieces to BYTES */
    
    if (! HexStringToDword(pdstrString->pwszString + 1, &pGuid->Data1, 8))
    {
        ChkDR(DRM_E_INVALIDARG);
    }
    if (! HexStringToDword(pdstrString->pwszString + 10, &dwValue, 4))
    {
        ChkDR(DRM_E_INVALIDARG);
    }

    pGuid->Data2 = (DRM_SHORT) dwValue;

    if (! HexStringToDword(pdstrString->pwszString + 15, &dwValue, 4))
    {
        ChkDR(DRM_E_INVALIDARG);
    }

    pGuid->Data3 = (DRM_SHORT) dwValue;

    if (! HexStringToDword(pdstrString->pwszString + 20, &dwValue, 2))
    {
        ChkDR(DRM_E_INVALIDARG);
    }

    PUT_BYTE(pGuid->Data4, 0, (DRM_BYTE) dwValue);

    if (! HexStringToDword(pdstrString->pwszString + 22, &dwValue, 2))
    {
        ChkDR(DRM_E_INVALIDARG);
    }

    PUT_BYTE(pGuid->Data4, 1, (DRM_BYTE) dwValue);

    for (ib = 2; ib < 8; ib++)
    {
        if (! HexStringToDword(pdstrString->pwszString  + (((ib - 1) * 2) + 23), &dwValue, 2))
        {
            ChkDR(DRM_E_INVALIDARG);
        }

        PUT_BYTE(pGuid->Data4,ib, (DRM_BYTE) dwValue);
    }
    
    FIX_ENDIAN_DWORD( pGuid->Data1 );
    FIX_ENDIAN_WORD(  pGuid->Data2 );
    FIX_ENDIAN_WORD(  pGuid->Data3 );

    /* We have validated the string && now the bits are converted. */
ErrorExit:
    return dr;
}

DRM_BOOL DRM_API DRM_UTL_DASSSTRStringsEqual(
    const DRM_CHAR      *f_pszBase0,
    const DRM_SUBSTRING *f_pdasstr0,
    const DRM_CHAR      *f_pszBase1,
    const DRM_SUBSTRING *f_pdasstr1)
{
    return (f_pdasstr0->m_cch == f_pdasstr1->m_cch
        &&  DRM_BYT_CompareBytes(f_pszBase0, 
                                 f_pdasstr0->m_ich, 
                                 f_pszBase1, 
                                 f_pdasstr1->m_ich,
                                 f_pdasstr1->m_cch) == 0);
}

/**********************************************************************
** Function:    PKEncryptLarge
**
** Synopsis:    Encrypt large amoutn of data (using PKEncrypt with large data is slow, this is fast)
**
** Arguments:   [f_ppubkey]        -- Caller supplied public key to encrypt with
**              [f_pbClear]        -- Array of bytes to encrypt
**              [f_cbClear]        -- Length ob f_pbClear in bytes
**              [f_pbCipher]       -- Buffer to hold encrypted data
**              [f_cbKeySymmetric] -- Desired length of internal symmertic key to be created
**              [f_pcontextCRYP]   -- A DRM_CRYPTO_CONTEXT so PKCrypto operations can be performed.
**
** Notes:       out must be of length f_cbClear + PK_ENC_CIPHERTEXT_LEN
**              Internally a rc4 symmetric key will be created to encrypt the content (because it's fast) &
**              that symmertic key will be encrypted with the PUBKEY.
**              In place encryption is possible if f_pbCipher equals to f_pbClear.
***********************************************************************/

DRM_RESULT DRM_API DRM_PK_EncryptLarge(
    IN const PUBKEY              *f_ppubkey,
    IN OUT   DRM_BYTE            *f_pbClear, 
    IN       DRM_DWORD            f_cbClear, 
       OUT   DRM_BYTE            *f_pbCipher, 
    IN       DRM_DWORD            f_cbKeySymmetric, 
    IN       DRM_CRYPTO_CONTEXT  *f_pcontextCRYP)
{
    DRM_RESULT     dr    = DRM_SUCCESS;
    DRM_BYTE      *pbKey = NULL; /* This needs __CB_DECL(PK_ENC_PLAINTEXT_LEN) bytes */
    RC4_KEYSTRUCT  rc4KS;
    
    ChkArg(f_ppubkey      != NULL
        && f_pbClear      != NULL
        && f_pbCipher     != NULL
        && f_pcontextCRYP != NULL);

    ChkArg(f_cbKeySymmetric <= (PK_ENC_PLAINTEXT_LEN - 2)) 
    
    if (f_cbKeySymmetric > DRMCIPHERKEYLEN)
    {
        f_cbKeySymmetric = DRMCIPHERKEYLEN;
    }

    /* Ensure the temporary buffer we are using is big enough */
    DRMCASSERT( PK_ENC_PLAINTEXT_LEN <= SIZEOF( f_pcontextCRYP->signature ) );
    pbKey = f_pcontextCRYP->signature;

    DRM_BYT_MoveBytes(f_pbCipher, PK_ENC_CIPHERTEXT_LEN, f_pbClear, 0, f_cbClear);

    PUT_BYTE( pbKey, 0, (DRM_BYTE) f_cbKeySymmetric );
    PUT_BYTE( pbKey, 1, (DRM_BYTE) PKSYMM_ALG_TYPE_RC4 );

    ChkDR(OEM_GenRandomBytes(pbKey + __CB_DECL(2), f_cbKeySymmetric));
    ChkDR(DRM_PK_Encrypt(f_pcontextCRYP->rgbCryptoContext, f_ppubkey, pbKey, f_pbCipher));
        
    DRM_RC4_KeySetup(&rc4KS, f_cbKeySymmetric, pbKey      + __CB_DECL(2));
    DRM_RC4_Cipher  (&rc4KS, f_cbClear,        f_pbCipher + __CB_DECL(PK_ENC_CIPHERTEXT_LEN));

ErrorExit:
    return dr;
}    

/**********************************************************************
** Function:    DRM_PK_DecryptLarge
**
** Synopsis:    Dencrypt data encrypted with DRM_PK_EncryptLarge
**
** Arguments:   [f_ppubkey] -- Caller supplied private key to decrypt with
**              [f_pbCipher]    -- Array of bytes to decrypt
**              [f_cbCipher]    -- Length of f_pbCipher in bytes
**              [f_pbClear]   -- Buffer to hold decrypted data
**              [f_pcontextCRYP] -- A DRM_CRYPT_CONTEXT so PKCrypto operations can be performed.
**
** Notes:       out must be at least f_cbCipher - PK_ENC_CIPHERTEXT_LEN in length.
**              In place decryption is possible if f_pbClear equals to f_pbCipher.
***********************************************************************/

DRM_RESULT DRM_API DRM_PK_DecryptLarge(
    IN const PRIVKEY            *f_ppubkey,
    IN OUT   DRM_BYTE           *f_pbCipher, 
    IN       DRM_DWORD           f_cbCipher, 
    OUT      DRM_BYTE           *f_pbClear, 
    IN       DRM_CRYPTO_CONTEXT *f_pcontextCRYP)
{
    DRM_RESULT     dr    = DRM_SUCCESS;
    DRM_BYTE      *pbKey = NULL;
    RC4_KEYSTRUCT  rc4KS;

    ChkArg(f_ppubkey      != NULL
        && f_pbCipher     != NULL
        && f_pbClear      != NULL
        && f_pcontextCRYP != NULL);         

    /* Ensure the temporary buffer we are using is big enough */
    DRMCASSERT( PK_ENC_PLAINTEXT_LEN <= SIZEOF( f_pcontextCRYP->signature ) );
    pbKey = f_pcontextCRYP->signature;

    ChkDR(DRM_PK_Decrypt( f_pcontextCRYP->rgbCryptoContext, 
                          f_ppubkey, 
                          f_pbCipher, 
                          pbKey));

    if (GET_BYTE(pbKey,1) != PKSYMM_ALG_TYPE_RC4)
    {
        ChkDR(DRM_E_INVALIDARG);
    }

    ChkOverflow( f_cbCipher, f_cbCipher - PK_ENC_CIPHERTEXT_LEN );

    DRM_BYT_MoveBytes(f_pbClear,
                      0,
                      f_pbCipher,
                      PK_ENC_CIPHERTEXT_LEN,
                      f_cbCipher - PK_ENC_CIPHERTEXT_LEN);

    DRM_RC4_KeySetup(&rc4KS, GET_BYTE(pbKey,0),                  pbKey + __CB_DECL(2));
    DRM_RC4_Cipher  (&rc4KS, f_cbCipher - PK_ENC_CIPHERTEXT_LEN, f_pbClear);

ErrorExit:
    return dr;
}
#endif //WMDRM_ON_SEP
/******************************************************************************
** 
** Function :   DRM_PK_SymmetricCrypt
** 
** Synopsis :   Encrypts/Decrypts data using a symmetric key derived from
**              a private key
** 
** Arguments :  f_pprivkey - Private key
**              f_cbCipher, f_pbCipher - # of bytes and buffer for ciphertext
**              f_cbClear, f_pbClear - # of bytes and buffer for cleartext. If 
**                  this buffer is NULL, decrypt/encrypt would be done in place.

******************************************************************************/
DRM_RESULT DRM_API DRM_PK_SymmetricCrypt(
    IN const DRM_BYTE  *f_pbKey,
    IN       DRM_DWORD  f_cbKey,
    IN       DRM_DWORD  f_cbCipher,
    IN       DRM_BYTE  *f_pbCipher,
    IN       DRM_DWORD  f_cbClear,
       OUT   DRM_BYTE  *f_pbClear )
{
    DRM_BYTE        rgbDigest[__CB_DECL(SHA_DIGEST_LEN)];
    DRM_BYTE       *pbClear = NULL;
    DRM_DWORD       cbClear = 0;
    DRM_RESULT      dr      = DRM_SUCCESS;
    RC4_KEYSTRUCT   rc4keystruct;
    SHA_CONTEXT     contextSHA;  
    
    ChkArg( f_pbKey    != NULL
         && f_cbKey     > 0
         && f_pbCipher != NULL );
    
    if ( f_pbClear != NULL )
    {
        ChkArg( f_cbCipher == f_cbClear );
        cbClear = f_cbClear;
        pbClear = f_pbClear;
        MEMCPY(pbClear, f_pbCipher, cbClear);
    }
    else
    {
        pbClear = f_pbCipher;
        cbClear = f_cbCipher;
    }
        
    /*
    **  1. Get a hash of the privkey
    **  2. Generate a RC4 key using this hash
    **  3. Decrypt ciphertext using this RC4 key
    */
    DRM_SHA_Init( &contextSHA );
    DRM_SHA_Update( f_pbKey, f_cbKey, &contextSHA );
    DRM_SHA_Finalize( &contextSHA, rgbDigest );

    DRM_RC4_KeySetup( &rc4keystruct, SIZEOF(rgbDigest) , rgbDigest );
    DRM_RC4_Cipher( &rc4keystruct, cbClear, pbClear );

    ZEROMEM( &rc4keystruct, SIZEOF(rc4keystruct) );
    ZEROMEM( &contextSHA,   SIZEOF(contextSHA) );
    
ErrorExit:
    return dr;
}


/*********************************************************************
**
**  Function:  DRM_PK_SymmetricSign
**
**  Synopsis:  Creates a symmetric signature using a private key provided by the caller.
**
**  Arguments:  
**     [f_pprivkey]  -- Private key to sign with
**     [f_pbData]    -- Data to create a signature over
**     [f_cbData]    -- Length of f_pbData in DRM_BYTEs
**     [f_rgbSymSig] -- Buffer of length SHA_DIGEST_LEN DRM_BYTEs where the signature will be returned.
**
**  Notes:  Use DRM_PK_SymmetricVerify to verify a signature created with DRM_PK_SymmetricSign.
**
*********************************************************************/
DRM_RESULT DRM_API DRM_PK_SymmetricSign(
    IN const DRM_BYTE  *f_pbKey,
    IN       DRM_DWORD  f_cbKey,
    IN const DRM_BYTE *f_pbData,
    IN       DRM_DWORD f_cbData,
       OUT   DRM_BYTE  f_rgbSymSig[__CB_DECL( SHA_DIGEST_LEN )] )
{
    HMAC_CONTEXT hmac;
    DRM_RESULT dr = DRM_SUCCESS;

    ChkDR( DRM_HMAC_Init(     &hmac, f_pbKey,     f_cbKey ) );
    ChkDR( DRM_HMAC_Update(   &hmac, f_pbData,    f_cbData ) );
    ChkDR( DRM_HMAC_Finalize( &hmac, f_rgbSymSig, SHA_DIGEST_LEN ) );

ErrorExit:
    return dr;
}

#if DRM_SUPPORT_SYMMETRIC_OPTIMIZATIONS
/*********************************************************************
**
**  Function:  DRM_PK_SymmetricVerify
**
**  Synopsis:  Verifies a symmetric signature using a private key provided by the caller.
**
**  Arguments:  
**     [f_pprivkey]  -- Private key with which to verify the signature
**     [f_pbData]    -- Data to verify
**     [f_cbData]    -- Length of f_pbData in DRM_BYTEs
**     [f_rgbSymSig] -- Buffer of length SHA_DIGEST_LEN DRM_BYTEs of the signature to verify
**
**  Notes:  Use DRM_PK_SymmetricSign to create a symmetric signature
*********************************************************************/
DRM_RESULT DRM_API DRM_PK_SymmetricVerify(
    IN const DRM_BYTE  *f_pbKey,
    IN       DRM_DWORD  f_cbKey,
    IN const DRM_BYTE *f_pbData,
    IN       DRM_DWORD f_cbData,
    IN const DRM_BYTE  f_rgbSymSig[__CB_DECL( SHA_DIGEST_LEN )] )
{
    HMAC_CONTEXT hmac;
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_BYTE   rgbSymSig[__CB_DECL( SHA_DIGEST_LEN )];

    ChkDR( DRM_HMAC_Init(     &hmac, f_pbKey,   f_cbKey ) );
    ChkDR( DRM_HMAC_Update(   &hmac, f_pbData,  f_cbData ) );
    ChkDR( DRM_HMAC_Finalize( &hmac, rgbSymSig, SHA_DIGEST_LEN ) );

    if( MEMCMP( f_rgbSymSig, rgbSymSig, SHA_DIGEST_LEN ) != 0 )
    {
        dr = DRM_E_INVALID_SIGNATURE;
    }

ErrorExit:
    return dr;
}
#endif

#ifndef WMDRM_ON_SEP

/**********************************************************************
** Function:    DRM_UTL_StringInsertSubString
**
** Synopsis:    Insert a substring to a target string at specific insertion point
**
** Arguments:   [f_pdstrTarget]  -- target string
**              [f_ichInsertion] -- insertion position of target string: index of f_pwsz.
**              [f_cch]          -- # of blank char to insert
**
** Notes:       
** - Caller is responsible to make sure there is enough room in target string.
***********************************************************************/
DRM_RESULT DRM_API DRM_UTL_StringInsertBlankSubString(
    IN OUT DRM_STRING *f_pdstrTarget,
    IN     DRM_DWORD   f_ichInsertion,
    IN     DRM_DWORD   f_cch)
{
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_DWORD  cchToMove     = 0;
    DRM_DWORD  ich           = 0;

    ChkDRMString(f_pdstrTarget);
    ChkArg(f_ichInsertion <= f_pdstrTarget->cchString);

    cchToMove = f_pdstrTarget->cchString - f_ichInsertion;
    
    MEMMOVE(&f_pdstrTarget->pwszString[f_ichInsertion+f_cch],
            &f_pdstrTarget->pwszString[f_ichInsertion], 
            cchToMove*SIZEOF(DRM_WCHAR));

    /* fill the hole in the blanks */
    for (ich=0; ich<f_cch; ich++)
    {
        f_pdstrTarget->pwszString[f_ichInsertion+ich] = g_wchSpace;
    }

    /* adjust destination string length */
    f_pdstrTarget->cchString += f_cch;

ErrorExit:
    return dr;
}



/**********************************************************************
** Function:    DRM_UTL_StringInsertSubString
**
** Synopsis:    Insert a substring to a target string at specific insertion point
**
** Arguments:   [f_pdstrTarget]  -- target string
**              [f_ichInsertion] -- insertion position of target string: index of f_pwsz.
**              [f_pdstrSub]     -- sub string to be inserted
**
** Notes:       
** - Caller is responsible to make sure there is enough room in target string.
***********************************************************************/

DRM_RESULT DRM_API DRM_UTL_StringInsertSubString(
    IN OUT   DRM_STRING       *f_pdstrTarget,
    IN       DRM_DWORD         f_ichInsertion,
    IN const DRM_CONST_STRING *f_pdstrSub)
{
    DRM_RESULT dr = DRM_SUCCESS;

    ChkDRMString(f_pdstrTarget);
    ChkDRMString(f_pdstrSub);
    ChkArg(f_ichInsertion <= f_pdstrTarget->cchString);

    ChkDR(DRM_UTL_StringInsertBlankSubString(f_pdstrTarget, f_ichInsertion, f_pdstrSub->cchString));

    /* copy the source string to target point */
    MEMCPY( (DRM_BYTE *) (f_pdstrTarget->pwszString + f_ichInsertion),
            PB_DSTR(f_pdstrSub), 
            CB_DSTR(f_pdstrSub));

ErrorExit:
    return dr;
}

/**********************************************************************
** Function:    DRM_UTL_StringRemoveSubString
** Synopsis:    remove a substring from the target string
** Arguments:   [f_pdstrSource] -- Source string
**              [f_pdstrSub]    -- A substring within f_pdstrSource
** Notes:       
** - Caller is responsible for sufficient buffer in destination string
***********************************************************************/

DRM_RESULT DRM_API DRM_UTL_StringRemoveSubString(
    IN OUT   DRM_STRING       *f_pdstrSource,
    IN const DRM_CONST_STRING *f_pdstrSub)
{
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_DWORD  cchToMove = 0;
    DRM_DWORD  ich       = 0;
    DRM_WCHAR *pwsz       = NULL;

    ChkDRMString(f_pdstrSource);
    if ( f_pdstrSub             == NULL 
      || f_pdstrSub->pwszString == NULL 
      || f_pdstrSub->cchString  == 0 )
    {
        goto ErrorExit;
    }

    /* make sure f_pdstrSub is within f_pdstrSource */
    ChkArg(f_pdstrSource->pwszString <= f_pdstrSub->pwszString);
     
    ChkArg((f_pdstrSub->pwszString    + f_pdstrSub->cchString) 
        <= (f_pdstrSource->pwszString + f_pdstrSource->cchString));
    
    cchToMove = f_pdstrSource->cchString 
              - f_pdstrSub->cchString 
              - (DRM_DWORD)(f_pdstrSub->pwszString 
              - f_pdstrSource->pwszString);

    /* move the rest of the string to cover the hole.
    ** alternative way: use memmove().
    */
    
    pwsz = (DRM_WCHAR *) f_pdstrSub->pwszString;
    
    for (ich = cchToMove; 
         ich > 0; 
         ich--)
    {
        *pwsz = pwsz [f_pdstrSub->cchString];
        pwsz++;
    }

    /* adjust string length */
    f_pdstrSource->cchString -= f_pdstrSub->cchString;

ErrorExit:
    return dr;
}

/*****************************************************************************
** Function : DRM_UTL_DSTRStringsEqual
**            DRM_UTL_DASTRStringsEqual
**
** Synopsis: case-sensitive comparison of two DRM_CONST_STRINGs or 
**           DRM_ANSI_CONST_STRINGs
**
** Arguments:
** [f_pdstr1]
** [f_pdstr2] -- strings to compare
**
** Return: TRUE if strings are same length and identical
**
** Note: No argument checking; make sure you pass valid strings
*****************************************************************************/

DRM_BOOL DRM_API DRM_UTL_DSTRStringsEqual(
    const DRM_CONST_STRING *pdstr1,
    const DRM_CONST_STRING *pdstr2)
{
    return (pdstr1->cchString == pdstr2->cchString
        &&  DRM_wcsncmp(pdstr1->pwszString, pdstr2->pwszString, pdstr1->cchString) == 0);
}

DRM_BOOL DRM_API DRM_UTL_DASTRStringsEqual(
    const DRM_ANSI_CONST_STRING *f_pdastr1,
    const DRM_ANSI_CONST_STRING *f_pdastr2)
{
    return (f_pdastr1->cchString == f_pdastr2->cchString
        &&  DRM_strncmp(f_pdastr1->pszString, f_pdastr2->pszString, f_pdastr1->cchString) == 0);
}

/*****************************************************************************
** Function : DRM_UTL_DASSTRStringsEqual
**
** Synopsis: case-sensitive comparison of an ANSI substring with a
**           DRM_ANSI_CONST_STRING
** Arguments:
** [f_pszBase] -- base of buffer into which f_pdasstr is an offset
** [f_pdasstr] -- substring of f_pszBase to compare
** [f_pdastr]  -- constant or literal string to compare
**
** Return: TRUE if strings are same length and identical
**
** Note: No argument checking; make sure you pass valid strings
*****************************************************************************/

DRM_BOOL DRM_API DRM_UTL_DASSTRStringsEqual(
    const DRM_CHAR              *f_pszBase,
    const DRM_SUBSTRING         *f_pdasstr,
    const DRM_ANSI_CONST_STRING *f_pdastr)
{
    return (f_pdasstr->m_cch  == f_pdastr->cchString
        &&  DRM_BYT_CompareBytes(f_pszBase, 
                                 f_pdasstr->m_ich, 
                                 f_pdastr->pszString, 
                                 0, 
                                 f_pdasstr->m_cch) == 0);
}

/**********************************************************************
** Function:    DRM_UTL_EnsureDataAlignment
**
** Synopsis:    return a pointer aligned on the specified data boundary
**
** Arguments:   [f_pbOriginal] -- original pointer, potentially misaligned
**              [f_cbOriginal] -- maximum size of f_pbOriginal
**              [f_ppbAligned] -- filled in with adjusted pointer
**              [f_pcbAligned] -- adjusted size, <= f_cbOriginal
**              [f_cbDataType] -- SIZEOF data type to align
**
** Notes:       address is moved forward from the original to the next
**              aligned boundary
***********************************************************************/

DRM_RESULT DRM_API DRM_UTL_EnsureDataAlignment(
    IN  DRM_BYTE   *f_pbOriginal,
    IN  DRM_DWORD   f_cbOriginal,
    OUT DRM_BYTE  **f_ppbAligned,
    OUT DRM_DWORD  *f_pcbAligned,
    IN  DRM_DWORD   f_cbDataType,
    OUT DRM_DWORD  *f_pcbAdjustment)
{
    DRM_RESULT      dr         = DRM_SUCCESS;
    DRM_DWORD_PTR   pvOriginal = (DRM_DWORD_PTR) f_pbOriginal;
    DRM_DWORD       cbScrap    = 0;
    DRM_DWORD       cbAdjust   = 0;

    ChkArg (f_pbOriginal != NULL
        &&  f_cbOriginal  > 0
        &&  f_ppbAligned != NULL
        &&  f_pcbAligned != NULL
        &&  f_cbDataType >= 1);

    /* e.g. for a DWORD on an 4n + 1 address, cbScrap = 1 */
        
    cbScrap  = (DRM_DWORD) (pvOriginal % f_cbDataType);

    /* e.g. for a DWORD on an 4n + 1 address, cbAdjust = 3 */
        
    if (cbScrap > 0)        
    {
        cbAdjust = (DRM_DWORD) (f_cbDataType - cbScrap);
    }
    
    /* check to see if it's already aligned */
        
    if (cbScrap == 0)
    {
        *f_ppbAligned = f_pbOriginal;
        *f_pcbAligned = f_cbOriginal;
    }
    else
    {
        /* verify that an adjusted pointer won't straddle the boundary */
        
        if ((f_cbDataType + cbAdjust) < f_cbOriginal)
        {
            *f_ppbAligned = (DRM_BYTE *) (pvOriginal) + (DRM_WORD) cbAdjust;
            *f_pcbAligned =  f_cbOriginal - cbAdjust;
        }
        else
        {
            ChkDR(DRM_E_BUFFERTOOSMALL);
        }
    }

    if (f_pcbAdjustment != NULL)
    {
        *f_pcbAdjustment = cbAdjust;
    }
    
ErrorExit:    
    return dr;
} /* DRM_UTL_EnsureDataAlignment */

/******************************************************************************
** 
** Function :   DRM_UTL_VerifyXMLSignature
** 
** Synopsis :   Verify the xml signature over some data
** 
** Arguments :  f_pdstrSignedData   -   The entire data that is signed
**              f_pdstrSignatureXML -   <SIGNATURE>...</SIGNATURE> 
**              f_pdstrCertChainXML -   <CERTIFICATECHAIN>...</CERTIFICATECHAIN>
**              f_fCheckExpiry      -   Check for cert expiration
**              f_fCheckCertChain   -   Verify cert chain. If this is false,
**                                      f_pcontextCrypto->pubKey must contain
**                                      the public key which will be used to
**                                      verify the signature.
**              f_eRootPubkey       -   Root Pubkey to use for cert verification
**                                      if it is WM_DRM_ROOTPUBKEY_CLK, 
**              f_pcontextLEVL      -   License eval context.  The BBX pointer must be valid.
**                                      The HDS and pcontextSSTRevocation pointers must be valid to perform cert caching.
**                                      
** Returns :    DRM_SUCCESS -   Signature verified successfully
**              DRM_E_INVALID_SIGNATURE -   Verification failed
**              some other problem
******************************************************************************/
DRM_RESULT DRM_API DRM_UTL_VerifyXMLSignature(
    IN  const   DRM_CONST_STRING        *f_pdstrSignedData,
    IN  const   DRM_CONST_STRING        *f_pdstrSignatureXML,
    IN  const   DRM_CONST_STRING        *f_pdstrCertChainXML,
    IN          DRM_BOOL                 f_fCheckExpiry,
    IN          DRM_BOOL                 f_fCheckCertChain,
    IN          DRM_ROOTPUBKEY_CATEGORY  f_eRootPubkey,
    IN          DRM_LICEVAL_CONTEXT     *f_pcontextLEVL)
{
    DRM_CONST_STRING dstrSignature = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrTmp       = EMPTY_DRM_STRING;
    DRM_RESULT       dr            = DRM_SUCCESS;    
       
    /*
    **  Verify input
    */
    ChkDRMString(f_pdstrSignedData);
    ChkDRMString(f_pdstrSignatureXML);
    if (f_fCheckCertChain)
    {
        ChkDRMString(f_pdstrCertChainXML); 
    }
    
    ChkArg( f_pcontextLEVL              != NULL
         && f_pcontextLEVL->pcontextBBX != NULL );

    /*
    **  Verify Hashing algorithm
    */
    dr = DRM_XML_GetSubNode( f_pdstrSignatureXML,
                            &g_dstrTagHashAlg, 
                            &g_dstrAttributeType,
                            &g_dstrSHA, 
                             0, 
                             NULL,
                            &dstrTmp,
                             1);
    if( DRM_FAILED(dr) )
    {
        ChkDR(DRM_E_UNSUPPORTEDALGORITHM);
    }

    /*
    **  Verify Signature algorithm
    */
    dr = DRM_XML_GetSubNode( f_pdstrSignatureXML,
                            &g_dstrTagSignAlg, 
                            &g_dstrAttributeType,
                            &g_dstrMSDRM, 
                             0, 
                             NULL,
                            &dstrTmp,
                            1);
    if( DRM_FAILED(dr) )
    {
        ChkDR(DRM_E_UNSUPPORTEDALGORITHM);
    }

    /*
    **  Get Signature value
    */
    ChkDR( DRM_XML_GetSubNode( f_pdstrSignatureXML, 
                              &g_dstrTagValue,
                               NULL,
                               NULL,
                               0,
                               NULL,
                              &dstrSignature,
                               1)  );

    dr = _UTL_VerifySignature( f_pdstrSignedData, 
                              &dstrSignature, 
                               f_pdstrCertChainXML, 
                               f_fCheckExpiry,
                               f_fCheckCertChain, 
                               f_eRootPubkey,
                               f_pcontextLEVL );
    
ErrorExit:
    return dr;
}

/******************************************************************************
** 
** Function :   DRM_UTL_VerifyXMLSignatureEx
** 
** Synopsis :   Verify the xml signature over some data
** 
** Arguments :  f_pdstrSignedData   -   The entire data that is signed
**              f_pdstrSignatureValue -   Sig value
**              f_pdstrCertChainXML -   <CERTIFICATECHAIN>...</CERTIFICATECHAIN>
**              f_fCheckExpiry      -   Check for cert expiration
**              f_fCheckCertChain   -   Verify cert chain. If this is false,
**                                      f_pcontextCrypto->pubKey must contain
**                                      the public key which will be used to
**                                      verify the signature.
**              f_eRootPubkey       -   Root Pubkey to use for cert verification
**              f_pcontextLEVL      -   License eval context.  The BBX pointer must be valid.
**                                      The HDS and pcontextSSTRevocation pointers must be valid to perform cert caching.
**                                      
** Returns :    DRM_SUCCESS -   Signature verified successfully
**              DRM_E_INVALID_SIGNATURE -   Verification failed
**              some other problem
******************************************************************************/
DRM_RESULT DRM_API DRM_UTL_VerifyXMLSignatureEx(
    IN  const   DRM_CONST_STRING        *f_pdstrSignedData,
    IN  const   DRM_CONST_STRING        *f_pdstrSignatureValue,
    IN  const   DRM_CONST_STRING        *f_pdstrCertChainXML,
    IN          DRM_BOOL                 f_fCheckExpiry,
    IN          DRM_BOOL                 f_fCheckCertChain,
    IN          DRM_ROOTPUBKEY_CATEGORY  f_eRootPubkey,
    IN          DRM_LICEVAL_CONTEXT     *f_pcontextLEVL)
{
    DRM_RESULT dr = DRM_SUCCESS;    
    
    /*
    **  Verify input
    */
    ChkDRMString(f_pdstrSignedData);
    ChkDRMString(f_pdstrSignatureValue);
    if (f_fCheckCertChain)
    {
        ChkDRMString(f_pdstrCertChainXML); 
    }
    ChkArg( f_pcontextLEVL              != NULL
         && f_pcontextLEVL->pcontextBBX != NULL );

    dr = _UTL_VerifySignature( f_pdstrSignedData, 
                               f_pdstrSignatureValue, 
                               f_pdstrCertChainXML, 
                               f_fCheckExpiry,
                               f_fCheckCertChain, 
                               f_eRootPubkey,
                               f_pcontextLEVL );
    
ErrorExit:
    return dr;
}

/*********************************************************************
**
**  Function:  DRM_UTL_DecodeKID
**
**  Synopsis:  Decode a KID value.  In general it just performs a Base64 decode but
**             also takes into account the possibility that a header or license
**             may technically have an invalid KID in it (wrong size) and we need
**             to work with it correctly.
**
**  Arguments:  
**     [f_pdstrKID] -- String representation of the KID
**     [f_pkid]     -- Binary KID will be copied to this out buffer.
**
**  Notes:     KIDs should really be 24 characters long when encoded but
**             some licenses and content have these strings incorrectly set
**             so this function compensates for that possiblity.
**
*********************************************************************/
DRM_RESULT DRM_API DRM_UTL_DecodeKID( 
    IN const DRM_CONST_STRING *f_pdstrKID, 
       OUT   DRM_KID          *f_pkid )
{    
    DRM_RESULT          dr      = DRM_SUCCESS;
    DRM_CONST_STRING    dstrKID = EMPTY_DRM_STRING;
    DRM_DWORD           cb      = SIZEOF(DRM_KID);
    DRM_WCHAR           wszKID[ CCH_BASE64_EQUIV(SIZEOF(DRM_KID)) + 1 ];
    DRM_DWORD           cchKID  = NO_OF( wszKID ) - 1 ;

    
    if ( f_pdstrKID->cchString <= cchKID )
    {
        DRM_DWORD cchDiff  = cchKID - f_pdstrKID->cchString;
        dstrKID.pwszString = wszKID, 
        dstrKID.cchString  = cchKID;

        for( cb = 0; cb < cchDiff; cb++ )
        {
            wszKID[cb] = g_wchAsterisk;
        }
    
        ChkDR( OEM_StringCchCopyN( wszKID + cchDiff,
                                   NO_OF( wszKID ) - cchDiff,
                                   f_pdstrKID->pwszString,
                                   f_pdstrKID->cchString ) );

        cb = SIZEOF( DRM_KID );
        dr = DRM_B64_DecodeW( &dstrKID, &cb, (DRM_BYTE*)f_pkid, 0 );    
    }
    else
    {
        dstrKID.pwszString = f_pdstrKID->pwszString, 
        dstrKID.cchString  = f_pdstrKID->cchString;
    }
    
    if (   DRM_FAILED( dr ) 
        || f_pdstrKID->cchString > cchKID )
    {                        
        /*
        **  Bad KID - Let's just take the first 16 bytes from original ANSI KID
        */ 
        DRM_DWORD   iKID = 0;        
       
        /*
        **  Now convert it ansi - Note we cannot use DRM_UTL_DemoteUNICODEtoANSI
        **  here because we are writing to DRM_KID directly instead of a string
        **  So we do not want the trailing '\0'
        */        
        while( iKID < DRM_ID_SIZE )
        {
            PUT_BYTE( f_pkid->rgb, iKID, (DRM_CHAR)NATIVE_WCHAR(dstrKID.pwszString[iKID]) );
            iKID++;
        } 
        dr = DRM_SUCCESS;
    }

ErrorExit:
    return dr;
}

/*****************************************************************************
** Function: DRM_UTL_PromoteANSItoUNICODE
**
** Synopsis: copies an ASCII ANSI string to wide-ASCII
**
** Arguments:
** [pdastrIn] -- ANSI original
** [pdstrOut] -- UNICODE copy
*****************************************************************************/

DRM_VOID DRM_API DRM_UTL_PromoteANSItoUNICODE(
    const DRM_CHAR      *f_pszBase,
    const DRM_SUBSTRING *f_pdasstr, 
          DRM_STRING    *f_pdstrOut)
{
    DRM_DWORD ich = 0;

    for (ich = 0; ich < f_pdasstr->m_cch; ich++)
    {
        f_pdstrOut->pwszString [ich] = WCHAR_CAST(GET_CHAR(f_pszBase, f_pdasstr->m_ich + ich));
    }
    
    f_pdstrOut->cchString = f_pdasstr->m_cch;
}
    
DRM_VOID DRM_API DRM_UTL_DemoteUNICODEtoANSI( 
    const DRM_WCHAR *pwszFileName, 
          DRM_CHAR  *pszFileName, 
          DRM_DWORD  cchMax )
{
    DRM_DWORD ich = 0;

    while( NATIVE_WCHAR(pwszFileName[ich]) != g_wchNull
        && cchMax                           > 0 )
    {
        PUT_BYTE( pszFileName, ich, (DRM_CHAR)NATIVE_WCHAR(pwszFileName[ich]) );
        cchMax--;
        ich++;
    }
    PUT_BYTE(pszFileName,ich,'\0');
}


DRM_BOOL DRM_API DRM_UTL_DSTRSearch( 
    IN const DRM_CONST_STRING *f_pdstrString,
    IN const DRM_CONST_STRING *f_pdstrSubString,
       OUT   DRM_CONST_STRING *f_pdstrFoundString )
{
    DRM_RESULT       dr            = DRM_SUCCESS;
    DRM_CONST_STRING dstrString    = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrSubString = EMPTY_DRM_STRING;

    /*
    ** NOTE:  All dr's in this function are meaningless.
    ** They will be masked before return into a TRUE/FALSE
    ** return value.
    */
    ChkArg( f_pdstrFoundString != NULL );

    ChkDRMString( f_pdstrString );
    ChkDRMString( f_pdstrSubString );

    dstrString    = *f_pdstrString;
    dstrSubString = *f_pdstrSubString;
    
    while( dstrString.cchString >= dstrSubString.cchString )
    {
        DRM_DWORD ich = 0;

        while( ich                        <  dstrSubString.cchString
            && dstrString.pwszString[ich] == dstrSubString.pwszString[ich] )
        {
            ich++;
        }

        if( ich == dstrSubString.cchString )
        {
            /* 
            ** We looped through all the characters in dstrSubString
            ** so there must have been a match 
            */

            *f_pdstrFoundString = dstrString;
            goto ErrorExit;
        }

        dstrString.cchString--;
        dstrString.pwszString++;
    }

    dr = DRM_E_FAIL;

ErrorExit:

    if( DRM_FAILED( dr ) )
    {
        return FALSE;
    }
    return TRUE;    
}

DRM_BOOL DRM_API DRM_UTL_DSTRSearchReverse( 
    IN const DRM_CONST_STRING *f_pdstrString,
    IN const DRM_CONST_STRING *f_pdstrSubString,
       OUT   DRM_CONST_STRING *f_pdstrFoundString )
{
    DRM_RESULT       dr            = DRM_SUCCESS;
    DRM_CONST_STRING dstrString    = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrSubString = EMPTY_DRM_STRING;

    /*
    ** NOTE:  All dr's in this function are meaningless.
    ** They will be masked before return into a TRUE/FALSE
    ** return value.
    */

    ChkArg( f_pdstrFoundString != NULL );

    ChkDRMString( f_pdstrString );
    ChkDRMString( f_pdstrSubString );

    dstrString    = *f_pdstrString;
    dstrSubString = *f_pdstrSubString;
    
    if( dstrSubString.cchString == 0 )
    {
        *f_pdstrFoundString = dstrString;
        goto ErrorExit;
    }

    if( dstrString.cchString < dstrSubString.cchString )
    {
        ChkDR( DRM_E_INVALIDARG );
    }
    
    dstrString.pwszString += (f_pdstrString->cchString - dstrSubString.cchString);
    dstrString.cchString   =  dstrSubString.cchString;
    
    while( dstrString.cchString <= f_pdstrString->cchString )
    {
        DRM_DWORD ich = 0;

        while( ich                        <  dstrSubString.cchString
            && dstrString.pwszString[ich] == dstrSubString.pwszString[ich] )
        {
            ich++;
        }

        if( ich == dstrSubString.cchString )
        {
            /* 
            ** We looped through all the characters in dstrSubString
            ** so there must have been a match 
            */
            *f_pdstrFoundString = dstrString;
            goto ErrorExit;
        }

        dstrString.cchString++;
        dstrString.pwszString--;
    }

    dr = DRM_E_FAIL;

ErrorExit:

    if( DRM_FAILED( dr ) )
    {
        return FALSE;
    }
    return TRUE;    
}

/*
**  Add encoding-decoding strings in pairs
*/

const DRM_WCHAR g_rgwchEncodedAmpersand[]   =  { ONE_WCHAR('&', '\0'), ONE_WCHAR('a', '\0'), ONE_WCHAR('m', '\0'), ONE_WCHAR('p', '\0'), ONE_WCHAR(';', '\0'), ONE_WCHAR('\0', '\0')};
const DRM_WCHAR g_rgwchEncodedQuote[]       =  { ONE_WCHAR('&', '\0'), ONE_WCHAR('q', '\0'), ONE_WCHAR('u', '\0'), ONE_WCHAR('o', '\0'), ONE_WCHAR('t', '\0'), ONE_WCHAR(';', '\0'), ONE_WCHAR('\0', '\0')};
const DRM_WCHAR g_rgwchEncodedLesserThan[]  =  { ONE_WCHAR('&', '\0'), ONE_WCHAR('l', '\0'), ONE_WCHAR('t', '\0'), ONE_WCHAR(';', '\0'), ONE_WCHAR('\0', '\0')};
const DRM_WCHAR g_rgwchEncodedGreaterThan[] =  { ONE_WCHAR('&', '\0'), ONE_WCHAR('g', '\0'), ONE_WCHAR('t', '\0'), ONE_WCHAR(';', '\0'), ONE_WCHAR('\0', '\0')};

const DRM_CONST_STRING g_dstrEncodedAmpersand    =  CREATE_DRM_STRING( g_rgwchEncodedAmpersand );
const DRM_CONST_STRING g_dstrEncodedQuote        =  CREATE_DRM_STRING( g_rgwchEncodedQuote );
const DRM_CONST_STRING g_dstrEncodedLesserThan   =  CREATE_DRM_STRING( g_rgwchEncodedLesserThan );
const DRM_CONST_STRING g_dstrEncodedGreaterThan  =  CREATE_DRM_STRING( g_rgwchEncodedGreaterThan );

typedef struct tagXMLEncodeMapping {
    const DRM_CONST_STRING  *pdstrEncoding;
    const DRM_WCHAR         wchEncodedCharacter;
} XMLEncodeMapping;

XMLEncodeMapping   g_rgXMLEncodeMapping[] = 
{
    { &g_dstrEncodedAmpersand,    WCHAR_CAST( '&' ) }, 
    { &g_dstrEncodedQuote,        WCHAR_CAST( '\"' ) },
    { &g_dstrEncodedLesserThan,   WCHAR_CAST( '<' ) },
    { &g_dstrEncodedGreaterThan,  WCHAR_CAST( '>' ) }
};

/******************************************************************************
** 
** Function :   DRM_UTL_XMLDecode
** 
** Synopsis :   Decode special characters like "&amp;" to plain old"&"
** 
** Arguments :  f_pwszEncoded   : Encoded string
**              f_cchEncoded    : Length of encoded string
**              f_pwszDecoded   : Buffer for decoded string; if NULL, the
**                                length of buffer required is returned and 
**                                errcode is DRM_E_BUFFERTOOSMALL
**              f_pcchDecoded   : Length of buffer for decoded string. After 
**                                returning, it contains length of decoded buffer,
**                                or length of buffer required.
**                              
** 
** Returns :    DRM_E_BUFFERTOOSMALL, if buffer for decoded string is not long 
**              enough  
** 
** Notes :      
** 
******************************************************************************/
DRM_RESULT DRM_UTL_XMLDecode(
    IN  const   DRM_WCHAR   *f_pwszEncoded,
    IN          DRM_DWORD    f_cchEncoded,
        OUT     DRM_WCHAR   *f_pwszDecoded,
    IN  OUT     DRM_DWORD   *f_pcchDecoded )
{
    DRM_RESULT      dr                  = DRM_SUCCESS;
    DRM_DWORD       iEncoded            = 0;
    DRM_DWORD       iDecoded            = 0;
    DRM_DWORD       iEncoding           = 0;
    DRM_BOOL        fFoundEncodedChar   = FALSE; 
    
    ChkArg( f_pwszEncoded != NULL && f_pcchDecoded != NULL );

    while ( iEncoded < f_cchEncoded )
    {                
        /*
        **  Check whether the next character is a encoded character
        */
        fFoundEncodedChar = FALSE; 
        for ( iEncoding = 0; 
              iEncoding < NO_OF( g_rgXMLEncodeMapping );
              iEncoding ++ )
        {
            if ( ( f_cchEncoded >= iEncoded + g_rgXMLEncodeMapping[iEncoding].pdstrEncoding->cchString )
              && DRM_wcsncmp( f_pwszEncoded + iEncoded, 
                          g_rgXMLEncodeMapping[iEncoding].pdstrEncoding->pwszString, 
                          g_rgXMLEncodeMapping[iEncoding].pdstrEncoding->cchString ) == 0 )
            {
                if ( f_pwszDecoded != NULL && iDecoded < (*f_pcchDecoded) )
                {
                    f_pwszDecoded[ iDecoded ] =   g_rgXMLEncodeMapping[iEncoding].wchEncodedCharacter;
                }
                iEncoded += g_rgXMLEncodeMapping[iEncoding].pdstrEncoding->cchString;
                fFoundEncodedChar = TRUE;
                break;
            }            
        }        
        
        if ( !fFoundEncodedChar )
        {
            if ( f_pwszDecoded != NULL && iDecoded < (*f_pcchDecoded) )
            {
                f_pwszDecoded[ iDecoded ] = f_pwszEncoded[ iEncoded ];
            }
            iEncoded++;
        }

        iDecoded++;        
    }
    
    if ( iDecoded > (*f_pcchDecoded) )
    {        
        *f_pcchDecoded = iDecoded;
        ChkDR( DRM_E_BUFFERTOOSMALL );
    }
    *f_pcchDecoded = iDecoded;
    
ErrorExit:    
    return dr;
}


DRM_VOID DRM_API DRM_XOR( 
    IN OUT   DRM_BYTE *pbLHS, 
    IN const DRM_BYTE *pbRHS, 
    IN       DRM_DWORD cb )
{
    DRM_DWORD i;
    for( i = 0; i < cb; i++ )
    {
        PUT_BYTE(pbLHS, i, GET_BYTE(pbLHS, i) ^ GET_BYTE(pbRHS,i) );
    }
}
#endif //WMDRM_ON_SEP
