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

#include <drmcommon.h>
#include <drmbase64.h>
#include <drmcrt.h>
#include <drmutilities.h>
#include <drmcipher.h>
#include <drmxmlbuilder.h>
#include <oemimpl.h>

#define CB_XML_BUFFER_MINIMUM  100


#ifdef __unix__
/**********
The Wchar in unix is twice the regular size,
but the program relies on it being half the size of DWord,
so it is adapted to that code.
***********/
#define CWCH_DWORD_STACK 2  
#else
#define CWCH_DWORD_STACK       (SIZEOF(DRM_DWORD)/SIZEOF(DRM_WCHAR))
#endif
#define CCH_SIMULATION_MININUM (2*CWCH_DWORD_STACK)

/*
***********************************************************************
** types used within this file
***********************************************************************
*/


const DRM_DWORD g_cbXMLBuilderMinimum = SIZEOF (_XMBContext) 
                                      + __CB_DECL(CB_XML_BUFFER_MINIMUM)
                                      + SIZEOF (DRM_WCHAR);

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

/******************************************************************************
** Function:   _PushDWORD
** 
** Synopsis:   push a DWORD value on the end of the stack and decrement it 
** 
** Arguments:  f_pcontextXML - the XML context in use; must be inited
**             f_dwValue     - the value to push  
******************************************************************************/

static DRM_RESULT _PushDWORD(_XMBContext *f_pcontextXML,
                             DRM_DWORD    f_dwValue)
{
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_DWORD  iPos = 0;

    ChkArg (f_pcontextXML != NULL);
    ChkBOOL(f_pcontextXML->wNextOpenNodePos >= CWCH_DWORD_STACK, DRM_E_BUFFERTOOSMALL);

    iPos = f_pcontextXML->wNextOpenNodePos;
    f_pcontextXML->XmlString [iPos+1] = (DRM_WCHAR) (f_dwValue  & 0x0000FFFF);
    f_pcontextXML->XmlString [iPos]   = (DRM_WCHAR) (f_dwValue >> 16);
    f_pcontextXML->wNextOpenNodePos  -= CWCH_DWORD_STACK;

ErrorExit:
    return dr;
}

/******************************************************************************
** Function:   _GetPushedDWORD
** 
** Synopsis:   pop a DWORD value from the end of the stack 
** 
** Arguments:  f_pcontextXML - the XML context in use; must be inited
**             f_iOffset     - an offset from the current stack position
**             f_pdwValue    - pointer to variable to hold the retrieved value
******************************************************************************/

static DRM_RESULT _GetPushedDWORD(_XMBContext *f_pcontextXML,
                                  DRM_DWORD    f_iOffset,
                                  DRM_DWORD   *f_pdwValue)
{
    DRM_RESULT dr = DRM_SUCCESS;
    
    ChkArg(f_pcontextXML != NULL
        && f_pdwValue    != NULL);

    ChkArg((f_pcontextXML->wNextOpenNodePos + f_iOffset) >= CWCH_DWORD_STACK);

    *f_pdwValue  =  (DRM_DWORD)  (f_pcontextXML->XmlString [f_pcontextXML->wNextOpenNodePos + f_iOffset + 1]);
    *f_pdwValue += ((DRM_DWORD) ((f_pcontextXML->XmlString [f_pcontextXML->wNextOpenNodePos + f_iOffset])) << 16);

ErrorExit:
    return dr;
}

/*
** Trim all leading and trailing blanks in given string 
** return TRUE if resulting string length > 0
*/
static DRM_BOOL _AllTrim2(
    IN const DRM_CONST_STRING *pdstrString,
       OUT   DRM_CONST_STRING *pdstrTrimedString)
{
    DRM_BOOL fResult = FALSE;

    if ( pdstrString             == NULL
      || pdstrTrimedString       == NULL
      || pdstrString->pwszString == NULL 
      || pdstrString->cchString  == 0 )
    {
        goto ErrorExit;
    }

    *pdstrTrimedString = *pdstrString;

    /* trim leading blanks */
    while ( pdstrTrimedString->pwszString[0] == g_wchSpace  && pdstrTrimedString->cchString>0 )
    {
        ++pdstrTrimedString->pwszString;
        --pdstrTrimedString->cchString;
    }

    /* trim trailing blanks */
    while ( pdstrTrimedString->cchString>0
        && pdstrTrimedString->pwszString[pdstrTrimedString->cchString-1] == g_wchSpace )
    {
        --pdstrTrimedString->cchString;
    }

    fResult = pdstrTrimedString->cchString > 0;

ErrorExit:
    return fResult;
}

/*
** Create context for the builder
*/
static DRM_RESULT _CreateContext(
    IN     DRM_DWORD    cbXmlContext,
    IN OUT _XMBContext *pbXmlContext,
    IN     DRM_BOOL     fIsSimMode)
{
    DRM_RESULT   dr = DRM_SUCCESS;

    if (cbXmlContext < g_cbXMLBuilderMinimum)
    {
        ChkDR (DRM_E_BUFFERTOOSMALL);
    }

    ZEROMEM(pbXmlContext, cbXmlContext);
    pbXmlContext->wSize            = cbXmlContext;
    pbXmlContext->wBuffSize        = ((cbXmlContext - SIZEOF(_XMBContext)) / SIZEOF(DRM_WCHAR));
    pbXmlContext->wNextStringPos   = 0;
    pbXmlContext->wNextOpenNodePos = pbXmlContext->wBuffSize - CWCH_DWORD_STACK; /* next position to be used on stack */
    pbXmlContext->fInited          = TRUE;
    pbXmlContext->fIsSimMode       = fIsSimMode;
    pbXmlContext->wMaxStackUsed    = 0;
    
ErrorExit:
    return dr;
}

/*
** Open a new node nested from current opened node
*/
static DRM_RESULT _OpenNode(
    IN OUT   _XMBContext      *pContext,
    IN const DRM_CONST_STRING *pdstrNodeName )
{
    DRM_RESULT       dr           = DRM_SUCCESS;
    DRM_DWORD        wNodePos     = 0;
    DRM_CONST_STRING dstrNodeName = EMPTY_DRM_STRING;
    
    ChkArg(_AllTrim2(pdstrNodeName, &dstrNodeName));

    if ( pContext->fIsSimMode )
    {
        /* fake mode pushes 2 DWORDs, one for node position, one for tag name size */
        
        if (pContext->wNextOpenNodePos < CCH_SIMULATION_MININUM)
        {
            ChkDR(DRM_E_BUFFERTOOSMALL);
        }
        wNodePos = pContext->wNextStringPos + 1;
        
        /* calculate length of the open tag now  2 = "<>" */
        
        pContext->wNextStringPos += dstrNodeName.cchString + 2;

        /* push the node position to end of XML string */
        
        ChkDR(_PushDWORD(pContext, wNodePos));

        /* push the tag name length to end of XML string */

        ChkDR(_PushDWORD(pContext, dstrNodeName.cchString));

        /* keep track of max stack used */
        {

            DRM_DWORD wCurrStack = (pContext->wBuffSize - 1 - pContext->wNextOpenNodePos) / 2;

            if ( pContext->wMaxStackUsed < wCurrStack )
            {
                pContext->wMaxStackUsed = wCurrStack;
            }
        }
        
    }
    else
    {
        if ( pContext->wNextStringPos + dstrNodeName.cchString                     < pContext->wNextStringPos
          || pContext->wNextStringPos + dstrNodeName.cchString + CWCH_DWORD_STACK  < pContext->wNextStringPos
          || pContext->wNextStringPos + dstrNodeName.cchString + CWCH_DWORD_STACK >= pContext->wNextOpenNodePos )
        {
            ChkDR(DRM_E_BUFFERTOOSMALL);
        }

        /* print "<tag>" to the XML string*/
        ChkDR( OEM_StringCchCopyN(&pContext->XmlString[pContext->wNextStringPos],
                                   pContext->wBuffSize - pContext->wNextStringPos, 
                                   g_dstrOpenTag.pwszString, 
                                   g_dstrOpenTag.cchString) );
        pContext->wNextStringPos += 1;
        wNodePos = pContext->wNextStringPos;

        ChkDR( OEM_StringCchCopyN(&pContext->XmlString[pContext->wNextStringPos], 
                                   pContext->wBuffSize - pContext->wNextStringPos, 
                                   dstrNodeName.pwszString, 
                                   dstrNodeName.cchString) );
        pContext->wNextStringPos += dstrNodeName.cchString;

        ChkDR( OEM_StringCchCopyN(&pContext->XmlString[pContext->wNextStringPos], 
                                   pContext->wBuffSize - pContext->wNextStringPos, 
                                   g_dstrCloseTag.pwszString, 
                                   g_dstrCloseTag.cchString) );
        pContext->wNextStringPos += 1;
        
        /* push the node position to end of XML string */

        ChkDR(_PushDWORD(pContext, wNodePos));
    }
    
ErrorExit:
    return dr;
}


/*
** Close the current opened node
*/
static DRM_RESULT DRM_API _CloseCurrNode(
    IN OUT _XMBContext *pXmlContext,
    IN     DRM_BOOL     fOkayToCloseRoot,
       OUT DRM_STRING  *pdstrXMLString)   /* optional parameter */
{
    DRM_RESULT   dr          = DRM_SUCCESS;
    DRM_DWORD    wLen        = 0;
    DRM_DWORD    wNodePos    = 0;
    DRM_DWORD    cwchMinStack = 0;

    ChkArg(pXmlContext->fInited);

    /*
    ** make sure the stack is not empty. Stack is count by CWCH_DWORD_STACK. 
    ** to close a node, we must have at least ONE CWCH_DWORD_STACK on stack.
    ** if fOkayToCloseRoot is FALSE, we must have at least two CWCH_DWORD_STACK
    ** on stack.
    */
    cwchMinStack = 2 * CWCH_DWORD_STACK;  /* do not pop off the root node */
    if (fOkayToCloseRoot)
    {
        cwchMinStack = CWCH_DWORD_STACK;  /* root node */
    }

    if ( (pXmlContext->wNextOpenNodePos + cwchMinStack) >= pXmlContext->wBuffSize )    /* do not pop off the root node !*/
    {
        dr = DRM_E_NOMORE;    /* stack is empty */
        goto ErrorExit;
    }

    if ( !pXmlContext->fIsSimMode )
    {
        /* parse length of node name */

        ChkDR(_GetPushedDWORD(pXmlContext, CWCH_DWORD_STACK, &wNodePos));

        if ( pdstrXMLString != NULL )
        {
            pdstrXMLString->pwszString = &(pXmlContext->XmlString[wNodePos - 1]);    
        }

        while (pXmlContext->XmlString [wNodePos + wLen] != g_wchSpace 
            && pXmlContext->XmlString [wNodePos + wLen] != g_wchGreaterThan )
        {
            wLen++;
        }
        
        if ( pXmlContext->wNextStringPos + wLen      < pXmlContext->wNextStringPos
          || pXmlContext->wNextStringPos + wLen + 3  < pXmlContext->wNextStringPos
          || pXmlContext->wNextStringPos + wLen + 3 >= pXmlContext->wNextOpenNodePos )
        {
            ChkDR(DRM_E_BUFFERTOOSMALL);
        }

        /* print </tag> to the XML string*/
        ChkDR( OEM_StringCchCopyN(&pXmlContext->XmlString[pXmlContext->wNextStringPos], 
                                   pXmlContext->wBuffSize - pXmlContext->wNextStringPos, 
                                   g_dstrOpenEndTag.pwszString, 
                                   g_dstrOpenEndTag.cchString) );
        pXmlContext->wNextStringPos += 2;
        
        ChkDR( OEM_StringCchCopyN(&pXmlContext->XmlString[pXmlContext->wNextStringPos], 
                                   pXmlContext->wBuffSize - pXmlContext->wNextStringPos, 
                                  &pXmlContext->XmlString[wNodePos], 
                                   wLen) );
        pXmlContext->wNextStringPos += wLen;
        
        ChkDR( OEM_StringCchCopyN(&pXmlContext->XmlString[pXmlContext->wNextStringPos], 
                                   pXmlContext->wBuffSize - pXmlContext->wNextStringPos, 
                                   g_dstrCloseTag.pwszString, 
                                   g_dstrCloseTag.cchString) );
        pXmlContext->wNextStringPos += 1;

        /* pop the node position from end of XML string */
        pXmlContext->wNextOpenNodePos += CWCH_DWORD_STACK;
    }
    else
    {
        DRM_DWORD cchPop = 0;
        
        ChkDR(_GetPushedDWORD(pXmlContext, CWCH_DWORD_STACK, &cchPop));
        ChkDR(_GetPushedDWORD(pXmlContext, 2 * CWCH_DWORD_STACK, &wNodePos));
                              
        /* calculate length of the close tag now 3 = "</>" */
        
        pXmlContext->wNextStringPos += cchPop + 3;

        /* pop the node position and tag name size from end of XML string */

        pXmlContext->wNextOpenNodePos += 2 * CWCH_DWORD_STACK;
    }

    if ( pdstrXMLString != NULL )
    {
        pdstrXMLString->cchString = pXmlContext->wNextStringPos - wNodePos + 1;
    }

ErrorExit:
    return dr;
}


/*
** calcuate the current node size
*/
static DRM_RESULT DRM_API _CalcNodeSize(
    IN  _XMBContext *pbXmlContext,
    OUT DRM_DWORD   *pcchContent,  /* ch count of node content */
    OUT DRM_DWORD   *pcchTagName)  /* ch count of node tag */
{
    DRM_RESULT   dr       = DRM_SUCCESS;
    DRM_DWORD    wLen     = 0;
    DRM_DWORD    wNodePos = 0;

    ChkArg( pbXmlContext != NULL
         && pcchContent  != NULL
         && pcchTagName  != NULL
         && pbXmlContext->fInited );

    if ( pbXmlContext->fIsSimMode )
    {
        _GetPushedDWORD(pbXmlContext, CWCH_DWORD_STACK,  pcchTagName);
        _GetPushedDWORD(pbXmlContext, 2 * CWCH_DWORD_STACK, &wNodePos);
        
        /* this is not accurate in case the open node has attributes */
        *pcchContent = pbXmlContext->wNextStringPos - wNodePos - *pcchTagName - 1;
    }
    else
    {
        /* parse length of node name */
        
        _GetPushedDWORD(pbXmlContext, CWCH_DWORD_STACK, &wNodePos);
        
        while (pbXmlContext->XmlString [wNodePos + wLen] != g_wchSpace 
            && pbXmlContext->XmlString [wNodePos + wLen] != g_wchGreaterThan )
        {
            wLen++;
        }
        *pcchTagName = wLen;

        /* parse first position of node content */
        while (pbXmlContext->XmlString[wNodePos + wLen] != g_wchGreaterThan )
        {
            wLen++;
        }
        *pcchContent = pbXmlContext->wNextStringPos - wNodePos - wLen - 1;
    }
    
ErrorExit:
    return dr;
}


DRM_RESULT DRM_API DRM_XMB_SimulateCreateDocument(
    IN const DRM_DWORD         cbXmlContext,
    IN OUT   _XMBContext      *pbXmlContext,
    IN const DRM_CONST_STRING *pdstrRootNodeName)
{
    DRM_RESULT dr = DRM_SUCCESS;

    /* create the context */
    ChkArg(pbXmlContext != NULL
        && cbXmlContext != 0    );

    ChkDRMString(pdstrRootNodeName);

    ChkDR(_CreateContext(cbXmlContext, pbXmlContext, TRUE));

    /* open the root node */
    ChkDR( _OpenNode(pbXmlContext, pdstrRootNodeName) );

ErrorExit:
    return dr;
}


DRM_RESULT DRM_API DRM_XMB_CreateDocument(
    IN const DRM_DWORD         cbXmlContext,
    IN OUT   _XMBContext      *pbXmlContext,
    IN const DRM_CONST_STRING *pdstrRootNodeName)
{
    DRM_RESULT dr = DRM_SUCCESS;

    /* create the context */
    ChkArg(pbXmlContext != NULL
        && cbXmlContext != 0    );

    ChkArg((((DRM_DWORD_PTR) pbXmlContext) % sizeof (DRM_DWORD)) == 0);

    ChkDRMString(pdstrRootNodeName);

    ChkDR(_CreateContext(cbXmlContext, pbXmlContext, FALSE));

    /* open the root node */
    ChkDR( _OpenNode(pbXmlContext, pdstrRootNodeName) );

ErrorExit:
    return dr;
}

DRM_RESULT DRM_API DRM_XMB_ReallocDocument(
    IN       _XMBContext *pbOldXmlContext,
    IN const DRM_DWORD    cbNewXmlContext,
    IN OUT   _XMBContext *pbNewXmlContext )
{
    DRM_RESULT dr = DRM_SUCCESS;

    ChkArg( pbOldXmlContext != NULL
         && cbNewXmlContext != 0
         && pbNewXmlContext != NULL 
         && pbOldXmlContext->fInited );

    if ( cbNewXmlContext <= pbOldXmlContext->wSize )
    {
        ChkDR(DRM_E_BUFFERTOOSMALL);
    }

    /* create new context using new buffer */
    if ( pbOldXmlContext != pbNewXmlContext )
    {
        ChkDR(_CreateContext(cbNewXmlContext, pbNewXmlContext, pbOldXmlContext->fIsSimMode));

        /* copy XML String to new context */
        MEMCPY(pbNewXmlContext->XmlString, pbOldXmlContext->XmlString, pbOldXmlContext->wNextStringPos * SIZEOF(DRM_WCHAR));
        pbNewXmlContext->wNextStringPos = pbOldXmlContext->wNextStringPos;

        /* copy node positions info to new context */
        pbNewXmlContext->wNextOpenNodePos = pbNewXmlContext->wBuffSize 
                                          - pbOldXmlContext->wBuffSize 
                                          + pbOldXmlContext->wNextOpenNodePos;

        MEMMOVE(&pbNewXmlContext->XmlString[pbNewXmlContext->wNextOpenNodePos],
                &pbOldXmlContext->XmlString[pbOldXmlContext->wNextOpenNodePos],
                (pbOldXmlContext->wBuffSize - pbOldXmlContext->wNextOpenNodePos) * SIZEOF(DRM_WCHAR));
        
        /* invalidate the old context */
        pbOldXmlContext->fInited = FALSE;
    }

    // the new buffer is the same as the old buffer except bigger.
    else
    {
        DRM_DWORD dwOldSize = pbOldXmlContext->wBuffSize;
        DRM_DWORD dwOldNextOpenNodePos = pbOldXmlContext->wNextOpenNodePos;
            
        pbNewXmlContext->wSize     = cbNewXmlContext;
        pbNewXmlContext->wBuffSize = ((cbNewXmlContext - SIZEOF(_XMBContext)) / SIZEOF(DRM_WCHAR));

        /* copy node positions info to new context */
        pbNewXmlContext->wNextOpenNodePos = pbNewXmlContext->wBuffSize - dwOldSize + dwOldNextOpenNodePos;

        MEMMOVE(&pbNewXmlContext->XmlString[pbNewXmlContext->wNextOpenNodePos],
                &pbOldXmlContext->XmlString[dwOldNextOpenNodePos],
                (dwOldSize - dwOldNextOpenNodePos) * SIZEOF(DRM_WCHAR));
    }
ErrorExit:
    return dr;
}
 

DRM_RESULT DRM_API DRM_XMB_CloseDocument(
    IN OUT _XMBContext *pbXmlContext,
       OUT DRM_STRING  *pdstrXML)
{
    DRM_RESULT dr = DRM_SUCCESS;

    ChkArg( pbXmlContext != NULL 
         && pdstrXML     != NULL
         && pbXmlContext->fInited );
    
    while (TRUE)
    {
        dr = _CloseCurrNode(pbXmlContext, TRUE, NULL);
        if ( dr == DRM_E_NOMORE )
        {
            dr = DRM_SUCCESS;
            break;
        }
        else
        {
            ChkDR(dr);
        }
    }

    if ( pbXmlContext->fIsSimMode )
    {
        DRM_DWORD cbOverhead = DRM_XMB_OVERHEAD;

        pdstrXML->pwszString = NULL;
        pdstrXML->cchString  = pbXmlContext->wNextStringPos;

        /* add extra room for simulate mode so that the call can allow enough 
        ** buffer size for creating this document */
        if ( cbOverhead < (pbXmlContext->wMaxStackUsed * SIZEOF(DRM_WCHAR) + SIZEOF(_XMBContext)) )
        {
            cbOverhead = pbXmlContext->wMaxStackUsed * SIZEOF(DRM_WCHAR) + SIZEOF(_XMBContext);
        }
        pdstrXML->cchString += (cbOverhead + 1) / SIZEOF(DRM_WCHAR);
    }
    else
    {
        pdstrXML->pwszString = pbXmlContext->XmlString;
        pdstrXML->cchString  = pbXmlContext->wNextStringPos;
    }
        

    /* invalidate the context */
    pbXmlContext->fInited = FALSE;

ErrorExit:
    return dr;
}


DRM_RESULT DRM_API DRM_XMB_OpenNode(
    IN OUT   _XMBContext      *pbXmlContext,
    IN const DRM_CONST_STRING *pdstrNodeName)
{
    DRM_RESULT dr = DRM_SUCCESS;

    ChkArg( pbXmlContext != NULL
         && pbXmlContext->fInited    ); 
    ChkDRMString(pdstrNodeName);

    /* open the root node */
    ChkDR( _OpenNode(pbXmlContext, pdstrNodeName) );

ErrorExit:
    return dr;
}


DRM_RESULT DRM_API DRM_XMB_CloseCurrNode(
    IN OUT _XMBContext *pbXmlContext,
       OUT DRM_STRING  *pdstrXMLFragment)
{
    DRM_RESULT dr = DRM_SUCCESS;

    ChkArg( pbXmlContext != NULL );
    ChkDR(_CloseCurrNode(pbXmlContext, FALSE, pdstrXMLFragment));

ErrorExit:
    return dr;
}


DRM_RESULT DRM_API DRM_XMB_SaveState(
    IN OUT   _XMBContext *pbXmlContext,
       OUT   XMBState    *pState)
{
    DRM_RESULT   dr = DRM_SUCCESS;

    ChkArg( pbXmlContext != NULL  &&  pState != NULL );

    pState->cwchMaxStackUsed    = pbXmlContext->wMaxStackUsed;
    pState->iwchNextOpenNodePos = pbXmlContext->wNextOpenNodePos;
    pState->iwchNextStringPos   = pbXmlContext->wNextStringPos;

ErrorExit:
    return dr;
}


DRM_RESULT DRM_API DRM_XMB_RestoreState(
    IN OUT   _XMBContext *pbXmlContext,
    IN       XMBState    *pState)
{
    DRM_RESULT   dr = DRM_SUCCESS;

    ChkArg( pbXmlContext != NULL  &&  pState != NULL );

    pbXmlContext->wMaxStackUsed    = pState->cwchMaxStackUsed;
    pbXmlContext->wNextOpenNodePos = pState->iwchNextOpenNodePos;
    pbXmlContext->wNextStringPos   = pState->iwchNextStringPos;

ErrorExit:
    return dr;
}


DRM_RESULT DRM_API DRM_XMB_EncryptAndCloseCurrNode(
    IN OUT   _XMBContext        *pXmlContext,
    IN       DRM_CRYPTO_CONTEXT *pCryptoContext,    /* Pointer to DRM_CRYPTO_CONTEXT */
    IN const PUBKEY             *pPubkey,           /* pub key to use for encrypt */
       OUT   DRM_STRING         *pdstrXMLFragment)  /* optional: XML fragment of the current node, optional. */
{
    DRM_RESULT   dr = DRM_SUCCESS;
    DRM_DWORD    cchContent = 0;
    DRM_DWORD    cbCryptSize= 0;
    DRM_DWORD    cchEncoded = 0;

    ChkArg( pXmlContext   != NULL
         && pCryptoContext != NULL
         && pPubkey        != NULL
         && pXmlContext->fInited );
    

    /*
    ** 1. check available buffer size: 
    **    req size = SIZEOF(WCHAR) * ((blob_size + PK_ENC_CIPHERTEXT_LEN + 2) / 3 * 4)
    ** 2. call EncryptLarge to encrypt the blob in place
    ** 3. call Base64Encode the encrypted data
    ** 4. close the node 
    */

    /* check available space */
    {
        DRM_DWORD cchTagName=0;

        ChkDR(_CalcNodeSize(pXmlContext, &cchContent, &cchTagName));

        /* req size for encryption */    
        cbCryptSize = cchContent * SIZEOF(DRM_WCHAR) + PK_ENC_CIPHERTEXT_LEN;
        
        /* req size for base64 encode after encryption */
        cchEncoded = CCH_BASE64_EQUIV( cbCryptSize );
        
        if ( !pXmlContext->fIsSimMode )
        {
            if( pXmlContext->wNextStringPos - cchContent  > pXmlContext->wNextStringPos 
             || pXmlContext->wNextStringPos - cchContent >= pXmlContext->wBuffSize )
            {
                ChkDR(DRM_E_BUFFERTOOSMALL);
            }
        }
    }

    if ( !pXmlContext->fIsSimMode )
    {
        /* encrypt the content */
        ChkDR(DRM_PK_EncryptLarge(pPubkey, 
                     (DRM_BYTE*)(&pXmlContext->XmlString[pXmlContext->wNextStringPos-cchContent]),
                                  cchContent * SIZEOF(DRM_WCHAR), 
                     (DRM_BYTE*)(&pXmlContext->XmlString[pXmlContext->wNextStringPos-cchContent]), /* encrypt in place */
                                  DRMCIPHERKEYLEN,
                                  pCryptoContext));
        
        /* base64 encode the crypted blob */
        ChkDR(DRM_B64_EncodeW( (DRM_BYTE*)(&pXmlContext->XmlString[pXmlContext->wNextStringPos-cchContent]), 
                                            cbCryptSize,
                                           &pXmlContext->XmlString[pXmlContext->wNextStringPos-cchContent],  /* encode in place */
                                           &cchEncoded, 
                                            0));
    }
    
    /* update wNextStringPos */
    pXmlContext->wNextStringPos = pXmlContext->wNextStringPos - cchContent + cchEncoded;

    /* now close the node */
    ChkDR(_CloseCurrNode(pXmlContext, FALSE, pdstrXMLFragment));

ErrorExit:
    return dr;
}


DRM_RESULT DRM_API DRM_XMB_SignAndCloseCurrNode(
    IN OUT   _XMBContext        *pbXmlContext,
    IN       DRM_CRYPTO_CONTEXT *pCryptoContext,  /* Pointer to DRM_CRYPTO_CONTEXT */
    IN const PRIVKEY            *pPrivkey,        /* pub key to use for encrypt */
    IN       DRM_BOOL            fIncludeTag,
       OUT   DRM_BYTE            rgbSign   [__CB_DECL(PK_ENC_SIGNATURE_LEN)],    /* cannot be both NULL */
       OUT   DRM_WCHAR           wszB64Sign [PK_ENC_SIGNATURE_B64LEN], /* cannot be both NULL */
       OUT   DRM_STRING         *pdstrXMLFragment)  /* optional: XML fragment of the current node, optional. */
{
    DRM_RESULT   dr = DRM_SUCCESS;
    DRM_DWORD    cchContent=0;
    DRM_DWORD    cchTagName=0;
    DRM_STRING   dstrXMLFragment = EMPTY_DRM_STRING;
    DRM_BYTE     _rgbSign[__CB_DECL(PK_ENC_SIGNATURE_LEN)];
    DRM_BYTE    *prgbSign = _rgbSign;

    ChkArg( pbXmlContext   != NULL
         && pCryptoContext != NULL
         && pPrivkey       != NULL
         && ( rgbSign      != NULL 
           || wszB64Sign   != NULL ) 
         && pbXmlContext->fInited   );
    
    ChkDR(_CalcNodeSize(pbXmlContext, &cchContent, &cchTagName));

    if ( rgbSign != NULL )
    {
        prgbSign = rgbSign;
    }

    if ( !pbXmlContext->fIsSimMode )
    {
        if ( !fIncludeTag )
        {
            /* sign the content */
            ChkDR(DRM_PK_Sign(pCryptoContext->rgbCryptoContext, 
                              pPrivkey,
                 (DRM_BYTE*)(&pbXmlContext->XmlString[pbXmlContext->wNextStringPos-cchContent]),
                             cchContent * SIZEOF(DRM_WCHAR), 
                             prgbSign));
        }
    }
    
    /* close the node */
    ChkDR(_CloseCurrNode(pbXmlContext, FALSE, &dstrXMLFragment));

    if ( !pbXmlContext->fIsSimMode )
    {
        if ( fIncludeTag )
        {
            /* sign the content */
            ChkDR(DRM_PK_Sign(pCryptoContext->rgbCryptoContext, 
                              pPrivkey,
                              PB_DSTR( &dstrXMLFragment ),
                              CB_DSTR( &dstrXMLFragment ),
                              prgbSign));
        }

        /* Base64 encode the signature */
        if ( wszB64Sign )
        {
            DRM_DWORD cchEncoded=PK_ENC_SIGNATURE_B64LEN;
            
            ChkDR(DRM_B64_EncodeW( prgbSign, 
                                   PK_ENC_SIGNATURE_LEN, 
                      (DRM_WCHAR *)wszB64Sign, 
                                  &cchEncoded, 
                                  0));
        }
    }
    
    if ( pdstrXMLFragment )
    {
        *pdstrXMLFragment = dstrXMLFragment;
    }

ErrorExit:
    return dr;
}


DRM_RESULT DRM_API DRM_XMB_KeyedHashAndCloseCurrNode(
    IN OUT   _XMBContext        *pbXmlContext,
    IN       HMAC_CONTEXT       *pHmacContext,     /* HMAC context */
    IN const DRM_BYTE           *pbHashkey,        /* Hash key for HMAC */
    IN       DRM_DWORD           cbHashkey,        /* byte count of HMAC */
    IN       DRM_BOOL            fIncludeTag,
    OUT      DRM_BYTE            rgbSign    [__CB_DECL(SHA_DIGEST_LEN)],       /* cannot be both NULL */
    OUT      DRM_WCHAR           wszB64Sign [SHA_B64ENC_DIGEST_LEN], /* cannot be both NULL */
    OUT      DRM_STRING         *pdstrXMLFragment) /* optional: XML fragment of the current node, optional. */
{
    DRM_RESULT   dr = DRM_SUCCESS;
    DRM_DWORD    cchContent = 0;
    DRM_DWORD    cchTagName = 0;
    DRM_STRING   dstrXMLFragment = EMPTY_DRM_STRING;
    DRM_BYTE     _rgbSign [__CB_DECL(SHA_DIGEST_LEN)];
    DRM_BYTE    *prgbSign = _rgbSign;

    ChkArg( pbXmlContext != NULL
         && pHmacContext != NULL
         && pbHashkey    != NULL
         && cbHashkey    != NULL
         && ( rgbSign    != NULL
           || wszB64Sign != NULL )
         && pbXmlContext->fInited );
    
    ChkDR(_CalcNodeSize(pbXmlContext, &cchContent, &cchTagName));

    if ( rgbSign )
    {
        prgbSign = rgbSign;
    }


    if ( !pbXmlContext->fIsSimMode )
    {
        ChkDR(DRM_HMAC_Init(pHmacContext,pbHashkey,cbHashkey));
        if ( !fIncludeTag )
        {
            /* sign the content */
            ChkDR(DRM_HMAC_Update( pHmacContext,
                      (DRM_BYTE*)(&pbXmlContext->XmlString[pbXmlContext->wNextStringPos - cchContent]),
                                   cchContent * SIZEOF(DRM_WCHAR)));
        }
    }
    
    /* close the node */
    ChkDR(_CloseCurrNode(pbXmlContext, FALSE, &dstrXMLFragment));

    if ( !pbXmlContext->fIsSimMode )
    {
        if ( fIncludeTag )
        {
            /* sign the content */
            ChkDR(DRM_HMAC_Update(pHmacContext, PB_DSTR(&dstrXMLFragment), CB_DSTR(&dstrXMLFragment)));
        }
        ChkDR(DRM_HMAC_Finalize(pHmacContext,prgbSign,SHA_DIGEST_LEN));
    
        /* Base64 encode the signature */
        if ( wszB64Sign )
        {
            DRM_DWORD cchEncoded=SHA_B64ENC_DIGEST_LEN;
            
            ChkDR(DRM_B64_EncodeW(prgbSign, 
                                  SHA_DIGEST_LEN, 
                     (DRM_WCHAR *)wszB64Sign, 
                                 &cchEncoded, 
                                  0));
        }
    }

    if ( pdstrXMLFragment )
    {
        *pdstrXMLFragment = dstrXMLFragment;
    }

ErrorExit:
    return dr;
}

DRM_RESULT DRM_API DRM_XMB_GetCurrNodeName(
    IN  _XMBContext *pbXmlContext,
    OUT DRM_STRING  *pdstrNodeName)
{
    DRM_RESULT   dr       = DRM_SUCCESS;
    DRM_DWORD    wLen     = 0;
    DRM_DWORD    wNodePos = 0;

    ChkArg( pbXmlContext  != NULL
         && pdstrNodeName != NULL
         && pbXmlContext->fInited);
         
    if ( pbXmlContext->fIsSimMode )
    {
        ChkDR(DRM_E_LOGICERR);  /* this function does not support fake mode */
    }

    /* parse length of node name */

    ChkDR(_GetPushedDWORD(pbXmlContext, CWCH_DWORD_STACK, &wNodePos));
    
    while (pbXmlContext->XmlString [wNodePos + wLen] != g_wchSpace 
        && pbXmlContext->XmlString [wNodePos + wLen] != g_wchGreaterThan)
    {
        wLen++;
    }

    pdstrNodeName->pwszString = &pbXmlContext->XmlString[wNodePos];
    pdstrNodeName->cchString  = wLen;

ErrorExit:
    return dr;
}



DRM_RESULT DRM_API DRM_XMB_GetContextSize(
    IN  _XMBContext *pbXmlContext,
    OUT DRM_DWORD   *pcbXmlContext)
{
    DRM_RESULT dr = DRM_SUCCESS;

    ChkArg( pbXmlContext  != NULL
         && pcbXmlContext != NULL 
         && pbXmlContext->fInited );
   
    *pcbXmlContext = pbXmlContext->wSize;

ErrorExit:
    return dr;
}


DRM_RESULT DRM_API DRM_XMB_AddAttribute(
    IN OUT   _XMBContext      *pXmlContext,
    IN const DRM_CONST_STRING *pdstrAttrName,
    IN const DRM_CONST_STRING *pdstrAttrValue )
{
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_DWORD    wReqSize = 0;
    DRM_DWORD    wOffset  = 0;
    DRM_DWORD    wDst     = 0;
    DRM_DWORD    wSrc     = 0;
    DRM_DWORD    i        = 0;
    DRM_CONST_STRING dstrAttrName  = EMPTY_DRM_STRING,
                     dstrAttrValue = EMPTY_DRM_STRING;

    ChkArg( pXmlContext != NULL
         && pXmlContext->fInited);
         
    ChkDRMString(pdstrAttrName);
    ChkDRMString(pdstrAttrValue);

    if ( (pXmlContext->wNextOpenNodePos + ( pXmlContext->fIsSimMode? 2 : 1 )) == pXmlContext->wBuffSize )
    {
        ChkDR(DRM_E_NOMORE);    /* stack is empty */
    }

    /* make sure it's not blank */
    ChkArg(_AllTrim2(pdstrAttrName, &dstrAttrName));

    /* trim leading and trialing blanks, but blank attrib value is allowed */
    _AllTrim2(pdstrAttrValue, &dstrAttrValue);

    /* calculate the buffer size needed to insert the ' attr="value"' 4 = space + equal sign + "" */
    wReqSize = dstrAttrName.cchString;

    ChkOverflow( wReqSize + dstrAttrValue.cchString , wReqSize );
    wReqSize += dstrAttrValue.cchString;

    ChkOverflow( wReqSize + g_dstrEqualQuote.cchString , wReqSize );
    wReqSize += g_dstrEqualQuote.cchString;

    ChkOverflow( wReqSize + g_dstrSpace.cchString , wReqSize );
    wReqSize += g_dstrSpace.cchString;

    ChkOverflow( wReqSize + g_dstrQuote.cchString , wReqSize );
    wReqSize += g_dstrQuote.cchString;

    if ( !pXmlContext->fIsSimMode )
    {
        if ( (pXmlContext->wNextStringPos + wReqSize)  < pXmlContext->wNextStringPos
          || (pXmlContext->wNextStringPos + wReqSize) >= pXmlContext->wNextOpenNodePos )
        {
            ChkDR(DRM_E_BUFFERTOOSMALL);
        }

        /* at this point, we've got the required buffer length,
        ** now parse for '>' start from the current node name */
        ChkDR(_GetPushedDWORD(pXmlContext, CWCH_DWORD_STACK, &wOffset));
        
        while ( wOffset < pXmlContext->wBuffSize
           &&   pXmlContext->XmlString[wOffset] != g_wchGreaterThan )
        {
            wOffset++;
        }

        if ( wOffset >= pXmlContext->wBuffSize
        ||   pXmlContext->XmlString[wOffset] != g_wchGreaterThan )
        {
            ChkDR(DRM_E_NOXMLCLOSETAG);
        }

        /* now, XmlString[wOffset] contains the '>' char, insert attr=value in there
        ** first, move everything, if any, from '>' to give space for the insertion 
        */
        wDst = pXmlContext->wNextStringPos+wReqSize-1;
        wSrc = pXmlContext->wNextStringPos-1;
        for (i=0; i<(pXmlContext->wNextStringPos - wOffset); i++)
        {
            pXmlContext->XmlString[wDst--] = pXmlContext->XmlString[wSrc--];
        }

        if( wOffset + wReqSize  < wOffset
         || wOffset + wReqSize >= pXmlContext->wBuffSize )
        {
            ChkDR(DRM_E_BUFFERTOOSMALL);
        }

        /* now, insert it in there */
        ChkOverflow( pXmlContext->wBuffSize , pXmlContext->wBuffSize - wOffset );
        ChkDR( OEM_StringCchCopyN( &pXmlContext->XmlString[wOffset], 
                                    pXmlContext->wBuffSize - wOffset, 
                                    g_dstrSpace.pwszString, 
                                    g_dstrSpace.cchString ) );
        wOffset += g_dstrSpace.cchString;
        
        ChkOverflow( pXmlContext->wBuffSize , pXmlContext->wBuffSize - wOffset );
        ChkDR( OEM_StringCchCopyN( &pXmlContext->XmlString[wOffset], 
                                    pXmlContext->wBuffSize - wOffset,
                                    dstrAttrName.pwszString, 
                                    dstrAttrName.cchString) );
        wOffset += dstrAttrName.cchString;
        
        ChkOverflow( pXmlContext->wBuffSize , pXmlContext->wBuffSize - wOffset );
        ChkDR( OEM_StringCchCopyN( &pXmlContext->XmlString[wOffset], 
                                    pXmlContext->wBuffSize - wOffset,
                                    g_dstrEqualQuote.pwszString, 
                                    g_dstrEqualQuote.cchString ) );
        wOffset += g_dstrEqualQuote.cchString;
        
        ChkOverflow( pXmlContext->wBuffSize , pXmlContext->wBuffSize - wOffset );
        ChkDR( OEM_StringCchCopyN( &pXmlContext->XmlString[wOffset], 
                                    pXmlContext->wBuffSize - wOffset,
                                    dstrAttrValue.pwszString, 
                                    dstrAttrValue.cchString) );
        wOffset += dstrAttrValue.cchString;
        
        ChkOverflow( pXmlContext->wBuffSize , pXmlContext->wBuffSize - wOffset );
        ChkDR( OEM_StringCchCopyN( &pXmlContext->XmlString[wOffset], 
                                    pXmlContext->wBuffSize - wOffset,
                                    g_dstrQuote.pwszString, 
                                    g_dstrQuote.cchString) );
        wOffset += g_dstrQuote.cchString;
        pXmlContext->XmlString[wOffset] = g_wchGreaterThan;
        
    }

    /* update next XML string position */
    pXmlContext->wNextStringPos += wReqSize;

ErrorExit:
    return dr;
}

DRM_RESULT DRM_API DRM_XMB_AddData(
    IN OUT   _XMBContext      *pXmlContext,
    IN const DRM_CONST_STRING *pdstrData )
{
    DRM_RESULT       dr       = DRM_SUCCESS;
    DRM_DWORD        wOffset  = 0;
    DRM_DWORD        wDst     = 0;
    DRM_DWORD        wSrc     = 0;
    DRM_DWORD        i        = 0;
    DRM_CONST_STRING dstrData = EMPTY_DRM_STRING;

    ChkArg( pXmlContext != NULL
         && pXmlContext->fInited    );
    ChkDRMString(pdstrData);

    if ( (pXmlContext->wNextOpenNodePos + ( pXmlContext->fIsSimMode? 2 : 1 )) == pXmlContext->wBuffSize )
    {
        ChkDR(DRM_E_NOMORE);    /* stack is empty */
    }

    if ( !_AllTrim2(pdstrData, &dstrData) )     /* a blank string found */
    {
        goto ErrorExit;
    }

    if ( !pXmlContext->fIsSimMode )
    {
        /* check if we have enough space */
        if ( (pXmlContext->wNextStringPos + dstrData.cchString)  < pXmlContext->wNextStringPos
          || (pXmlContext->wNextStringPos + dstrData.cchString) >= pXmlContext->wNextOpenNodePos)
        {
            ChkDR(DRM_E_BUFFERTOOSMALL);
        }

        /* at this point, we've got the required buffer length,
        ** now parse for '>' start from the current node name */
        
        ChkDR(_GetPushedDWORD(pXmlContext, CWCH_DWORD_STACK, &wOffset));

        while ( wOffset < pXmlContext->wBuffSize
           &&   pXmlContext->XmlString[wOffset] != g_wchGreaterThan )
        {
            wOffset++;
        }

        if ( wOffset >= pXmlContext->wBuffSize
        ||   pXmlContext->XmlString[wOffset] != g_wchGreaterThan )
        {
            ChkDR(DRM_E_NOXMLCLOSETAG);
        }

        /* now, XmlString[wOffset] contains the '>' char, insert data after it.
        ** first, move everything, if any, after '>' to give space for the insertion 
        */
        wOffset += 1;

        wDst = pXmlContext->wNextStringPos + dstrData.cchString - 1;
        wSrc = pXmlContext->wNextStringPos-1;

        for (i=0; i<(pXmlContext->wNextStringPos - wOffset); i++)
        {
            pXmlContext->XmlString[wDst--] = pXmlContext->XmlString[wSrc--];
        }

        /* now, insert it in there */
        ChkDR( OEM_StringCchCopyN(&pXmlContext->XmlString[wOffset], 
                                   pXmlContext->wBuffSize - wOffset, 
                                   dstrData.pwszString, 
                                   dstrData.cchString) );
        wOffset += dstrData.cchString;
        pXmlContext->XmlString[wOffset] = g_wchLessThan;
        
    }

    /* update next XML string position */
    pXmlContext->wNextStringPos += dstrData.cchString;

ErrorExit:
    return dr;
}

DRM_RESULT DRM_API DRM_XMB_AddCData(
    IN OUT   _XMBContext      *pXmlContext,
    IN const DRM_CONST_STRING *pdstrCData )
{
    DRM_RESULT       dr        = DRM_SUCCESS;
    DRM_DWORD        wReqSize  = 0;
    DRM_DWORD        wOffset   = 0;
    DRM_DWORD        wDst      = 0;
    DRM_DWORD        wSrc      = 0;
    DRM_DWORD        i         = 0;
    DRM_CONST_STRING dstrCData = EMPTY_DRM_STRING;

    ChkArg( pXmlContext != NULL
         && pXmlContext->fInited    );
    ChkDRMString(pdstrCData);

    if ( (pXmlContext->wNextOpenNodePos + ( pXmlContext->fIsSimMode? 2 : 1 )) < pXmlContext->wNextOpenNodePos
       || (pXmlContext->wNextOpenNodePos + ( pXmlContext->fIsSimMode? 2 : 1 )) >= pXmlContext->wBuffSize )
    {
        ChkDR(DRM_E_NOMORE);    /* stack is empty */
    }

    /* trim leading and trialing blanks, but blank CDATA value is allowed */
    _AllTrim2(pdstrCData, &dstrCData);

    /* calculate the buffer size needed to insert <!<CDATA[xxx]]> */
    wReqSize = dstrCData.cchString; 

    ChkOverflow( wReqSize + g_dstrOpenCDATATag.cchString , wReqSize );
    wReqSize += g_dstrOpenCDATATag.cchString; 
    
    ChkOverflow( wReqSize + g_dstrCloseCDATATag.cchString , wReqSize );
    wReqSize += g_dstrCloseCDATATag.cchString; 

    if ( !pXmlContext->fIsSimMode )
    {
        if ( pXmlContext->wNextStringPos + wReqSize  < pXmlContext->wNextStringPos
         ||  pXmlContext->wNextStringPos + wReqSize >= pXmlContext->wNextOpenNodePos
         ||  pXmlContext->wNextStringPos + wReqSize >= pXmlContext->wBuffSize )
        {
            ChkDR(DRM_E_BUFFERTOOSMALL);
        }

        /* at this point, we've got the required buffer length,
        ** now parse for '>' start from the current node name */
        ChkDR(_GetPushedDWORD(pXmlContext, CWCH_DWORD_STACK, &wOffset));
        
        while ( wOffset < pXmlContext->wBuffSize
           &&   pXmlContext->XmlString[wOffset] != g_wchGreaterThan )
        {
            wOffset++;
        }

        if ( wOffset >= pXmlContext->wBuffSize
        ||   pXmlContext->XmlString[wOffset] != g_wchGreaterThan )
        {
            ChkDR(DRM_E_NOXMLCLOSETAG);
        }

        /* now, XmlString[wOffset] contains the '>' char, insert CDATA after it
        ** first, move everything, if any, after '>' to give space for the insertion 
        */
        wOffset += 1;
        wDst = pXmlContext->wNextStringPos+wReqSize-1;
        wSrc = pXmlContext->wNextStringPos-1;
        
        for (i = 0; i < (pXmlContext->wNextStringPos - wOffset); i++)
        {
            pXmlContext->XmlString[wDst--] = pXmlContext->XmlString[wSrc--];
        }

        /* now, insert it in there */
        ChkDR( OEM_StringCchCopyN(&pXmlContext->XmlString[wOffset],
                                   pXmlContext->wBuffSize - wOffset, 
                                   g_dstrOpenCDATATag.pwszString, 
                                   g_dstrOpenCDATATag.cchString) );
        wOffset += g_dstrOpenCDATATag.cchString;
        
        ChkDR( OEM_StringCchCopyN(&pXmlContext->XmlString[wOffset], 
                                   pXmlContext->wBuffSize - wOffset, 
                                   dstrCData.pwszString, 
                                   dstrCData.cchString) );
        wOffset += dstrCData.cchString;
        
        ChkDR( OEM_StringCchCopyN(&pXmlContext->XmlString[wOffset], 
                                   pXmlContext->wBuffSize - wOffset, 
                                   g_dstrCloseCDATATag.pwszString, 
                                   g_dstrCloseCDATATag.cchString) );

    }

    /* update next XML string position */
    pXmlContext->wNextStringPos += wReqSize;

ErrorExit:
    return dr;
}



DRM_RESULT DRM_API DRM_XMB_AppendNode(
    IN OUT   _XMBContext      *pXmlContext,
    IN const DRM_CONST_STRING *pdstrXmlString)
{
    DRM_RESULT       dr       = DRM_SUCCESS;
    DRM_CONST_STRING dstrNode = EMPTY_DRM_STRING;

    ChkArg( pXmlContext != NULL
         && pXmlContext->fInited    );
    ChkDRMString( pdstrXmlString );

    /* open the root node */
    ChkArg(_AllTrim2(pdstrXmlString, &dstrNode));

    if (! pXmlContext->fIsSimMode)
    {
        if ( pXmlContext->wNextStringPos + dstrNode.cchString  < pXmlContext->wNextStringPos
          || pXmlContext->wNextStringPos + dstrNode.cchString >= pXmlContext->wNextOpenNodePos)
        {
            ChkDR(DRM_E_BUFFERTOOSMALL);
        }
        
        ChkOverflow( pXmlContext->wBuffSize, pXmlContext->wBuffSize - pXmlContext->wNextStringPos );

        ChkDR( OEM_StringCchCopyN(&pXmlContext->XmlString [pXmlContext->wNextStringPos], 
                                   pXmlContext->wBuffSize - pXmlContext->wNextStringPos,
                                   dstrNode.pwszString, 
                                   dstrNode.cchString) );
    }
    
    pXmlContext->wNextStringPos += dstrNode.cchString;

ErrorExit:
    return dr;
}


DRM_DWORD DRM_API DRM_XMB_RequiredCharsForTag (
    IN const DRM_DWORD cchTag,
    IN const DRM_DWORD cchData,
    IN const DRM_DWORD cchAttrLabel,
    IN const DRM_DWORD cchAttrText)
{
    DRM_DWORD  cch = 0;

    cch = (cchTag * 2) +  5; /* <TAG></TAG> */
    
    cch += cchData;

    if (cchAttrLabel != 0)
    {
        cch += cchAttrLabel + 4;  /* two quotes, space and equal sign */
        cch += cchAttrText;
    }

    return cch;
}



DRM_RESULT DRM_API DRM_XMB_WriteTag (
    IN OUT       _XMBContext      *pbDataOut,
    IN     const DRM_CONST_STRING *pdstrTag,
    IN     const DRM_CONST_STRING *pdstrData,
    IN     const DRM_CONST_STRING *pdstrAttrLabel,
    IN     const DRM_CONST_STRING *pdstrAttrText,
    IN     enum  WriteTagType      wtt)
{
    DRM_RESULT dr = DRM_SUCCESS;

    ChkArg(pbDataOut != NULL
        && pdstrTag  != NULL);

    ChkDR (DRM_XMB_OpenNode (pbDataOut, pdstrTag));

    if (pdstrAttrLabel != NULL
    &&  pdstrAttrText  != NULL)
    {
        ChkDR (DRM_XMB_AddAttribute (pbDataOut, pdstrAttrLabel, pdstrAttrText));
    }

    if (pdstrData != NULL)
    {
        ChkDR (DRM_XMB_AddData (pbDataOut, pdstrData));
    }
    
    if (wtt == wttClosed)
    {    
        ChkDR (DRM_XMB_CloseCurrNode (pbDataOut,  NULL));
    }

ErrorExit:    
    return dr;
} /* DRM_XMB_WriteTag */

DRM_RESULT DRM_API DRM_XMB_WriteCDATATag (
    IN OUT       _XMBContext      *pbDataOut,
    IN     const DRM_CONST_STRING *pdstrTag,
    IN     const DRM_CONST_STRING *pdstrCDATA,
    IN     const DRM_CONST_STRING *pdstrAttrLabel,
    IN     const DRM_CONST_STRING *pdstrAttrText,
    IN     enum  WriteTagType      wtt)
{
    DRM_RESULT dr = DRM_SUCCESS;

    ChkArg (pbDataOut  != NULL
        &&  pdstrTag   != NULL
        &&  pdstrCDATA != NULL);

    ChkDR (DRM_XMB_OpenNode (pbDataOut, pdstrTag));

    if (pdstrAttrLabel != NULL
    &&  pdstrAttrText  != NULL)
    {
        ChkDR (DRM_XMB_AddAttribute (pbDataOut, pdstrAttrLabel, pdstrAttrText));
    }

    ChkDR (DRM_XMB_AddCData (pbDataOut, pdstrCDATA));
    
    if (wtt == wttClosed)
    {    
        ChkDR (DRM_XMB_CloseCurrNode (pbDataOut,  NULL));
    }

ErrorExit:    
    return dr;
} /* DRM_XMB_WriteCDATATag */

DRM_RESULT DRM_API DRM_XMB_RemainingBuffer(
    IN  _XMBContext *f_pbXMB,
    OUT DRM_DWORD   *f_pcbRemaining)
{
    DRM_RESULT   dr       = DRM_SUCCESS;
    
    ChkArg(f_pbXMB        != NULL
        && f_pcbRemaining != NULL);
    
    ChkBOOL((f_pbXMB->wNextStringPos + CWCH_DWORD_STACK) < f_pbXMB->wNextOpenNodePos, DRM_E_BUFFERTOOSMALL);
    
    *f_pcbRemaining = (f_pbXMB->wNextOpenNodePos - (f_pbXMB->wNextStringPos + CWCH_DWORD_STACK));

ErrorExit:
    return dr;
}
